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);
         }
     }