Add single-package restore from an app's most-recent data

Renamed the RestoreSession performRestore() method to restoreAll(), and
added a new restorePackage() method that only restores the single
specified app.  In order to restore an app other than itself, the
caller must hold the android.permission.BACKUP permission.

This change also introduces dataset tracking:  the Backup Manager
persistently remembers both the current backup dataset's identity
and that of the "ancestral" dataset, i.e. the one most recently used
for a whole-device restore such as performed by SetupWizard.  When a
single package is restored via restorePackage(), the selection of
most-recent dataset to use is this:

1. The data from the currently-active backup dataset, if such exists.
   An app that has ever backed up data will therefore get its last-
   known-good data.

2. The app's data from the ancestral dataset, if such exists.  This
   covers the case of a factory reset followed by reinstallation of
   an app at a later time.  The app had not yet backed anything up
   post-wipe, but the old data is in the ancestral dataset and should
   be brought forward when the app reappears.

3. If neither 1. nor 2. exist, there is no data to restore, so just
   skip it and return failure.

Note that the infrastructure to automatically attempt a restore after
an application has been installed does not yet exist; that's coming.

Change-Id: I0ba170df9885128000c46ed28d3dddda3a63a143
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 8c15d0b..acfbb07 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -315,7 +315,7 @@
                 for (RestoreSet s : sets) {
                     if (s.token == token) {
                         System.out.println("Scheduling restore: " + s.name);
-                        didRestore = (mRestore.performRestore(token, observer) == 0);
+                        didRestore = (mRestore.restoreAll(token, observer) == 0);
                         break;
                     }
                 }
diff --git a/core/java/android/backup/IRestoreSession.aidl b/core/java/android/backup/IRestoreSession.aidl
index fd40d98..bead395 100644
--- a/core/java/android/backup/IRestoreSession.aidl
+++ b/core/java/android/backup/IRestoreSession.aidl
@@ -40,6 +40,8 @@
      * Restore the given set onto the device, replacing the current data of any app
      * contained in the restore set with the data previously backed up.
      *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
      * @return Zero on success; nonzero on error.  The observer will only receive
      *   progress callbacks if this method returned zero.
      * @param token The token from {@link getAvailableRestoreSets()} corresponding to
@@ -47,7 +49,24 @@
      * @param observer If non-null, this binder points to an object that will receive
      *   progress callbacks during the restore operation.
      */
-    int performRestore(long token, IRestoreObserver observer);
+    int restoreAll(long token, IRestoreObserver observer);
+
+    /**
+     * Restore a single application from backup.  The data will be restored from the
+     * current backup dataset if the given package has stored data there, or from
+     * the dataset used during the last full device setup operation if the current
+     * backup dataset has no matching data.  If no backup data exists for this package
+     * in either source, a nonzero value will be returned.
+     *
+     * @return Zero on success; nonzero on error.  The observer will only receive
+     *   progress callbacks if this method returned zero.
+     * @param packageName The name of the package whose data to restore.  If this is
+     *   not the name of the caller's own package, then the android.permission.BACKUP
+     *   permission must be held.
+     * @param observer If non-null, this binder points to an object that will receive
+     *   progress callbacks during the restore operation.
+     */
+    int restorePackage(in String packageName, IRestoreObserver observer);
 
     /**
      * End this restore session.  After this method is called, the IRestoreSession binder
diff --git a/core/java/android/backup/RestoreSession.java b/core/java/android/backup/RestoreSession.java
index 6b35fe8..d10831e 100644
--- a/core/java/android/backup/RestoreSession.java
+++ b/core/java/android/backup/RestoreSession.java
@@ -58,25 +58,56 @@
      * Restore the given set onto the device, replacing the current data of any app
      * contained in the restore set with the data previously backed up.
      *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
      * @return Zero on success; nonzero on error.  The observer will only receive
      *   progress callbacks if this method returned zero.
-     * @param token The token from {@link #getAvailableRestoreSets()} corresponding to
+     * @param token The token from {@link getAvailableRestoreSets()} corresponding to
      *   the restore set that should be used.
-     * @param observer If non-null, this argument points to an object that will receive
-     *   progress callbacks during the restore operation. These callbacks will occur
-     *   on the main thread of the application.
+     * @param observer If non-null, this binder points to an object that will receive
+     *   progress callbacks during the restore operation.
      */
