Harden against transiently unavailable backup transports

The init & clear operations are particularly important to ensure
delivery when at all possible, so we retry those periodically
if the transport is unavailable when we first attempt them.

Now with 100% less build break.

Bug 11716868

Change-Id: I2af4e93788068cfac97c0a48d3568c561eefa23d
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 2666b41..db3d8bb 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -187,6 +187,12 @@
     }
 
     private void doWipe() {
+        String transport = nextArg();
+        if (transport == null) {
+            showUsage();
+            return;
+        }
+
         String pkg = nextArg();
         if (pkg == null) {
             showUsage();
@@ -194,8 +200,8 @@
         }
 
         try {
-            mBmgr.clearBackupData(pkg);
-            System.out.println("Wiped backup data for " + pkg);
+            mBmgr.clearBackupData(transport, pkg);
+            System.out.println("Wiped backup data for " + pkg + " on " + transport);
         } catch (RemoteException e) {
             System.err.println(e.toString());
             System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -446,7 +452,7 @@
         System.err.println("       bmgr restore TOKEN PACKAGE...");
         System.err.println("       bmgr restore PACKAGE");
         System.err.println("       bmgr run");
-        System.err.println("       bmgr wipe PACKAGE");
+        System.err.println("       bmgr wipe TRANSPORT PACKAGE");
         System.err.println("");
         System.err.println("The 'backup' command schedules a backup pass for the named package.");
         System.err.println("Note that the backup pass will effectively be a no-op if the package");
@@ -462,8 +468,8 @@
         System.err.println("");
         System.err.println("The 'list transports' command reports the names of the backup transports");
         System.err.println("currently available on the device.  These names can be passed as arguments");
-        System.err.println("to the 'transport' command.  The currently selected transport is indicated");
-        System.err.println("with a '*' character.");
+        System.err.println("to the 'transport' and 'wipe' commands.  The currently selected transport");
+        System.err.println("is indicated with a '*' character.");
         System.err.println("");
         System.err.println("The 'list sets' command reports the token and name of each restore set");
         System.err.println("available to the device via the current transport.");
@@ -491,7 +497,8 @@
         System.err.println("data changes.");
         System.err.println("");
         System.err.println("The 'wipe' command causes all backed-up data for the given package to be");
-        System.err.println("erased from the current transport's storage.  The next backup operation");
+        System.err.println("erased from the given transport's storage.  The next backup operation");
         System.err.println("that the given application performs will rewrite its entire data set.");
+        System.err.println("Transport names to use here are those reported by 'list transports'.");
     }
 }
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index bb4f5f1..12ee3b6 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -43,14 +43,14 @@
     void dataChanged(String packageName);
 
     /**
-     * Erase all backed-up data for the given package from the storage
+     * Erase all backed-up data for the given package from the given storage
      * destination.
      *
      * Any application can invoke this method for its own package, but
      * only callers who hold the android.permission.BACKUP permission
      * may invoke it for arbitrary packages.
      */
