More restore plumbing, plus add suggested-backoff to transport API
Adds most of the code for a background-thread restore process, structured much
like the backup thread. Broke some common functionality out into a helper
function for doing a synchronous wait for a requested agent to attach.
Added a method to IBackupTransport whereby the transport will be asked for
an opinion on whether this is a good time for a backup to happen. It will
reply with the results of its policymaking around backoff intervals, time-of-day
selection, etc.
diff --git a/core/java/com/android/internal/backup/AdbTransport.java b/core/java/com/android/internal/backup/AdbTransport.java
index d8a2186..8d3bd1c 100644
--- a/core/java/com/android/internal/backup/AdbTransport.java
+++ b/core/java/com/android/internal/backup/AdbTransport.java
@@ -13,6 +13,10 @@
public class AdbTransport extends IBackupTransport.Stub {
+ public long requestBackupTime() throws RemoteException {
+ return 0;
+ }
+
public int startSession() throws RemoteException {
// TODO Auto-generated method stub
return 0;
diff --git a/core/java/com/android/internal/backup/GoogleTransport.java b/core/java/com/android/internal/backup/GoogleTransport.java
index 06deec4..c089c23 100644
--- a/core/java/com/android/internal/backup/GoogleTransport.java
+++ b/core/java/com/android/internal/backup/GoogleTransport.java
@@ -11,6 +11,10 @@
public class GoogleTransport extends IBackupTransport.Stub {
+ public long requestBackupTime() throws RemoteException {
+ return 0; // !!! TODO: implement real backoff policy
+ }
+
public int startSession() throws RemoteException {
// TODO Auto-generated method stub
return 0;
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 4821fd0..9daabca 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -41,6 +41,16 @@
- adb: close the file
*/
/**
+ * Verify that this is a suitable time for a backup pass. This should return zero
+ * if a backup is reasonable right now, false otherwise. This method will be called
+ * outside of the {@link #startSession}/{@link #endSession} pair.
+ *
+ * <p>If this is not a suitable time for a backup, the transport should suggest a
+ * backoff delay, in milliseconds, after which the Backup Manager should try again.
+ */
+ long requestBackupTime();
+
+ /**
* Establish a connection to the back-end data repository, if necessary. If the transport
* needs to initialize state that is not tied to individual applications' backup operations,
* this is where it should be done.
@@ -64,33 +74,30 @@
/**
* Get the set of backups currently available over this transport.
*
- * @return A bundle containing two elements: an int array under the key
- * "tokens" whose entries are a transport-private identifier for each backup set;
- * and a String array under the key "names" whose entries are the user-meaningful
- * names corresponding to the backup sets at each index in the tokens array.
+ * @return Descriptions of the set of restore images available for this device.
**/
RestoreSet[] getAvailableRestoreSets();
/**
- * Get the set of applications from a given backup image.
+ * Get the set of applications from a given restore image.
*
- * @param token A backup token as returned by {@link availableBackups}.
+ * @param token A backup token as returned by {@link #getAvailableRestoreSets}.
* @return An array of PackageInfo objects describing all of the applications
- * available for restore from the given backup set. This should include the list
+ * available for restore from this restore image. This should include the list
* of signatures for each package so that the Backup Manager can filter using that
* information.
*/
PackageInfo[] getAppSet(int token);
/**
- * Retrieve one application's data from the backup destination.
+ * Retrieve one application's data from the backing store.
*
* @param token The backup record from which a restore is being requested.
* @param packageInfo The identity of the application whose data is being restored.
* This must include the signature list for the package; it is up to the transport
* to verify that the requested app's signatures match the saved backup record
* because the transport cannot necessarily trust the client device.
- * @param data An open, writeable file into which the backup image should be stored.
+ * @param data An open, writable file into which the backup image should be stored.
* @return Zero on success; a nonzero error code on failure.
*/
int getRestoreData(int token, in PackageInfo packageInfo, in ParcelFileDescriptor data);
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index e90e0ad..42a895c 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -215,7 +215,8 @@
// Look up the package info & signatures. This is first so that if it
// throws an exception, there's no file setup yet that would need to
// be unraveled.
- PackageInfo packInfo = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ PackageInfo packInfo = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_SIGNATURES);
// !!! TODO: get the state file dir from the transport
File savedStateName = new File(mStateDir, packageName);
@@ -367,7 +368,8 @@
if (N > 0) {
for (int a = N-1; a >= 0; a--) {
ApplicationInfo app = allApps.get(a);
- if (app.backupAgentName == null) {
+ if (((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0)
+ || app.backupAgentName == null) {
allApps.remove(a);
}
}
@@ -411,6 +413,40 @@
return transport;
}
+ // fire off a backup agent, blocking until it attaches or times out
+ IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
+ IBackupAgent agent = null;
+ synchronized(mAgentConnectLock) {
+ mConnecting = true;
+ mConnectedAgent = null;
+ try {
+ if (mActivityManager.bindBackupAgent(app, mode)) {
+ Log.d(TAG, "awaiting agent for " + app);
+
+ // success; wait for the agent to arrive
+ while (mConnecting && mConnectedAgent == null) {
+ try {
+ mAgentConnectLock.wait(10000);
+ } catch (InterruptedException e) {
+ // just retry
+ return null;
+ }
+ }
+
+ // if we timed out with no connect, abort and move on
+ if (mConnecting == true) {
+ Log.w(TAG, "Timeout waiting for agent " + app);
+ return null;
+ }
+ agent = mConnectedAgent;
+ }
+ } catch (RemoteException e) {
+ // can't happen
+ }
+ }
+ return agent;
+ }
+
// ----- Back up a set of applications via a worker thread -----
class PerformBackupThread extends Thread {
@@ -425,15 +461,6 @@
@Override
public void run() {
- /*
- * 1. start up the current transport
- * 2. for each item in the queue:
- * 2a. bind the agent [wait for async attach]
- * 2b. set up the files and call doBackup()
- * 2c. unbind the agent
- * 3. tear down the transport
- * 4. done!
- */
if (DEBUG) Log.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
// stand up the current transport
@@ -442,6 +469,15 @@
return;
}
+ // start up the transport
+ try {
+ transport.startSession();
+ } catch (Exception e) {
+ Log.e(TAG, "Error session transport");
+ e.printStackTrace();
+ return;
+ }
+
// The transport is up and running; now run all the backups in our queue
doQueuedBackups(transport);
@@ -456,62 +492,155 @@
private void doQueuedBackups(IBackupTransport transport) {
for (BackupRequest request : mQueue) {
- Log.d(TAG, "starting agent for " + request);
- // !!! TODO: need to handle the restore case?
+ Log.d(TAG, "starting agent for backup of " + request);
IBackupAgent agent = null;
int mode = (request.fullBackup)
? IApplicationThread.BACKUP_MODE_FULL
: IApplicationThread.BACKUP_MODE_INCREMENTAL;
try {
- synchronized(mAgentConnectLock) {
- mConnecting = true;
- mConnectedAgent = null;
- if (mActivityManager.bindBackupAgent(request.appInfo, mode)) {
- Log.d(TAG, "awaiting agent for " + request);
-
- // success; wait for the agent to arrive
- while (mConnecting && mConnectedAgent == null) {
- try {
- mAgentConnectLock.wait(10000);
- } catch (InterruptedException e) {
- // just retry
- continue;
- }
- }
-
- // if we timed out with no connect, abort and move on
- if (mConnecting == true) {
- Log.w(TAG, "Timeout waiting for agent " + request);
- continue;
- }
- agent = mConnectedAgent;
- }
+ agent = bindToAgentSynchronous(request.appInfo, mode);
+ if (agent != null) {
+ processOneBackup(request, agent, transport);
}
- } catch (RemoteException e) {
- // can't happen; activity manager is local
+
+ // unbind even on timeout, just in case
+ mActivityManager.unbindBackupAgent(request.appInfo);
} catch (SecurityException ex) {
// Try for the next one.
Log.d(TAG, "error in bind", ex);
- }
-
- // successful bind? run the backup for this agent
- if (agent != null) {
- processOneBackup(request, agent, transport);
- }
-
- // send the unbind even on timeout, just in case
- try {
- mActivityManager.unbindBackupAgent(request.appInfo);
} catch (RemoteException e) {
// can't happen
}
+
}
}
}
+
+ // ----- Restore handling -----
+
+ // Is the given package restorable on this device? Returns the on-device app's
+ // ApplicationInfo struct if it is; null if not.
+ //
+ // !!! TODO: also consider signatures
+ ApplicationInfo isRestorable(PackageInfo packageInfo) {
+ if (packageInfo.packageName != null) {
+ try {
+ ApplicationInfo app = mPackageManager.getApplicationInfo(packageInfo.packageName,
+ PackageManager.GET_SIGNATURES);
+ if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
+ return app;
+ }
+ } catch (Exception e) {
+ // doesn't exist on this device, or other error -- just ignore it.
+ }
+ }
+ return null;
+ }
+
+ class PerformRestoreThread extends Thread {
+ private IBackupTransport mTransport;
+
+ PerformRestoreThread(IBackupTransport transport) {
+ mTransport = transport;
+ }
+
+ @Override
+ public void run() {
+ /**
+ * Restore sequence:
+ *
+ * 1. start up the transport session
+ * 2. get the restore set description for our identity
+ * 3. for each app in the restore set:
+ * 3.a. if it's restorable on this device, add it to the restore queue
+ * 4. for each app in the restore queue:
+ * 4.b. get the restore data for the app from the transport
+ * 4.c. launch the backup agent for the app
+ * 4.d. agent.doRestore() with the data from the server
+ * 4.e. unbind the agent [and kill the app?]
+ * 5. shut down the transport
+ */
+
+ int err = -1;
+ try {
+ err = mTransport.startSession();
+ } catch (Exception e) {
+ Log.e(TAG, "Error starting transport for restore");
+ e.printStackTrace();
+ }
+
+ if (err == 0) {
+ // build the set of apps to restore
+ try {
+ RestoreSet[] images = mTransport.getAvailableRestoreSets();
+ if (images.length > 0) {
+ // !!! for now we always take the first set
+ RestoreSet image = images[0];
+
+ // build the set of apps we will attempt to restore
+ PackageInfo[] packages = mTransport.getAppSet(image.token);
+ HashSet<ApplicationInfo> appsToRestore = new HashSet<ApplicationInfo>();
+ for (PackageInfo pkg: packages) {
+ ApplicationInfo app = isRestorable(pkg);
+ if (app != null) {
+ appsToRestore.add(app);
+ }
+ }
+
+ // now run the restore queue
+ doQueuedRestores(appsToRestore);
+ }
+ } catch (RemoteException e) {
+ // can't happen; transports run locally
+ }
+
+ // done; shut down the transport
+ try {
+ mTransport.endSession();
+ } catch (Exception e) {
+ Log.e(TAG, "Error ending transport for restore");
+ e.printStackTrace();
+ }
+ }
+
+ // even if the initial session startup failed, report that we're done here
+ }
+
+ // restore each app in the queue
+ void doQueuedRestores(HashSet<ApplicationInfo> appsToRestore) {
+ for (ApplicationInfo app : appsToRestore) {
+ Log.d(TAG, "starting agent for restore of " + app);
+
+ IBackupAgent agent = null;
+ try {
+ agent = bindToAgentSynchronous(app, IApplicationThread.BACKUP_MODE_RESTORE);
+ if (agent != null) {
+ processOneRestore(app, agent);
+ }
+
+ // unbind even on timeout, just in case
+ mActivityManager.unbindBackupAgent(app);
+ } catch (SecurityException ex) {
+ // Try for the next one.
+ Log.d(TAG, "error in bind", ex);
+ } catch (RemoteException e) {
+ // can't happen
+ }
+
+ }
+ }
+
+ // do the guts of a restore
+ void processOneRestore(ApplicationInfo app, IBackupAgent agent) {
+ // !!! TODO: actually run the restore through mTransport
+ }
+ }
+
+
// ----- IBackupManager binder interface -----
-
+
public void dataChanged(String packageName) throws RemoteException {
// Record that we need a backup pass for the caller. Since multiple callers
// may share a uid, we need to note all candidates within that uid and schedule
@@ -548,6 +677,8 @@
mBackupHandler.removeMessages(MSG_RUN_BACKUP);
mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
}
+ } else {
+ Log.w(TAG, "dataChanged but no participant pkg " + packageName);
}
}