-    public int performRestore(long token, RestoreObserver observer) {
+    public int restoreAll(long token, RestoreObserver observer) {
         int err = -1;
         if (mObserver != null) {
-            Log.d(TAG, "performRestore() called during active restore");
+            Log.d(TAG, "restoreAll() called during active restore");
             return -1;
         }
         mObserver = new RestoreObserverWrapper(mContext, observer);
         try {
-            err = mBinder.performRestore(token, mObserver);
+            err = mBinder.restoreAll(token, mObserver);
         } catch (RemoteException e) {
-            Log.d(TAG, "Can't contact server to perform restore");
+            Log.d(TAG, "Can't contact server to restore");
+        }
+        return err;
+    }
+
+    /**
+     * Restore a single application from backup.  The data will be restored from the
+     * current backup dataset if the given package has stored data there, or from
+     * the dataset used during the last full device setup operation if the current
+     * backup dataset has no matching data.  If no backup data exists for this package
+     * in either source, a nonzero value will be returned.
+     *
+     * @return Zero on success; nonzero on error.  The observer will only receive
+     *   progress callbacks if this method returned zero.
+     * @param packageName The name of the package whose data to restore.  If this is
+     *   not the name of the caller's own package, then the android.permission.BACKUP
+     *   permission must be held.
+     * @param observer If non-null, this binder points to an object that will receive
+     *   progress callbacks during the restore operation.
+     */
+    public int restorePackage(String packageName, RestoreObserver observer) {
+        int err = -1;
+        if (mObserver != null) {
+            Log.d(TAG, "restorePackage() called during active restore");
+            return -1;
+        }
+        mObserver = new RestoreObserverWrapper(mContext, observer);
+        try {
+            err = mBinder.restorePackage(packageName, mObserver);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Can't contact server to restore package");
         }
         return err;
     }
@@ -87,7 +118,7 @@
      *
      * <p><b>Note:</b> The caller <i>must</i> invoke this method to end the restore session,
      *   even if {@link #getAvailableRestoreSets()} or
-     *   {@link #performRestore(long, RestoreObserver)} failed.
+     *   {@link #restorePackage(long, String, RestoreObserver)} failed.
      */
     public void endRestoreSession() {
         try {
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 0c1e0602..6795bdd 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -176,11 +176,21 @@
         public IBackupTransport transport;
         public IRestoreObserver observer;
         public long token;
+        public PackageInfo pkgInfo;
+
+        RestoreParams(IBackupTransport _transport, IRestoreObserver _obs,
+                long _token, PackageInfo _pkg) {
+            transport = _transport;
+            observer = _obs;
+            token = _token;
+            pkgInfo = _pkg;
+        }
 
         RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token) {
             transport = _transport;
             observer = _obs;
             token = _token;
+            pkgInfo = null;
         }
     }
 
@@ -210,10 +220,16 @@
     File mJournalDir;
     File mJournal;
 
-    // Keep a log of all the apps we've ever backed up
+    // Keep a log of all the apps we've ever backed up, and what the
+    // dataset tokens are for both the current backup dataset and
+    // the ancestral dataset.
     private File mEverStored;
     HashSet<String> mEverStoredApps = new HashSet<String>();
 
+    File mTokenFile;
+    long mAncestralToken = 0;
+    long mCurrentToken = 0;
+
     // Persistently track the need to do a full init
     static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
     HashSet<String> mPendingInits = new HashSet<String>();  // transport names
@@ -277,7 +293,7 @@
                 RestoreParams params = (RestoreParams)msg.obj;
                 Log.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
                 (new PerformRestoreTask(params.transport, params.observer,
-                        params.token)).run();
+                        params.token, params.pkgInfo)).run();
                 break;
             }
 
@@ -475,6 +491,16 @@
     private void initPackageTracking() {
         if (DEBUG) Log.v(TAG, "Initializing package tracking");
 
+        // Remember our ancestral dataset
+        mTokenFile = new File(mBaseStateDir, "ancestral");
+        try {
+            RandomAccessFile tf = new RandomAccessFile(mTokenFile, "r");
+            mAncestralToken = tf.readLong();
+            mCurrentToken = tf.readLong();
+        } catch (IOException e) {
+            Log.w(TAG, "Unable to read token file", e);
+        }
+
         // Keep a log of what apps we've ever backed up.  Because we might have
         // rebooted in the middle of an operation that was removing something from
         // this log, we sanity-check its contents here and reconstruct it.
@@ -607,6 +633,9 @@
             mEverStoredApps.clear();
             mEverStored.delete();
 
+            mCurrentToken = 0;
+            writeRestoreTokens();
+
             // Remove all the state files
             for (File sf : stateFileDir.listFiles()) {
                 // ... but don't touch the needs-init sentinel
@@ -880,7 +909,7 @@
         addPackageParticipantsLockedInner(packageName, allApps);
     }
 
-    // Called from the backup thread: record that the given app has been successfully
+    // Called from the backup task: record that the given app has been successfully
     // backed up at least once
     void logBackupComplete(String packageName) {
         if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return;
@@ -938,6 +967,18 @@
         }
     }
 
+    // Record the current and ancestral backup tokens persistently
+    void writeRestoreTokens() {
+        try {
+            RandomAccessFile af = new RandomAccessFile(mTokenFile, "rwd");
+            af.writeLong(mAncestralToken);
+            af.writeLong(mCurrentToken);
+            af.close();
+        } catch (IOException e) {
+            Log.w(TAG, "Unable to write token file:", e);
+        }
+    }
+
     // Return the given transport
     private IBackupTransport getTransport(String transportName) {
         synchronized (mTransports) {
@@ -1154,6 +1195,16 @@
                 Log.e(TAG, "Error in backup thread", e);
                 status = BackupConstants.TRANSPORT_ERROR;
             } finally {
+                // If everything actually went through and this is the first time we've
+                // done a backup, we can now record what the current backup dataset token
+                // is.
+                if ((mCurrentToken == 0) && (status != BackupConstants.TRANSPORT_OK)) {
+                    try {
+                        mCurrentToken = mTransport.getCurrentRestoreSet();
+                    } catch (RemoteException e) { /* cannot happen */ }
+                    writeRestoreTokens();
+                }
+
                 // If things went wrong, we need to re-stage the apps we had expected
                 // to be backing up in this pass.  This journals the package names in
                 // the current active pending-backup file, not in the we are holding
@@ -1395,6 +1446,7 @@
         private IBackupTransport mTransport;
         private IRestoreObserver mObserver;
         private long mToken;
+        private PackageInfo mTargetPackage;
         private File mStateDir;
 
         class RestoreRequest {
@@ -1408,11 +1460,11 @@
         }
 
         PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
-                long restoreSetToken) {
+                long restoreSetToken, PackageInfo targetPackage) {
             mTransport = transport;
-            Log.d(TAG, "PerformRestoreThread mObserver=" + mObserver);
             mObserver = observer;
             mToken = restoreSetToken;
+            mTargetPackage = targetPackage;
 
             try {
                 mStateDir = new File(mBaseStateDir, transport.transportDirName());
@@ -1424,7 +1476,8 @@
         public void run() {
             long startRealtime = SystemClock.elapsedRealtime();
             if (DEBUG) Log.v(TAG, "Beginning restore process mTransport=" + mTransport
-                    + " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken));
+                    + " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken)
+                    + " mTargetPackage=" + mTargetPackage);
             /**
              * Restore sequence:
              *
@@ -1441,7 +1494,7 @@
              *
              * On errors, we try our best to recover and move on to the next
              * application, but if necessary we abort the whole operation --
-             * the user is waiting, after al.
+             * the user is waiting, after all.
              */
 
             int error = -1; // assume error
@@ -1459,7 +1512,12 @@
                 restorePackages.add(omPackage);
 
                 List<PackageInfo> agentPackages = allAgentPackages();
-                restorePackages.addAll(agentPackages);
+                if (mTargetPackage == null) {
+                    restorePackages.addAll(agentPackages);
+                } else {
+                    // Just one package to attempt restore of
+                    restorePackages.add(mTargetPackage);
+                }
 
                 // let the observer know that we're running
                 if (mObserver != null) {
@@ -1641,6 +1699,13 @@
                     }
                 }
 
+                // If this was a restoreAll operation, record that this was our
+                // ancestral dataset
+                if (mTargetPackage == null) {
+                    mAncestralToken = mToken;
+                    writeRestoreTokens();
+                }
+
                 // done; we can finally release the wakelock
                 mWakelock.release();
             }
@@ -2219,7 +2284,7 @@
             }
         }
 
-        public synchronized int performRestore(long token, IRestoreObserver observer) {
+        public synchronized int restoreAll(long token, IRestoreObserver observer) {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                     "performRestore");
 
@@ -2249,6 +2314,55 @@
             return -1;
         }
 
+        public synchronized int restorePackage(String packageName, IRestoreObserver observer) {
+            if (DEBUG) Log.v(TAG, "restorePackage pkg=" + packageName + " obs=" + observer);
+
+            PackageInfo app = null;
+            try {
+                app = mPackageManager.getPackageInfo(packageName, 0);
+            } catch (NameNotFoundException nnf) {
+                Log.w(TAG, "Asked to restore nonexistent pkg " + packageName);
+                return -1;
+            }
+
+            // If the caller is not privileged and is not coming from the target
+            // app's uid, throw a permission exception back to the caller.
+            int perm = mContext.checkPermission(android.Manifest.permission.BACKUP,
+                    Binder.getCallingPid(), Binder.getCallingUid());
+            if ((perm == PackageManager.PERMISSION_DENIED) &&
+                    (app.applicationInfo.uid != Binder.getCallingUid())) {
+                Log.w(TAG, "restorePackage: bad packageName=" + packageName
+                        + " or calling uid=" + Binder.getCallingUid());
+                throw new SecurityException("No permission to restore other packages");
+            }
+
+            // So far so good; we're allowed to try to restore this package.  Now
+            // check whether there is data for it in the current dataset, falling back
+            // to the ancestral dataset if not.
+            long token = mAncestralToken;
+            synchronized (mQueueLock) {
+                if (mEverStoredApps.contains(packageName)) {
+                    token = mCurrentToken;
+                }
+            }
+
+            // If we didn't come up with a place to look -- no ancestral dataset and
+            // the app has never been backed up from this device -- there's nothing
+            // to do but return failure.
+            if (token == 0) {
+                return -1;
+            }
+
+            // Ready to go:  enqueue the restore request and claim success
+            long oldId = Binder.clearCallingIdentity();
+            mWakelock.acquire();
+            Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
+            msg.obj = new RestoreParams(mRestoreTransport, observer, token, app);
+            mBackupHandler.sendMessage(msg);
+            Binder.restoreCallingIdentity(oldId);
+            return 0;
+        }
+
         public synchronized void endRestoreSession() {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                     "endRestoreSession");