Implement shared-storage full backup/restore

Every available shared-storage volume is backed up, tagged with its
ordinal in the set of mounted shared volumes.  This is an approximation
of "internal + the external card".  This lets us restore things to the
same volume [or "equivalent" volume, in the case of a cross-model
restore] as they originated on.

Also fixed a bug in the handling of files/dirs with spaces in
their names.

Change-Id: I380019da8d0bb5b3699bd7c11eeff621a88e78c3
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 7c6d3c1..b568af1 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -134,6 +134,7 @@
     // Timeout intervals for agent backup & restore operations
     static final long TIMEOUT_BACKUP_INTERVAL = 30 * 1000;
     static final long TIMEOUT_FULL_BACKUP_INTERVAL = 5 * 60 * 1000;
+    static final long TIMEOUT_SHARED_BACKUP_INTERVAL = 30 * 60 * 1000;
     static final long TIMEOUT_RESTORE_INTERVAL = 60 * 1000;
 
     // User confirmation timeout for a full backup/restore operation
@@ -1691,7 +1692,7 @@
         public void run() {
             final List<PackageInfo> packagesToBackup;
 
-            Slog.i(TAG, "--- Performing full-dataset restore ---");
+            Slog.i(TAG, "--- Performing full-dataset backup ---");
             sendStartBackup();
 
             // doAllApps supersedes the package set if any
@@ -1720,64 +1721,23 @@
                 }
             }
 
-            // Now back up the app data via the agent mechanism
             PackageInfo pkg = null;
             try {
+                // Now back up the app data via the agent mechanism
                 int N = packagesToBackup.size();
                 for (int i = 0; i < N; i++) {
                     pkg = packagesToBackup.get(i);
+                    backupOnePackage(pkg);
+                }
 
-                    Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName);
-
-                    IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo,
-                            IApplicationThread.BACKUP_MODE_FULL);
-                    if (agent != null) {
-                        try {
-                            ApplicationInfo app = pkg.applicationInfo;
-                            boolean sendApk = mIncludeApks
-                                    && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0)
-                                    && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
-                                        (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
-
-                            sendOnBackupPackage(pkg.packageName);
-
-                            {
-                                BackupDataOutput output = new BackupDataOutput(
-                                        mOutputFile.getFileDescriptor());
-
-                                if (DEBUG) Slog.d(TAG, "Writing manifest for " + pkg.packageName);
-                                writeAppManifest(pkg, mManifestFile, sendApk);
-                                FullBackup.backupToTar(pkg.packageName, null, null,
-                                        mFilesDir.getAbsolutePath(),
-                                        mManifestFile.getAbsolutePath(),
-                                        output);
-                            }
-
-                            if (DEBUG) Slog.d(TAG, "Calling doBackup()");
-                            final int token = generateToken();
-                            prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL);
-                            agent.doBackup(null, mOutputFile, null, sendApk,
-                                    token, mBackupManagerBinder);
-                            boolean success = waitUntilOperationComplete(token);
-                            if (!success) {
-                                Slog.d(TAG, "Full backup failed on package " + pkg.packageName);
-                            } else {
-                                if (DEBUG) Slog.d(TAG, "Full backup success: " + pkg.packageName);
-                            }
-                        } catch (IOException e) {
-                            Slog.e(TAG, "Error backing up " + pkg.packageName, e);
-                        }
-                    } else {
-                        Slog.w(TAG, "Unable to bind to full agent for " + pkg.packageName);
-                    }
-                    tearDown(pkg);
+                // Finally, shared storage if requested
+                if (mIncludeShared) {
+                    backupSharedStorage();
                 }
             } catch (RemoteException e) {
                 Slog.e(TAG, "App died during full backup");
             } finally {
-                if (pkg != null) {
-                    tearDown(pkg);
-                }
+                tearDown(pkg);
                 try {
                     mOutputFile.close();
                 } catch (IOException e) {
@@ -1796,6 +1756,79 @@
             }
         }
 
