| Index: build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java
|
| diff --git a/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java b/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..5d3bf112d0ee0b5f23b8ae4ebb37efba0a9740d4
|
| --- /dev/null
|
| +++ b/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java
|
| @@ -0,0 +1,167 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.incrementalinstall;
|
| +
|
| +import android.app.Application;
|
| +import android.content.Context;
|
| +import android.content.pm.ApplicationInfo;
|
| +import android.content.pm.PackageManager;
|
| +import android.content.pm.PackageManager.NameNotFoundException;
|
| +import android.util.Log;
|
| +
|
| +import java.io.File;
|
| +import java.lang.ref.WeakReference;
|
| +import java.util.List;
|
| +import java.util.Map;
|
| +
|
| +/**
|
| + * An Application that replaces itself with another Application (as defined in
|
| + * the "incremental-install-real-app" meta-data tag within the
|
| + * AndroidManifest.xml). It loads the other application only after side-loading
|
| + * its .so and .dex files from /data/local/tmp.
|
| + */
|
| +public final class BootstrapApplication extends Application {
|
| + private static final String TAG = "cr.incrementalinstall";
|
| + private static final String MANAGED_DIR_PREFIX = "/data/local/tmp/incremental-app-";
|
| + private static final String REAL_APP_META_DATA_NAME = "incremental-install-real-app";
|
| +
|
| + private ClassLoaderPatcher mClassLoaderPatcher;
|
| + private Application mRealApplication;
|
| + private Object mStashedProviderList;
|
| + private Object mActivityThread;
|
| +
|
| + @Override
|
| + protected void attachBaseContext(Context context) {
|
| + super.attachBaseContext(context);
|
| + File incrementalRootDir = new File(MANAGED_DIR_PREFIX + context.getPackageName());
|
| + File libDir = new File(incrementalRootDir, "lib");
|
| + File dexDir = new File(incrementalRootDir, "dex");
|
| + File installLockFile = new File(incrementalRootDir, "install.lock");
|
| + File firstRunLockFile = new File(incrementalRootDir, "firstrun.lock");
|
| +
|
| + try {
|
| + mActivityThread = Reflect.invokeMethod(Class.forName("android.app.ActivityThread"),
|
| + "currentActivityThread");
|
| + mClassLoaderPatcher = new ClassLoaderPatcher(context);
|
| +
|
| + boolean isFirstRun = LockFile.installerLockExists(firstRunLockFile);
|
| + if (isFirstRun) {
|
| + if (mClassLoaderPatcher.mIsPrimaryProcess) {
|
| + // Wait for incremental_install.py to finish.
|
| + LockFile.waitForInstallerLock(installLockFile, 20 * 1000);
|
| + } else {
|
| + // Wait for the browser process to create the optimized dex files
|
| + // (and for M+, copy the library files).
|
| + LockFile.waitForInstallerLock(firstRunLockFile, 30 * 1000);
|
| + }
|
| + }
|
| +
|
| + mClassLoaderPatcher.importNativeLibs(libDir);
|
| + mClassLoaderPatcher.loadDexFiles(dexDir);
|
| +
|
| + if (isFirstRun && mClassLoaderPatcher.mIsPrimaryProcess) {
|
| + LockFile.clearInstallerLock(firstRunLockFile);
|
| + }
|
| +
|
| + // attachBaseContext() is called from ActivityThread#handleBindApplication() and
|
| + // Application#mApplication is changed right after we return. Thus, we cannot swap
|
| + // the Application instances until onCreate() is called.
|
| + String realApplicationName = getRealApplicationName();
|
| + Log.i(TAG, "Instantiating " + realApplicationName);
|
| + mRealApplication =
|
| + (Application) Reflect.newInstance(Class.forName(realApplicationName));
|
| + Reflect.invokeMethod(mRealApplication, "attachBaseContext", context);
|
| +
|
| + // Between attachBaseContext() and onCreate(), ActivityThread tries to instantiate
|
| + // all ContentProviders. The ContentProviders break without the correct Application
|
| + // class being installed, so temporarily pretend there are no providers, and then
|
| + // instantiate them explicitly within onCreate().
|
| + disableContentProviders();
|
| + Log.i(TAG, "Waiting for onCreate");
|
| + } catch (Exception e) {
|
| + throw new RuntimeException("Incremental install failed.", e);
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void onCreate() {
|
| + super.onCreate();
|
| + try {
|
| + Log.i(TAG, "onCreate() called. Swapping Application references");
|
| + swapApplicationReferences();
|
| + enableContentProviders();
|
| + Log.i(TAG, "Calling onCreate");
|
| + mRealApplication.onCreate();
|
| + } catch (Exception e) {
|
| + throw new RuntimeException("Incremental install failed.", e);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Returns the class name of the real Application class (recorded in the
|
| + * AndroidManifest.xml)
|
| + */
|
| + private String getRealApplicationName() throws NameNotFoundException {
|
| + ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPackageName(),
|
| + PackageManager.GET_META_DATA);
|
| + return appInfo.metaData.getString(REAL_APP_META_DATA_NAME);
|
| + }
|
| +
|
| + /**
|
| + * Nulls out ActivityThread.mBoundApplication.providers.
|
| + */
|
| + private void disableContentProviders() throws ReflectiveOperationException {
|
| + Object data = Reflect.getField(mActivityThread, "mBoundApplication");
|
| + mStashedProviderList = Reflect.getField(data, "providers");
|
| + Reflect.setField(data, "providers", null);
|
| + }
|
| +
|
| + /**
|
| + * Restores the value of ActivityThread.mBoundApplication.providers, and invokes
|
| + * ActivityThread#installContentProviders().
|
| + */
|
| + private void enableContentProviders() throws ReflectiveOperationException {
|
| + Object data = Reflect.getField(mActivityThread, "mBoundApplication");
|
| + Reflect.setField(data, "providers", mStashedProviderList);
|
| + if (mStashedProviderList != null && mClassLoaderPatcher.mIsPrimaryProcess) {
|
| + Log.i(TAG, "Instantiating content providers");
|
| + Reflect.invokeMethod(mActivityThread, "installContentProviders", mRealApplication,
|
| + mStashedProviderList);
|
| + }
|
| + mStashedProviderList = null;
|
| + }
|
| +
|
| + /**
|
| + * Changes all fields within framework classes that have stored an reference to this
|
| + * BootstrapApplication to instead store references to mRealApplication.
|
| + * @throws NoSuchFieldException
|
| + */
|
| + @SuppressWarnings("unchecked")
|
| + private void swapApplicationReferences() throws ReflectiveOperationException {
|
| + if (Reflect.getField(mActivityThread, "mInitialApplication") == this) {
|
| + Reflect.setField(mActivityThread, "mInitialApplication", mRealApplication);
|
| + }
|
| +
|
| + List<Application> allApplications =
|
| + (List<Application>) Reflect.getField(mActivityThread, "mAllApplications");
|
| + for (int i = 0; i < allApplications.size(); i++) {
|
| + if (allApplications.get(i) == this) {
|
| + allApplications.set(i, mRealApplication);
|
| + }
|
| + }
|
| +
|
| + for (String fieldName : new String[] { "mPackages", "mResourcePackages" }) {
|
| + Map<String, WeakReference<?>> packageMap =
|
| + (Map<String, WeakReference<?>>) Reflect.getField(mActivityThread, fieldName);
|
| + for (Map.Entry<String, WeakReference<?>> entry : packageMap.entrySet()) {
|
| + Object loadedApk = entry.getValue().get();
|
| + if (loadedApk != null && Reflect.getField(loadedApk, "mApplication") == this) {
|
| + Reflect.setField(loadedApk, "mApplication", mRealApplication);
|
| + Reflect.setField(mRealApplication, "mLoadedApk", loadedApk);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +}
|
|
|