-    void clearBackupData(String packageName);
+    void clearBackupData(String transportName, String packageName);
 
     /**
      * Notifies the Backup Manager Service that an agent has become available.  This
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index baef607..00bfee7 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -161,6 +161,9 @@
     // the first backup pass.
     private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR;
 
+    // Retry interval for clear/init when the transport is unavailable
+    private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
+
     private static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
     private static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
     private static final String RUN_CLEAR_ACTION = "android.app.backup.intent.CLEAR";
@@ -174,6 +177,8 @@
     private static final int MSG_RESTORE_TIMEOUT = 8;
     private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
     private static final int MSG_RUN_FULL_RESTORE = 10;
+    private static final int MSG_RETRY_INIT = 11;
+    private static final int MSG_RETRY_CLEAR = 12;
 
     // backup task state machine tick
     static final int MSG_BACKUP_RESTORE_STEP = 20;
@@ -306,6 +311,7 @@
 
     class RestoreParams {
         public IBackupTransport transport;
+        public String dirName;
         public IRestoreObserver observer;
         public long token;
         public PackageInfo pkgInfo;
@@ -313,9 +319,10 @@
         public boolean needFullBackup;
         public String[] filterSet;
 
-        RestoreParams(IBackupTransport _transport, IRestoreObserver _obs,
+        RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
                 long _token, PackageInfo _pkg, int _pmToken, boolean _needFullBackup) {
             transport = _transport;
+            dirName = _dirName;
             observer = _obs;
             token = _token;
             pkgInfo = _pkg;
@@ -324,9 +331,10 @@
             filterSet = null;
         }
 
-        RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token,
-                boolean _needFullBackup) {
+        RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
+                long _token, boolean _needFullBackup) {
             transport = _transport;
+            dirName = _dirName;
             observer = _obs;
             token = _token;
             pkgInfo = null;
@@ -335,9 +343,10 @@
             filterSet = null;
         }
 
-        RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token,
-                String[] _filterSet, boolean _needFullBackup) {
+        RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
+                long _token, String[] _filterSet, boolean _needFullBackup) {
             transport = _transport;
+            dirName = _dirName;
             observer = _obs;
             token = _token;
             pkgInfo = null;
@@ -357,6 +366,16 @@
         }
     }
 
+    class ClearRetryParams {
+        public String transportName;
+        public String packageName;
+
+        ClearRetryParams(String transport, String pkg) {
+            transportName = transport;
+            packageName = pkg;
+        }
+    }
+
     class FullParams {
         public ParcelFileDescriptor fd;
         public final AtomicBoolean latch;
@@ -516,13 +535,28 @@
                 // When it completes successfully, that old journal file will be
                 // deleted.  If we crash prior to that, the old journal is parsed
                 // at next boot and the journaled requests fulfilled.
+                boolean staged = true;
                 if (queue.size() > 0) {
                     // Spin up a backup state sequence and set it running
-                    PerformBackupTask pbt = new PerformBackupTask(transport, queue, oldJournal);
-                    Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
-                    sendMessage(pbtMessage);
+                    try {
+                        String dirName = transport.transportDirName();
+                        PerformBackupTask pbt = new PerformBackupTask(transport, dirName,
+                                queue, oldJournal);
+                        Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
+                        sendMessage(pbtMessage);
+                    } catch (RemoteException e) {
+                        // unable to ask the transport its dir name -- transient failure, since
+                        // the above check succeeded.  Try again next time.
+                        Slog.e(TAG, "Transport became unavailable attempting backup");
+                        staged = false;
+                    }
                 } else {
                     Slog.v(TAG, "Backup requested but nothing pending");
+                    staged = false;
+                }
+
+                if (!staged) {
+                    // if we didn't actually hand off the wakelock, rewind until next time
                     synchronized (mQueueLock) {
                         mBackupRunning = false;
                     }
@@ -572,7 +606,7 @@
                 RestoreParams params = (RestoreParams)msg.obj;
                 Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
                 PerformRestoreTask task = new PerformRestoreTask(
-                        params.transport, params.observer,
+                        params.transport, params.dirName, params.observer,
                         params.token, params.pkgInfo, params.pmToken,
                         params.needFullBackup, params.filterSet);
                 Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task);
@@ -599,6 +633,14 @@
                 break;
             }
 
+            case MSG_RETRY_CLEAR:
+            {
+                // reenqueues if the transport remains unavailable
+                ClearRetryParams params = (ClearRetryParams)msg.obj;
+                clearBackupData(params.transportName, params.packageName);
+                break;
+            }
+
             case MSG_RUN_INITIALIZE:
             {
                 HashSet<String> queue;
@@ -613,6 +655,16 @@
                 break;
             }
 
+            case MSG_RETRY_INIT:
+            {
+                synchronized (mQueueLock) {
+                    recordInitPendingLocked(msg.arg1 != 0, (String)msg.obj);
+                    mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
+                            mRunInitIntent);
+                }
+                break;
+            }
+
             case MSG_RUN_GET_RESTORE_SETS:
             {
                 // Like other async operations, this is entered with the wakelock held
@@ -1250,29 +1302,47 @@
     void recordInitPendingLocked(boolean isPending, String transportName) {
         if (DEBUG) Slog.i(TAG, "recordInitPendingLocked: " + isPending
                 + " on transport " + transportName);
+        mBackupHandler.removeMessages(MSG_RETRY_INIT);
+
         try {
             IBackupTransport transport = getTransport(transportName);
-            String transportDirName = transport.transportDirName();
-            File stateDir = new File(mBaseStateDir, transportDirName);
-            File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+            if (transport != null) {
+                String transportDirName = transport.transportDirName();
+                File stateDir = new File(mBaseStateDir, transportDirName);
+                File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
 
-            if (isPending) {
-                // We need an init before we can proceed with sending backup data.
-                // Record that with an entry in our set of pending inits, as well as
-                // journaling it via creation of a sentinel file.
-                mPendingInits.add(transportName);
-                try {
-                    (new FileOutputStream(initPendingFile)).close();
-                } catch (IOException ioe) {
-                    // Something is badly wrong with our permissions; just try to move on
+                if (isPending) {
+                    // We need an init before we can proceed with sending backup data.
+                    // Record that with an entry in our set of pending inits, as well as
+                    // journaling it via creation of a sentinel file.
+                    mPendingInits.add(transportName);
+                    try {
+                        (new FileOutputStream(initPendingFile)).close();
+                    } catch (IOException ioe) {
+                        // Something is badly wrong with our permissions; just try to move on
+                    }
+                } else {
+                    // No more initialization needed; wipe the journal and reset our state.
+                    initPendingFile.delete();
+                    mPendingInits.remove(transportName);
                 }
-            } else {
-                // No more initialization needed; wipe the journal and reset our state.
-                initPendingFile.delete();
-                mPendingInits.remove(transportName);
+                return; // done; don't fall through to the error case
             }
         } catch (RemoteException e) {
-            // can't happen; the transport is local
+            // transport threw when asked its name; fall through to the lookup-failed case
+        }
+
+        // The named transport doesn't exist or threw.  This operation is
+        // important, so we record the need for a an init and post a message
+        // to retry the init later.
+        if (isPending) {
+            mPendingInits.add(transportName);
+            mBackupHandler.sendMessageDelayed(
+                    mBackupHandler.obtainMessage(MSG_RETRY_INIT,
+                            (isPending ? 1 : 0),
+                            0,
+                            transportName),
+                    TRANSPORT_RETRY_INTERVAL);
         }
     }
 
@@ -1348,7 +1418,10 @@
                 }
             }
         } catch (RemoteException e) {
-            // can't happen, the transport is local
+            // the transport threw when asked its file naming prefs; declare it invalid
+            Slog.e(TAG, "Unable to register transport as " + name);
+            mTransportNames.remove(component);
+            mTransports.remove(name);
         }
     }
 
@@ -1668,7 +1741,7 @@
                     agent = mConnectedAgent;
                 }
             } catch (RemoteException e) {
-                // can't happen
+                // can't happen - ActivityManager is local
             }
         }
         return agent;
@@ -1844,17 +1917,13 @@
         int mStatus;
         boolean mFinished;
 
-        public PerformBackupTask(IBackupTransport transport, ArrayList<BackupRequest> queue,
-                File journal) {
+        public PerformBackupTask(IBackupTransport transport, String dirName,
+                ArrayList<BackupRequest> queue, File journal) {
             mTransport = transport;
             mOriginalQueue = queue;
             mJournal = journal;
 
-            try {
-                mStateDir = new File(mBaseStateDir, transport.transportDirName());
-            } catch (RemoteException e) {
-                // can't happen; the transport is local
-            }
+            mStateDir = new File(mBaseStateDir, dirName);
 
             mCurrentState = BackupState.INITIAL;
             mFinished = false;
@@ -2100,8 +2169,12 @@
                 addBackupTrace("success; recording token");
                 try {
                     mCurrentToken = mTransport.getCurrentRestoreSet();
-                } catch (RemoteException e) {} // can't happen
-                writeRestoreTokens();
+                    writeRestoreTokens();
+                } catch (RemoteException e) {
+                    // nothing for it at this point, unfortunately, but this will be
+                    // recorded the next time we fully succeed.
+                    addBackupTrace("transport threw returning token");
+                }
             }
 
             // Set up the next backup pass - at this point we can set mBackupRunning
@@ -2322,7 +2395,7 @@
                 addBackupTrace("unbinding " + mCurrentPackage.packageName);
                 try {  // unbind even on timeout, just in case
                     mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo);
-                } catch (RemoteException e) {}
+                } catch (RemoteException e) { /* can't happen; activity manager is local */ }
             }
         }
 
