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 static String getClassNameFromMetadata(String key, Context context) |
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 |