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/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index d1c40b4..3b26cb7 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -42,6 +42,7 @@
import android.app.Instrumentation;
import android.app.PendingIntent;
import android.app.ResultInfo;
+import android.backup.IBackupManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -131,6 +132,7 @@
static final boolean DEBUG_PROCESSES = localLOGV || false;
static final boolean DEBUG_USER_LEAVING = localLOGV || false;
static final boolean DEBUG_RESULTS = localLOGV || false;
+ static final boolean DEBUG_BACKUP = localLOGV || true;
static final boolean VALIDATE_TOKENS = false;
static final boolean SHOW_ACTIVITY_START_TIME = true;
@@ -633,6 +635,12 @@
= new ArrayList<ServiceRecord>();
/**
+ * Backup/restore process management
+ */
+ String mBackupAppName = null;
+ BackupRecord mBackupTarget = null;
+
+ /**
* List of PendingThumbnailsRecord objects of clients who are still
* waiting to receive all of the thumbnails for a task.
*/
@@ -4669,6 +4677,16 @@
mPendingBroadcast = null;
scheduleBroadcastsLocked();
}
+ if (mBackupTarget != null && mBackupTarget.app.pid == pid) {
+ Log.w(TAG, "Unattached app died before backup, skipping");
+ try {
+ IBackupManager bm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ bm.agentDisconnected(app.info.packageName);
+ } catch (RemoteException e) {
+ // Can't happen; the backup manager is local
+ }
+ }
} else {
Log.w(TAG, "Spurious process start timeout - pid not known for " + app);
}
@@ -4757,11 +4775,17 @@
mWaitForDebugger = mOrigWaitForDebugger;
}
}
+ // If the app is being launched for restore or full backup, set it up specially
+ boolean isRestrictedBackupMode = false;
+ if (mBackupTarget != null && mBackupAppName.equals(processName)) {
+ isRestrictedBackupMode = (mBackupTarget.backupMode == BackupRecord.RESTORE)
+ || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL);
+ }
thread.bindApplication(processName, app.instrumentationInfo != null
? app.instrumentationInfo : app.info, providers,
app.instrumentationClass, app.instrumentationProfileFile,
app.instrumentationArguments, app.instrumentationWatcher, testMode,
- mConfiguration, getCommonServicesLocked());
+ isRestrictedBackupMode, mConfiguration, getCommonServicesLocked());
updateLRUListLocked(app, false);
app.lastRequestedGc = SystemClock.uptimeMillis();
} catch (Exception e) {
@@ -4842,6 +4866,17 @@
}
}
+ // Check whether the next backup agent is in this process...
+ if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.info.uid) {
+ if (DEBUG_BACKUP) Log.v(TAG, "New app is backup target, launching agent for " + app);
+ try {
+ thread.scheduleCreateBackupAgent(mBackupTarget.appInfo, mBackupTarget.backupMode);
+ } catch (Exception e) {
+ Log.w(TAG, "Exception scheduling backup agent creation: ");
+ e.printStackTrace();
+ }
+ }
+
if (badApp) {
// todo: Also need to kill application to deal with all
// kinds of exceptions.
@@ -9118,6 +9153,18 @@
app.receivers.clear();
}
+ // If the app is undergoing backup, tell the backup manager about it
+ if (mBackupTarget != null && app.pid == mBackupTarget.app.pid) {
+ if (DEBUG_BACKUP) Log.d(TAG, "App " + mBackupTarget.appInfo + " died during backup");
+ try {
+ IBackupManager bm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ bm.agentDisconnected(app.info.packageName);
+ } catch (RemoteException e) {
+ // can't happen; backup manager is local
+ }
+ }
+
// If the caller is restarting this app, then leave it in its
// current lists and let the caller take care of it.
if (restarting) {
@@ -10234,6 +10281,105 @@
}
// =========================================================
+ // BACKUP AND RESTORE
+ // =========================================================
+
+ // Cause the target app to be launched if necessary and its backup agent
+ // instantiated. The backup agent will invoke backupAgentCreated() on the
+ // activity manager to announce its creation.
+ public boolean bindBackupAgent(ApplicationInfo app, int backupMode) {
+ if (DEBUG_BACKUP) Log.v(TAG, "startBackupAgent: app=" + app + " mode=" + backupMode);
+ enforceCallingPermission("android.permission.BACKUP", "startBackupAgent");
+
+ synchronized(this) {
+ // !!! TODO: currently no check here that we're already bound
+ BatteryStatsImpl.Uid.Pkg.Serv ss = null;
+ BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+ synchronized (stats) {
+ ss = stats.getServiceStatsLocked(app.uid, app.packageName, app.name);
+ }
+
+ BackupRecord r = new BackupRecord(ss, app, backupMode);
+ ComponentName hostingName = new ComponentName(app.packageName, app.backupAgentName);
+ // startProcessLocked() returns existing proc's record if it's already running
+ ProcessRecord proc = startProcessLocked(app.processName, app,
+ false, 0, "backup", hostingName);
+ if (proc == null) {
+ Log.e(TAG, "Unable to start backup agent process " + r);
+ return false;
+ }
+
+ r.app = proc;
+ mBackupTarget = r;
+ mBackupAppName = app.packageName;
+
+ // If the process is already attached, schedule the creation of the backup agent now.
+ // If it is not yet live, this will be done when it attaches to the framework.
+ if (proc.thread != null) {
+ if (DEBUG_BACKUP) Log.v(TAG, "Agent proc already running: " + proc);
+ try {
+ proc.thread.scheduleCreateBackupAgent(app, backupMode);
+ } catch (RemoteException e) {
+ // !!! TODO: notify the backup manager that we crashed, or rely on
+ // death notices, or...?
+ }
+ } else {
+ if (DEBUG_BACKUP) Log.v(TAG, "Agent proc not running, waiting for attach");
+ }
+ // Invariants: at this point, the target app process exists and the application
+ // is either already running or in the process of coming up. mBackupTarget and
+ // mBackupAppName describe the app, so that when it binds back to the AM we
+ // know that it's scheduled for a backup-agent operation.
+ }
+
+ return true;
+ }
+
+ // A backup agent has just come up
+ public void backupAgentCreated(String agentPackageName, IBinder agent) {
+ if (DEBUG_BACKUP) Log.v(TAG, "backupAgentCreated: " + agentPackageName
+ + " = " + agent);
+
+ synchronized(this) {
+ if (!agentPackageName.equals(mBackupAppName)) {
+ Log.e(TAG, "Backup agent created for " + agentPackageName + " but not requested!");
+ return;
+ }
+
+ try {
+ IBackupManager bm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ bm.agentConnected(agentPackageName, agent);
+ } catch (RemoteException e) {
+ // can't happen; the backup manager service is local
+ } catch (Exception e) {
+ Log.w(TAG, "Exception trying to deliver BackupAgent binding: ");
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // done with this agent
+ public void unbindBackupAgent(ApplicationInfo appInfo) {
+ if (DEBUG_BACKUP) Log.v(TAG, "unbindBackupAgent: " + appInfo);
+
+ synchronized(this) {
+ if (!mBackupAppName.equals(appInfo.packageName)) {
+ Log.e(TAG, "Unbind of " + appInfo + " but is not the current backup target");
+ return;
+ }
+
+ try {
+ mBackupTarget.app.thread.scheduleDestroyBackupAgent(appInfo);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception when unbinding backup agent:");
+ e.printStackTrace();
+ }
+ mBackupTarget = null;
+ mBackupAppName = null;
+ }
+ }
+ // =========================================================
// BROADCASTS
// =========================================================