OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.incrementalinstall; | 5 package org.chromium.incrementalinstall; |
6 | 6 |
7 import android.app.Application; | 7 import android.app.Application; |
8 import android.app.Instrumentation; | 8 import android.app.Instrumentation; |
9 import android.content.ComponentName; | 9 import android.content.ComponentName; |
10 import android.content.Context; | 10 import android.content.Context; |
(...skipping 19 matching lines...) Loading... | |
30 */ | 30 */ |
31 public final class BootstrapApplication extends Application { | 31 public final class BootstrapApplication extends Application { |
32 private static final String TAG = "cr.incrementalinstall"; | 32 private static final String TAG = "cr.incrementalinstall"; |
33 private static final String MANAGED_DIR_PREFIX = "/data/local/tmp/incrementa l-app-"; | 33 private static final String MANAGED_DIR_PREFIX = "/data/local/tmp/incrementa l-app-"; |
34 private static final String REAL_APP_META_DATA_NAME = "incremental-install-r eal-app"; | 34 private static final String REAL_APP_META_DATA_NAME = "incremental-install-r eal-app"; |
35 private static final String REAL_INSTRUMENTATION_META_DATA_NAME = | 35 private static final String REAL_INSTRUMENTATION_META_DATA_NAME = |
36 "incremental-install-real-instrumentation"; | 36 "incremental-install-real-instrumentation"; |
37 | 37 |
38 private ClassLoaderPatcher mClassLoaderPatcher; | 38 private ClassLoaderPatcher mClassLoaderPatcher; |
39 private Application mRealApplication; | 39 private Application mRealApplication; |
40 private Instrumentation mOrigInstrumentation; | |
40 private Instrumentation mRealInstrumentation; | 41 private Instrumentation mRealInstrumentation; |
41 private Object mStashedProviderList; | 42 private Object mStashedProviderList; |
42 private Object mActivityThread; | 43 private Object mActivityThread; |
43 | 44 |
44 @Override | 45 @Override |
45 protected void attachBaseContext(Context context) { | 46 protected void attachBaseContext(Context context) { |
46 super.attachBaseContext(context); | 47 super.attachBaseContext(context); |
47 File incrementalRootDir = new File(MANAGED_DIR_PREFIX + context.getPacka geName()); | |
48 File libDir = new File(incrementalRootDir, "lib"); | |
49 File dexDir = new File(incrementalRootDir, "dex"); | |
50 File installLockFile = new File(incrementalRootDir, "install.lock"); | |
51 File firstRunLockFile = new File(incrementalRootDir, "firstrun.lock"); | |
52 | |
53 try { | 48 try { |
54 mActivityThread = Reflect.invokeMethod(Class.forName("android.app.Ac tivityThread"), | 49 mActivityThread = Reflect.invokeMethod(Class.forName("android.app.Ac tivityThread"), |
55 "currentActivityThread"); | 50 "currentActivityThread"); |
56 mClassLoaderPatcher = new ClassLoaderPatcher(context); | 51 mClassLoaderPatcher = new ClassLoaderPatcher(context); |
57 | 52 |
58 boolean isFirstRun = LockFile.installerLockExists(firstRunLockFile); | 53 mOrigInstrumentation = |
54 (Instrumentation) Reflect.getField(mActivityThread, "mInstru mentation"); | |
55 Context instContext = mOrigInstrumentation.getContext(); | |
56 if (instContext == null) { | |
57 instContext = context; | |
58 } | |
59 | |
60 // When running with an instrumentation that lives in a different pa ckage from the | |
61 // application, we must load the dex files and native libraries from both pacakges. | |
62 // This logic likely won't work when the instrumentation is incremen tal, but the app is | |
63 // non-incremental. This configuration isn't used right now though. | |
64 String appPackageName = getPackageName(); | |
65 String instPackageName = instContext.getPackageName(); | |
66 boolean instPackageNameDiffers = !appPackageName.equals(instPackageN ame); | |
67 Log.i(TAG, "App PackageName: " + appPackageName); | |
68 if (instPackageNameDiffers) { | |
69 Log.i(TAG, "Inst PackageName: " + instPackageName); | |
70 } | |
71 | |
72 File appIncrementalRootDir = new File(MANAGED_DIR_PREFIX + appPackag eName); | |
73 File appLibDir = new File(appIncrementalRootDir, "lib"); | |
74 File appDexDir = new File(appIncrementalRootDir, "dex"); | |
75 File appInstallLockFile = new File(appIncrementalRootDir, "install.l ock"); | |
76 File appFirstRunLockFile = new File(appIncrementalRootDir, "firstrun .lock"); | |
77 File instIncrementalRootDir = new File(MANAGED_DIR_PREFIX + instPack ageName); | |
78 File instLibDir = new File(instIncrementalRootDir, "lib"); | |
79 File instDexDir = new File(instIncrementalRootDir, "dex"); | |
80 File instInstallLockFile = new File(instIncrementalRootDir, "install .lock"); | |
81 File instFirstRunLockFile = new File(instIncrementalRootDir , "first run.lock"); | |
82 | |
83 boolean isFirstRun = LockFile.installerLockExists(appFirstRunLockFil e) | |
84 || (instPackageNameDiffers | |
85 && LockFile.installerLockExists(instFirstRunLockF ile)); | |
59 if (isFirstRun) { | 86 if (isFirstRun) { |
60 if (mClassLoaderPatcher.mIsPrimaryProcess) { | 87 if (mClassLoaderPatcher.mIsPrimaryProcess) { |
61 // Wait for incremental_install.py to finish. | 88 // Wait for incremental_install.py to finish. |
62 LockFile.waitForInstallerLock(installLockFile, 30 * 1000); | 89 LockFile.waitForInstallerLock(appInstallLockFile, 30 * 1000) ; |
90 LockFile.waitForInstallerLock(instInstallLockFile, 30 * 1000 ); | |
63 } else { | 91 } else { |
64 // Wait for the browser process to create the optimized dex files | 92 // Wait for the browser process to create the optimized dex files |
65 // and copy the library files. | 93 // and copy the library files. |
66 LockFile.waitForInstallerLock(firstRunLockFile, 60 * 1000); | 94 LockFile.waitForInstallerLock(appFirstRunLockFile, 60 * 1000 ); |
95 LockFile.waitForInstallerLock(instFirstRunLockFile, 60 * 100 0); | |
67 } | 96 } |
68 } | 97 } |
69 | 98 |
70 mClassLoaderPatcher.importNativeLibs(libDir); | 99 mClassLoaderPatcher.importNativeLibs(instLibDir); |
71 mClassLoaderPatcher.loadDexFiles(dexDir); | 100 mClassLoaderPatcher.loadDexFiles(instDexDir); |
101 if (instPackageNameDiffers) { | |
102 mClassLoaderPatcher.importNativeLibs(appLibDir); | |
103 mClassLoaderPatcher.loadDexFiles(appDexDir); | |
104 } | |
72 | 105 |
73 if (isFirstRun && mClassLoaderPatcher.mIsPrimaryProcess) { | 106 if (isFirstRun && mClassLoaderPatcher.mIsPrimaryProcess) { |
74 LockFile.clearInstallerLock(firstRunLockFile); | 107 LockFile.clearInstallerLock(appFirstRunLockFile); |
108 if (instPackageNameDiffers) { | |
109 LockFile.clearInstallerLock(instFirstRunLockFile); | |
110 } | |
75 } | 111 } |
76 | 112 |
77 // mInstrumentationAppDir is one of a set of fields that is initiali zed only when | 113 // mInstrumentationAppDir is one of a set of fields that is initiali zed only when |
78 // instrumentation is active. | 114 // instrumentation is active. |
79 if (Reflect.getField(mActivityThread, "mInstrumentationAppDir") != n ull) { | 115 if (Reflect.getField(mActivityThread, "mInstrumentationAppDir") != n ull) { |
80 String realInstrumentationName = | 116 String realInstrumentationName = |
81 getClassNameFromMetadata(REAL_INSTRUMENTATION_META_DATA_ NAME); | 117 getClassNameFromMetadata(REAL_INSTRUMENTATION_META_DATA_ NAME, instContext); |
82 initInstrumentation(realInstrumentationName); | 118 initInstrumentation(realInstrumentationName); |
83 } else { | 119 } else { |
84 Log.i(TAG, "No instrumentation active."); | 120 Log.i(TAG, "No instrumentation active."); |
85 } | 121 } |
86 | 122 |
87 // Even when instrumentation is not enabled, ActivityThread uses a d efault | 123 // Even when instrumentation is not enabled, ActivityThread uses a d efault |
88 // Instrumentation instance internally. We hook it here in order to hook into the | 124 // Instrumentation instance internally. We hook it here in order to hook into the |
89 // call to Instrumentation.onCreate(). | 125 // call to Instrumentation.onCreate(). |
90 Reflect.setField(mActivityThread, "mInstrumentation", | 126 Reflect.setField(mActivityThread, "mInstrumentation", |
91 new BootstrapInstrumentation(this)); | 127 new BootstrapInstrumentation(this)); |
92 | 128 |
93 // attachBaseContext() is called from ActivityThread#handleBindAppli cation() and | 129 // attachBaseContext() is called from ActivityThread#handleBindAppli cation() and |
94 // Application#mApplication is changed right after we return. Thus, we cannot swap | 130 // Application#mApplication is changed right after we return. Thus, we cannot swap |
95 // the Application instances until onCreate() is called. | 131 // the Application instances until onCreate() is called. |
96 String realApplicationName = getClassNameFromMetadata(REAL_APP_META_ DATA_NAME); | 132 String realApplicationName = getClassNameFromMetadata(REAL_APP_META_ DATA_NAME, context); |
97 Log.i(TAG, "Instantiating " + realApplicationName); | 133 Log.i(TAG, "Instantiating " + realApplicationName); |
98 mRealApplication = | 134 mRealApplication = |
99 (Application) Reflect.newInstance(Class.forName(realApplicat ionName)); | 135 (Application) Reflect.newInstance(Class.forName(realApplicat ionName)); |
100 Reflect.invokeMethod(mRealApplication, "attachBaseContext", context) ; | 136 Reflect.invokeMethod(mRealApplication, "attachBaseContext", context) ; |
101 | 137 |
102 // Between attachBaseContext() and onCreate(), ActivityThread tries to instantiate | 138 // Between attachBaseContext() and onCreate(), ActivityThread tries to instantiate |
103 // all ContentProviders. The ContentProviders break without the corr ect Application | 139 // all ContentProviders. The ContentProviders break without the corr ect Application |
104 // class being installed, so temporarily pretend there are no provid ers, and then | 140 // class being installed, so temporarily pretend there are no provid ers, and then |
105 // instantiate them explicitly within onCreate(). | 141 // instantiate them explicitly within onCreate(). |
106 disableContentProviders(); | 142 disableContentProviders(); |
107 Log.i(TAG, "Waiting for Instrumentation.onCreate"); | 143 Log.i(TAG, "Waiting for Instrumentation.onCreate"); |
108 } catch (Exception e) { | 144 } catch (Exception e) { |
109 throw new RuntimeException("Incremental install failed.", e); | 145 throw new RuntimeException("Incremental install failed.", e); |
110 } | 146 } |
111 } | 147 } |
112 | 148 |
113 /** | 149 /** |
114 * Returns the fully-qualified class name for the given key, stored in a | 150 * Returns the fully-qualified class name for the given key, stored in a |
115 * <meta> witin the manifest. | 151 * <meta> witin the manifest. |
116 */ | 152 */ |
117 private String getClassNameFromMetadata(String key) throws NameNotFoundExcep tion { | 153 private String getClassNameFromMetadata(String key, Context context) |
nyquist
2016/02/10 07:08:32
Nit: Could this be static now?
agrieve
2016/02/10 16:47:15
Done.
| |
118 ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPack ageName(), | 154 throws NameNotFoundException { |
155 String pkgName = context.getPackageName(); | |
156 ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo (pkgName, | |
119 PackageManager.GET_META_DATA); | 157 PackageManager.GET_META_DATA); |
120 String value = appInfo.metaData.getString(key); | 158 String value = appInfo.metaData.getString(key); |
121 if (value != null && !value.contains(".")) { | 159 if (value != null && !value.contains(".")) { |
122 value = getPackageName() + "." + value; | 160 value = pkgName + "." + value; |
123 } | 161 } |
124 return value; | 162 return value; |
125 } | 163 } |
126 | 164 |
127 /** | 165 /** |
128 * Instantiates and initializes mRealInstrumentation (the real Instrumentati on class). | 166 * Instantiates and initializes mRealInstrumentation (the real Instrumentati on class). |
129 */ | 167 */ |
130 private void initInstrumentation(String realInstrumentationName) | 168 private void initInstrumentation(String realInstrumentationName) |
131 throws ReflectiveOperationException { | 169 throws ReflectiveOperationException { |
132 Instrumentation oldInstrumentation = | |
133 (Instrumentation) Reflect.getField(mActivityThread, "mInstrument ation"); | |
134 if (realInstrumentationName == null) { | 170 if (realInstrumentationName == null) { |
135 // This is the case when an incremental app is used as a target for an instrumentation | 171 // This is the case when an incremental app is used as a target for an instrumentation |
136 // test. In this case, ActivityThread can instantiate the proper cla ss just fine since | 172 // test. In this case, ActivityThread can instantiate the proper cla ss just fine since |
137 // it exists within the test apk (as opposed to the incremental apk- under-test). | 173 // it exists within the test apk (as opposed to the incremental apk- under-test). |
138 Log.i(TAG, "Running with external instrumentation"); | 174 Log.i(TAG, "Running with external instrumentation"); |
139 mRealInstrumentation = oldInstrumentation; | 175 mRealInstrumentation = mOrigInstrumentation; |
140 return; | 176 return; |
141 } | 177 } |
142 // For unit tests, the instrumentation class is replaced in the manifest by a build step | 178 // For unit tests, the instrumentation class is replaced in the manifest by a build step |
143 // because ActivityThread tries to instantiate it before we get a chance to load the | 179 // because ActivityThread tries to instantiate it before we get a chance to load the |
144 // incremental dex files. | 180 // incremental dex files. |
145 Log.i(TAG, "Instantiating instrumentation " + realInstrumentationName); | 181 Log.i(TAG, "Instantiating instrumentation " + realInstrumentationName); |
146 mRealInstrumentation = (Instrumentation) Reflect.newInstance( | 182 mRealInstrumentation = (Instrumentation) Reflect.newInstance( |
147 Class.forName(realInstrumentationName)); | 183 Class.forName(realInstrumentationName)); |
148 | 184 |
149 // Initialize the fields that are set by Instrumentation.init(). | 185 // Initialize the fields that are set by Instrumentation.init(). |
150 String[] initFields = {"mThread", "mMessageQueue", "mInstrContext", "mAp pContext", | 186 String[] initFields = {"mThread", "mMessageQueue", "mInstrContext", "mAp pContext", |
151 "mWatcher", "mUiAutomationConnection"}; | 187 "mWatcher", "mUiAutomationConnection"}; |
152 for (String fieldName : initFields) { | 188 for (String fieldName : initFields) { |
153 Reflect.setField(mRealInstrumentation, fieldName, | 189 Reflect.setField(mRealInstrumentation, fieldName, |
154 Reflect.getField(oldInstrumentation, fieldName)); | 190 Reflect.getField(mOrigInstrumentation, fieldName)); |
155 } | 191 } |
156 // But make sure the correct ComponentName is used. | 192 // But make sure the correct ComponentName is used. |
157 ComponentName newName = new ComponentName( | 193 ComponentName newName = new ComponentName( |
158 oldInstrumentation.getComponentName().getPackageName(), realInst rumentationName); | 194 mOrigInstrumentation.getComponentName().getPackageName(), realIn strumentationName); |
159 Reflect.setField(mRealInstrumentation, "mComponent", newName); | 195 Reflect.setField(mRealInstrumentation, "mComponent", newName); |
160 } | 196 } |
161 | 197 |
162 /** | 198 /** |
163 * Called by BootstrapInstrumentation from Instrumentation.onCreate(). | 199 * Called by BootstrapInstrumentation from Instrumentation.onCreate(). |
164 * This happens regardless of whether or not instrumentation is enabled. | 200 * This happens regardless of whether or not instrumentation is enabled. |
165 */ | 201 */ |
166 void onInstrumentationCreate(Bundle arguments) { | 202 void onInstrumentationCreate(Bundle arguments) { |
167 Log.i(TAG, "Instrumentation.onCreate() called. Swapping references."); | 203 Log.i(TAG, "Instrumentation.onCreate() called. Swapping references."); |
168 try { | 204 try { |
(...skipping 68 matching lines...) Loading... | |
237 for (Map.Entry<String, WeakReference<?>> entry : packageMap.entrySet ()) { | 273 for (Map.Entry<String, WeakReference<?>> entry : packageMap.entrySet ()) { |
238 Object loadedApk = entry.getValue().get(); | 274 Object loadedApk = entry.getValue().get(); |
239 if (loadedApk != null && Reflect.getField(loadedApk, "mApplicati on") == this) { | 275 if (loadedApk != null && Reflect.getField(loadedApk, "mApplicati on") == this) { |
240 Reflect.setField(loadedApk, "mApplication", mRealApplication ); | 276 Reflect.setField(loadedApk, "mApplication", mRealApplication ); |
241 Reflect.setField(mRealApplication, "mLoadedApk", loadedApk); | 277 Reflect.setField(mRealApplication, "mLoadedApk", loadedApk); |
242 } | 278 } |
243 } | 279 } |
244 } | 280 } |
245 } | 281 } |
246 } | 282 } |
OLD | NEW |