OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.incrementalinstall; | |
nyquist
2015/09/16 06:30:45
Optional nit: How do you feel about org.chromium.b
agrieve
2015/09/16 14:59:58
All the same to me, so will leave it if you don't
| |
6 | |
7 import android.app.Application; | |
8 import android.content.Context; | |
9 import android.content.pm.ApplicationInfo; | |
10 import android.content.pm.PackageManager; | |
11 import android.content.pm.PackageManager.NameNotFoundException; | |
12 import android.util.Log; | |
13 | |
14 import java.io.File; | |
15 import java.lang.ref.WeakReference; | |
16 import java.lang.reflect.InvocationTargetException; | |
17 import java.util.List; | |
18 import java.util.Map; | |
19 | |
20 /** | |
21 * An Application that replaces itself with another Application (as defined in | |
22 * the "incremental-install-real-app" meta-data tag within the | |
23 * AndroidManifest.xml). It loads the other application only after side-loading | |
24 * its .so and .dex files from /data/local/tmp. | |
25 */ | |
26 public final class BootstrapApplication extends Application { | |
27 private static final String TAG = "cr.incrementalinstall"; | |
28 private static final String MANAGED_DIR_PREFIX = "/data/local/tmp/incrementa l-app-"; | |
29 private static final String REAL_APP_META_DATA_NAME = "incremental-install-r eal-app"; | |
30 | |
31 private ClassLoaderPatcher mClassLoaderPatcher; | |
32 private Application mRealApplication; | |
33 private Object mStashedProviderList; | |
34 private Object mActivityThread; | |
35 | |
36 @Override | |
37 protected void attachBaseContext(Context context) { | |
38 super.attachBaseContext(context); | |
39 File incrementalRootDir = new File(MANAGED_DIR_PREFIX + context.getPacka geName()); | |
40 File libDir = new File(incrementalRootDir, "lib"); | |
41 File dexDir = new File(incrementalRootDir, "dex"); | |
42 File installLockFile = new File(incrementalRootDir, "install.lock"); | |
43 File firstRunLockFile = new File(incrementalRootDir, "firstrun.lock"); | |
44 | |
45 try { | |
46 mActivityThread = Reflect.invokeMethod(Class.forName("android.app.Ac tivityThread"), | |
47 "currentActivityThread"); | |
48 mClassLoaderPatcher = new ClassLoaderPatcher(context); | |
49 | |
50 boolean isFirstRun = LockFile.installerLockExists(firstRunLockFile); | |
51 if (isFirstRun && mClassLoaderPatcher.mIsPrimaryProcess) { | |
nyquist
2015/09/16 06:30:45
Nit: I know it would be an additional indent, but
agrieve
2015/09/16 14:59:58
Done.
| |
52 // Wait for incremental_install.py to finish. | |
53 LockFile.waitForInstallerLock(installLockFile, 20 * 1000); | |
54 } else if (isFirstRun) { | |
55 // Wait for the browser process to create the optimized dex file s | |
56 // (and for M+, copy the library files). | |
57 LockFile.waitForInstallerLock(firstRunLockFile, 30 * 1000); | |
58 } | |
59 | |
60 mClassLoaderPatcher.importNativeLibs(libDir); | |
61 mClassLoaderPatcher.loadDexFiles(dexDir); | |
62 | |
63 if (isFirstRun && mClassLoaderPatcher.mIsPrimaryProcess) { | |
64 LockFile.clearInstallerLock(firstRunLockFile); | |
65 } | |
66 | |
67 // attachBaseContext() is called from ActivityThread#handleBindAppli cation() and | |
68 // Application#mApplication is changed right after we return. Thus, we cannot swap | |
69 // the Application instances until onCreate() is called. | |
70 String mRealApplicationName = getRealApplicationName(); | |
nyquist
2015/09/16 06:30:45
Nit: Just |realApplicationName|
agrieve
2015/09/16 14:59:58
Done.
| |
71 Log.i(TAG, "Instantiating " + mRealApplicationName); | |
72 mRealApplication = | |
73 (Application) Reflect.newInstance(Class.forName(mRealApplica tionName)); | |
74 Reflect.invokeMethod(mRealApplication, "attachBaseContext", context) ; | |
75 | |
76 // Between attachBaseContext() and onCreate(), ActivityThread tries to instantiate | |
77 // all ContentProviders. The ContentProviders break without the corr ect Application | |
78 // class being installed, so temporarily pretend there are no provid ers, and then | |
79 // instantiate them explicitly within onCreate(). | |
80 disableContentProviders(); | |
81 Log.i(TAG, "Waiting for onCreate"); | |
82 } catch (Exception e) { | |
83 throw new RuntimeException(e); | |
84 } | |
85 } | |
86 | |
87 @Override | |
88 public void onCreate() { | |
89 super.onCreate(); | |
90 try { | |
91 Log.i(TAG, "onCreate() called. Swapping Application references"); | |
92 swapApplicationReferences(); | |
93 enableContentProviders(); | |
94 Log.i(TAG, "Calling onCreate"); | |
95 mRealApplication.onCreate(); | |
96 } catch (Exception e) { | |
97 throw new RuntimeException(e); | |
nyquist
2015/09/16 06:30:45
Nit: I know we log that we're trying above here, b
agrieve
2015/09/16 14:59:58
Done.
| |
98 } | |
99 } | |
100 | |
101 /** | |
102 * Returns the class name of the real Application class (recorded in the | |
103 * AndroidManifest.xml) | |
104 */ | |
105 private String getRealApplicationName() throws NameNotFoundException { | |
106 ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPack ageName(), | |
107 PackageManager.GET_META_DATA); | |
108 return appInfo.metaData.getString(REAL_APP_META_DATA_NAME); | |
109 } | |
110 | |
111 /** | |
112 * Nulls out ActivityThread.mBoundApplication.providers. | |
113 */ | |
114 private void disableContentProviders() throws NoSuchFieldException { | |
115 Object data = Reflect.getField(mActivityThread, "mBoundApplication"); | |
116 mStashedProviderList = Reflect.getField(data, "providers"); | |
117 Reflect.setField(data, "providers", null); | |
118 } | |
119 | |
120 /** | |
121 * Restores the value of ActivityThread.mBoundApplication.providers, and inv okes | |
122 * ActivityThread#installContentProviders(). | |
123 */ | |
124 private void enableContentProviders() throws NoSuchMethodException, Invocati onTargetException, | |
125 NoSuchFieldException { | |
126 Object data = Reflect.getField(mActivityThread, "mBoundApplication"); | |
127 Reflect.setField(data, "providers", mStashedProviderList); | |
128 if (mStashedProviderList != null && mClassLoaderPatcher.mIsPrimaryProces s) { | |
129 Log.i(TAG, "Instantiating content providers"); | |
130 Reflect.invokeMethod(mActivityThread, "installContentProviders", mRe alApplication, | |
131 mStashedProviderList); | |
132 } | |
133 mStashedProviderList = null; | |
134 } | |
135 | |
136 /** | |
137 * Changes all fields within framework classes that have stored an reference to this | |
138 * BootstrapApplication to instead store references to mRealApplication. | |
139 * @throws NoSuchFieldException | |
140 */ | |
141 @SuppressWarnings("unchecked") | |
142 private void swapApplicationReferences() throws NoSuchFieldException { | |
143 if (Reflect.getField(mActivityThread, "mInitialApplication") == this) { | |
144 Reflect.setField(mActivityThread, "mInitialApplication", mRealApplic ation); | |
145 } | |
146 | |
147 List<Application> allApplications = | |
148 (List<Application>) Reflect.getField(mActivityThread, "mAllAppli cations"); | |
149 for (int i = 0; i < allApplications.size(); i++) { | |
150 if (allApplications.get(i) == this) { | |
151 allApplications.set(i, mRealApplication); | |
152 } | |
153 } | |
154 | |
155 for (String fieldName : new String[] { "mPackages", "mResourcePackages" }) { | |
156 Map<String, WeakReference<?>> packageMap = | |
157 (Map<String, WeakReference<?>>) Reflect.getField(mActivityTh read, fieldName); | |
158 for (Map.Entry<String, WeakReference<?>> entry : packageMap.entrySet ()) { | |
159 Object loadedApk = entry.getValue().get(); | |
160 if (loadedApk != null && Reflect.getField(loadedApk, "mApplicati on") == this) { | |
161 Reflect.setField(loadedApk, "mApplication", mRealApplication ); | |
162 Reflect.setField(mRealApplication, "mLoadedApk", loadedApk); | |
163 } | |
164 } | |
165 } | |
166 } | |
167 } | |
OLD | NEW |