@@ -4337,7 +4410,7 @@
             }
         }
 
-        PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
+        PerformRestoreTask(IBackupTransport transport, String dirName, IRestoreObserver observer,
                 long restoreSetToken, PackageInfo targetPackage, int pmToken,
                 boolean needFullBackup, String[] filterSet) {
             mCurrentState = RestoreState.INITIAL;
@@ -4360,11 +4433,7 @@
                 mFilterSet = null;
             }
 
-            try {
-                mStateDir = new File(mBaseStateDir, transport.transportDirName());
-            } catch (RemoteException e) {
-                // can't happen; the transport is local
-            }
+            mStateDir = new File(mBaseStateDir, dirName);
         }
 
         // Execute one tick of whatever state machine the task implements
@@ -5090,8 +5159,8 @@
     }
 
     // Clear the given package's backup data from the current transport
-    public void clearBackupData(String packageName) {
-        if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName);
+    public void clearBackupData(String transportName, String packageName) {
+        if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName);
         PackageInfo info;
         try {
             info = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
@@ -5122,13 +5191,22 @@
 
         // Is the given app an available participant?
         if (apps.contains(packageName)) {
-            if (DEBUG) Slog.v(TAG, "Found the app - running clear process");
             // found it; fire off the clear request
+            if (DEBUG) Slog.v(TAG, "Found the app - running clear process");
+            mBackupHandler.removeMessages(MSG_RETRY_CLEAR);
             synchronized (mQueueLock) {
+                final IBackupTransport transport = getTransport(transportName);
+                if (transport == null) {
+                    // transport is currently unavailable -- make sure to retry
+                    Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR,
+                            new ClearRetryParams(transportName, packageName));
+                    mBackupHandler.sendMessageDelayed(msg, TRANSPORT_RETRY_INTERVAL);
+                    return;
+                }
                 long oldId = Binder.clearCallingIdentity();
                 mWakelock.acquire();
                 Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR,
-                        new ClearParams(getTransport(mCurrentTransport), info));
+                        new ClearParams(transport, info));
                 mBackupHandler.sendMessage(msg);
                 Binder.restoreCallingIdentity(oldId);
             }
