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; |
| 9 import android.content.ComponentName; |
8 import android.content.Context; | 10 import android.content.Context; |
9 import android.content.pm.ApplicationInfo; | 11 import android.content.pm.ApplicationInfo; |
10 import android.content.pm.PackageManager; | 12 import android.content.pm.PackageManager; |
11 import android.content.pm.PackageManager.NameNotFoundException; | 13 import android.content.pm.PackageManager.NameNotFoundException; |
| 14 import android.os.Bundle; |
12 import android.util.Log; | 15 import android.util.Log; |
13 | 16 |
14 import java.io.File; | 17 import java.io.File; |
15 import java.lang.ref.WeakReference; | 18 import java.lang.ref.WeakReference; |
16 import java.util.List; | 19 import java.util.List; |
17 import java.util.Map; | 20 import java.util.Map; |
18 | 21 |
19 /** | 22 /** |
20 * An Application that replaces itself with another Application (as defined in | 23 * An Application that replaces itself with another Application (as defined in |
21 * the "incremental-install-real-app" meta-data tag within the | 24 * an AndroidManifext.xml meta-data tag). It loads the other application only |
22 * AndroidManifest.xml). It loads the other application only after side-loading | 25 * after side-loading its .so and .dex files from /data/local/tmp. |
23 * its .so and .dex files from /data/local/tmp. | 26 * |
| 27 * This class is highly dependent on the private implementation details of |
| 28 * Android's ActivityThread.java. However, it has been tested to work with |
| 29 * JellyBean through Marshmallow. |
24 */ | 30 */ |
25 public final class BootstrapApplication extends Application { | 31 public final class BootstrapApplication extends Application { |
26 private static final String TAG = "cr.incrementalinstall"; | 32 private static final String TAG = "cr.incrementalinstall"; |
27 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-"; |
28 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 = |
| 36 "incremental-install-real-instrumentation"; |
29 | 37 |
30 private ClassLoaderPatcher mClassLoaderPatcher; | 38 private ClassLoaderPatcher mClassLoaderPatcher; |
31 private Application mRealApplication; | 39 private Application mRealApplication; |
| 40 private Instrumentation mRealInstrumentation; |
32 private Object mStashedProviderList; | 41 private Object mStashedProviderList; |
33 private Object mActivityThread; | 42 private Object mActivityThread; |
34 | 43 |
35 @Override | 44 @Override |
36 protected void attachBaseContext(Context context) { | 45 protected void attachBaseContext(Context context) { |
37 super.attachBaseContext(context); | 46 super.attachBaseContext(context); |
38 File incrementalRootDir = new File(MANAGED_DIR_PREFIX + context.getPacka
geName()); | 47 File incrementalRootDir = new File(MANAGED_DIR_PREFIX + context.getPacka
geName()); |
39 File libDir = new File(incrementalRootDir, "lib"); | 48 File libDir = new File(incrementalRootDir, "lib"); |
40 File dexDir = new File(incrementalRootDir, "dex"); | 49 File dexDir = new File(incrementalRootDir, "dex"); |
41 File installLockFile = new File(incrementalRootDir, "install.lock"); | 50 File installLockFile = new File(incrementalRootDir, "install.lock"); |
(...skipping 16 matching lines...) Expand all Loading... |
58 } | 67 } |
59 } | 68 } |
60 | 69 |
61 mClassLoaderPatcher.importNativeLibs(libDir); | 70 mClassLoaderPatcher.importNativeLibs(libDir); |
62 mClassLoaderPatcher.loadDexFiles(dexDir); | 71 mClassLoaderPatcher.loadDexFiles(dexDir); |
63 | 72 |
64 if (isFirstRun && mClassLoaderPatcher.mIsPrimaryProcess) { | 73 if (isFirstRun && mClassLoaderPatcher.mIsPrimaryProcess) { |
65 LockFile.clearInstallerLock(firstRunLockFile); | 74 LockFile.clearInstallerLock(firstRunLockFile); |
66 } | 75 } |
67 | 76 |
| 77 Bundle metadata = getManifestMetadata(); |
| 78 // mInstrumentationAppDir is one of a set of fields that is initiali
zed only when |
| 79 // instrumentation is active. |
| 80 if (Reflect.getField(mActivityThread, "mInstrumentationAppDir") != n
ull) { |
| 81 initInstrumentation(metadata.getString(REAL_INSTRUMENTATION_META
_DATA_NAME)); |
| 82 } else { |
| 83 Log.i(TAG, "No instrumentation active."); |
| 84 } |
| 85 |
| 86 // Even when instrumentation is not enabled, ActivityThread uses a d
efault |
| 87 // Instrumentation instance internally. We hook it here in order to
hook into the |
| 88 // call to Instrumentation.onCreate(). |
| 89 Reflect.setField(mActivityThread, "mInstrumentation", |
| 90 new BootstrapInstrumentation(this)); |
| 91 |
68 // attachBaseContext() is called from ActivityThread#handleBindAppli
cation() and | 92 // attachBaseContext() is called from ActivityThread#handleBindAppli
cation() and |
69 // Application#mApplication is changed right after we return. Thus,
we cannot swap | 93 // Application#mApplication is changed right after we return. Thus,
we cannot swap |
70 // the Application instances until onCreate() is called. | 94 // the Application instances until onCreate() is called. |
71 String realApplicationName = getRealApplicationName(); | 95 String realApplicationName = metadata.getString(REAL_APP_META_DATA_N
AME); |
72 Log.i(TAG, "Instantiating " + realApplicationName); | 96 Log.i(TAG, "Instantiating " + realApplicationName); |
73 mRealApplication = | 97 mRealApplication = |
74 (Application) Reflect.newInstance(Class.forName(realApplicat
ionName)); | 98 (Application) Reflect.newInstance(Class.forName(realApplicat
ionName)); |
75 Reflect.invokeMethod(mRealApplication, "attachBaseContext", context)
; | 99 Reflect.invokeMethod(mRealApplication, "attachBaseContext", context)
; |
76 | 100 |
77 // Between attachBaseContext() and onCreate(), ActivityThread tries
to instantiate | 101 // Between attachBaseContext() and onCreate(), ActivityThread tries
to instantiate |
78 // all ContentProviders. The ContentProviders break without the corr
ect Application | 102 // all ContentProviders. The ContentProviders break without the corr
ect Application |
79 // class being installed, so temporarily pretend there are no provid
ers, and then | 103 // class being installed, so temporarily pretend there are no provid
ers, and then |
80 // instantiate them explicitly within onCreate(). | 104 // instantiate them explicitly within onCreate(). |
81 disableContentProviders(); | 105 disableContentProviders(); |
82 Log.i(TAG, "Waiting for onCreate"); | 106 Log.i(TAG, "Waiting for Instrumentation.onCreate"); |
83 } catch (Exception e) { | 107 } catch (Exception e) { |
84 throw new RuntimeException("Incremental install failed.", e); | 108 throw new RuntimeException("Incremental install failed.", e); |
85 } | 109 } |
| 110 } |
| 111 |
| 112 /** |
| 113 * Instantiates and initializes mRealInstrumentation (the real Instrumentati
on class). |
| 114 */ |
| 115 private void initInstrumentation(String realInstrumentationName) |
| 116 throws ReflectiveOperationException { |
| 117 Log.i(TAG, "Instantiating instrumentation " + realInstrumentationName); |
| 118 mRealInstrumentation = (Instrumentation) Reflect.newInstance( |
| 119 Class.forName(realInstrumentationName)); |
| 120 Instrumentation oldInstrumentation = |
| 121 (Instrumentation) Reflect.getField(mActivityThread, "mInstrument
ation"); |
| 122 |
| 123 // Initialize the fields that are set by Instrumentation.init(). |
| 124 String[] initFields = {"mThread", "mMessageQueue", "mInstrContext", "mAp
pContext", |
| 125 "mWatcher", "mUiAutomationConnection"}; |
| 126 for (String fieldName : initFields) { |
| 127 Reflect.setField(mRealInstrumentation, fieldName, |
| 128 Reflect.getField(oldInstrumentation, fieldName)); |
| 129 } |
| 130 // But make sure the correct ComponentName is used. |
| 131 ComponentName newName = new ComponentName( |
| 132 oldInstrumentation.getComponentName().getPackageName(), realInst
rumentationName); |
| 133 Reflect.setField(mRealInstrumentation, "mComponent", newName); |
| 134 } |
| 135 |
| 136 /** |
| 137 * Called by BootstrapInstrumentation from Instrumentation.onCreate(). |
| 138 * This happens regardless of whether or not instrumentation is enabled. |
| 139 */ |
| 140 void onInstrumentationCreate(Bundle arguments) { |
| 141 Log.i(TAG, "Instrumentation.onCreate() called. Swapping references."); |
| 142 try { |
| 143 swapApplicationReferences(); |
| 144 enableContentProviders(); |
| 145 if (mRealInstrumentation != null) { |
| 146 Reflect.setField(mActivityThread, "mInstrumentation", mRealInstr
umentation); |
| 147 mRealInstrumentation.onCreate(arguments); |
| 148 } |
| 149 } catch (Exception e) { |
| 150 throw new RuntimeException("Incremental install failed.", e); |
| 151 } |
86 } | 152 } |
87 | 153 |
88 @Override | 154 @Override |
89 public void onCreate() { | 155 public void onCreate() { |
90 super.onCreate(); | 156 super.onCreate(); |
91 try { | 157 try { |
92 Log.i(TAG, "onCreate() called. Swapping Application references"); | 158 Log.i(TAG, "Application.onCreate() called."); |
93 swapApplicationReferences(); | |
94 enableContentProviders(); | |
95 Log.i(TAG, "Calling onCreate"); | |
96 mRealApplication.onCreate(); | 159 mRealApplication.onCreate(); |
97 } catch (Exception e) { | 160 } catch (Exception e) { |
98 throw new RuntimeException("Incremental install failed.", e); | 161 throw new RuntimeException("Incremental install failed.", e); |
99 } | 162 } |
100 } | 163 } |
101 | 164 |
102 /** | 165 /** |
103 * Returns the class name of the real Application class (recorded in the | 166 * Returns the class name of the real Application class (recorded in the |
104 * AndroidManifest.xml) | 167 * AndroidManifest.xml) |
105 */ | 168 */ |
106 private String getRealApplicationName() throws NameNotFoundException { | 169 private Bundle getManifestMetadata() throws NameNotFoundException { |
107 ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPack
ageName(), | 170 ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPack
ageName(), |
108 PackageManager.GET_META_DATA); | 171 PackageManager.GET_META_DATA); |
109 return appInfo.metaData.getString(REAL_APP_META_DATA_NAME); | 172 return appInfo.metaData; |
110 } | 173 } |
111 | 174 |
112 /** | 175 /** |
113 * Nulls out ActivityThread.mBoundApplication.providers. | 176 * Nulls out ActivityThread.mBoundApplication.providers. |
114 */ | 177 */ |
115 private void disableContentProviders() throws ReflectiveOperationException { | 178 private void disableContentProviders() throws ReflectiveOperationException { |
116 Object data = Reflect.getField(mActivityThread, "mBoundApplication"); | 179 Object data = Reflect.getField(mActivityThread, "mBoundApplication"); |
117 mStashedProviderList = Reflect.getField(data, "providers"); | 180 mStashedProviderList = Reflect.getField(data, "providers"); |
118 Reflect.setField(data, "providers", null); | 181 Reflect.setField(data, "providers", null); |
119 } | 182 } |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
158 for (Map.Entry<String, WeakReference<?>> entry : packageMap.entrySet
()) { | 221 for (Map.Entry<String, WeakReference<?>> entry : packageMap.entrySet
()) { |
159 Object loadedApk = entry.getValue().get(); | 222 Object loadedApk = entry.getValue().get(); |
160 if (loadedApk != null && Reflect.getField(loadedApk, "mApplicati
on") == this) { | 223 if (loadedApk != null && Reflect.getField(loadedApk, "mApplicati
on") == this) { |
161 Reflect.setField(loadedApk, "mApplication", mRealApplication
); | 224 Reflect.setField(loadedApk, "mApplication", mRealApplication
); |
162 Reflect.setField(mRealApplication, "mLoadedApk", loadedApk); | 225 Reflect.setField(mRealApplication, "mLoadedApk", loadedApk); |
163 } | 226 } |
164 } | 227 } |
165 } | 228 } |
166 } | 229 } |
167 } | 230 } |
OLD | NEW |