Chromium Code Reviews| 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...) Expand all 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...) Expand 10 before | Expand all | Expand 10 after 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 |