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.
5 package org
.chromium
.incrementalinstall
;
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
;
15 import java
.lang
.ref
.WeakReference
;
16 import java
.util
.List
;
20 * An Application that replaces itself with another Application (as defined in
21 * the "incremental-install-real-app" meta-data tag within the
22 * AndroidManifest.xml). It loads the other application only after side-loading
23 * its .so and .dex files from /data/local/tmp.
25 public final class BootstrapApplication
extends Application
{
26 private static final String TAG
= "cr.incrementalinstall";
27 private static final String MANAGED_DIR_PREFIX
= "/data/local/tmp/incremental-app-";
28 private static final String REAL_APP_META_DATA_NAME
= "incremental-install-real-app";
30 private ClassLoaderPatcher mClassLoaderPatcher
;
31 private Application mRealApplication
;
32 private Object mStashedProviderList
;
33 private Object mActivityThread
;
36 protected void attachBaseContext(Context context
) {
37 super.attachBaseContext(context
);
38 File incrementalRootDir
= new File(MANAGED_DIR_PREFIX
+ context
.getPackageName());
39 File libDir
= new File(incrementalRootDir
, "lib");
40 File dexDir
= new File(incrementalRootDir
, "dex");
41 File installLockFile
= new File(incrementalRootDir
, "install.lock");
42 File firstRunLockFile
= new File(incrementalRootDir
, "firstrun.lock");
45 mActivityThread
= Reflect
.invokeMethod(Class
.forName("android.app.ActivityThread"),
46 "currentActivityThread");
47 mClassLoaderPatcher
= new ClassLoaderPatcher(context
);
49 boolean isFirstRun
= LockFile
.installerLockExists(firstRunLockFile
);
51 if (mClassLoaderPatcher
.mIsPrimaryProcess
) {
52 // Wait for incremental_install.py to finish.
53 LockFile
.waitForInstallerLock(installLockFile
, 20 * 1000);
55 // Wait for the browser process to create the optimized dex files
56 // (and for M+, copy the library files).
57 LockFile
.waitForInstallerLock(firstRunLockFile
, 30 * 1000);
61 mClassLoaderPatcher
.importNativeLibs(libDir
);
62 mClassLoaderPatcher
.loadDexFiles(dexDir
);
64 if (isFirstRun
&& mClassLoaderPatcher
.mIsPrimaryProcess
) {
65 LockFile
.clearInstallerLock(firstRunLockFile
);
68 // attachBaseContext() is called from ActivityThread#handleBindApplication() and
69 // Application#mApplication is changed right after we return. Thus, we cannot swap
70 // the Application instances until onCreate() is called.
71 String realApplicationName
= getRealApplicationName();
72 Log
.i(TAG
, "Instantiating " + realApplicationName
);
74 (Application
) Reflect
.newInstance(Class
.forName(realApplicationName
));
75 Reflect
.invokeMethod(mRealApplication
, "attachBaseContext", context
);
77 // Between attachBaseContext() and onCreate(), ActivityThread tries to instantiate
78 // all ContentProviders. The ContentProviders break without the correct Application
79 // class being installed, so temporarily pretend there are no providers, and then
80 // instantiate them explicitly within onCreate().
81 disableContentProviders();
82 Log
.i(TAG
, "Waiting for onCreate");
83 } catch (Exception e
) {
84 throw new RuntimeException("Incremental install failed.", e
);
89 public void onCreate() {
92 Log
.i(TAG
, "onCreate() called. Swapping Application references");
93 swapApplicationReferences();
94 enableContentProviders();
95 Log
.i(TAG
, "Calling onCreate");
96 mRealApplication
.onCreate();
97 } catch (Exception e
) {
98 throw new RuntimeException("Incremental install failed.", e
);
103 * Returns the class name of the real Application class (recorded in the
104 * AndroidManifest.xml)
106 private String
getRealApplicationName() throws NameNotFoundException
{
107 ApplicationInfo appInfo
= getPackageManager().getApplicationInfo(getPackageName(),
108 PackageManager
.GET_META_DATA
);
109 return appInfo
.metaData
.getString(REAL_APP_META_DATA_NAME
);
113 * Nulls out ActivityThread.mBoundApplication.providers.
115 private void disableContentProviders() throws ReflectiveOperationException
{
116 Object data
= Reflect
.getField(mActivityThread
, "mBoundApplication");
117 mStashedProviderList
= Reflect
.getField(data
, "providers");
118 Reflect
.setField(data
, "providers", null);
122 * Restores the value of ActivityThread.mBoundApplication.providers, and invokes
123 * ActivityThread#installContentProviders().
125 private void enableContentProviders() throws ReflectiveOperationException
{
126 Object data
= Reflect
.getField(mActivityThread
, "mBoundApplication");
127 Reflect
.setField(data
, "providers", mStashedProviderList
);
128 if (mStashedProviderList
!= null && mClassLoaderPatcher
.mIsPrimaryProcess
) {
129 Log
.i(TAG
, "Instantiating content providers");
130 Reflect
.invokeMethod(mActivityThread
, "installContentProviders", mRealApplication
,
131 mStashedProviderList
);
133 mStashedProviderList
= null;
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
141 @SuppressWarnings("unchecked")
142 private void swapApplicationReferences() throws ReflectiveOperationException
{
143 if (Reflect
.getField(mActivityThread
, "mInitialApplication") == this) {
144 Reflect
.setField(mActivityThread
, "mInitialApplication", mRealApplication
);
147 List
<Application
> allApplications
=
148 (List
<Application
>) Reflect
.getField(mActivityThread
, "mAllApplications");
149 for (int i
= 0; i
< allApplications
.size(); i
++) {
150 if (allApplications
.get(i
) == this) {
151 allApplications
.set(i
, mRealApplication
);
155 for (String fieldName
: new String
[] { "mPackages", "mResourcePackages" }) {
156 Map
<String
, WeakReference
<?
>> packageMap
=
157 (Map
<String
, WeakReference
<?
>>) Reflect
.getField(mActivityThread
, fieldName
);
158 for (Map
.Entry
<String
, WeakReference
<?
>> entry
: packageMap
.entrySet()) {
159 Object loadedApk
= entry
.getValue().get();
160 if (loadedApk
!= null && Reflect
.getField(loadedApk
, "mApplication") == this) {
161 Reflect
.setField(loadedApk
, "mApplication", mRealApplication
);
162 Reflect
.setField(mRealApplication
, "mLoadedApk", loadedApk
);