+        private void backupOnePackage(PackageInfo pkg) throws RemoteException {
+            Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName);
+
+            IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo,
+                    IApplicationThread.BACKUP_MODE_FULL);
+            if (agent != null) {
+                try {
+                    ApplicationInfo app = pkg.applicationInfo;
+                    boolean sendApk = mIncludeApks
+                            && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0)
+                            && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
+                                (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+
+                    sendOnBackupPackage(pkg.packageName);
+
+                    {
+                        BackupDataOutput output = new BackupDataOutput(
+                                mOutputFile.getFileDescriptor());
+
+                        if (DEBUG) Slog.d(TAG, "Writing manifest for " + pkg.packageName);
+                        writeAppManifest(pkg, mManifestFile, sendApk);
+                        FullBackup.backupToTar(pkg.packageName, null, null,
+                                mFilesDir.getAbsolutePath(),
+                                mManifestFile.getAbsolutePath(),
+                                output);
+                    }
+
+                    if (DEBUG) Slog.d(TAG, "Calling doBackup()");
+                    final int token = generateToken();
+                    prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL);
+                    agent.doBackup(null, mOutputFile, null, sendApk,
+                            token, mBackupManagerBinder);
+                    if (!waitUntilOperationComplete(token)) {
+                        Slog.e(TAG, "Full backup failed on package " + pkg.packageName);
+                    } else {
+                        if (DEBUG) Slog.d(TAG, "Full backup success: " + pkg.packageName);
+                    }
+                } catch (IOException e) {
+                    Slog.e(TAG, "Error backing up " + pkg.packageName, e);
+                }
+            } else {
+                Slog.w(TAG, "Unable to bind to full agent for " + pkg.packageName);
+            }
+            tearDown(pkg);
+        }
+
+        private void backupSharedStorage() throws RemoteException {
+            PackageInfo pkg = null;
+            try {
+                pkg = mPackageManager.getPackageInfo("com.android.sharedstoragebackup", 0);
+                IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo,
+                        IApplicationThread.BACKUP_MODE_FULL);
+                if (agent != null) {
+                    sendOnBackupPackage("Shared storage");
+
+                    final int token = generateToken();
+                    prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL);
+                    agent.doBackup(null, mOutputFile, null, false, token, mBackupManagerBinder);
+                    if (!waitUntilOperationComplete(token)) {
+                        Slog.e(TAG, "Full backup failed on shared storage");
+                    } else {
+                        if (DEBUG) Slog.d(TAG, "Full shared storage backup success");
+                    }
+                } else {
+                    Slog.e(TAG, "Could not bind to shared storage backup agent");
+                }
+            } catch (NameNotFoundException e) {
+                Slog.e(TAG, "Shared storage backup package not found");
+            } finally {
+                tearDown(pkg);
+            }
+        }
+
         private void writeAppManifest(PackageInfo pkg, File manifestFile, boolean withApk)
                 throws IOException {
             // Manifest format. All data are strings ending in LF:
@@ -1836,23 +1869,24 @@
         }
 
         private void tearDown(PackageInfo pkg) {
-            final ApplicationInfo app = pkg.applicationInfo;
-            try {
-                // unbind and tidy up even on timeout or failure, just in case
-                mActivityManager.unbindBackupAgent(app);
+            if (pkg != null) {
+                final ApplicationInfo app = pkg.applicationInfo;
+                if (app != null) {
+                    try {
+                        // unbind and tidy up even on timeout or failure, just in case
+                        mActivityManager.unbindBackupAgent(app);
 
-                // The agent was running with a stub Application object, so shut it down.
-                // !!! We hardcode the confirmation UI's package name here rather than use a
-                //     manifest flag!  TODO something less direct.
-                if (app.uid != Process.SYSTEM_UID
-                        && !pkg.packageName.equals("com.android.backupconfirm")) {
-                    if (DEBUG) Slog.d(TAG, "Backup complete, killing host process");
-                    mActivityManager.killApplicationProcess(app.processName, app.uid);
-                } else {
-                    if (DEBUG) Slog.d(TAG, "Not killing after restore: " + app.processName);
+                        // The agent was running with a stub Application object, so shut it down.
+                        if (app.uid != Process.SYSTEM_UID) {
+                            if (DEBUG) Slog.d(TAG, "Backup complete, killing host process");
+                            mActivityManager.killApplicationProcess(app.processName, app.uid);
+                        } else {
+                            if (DEBUG) Slog.d(TAG, "Not killing after restore: " + app.processName);
+                        }
+                    } catch (RemoteException e) {
+                        Slog.d(TAG, "Lost app trying to shut down");
+                    }
                 }
-            } catch (RemoteException e) {
-                Slog.d(TAG, "Lost app trying to shut down");
             }
         }
 
@@ -1949,6 +1983,7 @@
             // with a whitelist of packages known to be unclearable.
             mClearedPackages.add("android");
             mClearedPackages.add("com.android.providers.settings");
+
         }
 
         class RestoreFileRunnable implements Runnable {
@@ -1988,6 +2023,11 @@
             Slog.i(TAG, "--- Performing full-dataset restore ---");
             sendStartRestore();
 
+            // Are we able to restore shared-storage data?
+            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+                mPackagePolicies.put("com.android.sharedstoragebackup", RestorePolicy.ACCEPT);
+            }
+
             try {
                 byte[] buffer = new byte[32 * 1024];
                 FileInputStream instream = new FileInputStream(mInputFile.getFileDescriptor());
@@ -2707,7 +2747,9 @@
                         info.path, 0, FullBackup.SHARED_PREFIX.length())) {
                     // File in shared storage.  !!! TODO: implement this.
                     info.path = info.path.substring(FullBackup.SHARED_PREFIX.length());
+                    info.packageName = "com.android.sharedstoragebackup";
                     info.domain = FullBackup.SHARED_STORAGE_TOKEN;
+                    if (DEBUG) Slog.i(TAG, "File in shared storage: " + info.path);
                 } else if (FullBackup.APPS_PREFIX.regionMatches(0,
                         info.path, 0, FullBackup.APPS_PREFIX.length())) {
                     // App content!  Parse out the package name and domain