| 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
|
| index 81e06a23d3ea5108a1b6ea09966a6f5e2c4045b4..e932c1bde3df5e5950af64171e32a5e126891fda 100644
|
| --- a/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java
|
| +++ b/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java
|
| @@ -5,10 +5,13 @@
|
| package org.chromium.incrementalinstall;
|
|
|
| import android.app.Application;
|
| +import android.app.Instrumentation;
|
| +import android.content.ComponentName;
|
| import android.content.Context;
|
| import android.content.pm.ApplicationInfo;
|
| import android.content.pm.PackageManager;
|
| import android.content.pm.PackageManager.NameNotFoundException;
|
| +import android.os.Bundle;
|
| import android.util.Log;
|
|
|
| import java.io.File;
|
| @@ -18,17 +21,23 @@ 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.
|
| + * an AndroidManifext.xml meta-data tag). It loads the other application only
|
| + * after side-loading its .so and .dex files from /data/local/tmp.
|
| + *
|
| + * This class is highly dependent on the private implementation details of
|
| + * Android's ActivityThread.java. However, it has been tested to work with
|
| + * JellyBean through Marshmallow.
|
| */
|
| 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 static final String REAL_INSTRUMENTATION_META_DATA_NAME =
|
| + "incremental-install-real-instrumentation";
|
|
|
| private ClassLoaderPatcher mClassLoaderPatcher;
|
| private Application mRealApplication;
|
| + private Instrumentation mRealInstrumentation;
|
| private Object mStashedProviderList;
|
| private Object mActivityThread;
|
|
|
| @@ -65,10 +74,25 @@ public final class BootstrapApplication extends Application {
|
| LockFile.clearInstallerLock(firstRunLockFile);
|
| }
|
|
|
| + Bundle metadata = getManifestMetadata();
|
| + // mInstrumentationAppDir is one of a set of fields that is initialized only when
|
| + // instrumentation is active.
|
| + if (Reflect.getField(mActivityThread, "mInstrumentationAppDir") != null) {
|
| + initInstrumentation(metadata.getString(REAL_INSTRUMENTATION_META_DATA_NAME));
|
| + } else {
|
| + Log.i(TAG, "No instrumentation active.");
|
| + }
|
| +
|
| + // Even when instrumentation is not enabled, ActivityThread uses a default
|
| + // Instrumentation instance internally. We hook it here in order to hook into the
|
| + // call to Instrumentation.onCreate().
|
| + Reflect.setField(mActivityThread, "mInstrumentation",
|
| + new BootstrapInstrumentation(this));
|
| +
|
| // 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();
|
| + String realApplicationName = metadata.getString(REAL_APP_META_DATA_NAME);
|
| Log.i(TAG, "Instantiating " + realApplicationName);
|
| mRealApplication =
|
| (Application) Reflect.newInstance(Class.forName(realApplicationName));
|
| @@ -79,7 +103,49 @@ public final class BootstrapApplication extends 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");
|
| + Log.i(TAG, "Waiting for Instrumentation.onCreate");
|
| + } catch (Exception e) {
|
| + throw new RuntimeException("Incremental install failed.", e);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Instantiates and initializes mRealInstrumentation (the real Instrumentation class).
|
| + */
|
| + private void initInstrumentation(String realInstrumentationName)
|
| + throws ReflectiveOperationException {
|
| + Log.i(TAG, "Instantiating instrumentation " + realInstrumentationName);
|
| + mRealInstrumentation = (Instrumentation) Reflect.newInstance(
|
| + Class.forName(realInstrumentationName));
|
| + Instrumentation oldInstrumentation =
|
| + (Instrumentation) Reflect.getField(mActivityThread, "mInstrumentation");
|
| +
|
| + // Initialize the fields that are set by Instrumentation.init().
|
| + String[] initFields = {"mThread", "mMessageQueue", "mInstrContext", "mAppContext",
|
| + "mWatcher", "mUiAutomationConnection"};
|
| + for (String fieldName : initFields) {
|
| + Reflect.setField(mRealInstrumentation, fieldName,
|
| + Reflect.getField(oldInstrumentation, fieldName));
|
| + }
|
| + // But make sure the correct ComponentName is used.
|
| + ComponentName newName = new ComponentName(
|
| + oldInstrumentation.getComponentName().getPackageName(), realInstrumentationName);
|
| + Reflect.setField(mRealInstrumentation, "mComponent", newName);
|
| + }
|
| +
|
| + /**
|
| + * Called by BootstrapInstrumentation from Instrumentation.onCreate().
|
| + * This happens regardless of whether or not instrumentation is enabled.
|
| + */
|
| + void onInstrumentationCreate(Bundle arguments) {
|
| + Log.i(TAG, "Instrumentation.onCreate() called. Swapping references.");
|
| + try {
|
| + swapApplicationReferences();
|
| + enableContentProviders();
|
| + if (mRealInstrumentation != null) {
|
| + Reflect.setField(mActivityThread, "mInstrumentation", mRealInstrumentation);
|
| + mRealInstrumentation.onCreate(arguments);
|
| + }
|
| } catch (Exception e) {
|
| throw new RuntimeException("Incremental install failed.", e);
|
| }
|
| @@ -89,10 +155,7 @@ public final class BootstrapApplication extends Application {
|
| public void onCreate() {
|
| super.onCreate();
|
| try {
|
| - Log.i(TAG, "onCreate() called. Swapping Application references");
|
| - swapApplicationReferences();
|
| - enableContentProviders();
|
| - Log.i(TAG, "Calling onCreate");
|
| + Log.i(TAG, "Application.onCreate() called.");
|
| mRealApplication.onCreate();
|
| } catch (Exception e) {
|
| throw new RuntimeException("Incremental install failed.", e);
|
| @@ -103,10 +166,10 @@ public final class BootstrapApplication extends Application {
|
| * Returns the class name of the real Application class (recorded in the
|
| * AndroidManifest.xml)
|
| */
|
| - private String getRealApplicationName() throws NameNotFoundException {
|
| + private Bundle getManifestMetadata() throws NameNotFoundException {
|
| ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPackageName(),
|
| PackageManager.GET_META_DATA);
|
| - return appInfo.metaData.getString(REAL_APP_META_DATA_NAME);
|
| + return appInfo.metaData;
|
| }
|
|
|
| /**
|
|
|