@@ -5626,21 +5704,36 @@
                 + " restoreSet=" + Long.toHexString(restoreSet));
 
         if (mAutoRestore && mProvisioned && restoreSet != 0) {
-            // okay, we're going to attempt a restore of this package from this restore set.
-            // The eventual message back into the Package Manager to run the post-install
-            // steps for 'token' will be issued from the restore handling code.
+            // Do we have a transport to fetch data for us?
+            IBackupTransport transport = getTransport(mCurrentTransport);
+            if (transport == null) {
+                if (DEBUG) Slog.w(TAG, "No transport for install-time restore");
+                return;
+            }
 
-            // We can use a synthetic PackageInfo here because:
-            //   1. We know it's valid, since the Package Manager supplied the name
-            //   2. Only the packageName field will be used by the restore code
-            PackageInfo pkg = new PackageInfo();
-            pkg.packageName = packageName;
+            try {
+                // okay, we're going to attempt a restore of this package from this restore set.
+                // The eventual message back into the Package Manager to run the post-install
+                // steps for 'token' will be issued from the restore handling code.
 
-            mWakelock.acquire();
-            Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
-            msg.obj = new RestoreParams(getTransport(mCurrentTransport), null,
-                    restoreSet, pkg, token, true);
-            mBackupHandler.sendMessage(msg);
+                // This can throw and so *must* happen before the wakelock is acquired
+                String dirName = transport.transportDirName();
+
+                // We can use a synthetic PackageInfo here because:
+                //   1. We know it's valid, since the Package Manager supplied the name
+                //   2. Only the packageName field will be used by the restore code
+                PackageInfo pkg = new PackageInfo();
+                pkg.packageName = packageName;
+
+                mWakelock.acquire();
+                Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
+                msg.obj = new RestoreParams(transport, dirName, null,
+                        restoreSet, pkg, token, true);
+                mBackupHandler.sendMessage(msg);
+            } catch (RemoteException e) {
+                // Binding to the transport broke; back off and proceed with the installation.
+                Slog.e(TAG, "Unable to contact transport for install-time restore");
+            }
         } else {
             // Auto-restore disabled or no way to attempt a restore; just tell the Package
             // Manager to proceed with the post-install handling for this package.
@@ -5797,13 +5890,23 @@
                 return -1;
             }
 
