Retool the backup process to use a new 'BackupAgent' class
Backups will be handled by launching the application in a special
mode under which no activities or services will be started, only
the BackupAgent subclass named in the app's android:backupAgent
manifest property. This takes the place of the BackupService class
used earlier during development.
In the cases of *full* backup or restore, an application that does
not supply its own BackupAgent will be launched in a restricted
manner; in particular, it will be using the default Application
class rather than any manifest-declared one. This ensures that the
app is not running any code that may try to manipulate its data
while the backup system reads/writes its data set.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 06e0a453..29e57cd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -115,6 +115,7 @@
private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
private static final boolean DEBUG_BROADCAST = false;
private static final boolean DEBUG_RESULTS = false;
+ private static final boolean DEBUG_BACKUP = true;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
@@ -499,7 +500,7 @@
return mResources;
}
- public Application makeApplication() {
+ public Application makeApplication(boolean forceDefaultAppClass) {
if (mApplication != null) {
return mApplication;
}
@@ -507,7 +508,7 @@
Application app = null;
String appClass = mApplicationInfo.className;
- if (appClass == null) {
+ if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
@@ -1199,6 +1200,16 @@
}
}
+ private static final class CreateBackupAgentData {
+ ApplicationInfo appInfo;
+ int backupMode;
+ public String toString() {
+ return "CreateBackupAgentData{appInfo=" + appInfo
+ + " backupAgent=" + appInfo.backupAgentName
+ + " mode=" + backupMode + "}";
+ }
+ }
+
private static final class CreateServiceData {
IBinder token;
ServiceInfo info;
@@ -1239,6 +1250,7 @@
Bundle instrumentationArgs;
IInstrumentationWatcher instrumentationWatcher;
int debugMode;
+ boolean restrictedBackupMode;
Configuration config;
boolean handlingProfiling;
public String toString() {
@@ -1374,6 +1386,21 @@
queueOrSendMessage(H.RECEIVER, r);
}
+ public final void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) {
+ CreateBackupAgentData d = new CreateBackupAgentData();
+ d.appInfo = app;
+ d.backupMode = backupMode;
+
+ queueOrSendMessage(H.CREATE_BACKUP_AGENT, d);
+ }
+
+ public final void scheduleDestroyBackupAgent(ApplicationInfo app) {
+ CreateBackupAgentData d = new CreateBackupAgentData();
+ d.appInfo = app;
+
+ queueOrSendMessage(H.DESTROY_BACKUP_AGENT, d);
+ }
+
public final void scheduleCreateService(IBinder token,
ServiceInfo info) {
CreateServiceData s = new CreateServiceData();
@@ -1419,7 +1446,7 @@
ApplicationInfo appInfo, List<ProviderInfo> providers,
ComponentName instrumentationName, String profileFile,
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
- int debugMode, Configuration config,
+ int debugMode, boolean isRestrictedBackupMode, Configuration config,
Map<String, IBinder> services) {
Process.setArgV0(processName);
@@ -1437,6 +1464,7 @@
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.debugMode = debugMode;
+ data.restrictedBackupMode = isRestrictedBackupMode;
data.config = config;
queueOrSendMessage(H.BIND_APPLICATION, data);
}
@@ -1718,6 +1746,8 @@
public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
public static final int RELAUNCH_ACTIVITY = 126;
public static final int PROFILER_CONTROL = 127;
+ public static final int CREATE_BACKUP_AGENT = 128;
+ public static final int DESTROY_BACKUP_AGENT = 129;
String codeToString(int code) {
if (localLOGV) {
switch (code) {
@@ -1749,6 +1779,8 @@
case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
case PROFILER_CONTROL: return "PROFILER_CONTROL";
+ case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
+ case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
}
}
return "(unknown)";
@@ -1851,6 +1883,12 @@
case PROFILER_CONTROL:
handleProfilerControl(msg.arg1 != 0, (String)msg.obj);
break;
+ case CREATE_BACKUP_AGENT:
+ handleCreateBackupAgent((CreateBackupAgentData)msg.obj);
+ break;
+ case DESTROY_BACKUP_AGENT:
+ handleDestroyBackupAgent((CreateBackupAgentData)msg.obj);
+ break;
}
}
}
@@ -1908,6 +1946,8 @@
Application mInitialApplication;
final ArrayList<Application> mAllApplications
= new ArrayList<Application>();
+ // set of instantiated backup agents, keyed by package name
+ final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>();
static final ThreadLocal sThreadLocal = new ThreadLocal();
Instrumentation mInstrumentation;
String mInstrumentationAppDir = null;
@@ -2269,7 +2309,7 @@
}
try {
- Application app = r.packageInfo.makeApplication();
+ Application app = r.packageInfo.makeApplication(false);
if (localLOGV) Log.v(TAG, "Performing launch of " + r);
if (localLOGV) Log.v(
@@ -2464,7 +2504,7 @@
}
try {
- Application app = packageInfo.makeApplication();
+ Application app = packageInfo.makeApplication(false);
if (localLOGV) Log.v(
TAG, "Performing receive of " + data.intent
@@ -2507,6 +2547,85 @@
}
}
+ // Instantiate a BackupAgent and tell it that it's alive
+ private final void handleCreateBackupAgent(CreateBackupAgentData data) {
+ if (DEBUG_BACKUP) Log.v(TAG, "handleCreateBackupAgent: " + data);
+
+ // no longer idle; we have backup work to do
+ unscheduleGcIdler();
+
+ // instantiate the BackupAgent class named in the manifest
+ PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
+ String packageName = packageInfo.mPackageName;
+ if (mBackupAgents.get(packageName) != null) {
+ Log.d(TAG, "BackupAgent " + " for " + packageName
+ + " already exists");
+ return;
+ }
+
+ BackupAgent agent = null;
+ String classname = data.appInfo.backupAgentName;
+ if (classname == null) {
+ if (data.backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL) {
+ Log.e(TAG, "Attempted incremental backup but no defined agent for "
+ + packageName);
+ return;
+ }
+ classname = "android.app.FullBackupAgent";
+ }
+ try {
+ java.lang.ClassLoader cl = packageInfo.getClassLoader();
+ agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to instantiate backup agent "
+ + data.appInfo.backupAgentName + ": " + e.toString(), e);
+ }
+
+ // set up the agent's context
+ try {
+ if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent "
+ + data.appInfo.backupAgentName);
+
+ ApplicationContext context = new ApplicationContext();
+ context.init(packageInfo, null, this);
+ context.setOuterContext(agent);
+ agent.attach(context);
+ agent.onCreate();
+
+ // tell the OS that we're live now
+ IBinder binder = agent.onBind();
+ try {
+ ActivityManagerNative.getDefault().backupAgentCreated(packageName, binder);
+ } catch (RemoteException e) {
+ // nothing to do.
+ }
+ mBackupAgents.put(packageName, agent);
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to create BackupAgent "
+ + data.appInfo.backupAgentName + ": " + e.toString(), e);
+ }
+ }
+
+ // Tear down a BackupAgent
+ private final void handleDestroyBackupAgent(CreateBackupAgentData data) {
+ if (DEBUG_BACKUP) Log.v(TAG, "handleDestroyBackupAgent: " + data);
+
+ PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
+ String packageName = packageInfo.mPackageName;
+ BackupAgent agent = mBackupAgents.get(packageName);
+ if (agent != null) {
+ try {
+ agent.onDestroy();
+ } catch (Exception e) {
+ Log.w(TAG, "Exception thrown in onDestroy by backup agent of " + data.appInfo);
+ e.printStackTrace();
+ }
+ mBackupAgents.remove(packageName);
+ } else {
+ Log.w(TAG, "Attempt to destroy unknown backup agent " + data);
+ }
+ }
+
private final void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
@@ -2532,7 +2651,7 @@
ApplicationContext context = new ApplicationContext();
context.init(packageInfo, null, this);
- Application app = packageInfo.makeApplication();
+ Application app = packageInfo.makeApplication(false);
context.setOuterContext(service);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
@@ -3694,7 +3813,9 @@
mInstrumentation = new Instrumentation();
}
- Application app = data.info.makeApplication();
+ // If the app is being launched for full backup or restore, bring it up in
+ // a restricted environment with the base application class.
+ Application app = data.info.makeApplication(data.restrictedBackupMode);
mInitialApplication = app;
List<ProviderInfo> providers = data.providers;