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; |
| 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) { |
| 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(); |
| 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); |
| 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 |