+            String dirName;
+            try {
+                dirName = mRestoreTransport.transportDirName();
+            } catch (RemoteException e) {
+                // Transport went AWOL; fail.
+                Slog.e(TAG, "Unable to contact transport for restore");
+                return -1;
+            }
+
             synchronized (mQueueLock) {
                 for (int i = 0; i < mRestoreSets.length; i++) {
                     if (token == mRestoreSets[i].token) {
                         long oldId = Binder.clearCallingIdentity();
                         mWakelock.acquire();
                         Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
-                        msg.obj = new RestoreParams(mRestoreTransport, observer, token, true);
+                        msg.obj = new RestoreParams(mRestoreTransport, dirName,
+                                observer, token, true);
                         mBackupHandler.sendMessage(msg);
                         Binder.restoreCallingIdentity(oldId);
                         return 0;
@@ -5857,13 +5960,22 @@
                 return -1;
             }
 
+            String dirName;
+            try {
+                dirName = mRestoreTransport.transportDirName();
+            } catch (RemoteException e) {
+                // Transport went AWOL; fail.
+                Slog.e(TAG, "Unable to contact transport for restore");
+                return -1;
+            }
+
             synchronized (mQueueLock) {
                 for (int i = 0; i < mRestoreSets.length; i++) {
                     if (token == mRestoreSets[i].token) {
                         long oldId = Binder.clearCallingIdentity();
                         mWakelock.acquire();
                         Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
-                        msg.obj = new RestoreParams(mRestoreTransport, observer, token,
+                        msg.obj = new RestoreParams(mRestoreTransport, dirName, observer, token,
                                 packages, true);
                         mBackupHandler.sendMessage(msg);
                         Binder.restoreCallingIdentity(oldId);
@@ -5929,11 +6041,21 @@
                 return -1;
             }
 
+            String dirName;
+            try {
+                dirName = mRestoreTransport.transportDirName();
+            } catch (RemoteException e) {
+                // Transport went AWOL; fail.
+                Slog.e(TAG, "Unable to contact transport for restore");
+                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, 0, false);
+            msg.obj = new RestoreParams(mRestoreTransport, dirName,
+                    observer, token, app, 0, false);
             mBackupHandler.sendMessage(msg);
             Binder.restoreCallingIdentity(oldId);
             return 0;