OSDN Git Service

Add support for key-value packages to adb backup/restore.
authorJohan Toras Halseth <johanth@google.com>
Fri, 3 Mar 2017 15:37:43 +0000 (15:37 +0000)
committerMichal Karpinski <mkarpinski@google.com>
Fri, 24 Mar 2017 23:51:48 +0000 (23:51 +0000)
For adding CTS tests for packages having key-value backup agents, we add
support for key-value backups to the adb backup/restore command.
Previously, packages not supporting fullbackup would just be skipped on
this command. Now, by adding the -keyvalue flag to the adb backup command,
packages supporting key-value will also be added to the resulting
tarball. Similarly, if the tarball being supplied to adb restore
contains data from key-value packages, it will be restored.

This will later be utilized for writing CTS tests for such packages.

Test: adb backup -includekeyvalue -all && adb restore backup.ab

Change-Id: I7b4ccfb7072d01d29a888952145d7cce90a4f59e

cmds/bu/src/com/android/commands/bu/Backup.java
core/java/android/app/backup/FullBackup.java
core/java/android/app/backup/IBackupManager.aidl
services/backup/java/com/android/server/backup/BackupManagerService.java
services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java [new file with mode: 0644]
services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java [new file with mode: 0644]
services/backup/java/com/android/server/backup/Trampoline.java

index ffc0f87..db17b28 100644 (file)
@@ -53,15 +53,15 @@ public final class Backup {
 
         String arg = nextArg();
         if (arg.equals("backup")) {
-            doFullBackup(OsConstants.STDOUT_FILENO);
+            doBackup(OsConstants.STDOUT_FILENO);
         } else if (arg.equals("restore")) {
-            doFullRestore(OsConstants.STDIN_FILENO);
+            doRestore(OsConstants.STDIN_FILENO);
         } else {
             Log.e(TAG, "Invalid operation '" + arg + "'");
         }
     }
 
-    private void doFullBackup(int socketFd) {
+    private void doBackup(int socketFd) {
         ArrayList<String> packages = new ArrayList<String>();
         boolean saveApks = false;
         boolean saveObbs = false;
@@ -70,6 +70,7 @@ public final class Backup {
         boolean doWidgets = false;
         boolean allIncludesSystem = true;
         boolean doCompress = true;
+        boolean doKeyValue = false;
 
         String arg;
         while ((arg = nextArg()) != null) {
@@ -100,6 +101,8 @@ public final class Backup {
                     doCompress = true;
                 } else if ("-nocompress".equals(arg)) {
                     doCompress = false;
+                } else if ("-includekeyvalue".equals(arg)) {
+                    doKeyValue = true;
                 } else {
                     Log.w(TAG, "Unknown backup flag " + arg);
                     continue;
@@ -123,8 +126,8 @@ public final class Backup {
         try {
             fd = ParcelFileDescriptor.adoptFd(socketFd);
             String[] packArray = new String[packages.size()];
-            mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doWidgets,
-                    doEverything, allIncludesSystem, doCompress, packages.toArray(packArray));
+            mBackupManager.adbBackup(fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
+                    allIncludesSystem, doCompress, doKeyValue, packages.toArray(packArray));
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to invoke backup manager for backup");
         } finally {
@@ -136,12 +139,12 @@ public final class Backup {
         }
     }
 
-    private void doFullRestore(int socketFd) {
+    private void doRestore(int socketFd) {
         // No arguments to restore
         ParcelFileDescriptor fd = null;
         try {
             fd = ParcelFileDescriptor.adoptFd(socketFd);
-            mBackupManager.fullRestore(fd);
+            mBackupManager.adbRestore(fd);
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to invoke backup manager for restore");
         } finally {
index 76828ee..a5dd5bd 100644 (file)
@@ -56,6 +56,7 @@ public class FullBackup {
 
     public static final String APK_TREE_TOKEN = "a";
     public static final String OBB_TREE_TOKEN = "obb";
+    public static final String KEY_VALUE_DATA_TOKEN = "k";
 
     public static final String ROOT_TREE_TOKEN = "r";
     public static final String FILES_TREE_TOKEN = "f";
index 59a941a..9c3b110 100644 (file)
@@ -144,9 +144,10 @@ interface IBackupManager {
     void backupNow();
 
     /**
-     * Write a full backup of the given package to the supplied file descriptor.
+     * Write a backup of the given package to the supplied file descriptor.
      * The fd may be a socket or other non-seekable destination.  If no package names
      * are supplied, then every application on the device will be backed up to the output.
+     * Currently only used by the 'adb backup' command.
      *
      * <p>This method is <i>synchronous</i> -- it does not return until the backup has
      * completed.
@@ -167,12 +168,14 @@ interface IBackupManager {
      *     as including packages pre-installed as part of the system. If {@code false},
      *     then setting {@code allApps} to {@code true} will mean only that all 3rd-party
      *     applications will be included in the dataset.
+     * @param doKeyValue If {@code true}, also packages supporting key-value backup will be backed
+     *     up. If {@code false}, key-value packages will be skipped.
      * @param packageNames The package names of the apps whose data (and optionally .apk files)
      *     are to be backed up.  The <code>allApps</code> parameter supersedes this.
      */
-    void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+    void adbBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
             boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
-            boolean doCompress, in String[] packageNames);
+            boolean doCompress, boolean doKeyValue, in String[] packageNames);
 
     /**
      * Perform a full-dataset backup of the given applications via the currently active
@@ -184,11 +187,12 @@ interface IBackupManager {
 
     /**
      * Restore device content from the data stream passed through the given socket.  The
-     * data stream must be in the format emitted by fullBackup().
+     * data stream must be in the format emitted by adbBackup().
+     * Currently only used by the 'adb restore' command.
      *
      * <p>Callers must hold the android.permission.BACKUP permission to use this method.
      */
-    void fullRestore(in ParcelFileDescriptor fd);
+    void adbRestore(in ParcelFileDescriptor fd);
 
     /**
      * Confirm that the requested full backup/restore operation can proceed.  The system will
index 30d06db..037804e 100644 (file)
@@ -222,12 +222,27 @@ public class BackupManagerService {
     // 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection
     // 3 : introduced "_meta" metadata file; no other format change per se
     // 4 : added support for new device-encrypted storage locations
-    static final int BACKUP_FILE_VERSION = 4;
+    // 5 : added support for key-value packages
+    static final int BACKUP_FILE_VERSION = 5;
     static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
     static final int BACKUP_PW_FILE_VERSION = 2;
     static final String BACKUP_METADATA_FILENAME = "_meta";
     static final int BACKUP_METADATA_VERSION = 1;
     static final int BACKUP_WIDGET_METADATA_TOKEN = 0x01FFED01;
+
+    static final int TAR_HEADER_LONG_RADIX = 8;
+    static final int TAR_HEADER_OFFSET_FILESIZE = 124;
+    static final int TAR_HEADER_LENGTH_FILESIZE = 12;
+    static final int TAR_HEADER_OFFSET_MODTIME = 136;
+    static final int TAR_HEADER_LENGTH_MODTIME = 12;
+    static final int TAR_HEADER_OFFSET_MODE = 100;
+    static final int TAR_HEADER_LENGTH_MODE = 8;
+    static final int TAR_HEADER_OFFSET_PATH_PREFIX = 345;
+    static final int TAR_HEADER_LENGTH_PATH_PREFIX = 155;
+    static final int TAR_HEADER_OFFSET_PATH = 0;
+    static final int TAR_HEADER_LENGTH_PATH = 100;
+    static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
+
     static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
 
     static final String SETTINGS_PACKAGE = "com.android.providers.settings";
@@ -553,19 +568,20 @@ public class BackupManagerService {
         }
     }
 
-    class FullParams {
+    // Parameters used by adbBackup() and adbRestore()
+    class AdbParams {
         public ParcelFileDescriptor fd;
         public final AtomicBoolean latch;
         public IFullBackupRestoreObserver observer;
         public String curPassword;     // filled in by the confirmation step
         public String encryptPassword;
 
-        FullParams() {
+        AdbParams() {
             latch = new AtomicBoolean(false);
         }
     }
 
-    class FullBackupParams extends FullParams {
+    class AdbBackupParams extends AdbParams {
         public boolean includeApks;
         public boolean includeObbs;
         public boolean includeShared;
@@ -573,11 +589,12 @@ public class BackupManagerService {
         public boolean allApps;
         public boolean includeSystem;
         public boolean doCompress;
+        public boolean includeKeyValue;
         public String[] packages;
 
-        FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
+        AdbBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
                 boolean saveShared, boolean alsoWidgets, boolean doAllApps, boolean doSystem,
-                boolean compress, String[] pkgList) {
+                boolean compress, boolean doKeyValue, String[] pkgList) {
             fd = output;
             includeApks = saveApks;
             includeObbs = saveObbs;
@@ -586,12 +603,13 @@ public class BackupManagerService {
             allApps = doAllApps;
             includeSystem = doSystem;
             doCompress = compress;
+            includeKeyValue = doKeyValue;
             packages = pkgList;
         }
     }
 
-    class FullRestoreParams extends FullParams {
-        FullRestoreParams(ParcelFileDescriptor input) {
+    class AdbRestoreParams extends AdbParams {
+        AdbRestoreParams(ParcelFileDescriptor input) {
             fd = input;
         }
     }
@@ -627,10 +645,10 @@ public class BackupManagerService {
     static final int OP_TIMEOUT = -1;
 
     // Waiting for backup agent to respond during backup operation.
-    private static final int OP_TYPE_BACKUP_WAIT = 0;
+    static final int OP_TYPE_BACKUP_WAIT = 0;
 
     // Waiting for backup agent to respond during restore operation.
-    private static final int OP_TYPE_RESTORE_WAIT = 1;
+    static final int OP_TYPE_RESTORE_WAIT = 1;
 
     // An entire backup operation spanning multiple packages.
     private static final int OP_TYPE_BACKUP = 2;
@@ -672,7 +690,7 @@ public class BackupManagerService {
     final Object mCurrentOpLock = new Object();
     final Random mTokenGenerator = new Random();
 
-    final SparseArray<FullParams> mFullConfirmations = new SparseArray<FullParams>();
+    final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<AdbParams>();
 
     // Where we keep our journal files and other bookkeeping
     File mBaseStateDir;
@@ -791,15 +809,9 @@ public class BackupManagerService {
     }
 
     /* adb backup: is this app only capable of doing key/value?  We say otherwise if
-     * the app has a backup agent and does not say fullBackupOnly, *unless* it
-     * is a package that we know _a priori_ explicitly supports both key/value and
-     * full-data backup.
+     * the app has a backup agent and does not say fullBackupOnly,
      */
     private static boolean appIsKeyValueOnly(PackageInfo pkg) {
-        if ("com.android.providers.settings".equals(pkg.packageName)) {
-            return false;
-        }
-
         return !appGetsFullBackup(pkg);
     }
 
@@ -912,13 +924,12 @@ public class BackupManagerService {
             {
                 // TODO: refactor full backup to be a looper-based state machine
                 // similar to normal backup/restore.
-                FullBackupParams params = (FullBackupParams)msg.obj;
+                AdbBackupParams params = (AdbBackupParams)msg.obj;
                 PerformAdbBackupTask task = new PerformAdbBackupTask(params.fd,
                         params.observer, params.includeApks, params.includeObbs,
-                        params.includeShared, params.doWidgets,
-                        params.curPassword, params.encryptPassword,
-                        params.allApps, params.includeSystem, params.doCompress,
-                        params.packages, params.latch);
+                        params.includeShared, params.doWidgets, params.curPassword,
+                        params.encryptPassword, params.allApps, params.includeSystem,
+                        params.doCompress, params.includeKeyValue, params.packages, params.latch);
                 (new Thread(task, "adb-backup")).start();
                 break;
             }
@@ -963,7 +974,7 @@ public class BackupManagerService {
             {
                 // TODO: refactor full restore to be a looper-based state machine
                 // similar to normal backup/restore.
-                FullRestoreParams params = (FullRestoreParams)msg.obj;
+                AdbRestoreParams params = (AdbRestoreParams)msg.obj;
                 PerformAdbRestoreTask task = new PerformAdbRestoreTask(params.fd,
                         params.curPassword, params.encryptPassword,
                         params.observer, params.latch);
@@ -1071,16 +1082,16 @@ public class BackupManagerService {
 
             case MSG_FULL_CONFIRMATION_TIMEOUT:
             {
-                synchronized (mFullConfirmations) {
-                    FullParams params = mFullConfirmations.get(msg.arg1);
+                synchronized (mAdbBackupRestoreConfirmations) {
+                    AdbParams params = mAdbBackupRestoreConfirmations.get(msg.arg1);
                     if (params != null) {
                         Slog.i(TAG, "Full backup/restore timed out waiting for user confirmation");
 
                         // Release the waiter; timeout == completion
-                        signalFullBackupRestoreCompletion(params);
+                        signalAdbBackupRestoreCompletion(params);
 
                         // Remove the token from the set
-                        mFullConfirmations.delete(msg.arg1);
+                        mAdbBackupRestoreConfirmations.delete(msg.arg1);
 
                         // Report a timeout to the observer, if any
                         if (params.observer != null) {
@@ -3719,7 +3730,7 @@ public class BackupManagerService {
 
     }
 
-    private void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
+    static void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
             throws IOException {
         // We do not take close() responsibility for the pipe FD
         FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor());
@@ -3822,7 +3833,7 @@ public class BackupManagerService {
                     if (mWriteManifest) {
                         final boolean writeWidgetData = mWidgetData != null;
                         if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
-                        writeAppManifest(mPackage, mManifestFile, mSendApk, writeWidgetData);
+                        writeAppManifest(mPackage, mPackageManager, mManifestFile, mSendApk, writeWidgetData);
                         FullBackup.backupToTar(mPackage.packageName, null, null,
                                 mFilesDir.getAbsolutePath(),
                                 mManifestFile.getAbsolutePath(),
@@ -4006,52 +4017,6 @@ public class BackupManagerService {
             }
         }
 
-        private void writeAppManifest(PackageInfo pkg, File manifestFile,
-                boolean withApk, boolean withWidgets) throws IOException {
-            // Manifest format. All data are strings ending in LF:
-            //     BACKUP_MANIFEST_VERSION, currently 1
-            //
-            // Version 1:
-            //     package name
-            //     package's versionCode
-            //     platform versionCode
-            //     getInstallerPackageName() for this package (maybe empty)
-            //     boolean: "1" if archive includes .apk; any other string means not
-            //     number of signatures == N
-            // N*:    signature byte array in ascii format per Signature.toCharsString()
-            StringBuilder builder = new StringBuilder(4096);
-            StringBuilderPrinter printer = new StringBuilderPrinter(builder);
-
-            printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
-            printer.println(pkg.packageName);
-            printer.println(Integer.toString(pkg.versionCode));
-            printer.println(Integer.toString(Build.VERSION.SDK_INT));
-
-            String installerName = mPackageManager.getInstallerPackageName(pkg.packageName);
-            printer.println((installerName != null) ? installerName : "");
-
-            printer.println(withApk ? "1" : "0");
-            if (pkg.signatures == null) {
-                printer.println("0");
-            } else {
-                printer.println(Integer.toString(pkg.signatures.length));
-                for (Signature sig : pkg.signatures) {
-                    printer.println(sig.toCharsString());
-                }
-            }
-
-            FileOutputStream outstream = new FileOutputStream(manifestFile);
-            outstream.write(builder.toString().getBytes());
-            outstream.close();
-
-            // We want the manifest block in the archive stream to be idempotent:
-            // each time we generate a backup stream for the app, we want the manifest
-            // block to be identical.  The underlying tar mechanism sees it as a file,
-            // though, and will propagate its mtime, causing the tar header to vary.
-            // Avoid this problem by pinning the mtime to zero.
-            manifestFile.setLastModified(0);
-        }
-
         // Widget metadata format. All header entries are strings ending in LF:
         //
         // Version 1 header:
@@ -4100,6 +4065,52 @@ public class BackupManagerService {
         }
     }
 
+    static void writeAppManifest(PackageInfo pkg, PackageManager packageManager, File manifestFile,
+            boolean withApk, boolean withWidgets) throws IOException {
+        // Manifest format. All data are strings ending in LF:
+        //     BACKUP_MANIFEST_VERSION, currently 1
+        //
+        // Version 1:
+        //     package name
+        //     package's versionCode
+        //     platform versionCode
+        //     getInstallerPackageName() for this package (maybe empty)
+        //     boolean: "1" if archive includes .apk; any other string means not
+        //     number of signatures == N
+        // N*:    signature byte array in ascii format per Signature.toCharsString()
+        StringBuilder builder = new StringBuilder(4096);
+        StringBuilderPrinter printer = new StringBuilderPrinter(builder);
+
+        printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
+        printer.println(pkg.packageName);
+        printer.println(Integer.toString(pkg.versionCode));
+        printer.println(Integer.toString(Build.VERSION.SDK_INT));
+
+        String installerName = packageManager.getInstallerPackageName(pkg.packageName);
+        printer.println((installerName != null) ? installerName : "");
+
+        printer.println(withApk ? "1" : "0");
+        if (pkg.signatures == null) {
+            printer.println("0");
+        } else {
+            printer.println(Integer.toString(pkg.signatures.length));
+            for (Signature sig : pkg.signatures) {
+                printer.println(sig.toCharsString());
+            }
+        }
+
+        FileOutputStream outstream = new FileOutputStream(manifestFile);
+        outstream.write(builder.toString().getBytes());
+        outstream.close();
+
+        // We want the manifest block in the archive stream to be idempotent:
+        // each time we generate a backup stream for the app, we want the manifest
+        // block to be identical.  The underlying tar mechanism sees it as a file,
+        // though, and will propagate its mtime, causing the tar header to vary.
+        // Avoid this problem by pinning the mtime to zero.
+        manifestFile.setLastModified(0);
+    }
+
     // Generic driver skeleton for full backup operations
     abstract class FullBackupTask implements Runnable {
         IFullBackupRestoreObserver mObserver;
@@ -4172,6 +4183,7 @@ public class BackupManagerService {
         boolean mAllApps;
         boolean mIncludeSystem;
         boolean mCompress;
+        boolean mKeyValue;
         ArrayList<String> mPackages;
         PackageInfo mCurrentTarget;
         String mCurrentPassword;
@@ -4179,9 +4191,9 @@ public class BackupManagerService {
         private final int mCurrentOpToken;
 
         PerformAdbBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
-                boolean includeApks, boolean includeObbs, boolean includeShared,
-                boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps,
-                boolean doSystem, boolean doCompress, String[] packages, AtomicBoolean latch) {
+                boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
+                String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem,
+                boolean doCompress, boolean doKeyValue, String[] packages, AtomicBoolean latch) {
             super(observer);
             mCurrentOpToken = generateToken();
             mLatch = latch;
@@ -4210,6 +4222,7 @@ public class BackupManagerService {
                 Slog.w(TAG, "Encrypting backup with passphrase=" + mEncryptPassword);
             }
             mCompress = doCompress;
+            mKeyValue = doKeyValue;
         }
 
         void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) {
@@ -4309,7 +4322,8 @@ public class BackupManagerService {
 
         @Override
         public void run() {
-            Slog.i(TAG, "--- Performing full-dataset adb backup ---");
+            String includeKeyValue = mKeyValue ? ", including key-value backups" : "";
+            Slog.i(TAG, "--- Performing adb backup" + includeKeyValue + " ---");
 
             TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<String, PackageInfo>();
             FullBackupObbConnection obbConnection = new FullBackupObbConnection();
@@ -4361,14 +4375,26 @@ public class BackupManagerService {
 
             // Now we cull any inapplicable / inappropriate packages from the set.  This
             // includes the special shared-storage agent package; we handle that one
-            // explicitly at the end of the backup pass.
+            // explicitly at the end of the backup pass. Packages supporting key-value backup are
+            // added to their own queue, and handled after packages supporting fullbackup.
+            ArrayList<PackageInfo> keyValueBackupQueue = new ArrayList<>();
             Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
             while (iter.hasNext()) {
                 PackageInfo pkg = iter.next().getValue();
                 if (!appIsEligibleForBackup(pkg.applicationInfo)
-                        || appIsStopped(pkg.applicationInfo)
-                        || appIsKeyValueOnly(pkg)) {
+                        || appIsStopped(pkg.applicationInfo)) {
+                    iter.remove();
+                    if (DEBUG) {
+                        Slog.i(TAG, "Package " + pkg.packageName
+                                + " is not eligible for backup, removing.");
+                    }
+                } else if (appIsKeyValueOnly(pkg)) {
                     iter.remove();
+                    if (DEBUG) {
+                        Slog.i(TAG, "Package " + pkg.packageName
+                                + " is key-value.");
+                    }
+                    keyValueBackupQueue.add(pkg);
                 }
             }
 
@@ -4402,7 +4428,7 @@ public class BackupManagerService {
                 // final '\n'.
                 //
                 // line 1: "ANDROID BACKUP"
-                // line 2: backup file format version, currently "2"
+                // line 2: backup file format version, currently "5"
                 // line 3: compressed?  "0" if not compressed, "1" if compressed.
                 // line 4: name of encryption algorithm [currently only "none" or "AES-256"]
                 //
@@ -4462,10 +4488,14 @@ public class BackupManagerService {
                     }
                 }
 
-                // Now actually run the constructed backup sequence
+                // Now actually run the constructed backup sequence for full backup
                 int N = backupQueue.size();
                 for (int i = 0; i < N; i++) {
                     pkg = backupQueue.get(i);
+                    if (DEBUG) {
+                        Slog.i(TAG,"--- Performing full backup for package " + pkg.packageName
+                                + " ---");
+                    }
                     final boolean isSharedStorage =
                             pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
 
@@ -4485,6 +4515,21 @@ public class BackupManagerService {
                         }
                     }
                 }
+                // And for key-value backup if enabled
+                if (mKeyValue) {
+                    for (PackageInfo keyValuePackage : keyValueBackupQueue) {
+                        if (DEBUG) {
+                            Slog.i(TAG, "--- Performing key-value backup for package "
+                                    + keyValuePackage.packageName + " ---");
+                        }
+                        KeyValueAdbBackupEngine kvBackupEngine =
+                                new KeyValueAdbBackupEngine(out, keyValuePackage,
+                                        BackupManagerService.this,
+                                        mPackageManager, mBaseStateDir, mDataDir);
+                        sendOnBackupPackage(keyValuePackage.packageName);
+                        kvBackupEngine.backupOnePackage();
+                    }
+                }
 
                 // Done!
                 finalizeBackup(out);
@@ -6693,19 +6738,24 @@ public class BackupManagerService {
                 try {
                     // okay, presume we're okay, and extract the various metadata
                     info = new FileMetadata();
-                    info.size = extractRadix(block, 124, 12, 8);
-                    info.mtime = extractRadix(block, 136, 12, 8);
-                    info.mode = extractRadix(block, 100, 8, 8);
-
-                    info.path = extractString(block, 345, 155); // prefix
-                    String path = extractString(block, 0, 100);
+                    info.size = extractRadix(block, TAR_HEADER_OFFSET_FILESIZE,
+                            TAR_HEADER_LENGTH_FILESIZE, TAR_HEADER_LONG_RADIX);
+                    info.mtime = extractRadix(block, TAR_HEADER_OFFSET_MODTIME,
+                            TAR_HEADER_LENGTH_MODTIME, TAR_HEADER_LONG_RADIX);
+                    info.mode = extractRadix(block, TAR_HEADER_OFFSET_MODE,
+                            TAR_HEADER_LENGTH_MODE, TAR_HEADER_LONG_RADIX);
+
+                    info.path = extractString(block, TAR_HEADER_OFFSET_PATH_PREFIX,
+                            TAR_HEADER_LENGTH_PATH_PREFIX);
+                    String path = extractString(block, TAR_HEADER_OFFSET_PATH,
+                            TAR_HEADER_LENGTH_PATH);
                     if (path.length() > 0) {
                         if (info.path.length() > 0) info.path += '/';
                         info.path += path;
                     }
 
                     // tar link indicator field: 1 byte at offset 156 in the header.
-                    int typeChar = block[156];
+                    int typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
                     if (typeChar == 'x') {
                         // pax extended header, so we need to read that
                         gotHeader = readPaxExtendedHeader(instream, info);
@@ -6716,7 +6766,7 @@ public class BackupManagerService {
                         }
                         if (!gotHeader) throw new IOException("Bad or missing pax header");
 
-                        typeChar = block[156];
+                        typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
                     }
 
                     switch (typeChar) {
@@ -7037,6 +7087,7 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
         IFullBackupRestoreObserver mObserver;
         AtomicBoolean mLatchObject;
         IBackupAgent mAgent;
+        PackageManagerBackupAgent mPackageManagerBackupAgent;
         String mAgentPackage;
         ApplicationInfo mTargetApp;
         FullBackupObbConnection mObbConnection = null;
@@ -7088,6 +7139,7 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
             mObserver = observer;
             mLatchObject = latch;
             mAgent = null;
+            mPackageManagerBackupAgent = new PackageManagerBackupAgent(mPackageManager);
             mAgentPackage = null;
             mTargetApp = null;
             mObbConnection = new FullBackupObbConnection();
@@ -7505,14 +7557,21 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
                             long toCopy = info.size;
                             final int token = generateToken();
                             try {
-                                prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null,
+                                prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, null,
                                         OP_TYPE_RESTORE_WAIT);
-                                if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) {
+                                if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
                                     if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg
                                             + " : " + info.path);
                                     mObbConnection.restoreObbFile(pkg, mPipes[0],
                                             info.size, info.type, info.path, info.mode,
                                             info.mtime, token, mBackupManagerBinder);
+                                } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) {
+                                    if (DEBUG) Slog.d(TAG, "Restoring key-value file for " + pkg
+                                            + " : " + info.path);
+                                    KeyValueAdbRestoreEngine restoreEngine =
+                                            new KeyValueAdbRestoreEngine(BackupManagerService.this,
+                                                    mDataDir, info, mPipes[0], mAgent, token);
+                                    new Thread(restoreEngine, "restore-key-value-runner").start();
                                 } else {
                                     if (DEBUG) Slog.d(TAG, "Invoking agent to restore file "
                                             + info.path);
@@ -8100,6 +8159,7 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
                 Slog.i(TAG, b.toString());
             }
         }
+
         // Consume a tar file header block [sequence] and accumulate the relevant metadata
         FileMetadata readTarHeaders(InputStream instream) throws IOException {
             byte[] block = new byte[512];
@@ -9920,16 +9980,16 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
         return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0);
     }
 
-    // Run a *full* backup pass for the given packages, writing the resulting data stream
+    // Run a backup pass for the given packages, writing the resulting data stream
     // to the supplied file descriptor.  This method is synchronous and does not return
     // to the caller until the backup has been completed.
     //
     // This is the variant used by 'adb backup'; it requires on-screen confirmation
     // by the user because it can be used to offload data over untrusted USB.
-    public void fullBackup(ParcelFileDescriptor fd, boolean includeApks,
-            boolean includeObbs, boolean includeShared, boolean doWidgets,
-            boolean doAllApps, boolean includeSystem, boolean compress, String[] pkgList) {
-        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");
+    public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+            boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
+            boolean compress, boolean doKeyValue, String[] pkgList) {
+        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup");
 
         final int callingUserHandle = UserHandle.getCallingUserId();
         // TODO: http://b/22388012
@@ -9954,27 +10014,28 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
         try {
             // Doesn't make sense to do a full backup prior to setup
             if (!deviceIsProvisioned()) {
-                Slog.i(TAG, "Full backup not supported before setup");
+                Slog.i(TAG, "Backup not supported before setup");
                 return;
             }
 
-            if (DEBUG) Slog.v(TAG, "Requesting full backup: apks=" + includeApks
-                    + " obb=" + includeObbs + " shared=" + includeShared + " all=" + doAllApps
-                    + " system=" + includeSystem + " pkgs=" + pkgList);
-            Slog.i(TAG, "Beginning full backup...");
+            if (DEBUG) Slog.v(TAG, "Requesting backup: apks=" + includeApks + " obb=" + includeObbs
+                    + " shared=" + includeShared + " all=" + doAllApps + " system="
+                    + includeSystem + " includekeyvalue=" + doKeyValue + " pkgs=" + pkgList);
+            Slog.i(TAG, "Beginning adb backup...");
 
-            FullBackupParams params = new FullBackupParams(fd, includeApks, includeObbs,
-                    includeShared, doWidgets, doAllApps, includeSystem, compress, pkgList);
+            AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs,
+                    includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue,
+                    pkgList);
             final int token = generateToken();
-            synchronized (mFullConfirmations) {
-                mFullConfirmations.put(token, params);
+            synchronized (mAdbBackupRestoreConfirmations) {
+                mAdbBackupRestoreConfirmations.put(token, params);
             }
 
             // start up the confirmation UI
             if (DEBUG) Slog.d(TAG, "Starting backup confirmation UI, token=" + token);
             if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
-                Slog.e(TAG, "Unable to launch full backup confirmation");
-                mFullConfirmations.delete(token);
+                Slog.e(TAG, "Unable to launch backup confirmation UI");
+                mAdbBackupRestoreConfirmations.delete(token);
                 return;
             }
 
@@ -9987,7 +10048,7 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
             startConfirmationTimeout(token, params);
 
             // wait for the backup to be performed
-            if (DEBUG) Slog.d(TAG, "Waiting for full backup completion...");
+            if (DEBUG) Slog.d(TAG, "Waiting for backup completion...");
             waitForCompletion(params);
         } finally {
             try {
@@ -9996,7 +10057,7 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
                 // just eat it
             }
             Binder.restoreCallingIdentity(oldId);
-            Slog.d(TAG, "Full backup processing complete.");
+            Slog.d(TAG, "Adb backup processing complete.");
         }
     }
 
@@ -10049,8 +10110,8 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
         }
     }
 
-    public void fullRestore(ParcelFileDescriptor fd) {
-        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullRestore");
+    public void adbRestore(ParcelFileDescriptor fd) {
+        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore");
 
         final int callingUserHandle = UserHandle.getCallingUserId();
         // TODO: http://b/22388012
@@ -10068,19 +10129,19 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
                 return;
             }
 
-            Slog.i(TAG, "Beginning full restore...");
+            Slog.i(TAG, "Beginning restore...");
 
-            FullRestoreParams params = new FullRestoreParams(fd);
+            AdbRestoreParams params = new AdbRestoreParams(fd);
             final int token = generateToken();
-            synchronized (mFullConfirmations) {
-                mFullConfirmations.put(token, params);
+            synchronized (mAdbBackupRestoreConfirmations) {
+                mAdbBackupRestoreConfirmations.put(token, params);
             }
 
             // start up the confirmation UI
             if (DEBUG) Slog.d(TAG, "Starting restore confirmation UI, token=" + token);
             if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) {
-                Slog.e(TAG, "Unable to launch full restore confirmation");
-                mFullConfirmations.delete(token);
+                Slog.e(TAG, "Unable to launch restore confirmation");
+                mAdbBackupRestoreConfirmations.delete(token);
                 return;
             }
 
@@ -10093,16 +10154,16 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
             startConfirmationTimeout(token, params);
 
             // wait for the restore to be performed
-            if (DEBUG) Slog.d(TAG, "Waiting for full restore completion...");
+            if (DEBUG) Slog.d(TAG, "Waiting for restore completion...");
             waitForCompletion(params);
         } finally {
             try {
                 fd.close();
             } catch (IOException e) {
-                Slog.w(TAG, "Error trying to close fd after full restore: " + e);
+                Slog.w(TAG, "Error trying to close fd after adb restore: " + e);
             }
             Binder.restoreCallingIdentity(oldId);
-            Slog.i(TAG, "Full restore processing complete.");
+            Slog.i(TAG, "adb restore processing complete.");
         }
     }
 
@@ -10120,7 +10181,7 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
         return true;
     }
 
-    void startConfirmationTimeout(int token, FullParams params) {
+    void startConfirmationTimeout(int token, AdbParams params) {
         if (MORE_DEBUG) Slog.d(TAG, "Posting conf timeout msg after "
                 + TIMEOUT_FULL_CONFIRMATION + " millis");
         Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
@@ -10128,7 +10189,7 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
         mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION);
     }
 
-    void waitForCompletion(FullParams params) {
+    void waitForCompletion(AdbParams params) {
         synchronized (params.latch) {
             while (params.latch.get() == false) {
                 try {
@@ -10138,7 +10199,7 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
         }
     }
 
-    void signalFullBackupRestoreCompletion(FullParams params) {
+    void signalAdbBackupRestoreCompletion(AdbParams params) {
         synchronized (params.latch) {
             params.latch.set(true);
             params.latch.notifyAll();
@@ -10147,27 +10208,27 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
 
     // Confirm that the previously-requested full backup/restore operation can proceed.  This
     // is used to require a user-facing disclosure about the operation.
-    public void acknowledgeFullBackupOrRestore(int token, boolean allow,
+    public void acknowledgeAdbBackupOrRestore(int token, boolean allow,
             String curPassword, String encPpassword, IFullBackupRestoreObserver observer) {
-        if (DEBUG) Slog.d(TAG, "acknowledgeFullBackupOrRestore : token=" + token
+        if (DEBUG) Slog.d(TAG, "acknowledgeAdbBackupOrRestore : token=" + token
                 + " allow=" + allow);
 
         // TODO: possibly require not just this signature-only permission, but even
         // require that the specific designated confirmation-UI app uid is the caller?
-        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "acknowledgeFullBackupOrRestore");
+        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "acknowledgeAdbBackupOrRestore");
 
         long oldId = Binder.clearCallingIdentity();
         try {
 
-            FullParams params;
-            synchronized (mFullConfirmations) {
-                params = mFullConfirmations.get(token);
+            AdbParams params;
+            synchronized (mAdbBackupRestoreConfirmations) {
+                params = mAdbBackupRestoreConfirmations.get(token);
                 if (params != null) {
                     mBackupHandler.removeMessages(MSG_FULL_CONFIRMATION_TIMEOUT, params);
-                    mFullConfirmations.delete(token);
+                    mAdbBackupRestoreConfirmations.delete(token);
 
                     if (allow) {
-                        final int verb = params instanceof FullBackupParams
+                        final int verb = params instanceof AdbBackupParams
                                 ? MSG_RUN_ADB_BACKUP
                                 : MSG_RUN_ADB_RESTORE;
 
@@ -10183,7 +10244,7 @@ if (MORE_DEBUG) Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soF
                     } else {
                         Slog.w(TAG, "User rejected full backup/restore operation");
                         // indicate completion without having actually transferred any data
-                        signalFullBackupRestoreCompletion(params);
+                        signalAdbBackupRestoreCompletion(params);
                     }
                 } else {
                     Slog.w(TAG, "Attempted to ack full backup/restore with invalid token");
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
new file mode 100644 (file)
index 0000000..cd13760
--- /dev/null
@@ -0,0 +1,281 @@
+package com.android.server.backup;
+
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL;
+
+import android.app.ApplicationThreadConstants;
+import android.app.IBackupAgent;
+import android.app.backup.FullBackup;
+import android.app.backup.FullBackupDataOutput;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.util.Slog;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Used by BackupManagerService to perform adb backup for key-value packages. At the moment this
+ * class resembles what is done in the standard key-value code paths in BackupManagerService, and
+ * should be unified later.
+ *
+ * TODO: We should create unified backup/restore engines that can be used for both transport and
+ * adb backup/restore, and for fullbackup and key-value backup.
+ */
+class KeyValueAdbBackupEngine {
+    private static final String TAG = "KeyValueAdbBackupEngine";
+    private static final boolean DEBUG = false;
+
+    private static final String BACKUP_KEY_VALUE_DIRECTORY_NAME = "key_value_dir";
+    private static final String BACKUP_KEY_VALUE_BLANK_STATE_FILENAME = "blank_state";
+    private static final String BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX = ".data";
+    private static final String BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX = ".new";
+
+    private BackupManagerService mBackupManagerService;
+    private final PackageManager mPackageManager;
+    private final OutputStream mOutput;
+    private final PackageInfo mCurrentPackage;
+    private final File mDataDir;
+    private final File mStateDir;
+    private final File mBlankStateName;
+    private final File mBackupDataName;
+    private final File mNewStateName;
+    private final File mManifestFile;
+    private ParcelFileDescriptor mSavedState;
+    private ParcelFileDescriptor mBackupData;
+    private ParcelFileDescriptor mNewState;
+
+    KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo,
+            BackupManagerService backupManagerService, PackageManager packageManager,
+            File baseStateDir, File dataDir) {
+        mOutput = output;
+        mCurrentPackage = packageInfo;
+        mBackupManagerService = backupManagerService;
+        mPackageManager = packageManager;
+
+        mDataDir = dataDir;
+        mStateDir = new File(baseStateDir, BACKUP_KEY_VALUE_DIRECTORY_NAME);
+        mStateDir.mkdirs();
+
+        String pkg = mCurrentPackage.packageName;
+
+        mBlankStateName = new File(mStateDir, BACKUP_KEY_VALUE_BLANK_STATE_FILENAME);
+        mBackupDataName = new File(mDataDir,
+                pkg + BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX);
+        mNewStateName = new File(mStateDir,
+                pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);
+
+        mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME);
+    }
+
+    void backupOnePackage() throws IOException {
+        ApplicationInfo targetApp = mCurrentPackage.applicationInfo;
+
+        try {
+            prepareBackupFiles(mCurrentPackage.packageName);
+
+            IBackupAgent agent = bindToAgent(targetApp);
+
+            if (agent == null) {
+                // We failed binding to the agent, so ignore this package
+                Slog.e(TAG, "Failed binding to BackupAgent for package "
+                        + mCurrentPackage.packageName);
+                return;
+            }
+
+            // We are bound to agent, initiate backup.
+            if (!invokeAgentForAdbBackup(mCurrentPackage.packageName, agent)) {
+                // Backup failed, skip package.
+                Slog.e(TAG, "Backup Failed for package " + mCurrentPackage.packageName);
+                return;
+            }
+
+            // Backup finished successfully. Copy the backup data to the output stream.
+            writeBackupData();
+        } catch (FileNotFoundException e) {
+            Slog.e(TAG, "Failed creating files for package " + mCurrentPackage.packageName
+                    + " will ignore package. " + e);
+        } finally {
+            // We are either done, failed or have timed out, so do cleanup and kill the agent.
+            cleanup();
+        }
+    }
+
+    private void  prepareBackupFiles(String packageName) throws FileNotFoundException {
+
+        // We pass a blank state to make sure we are getting the complete backup, not just an
+        // increment
+        mSavedState = ParcelFileDescriptor.open(mBlankStateName,
+                MODE_READ_ONLY | MODE_CREATE);  // Make an empty file if necessary
+
+        mBackupData = ParcelFileDescriptor.open(mBackupDataName,
+                MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+
+        if (!SELinux.restorecon(mBackupDataName)) {
+            Slog.e(TAG, "SELinux restorecon failed on " + mBackupDataName);
+        }
+
+        mNewState = ParcelFileDescriptor.open(mNewStateName,
+                MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+    }
+
+    private IBackupAgent bindToAgent(ApplicationInfo targetApp) {
+        try {
+            return mBackupManagerService.bindToAgentSynchronous(targetApp,
+                    ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName
+                    + ". " + e);
+            return null;
+        }
+    }
+
+    // Return true on backup success, false otherwise
+    private boolean invokeAgentForAdbBackup(String packageName, IBackupAgent agent) {
+        int token = mBackupManagerService.generateToken();
+        try {
+            mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
+                    OP_TYPE_BACKUP_WAIT);
+
+            // Start backup and wait for BackupManagerService to get callback for success or timeout
+            agent.doBackup(mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
+                    mBackupManagerService.mBackupManagerBinder);
+            if (!mBackupManagerService.waitUntilOperationComplete(token)) {
+                Slog.e(TAG, "Key-value backup failed on package " + packageName);
+                return false;
+            }
+            if (DEBUG) {
+                Slog.i(TAG, "Key-value backup success for package " + packageName);
+            }
+            return true;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error invoking agent for backup on " + packageName + ". " + e);
+            return false;
+        }
+    }
+
+    class KeyValueAdbBackupDataCopier implements Runnable {
+        private final PackageInfo mPackage;
+        private final ParcelFileDescriptor mPipe;
+        private final int mToken;
+
+        KeyValueAdbBackupDataCopier(PackageInfo pack, ParcelFileDescriptor pipe,
+                int token)
+                throws IOException {
+            mPackage = pack;
+            mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
+            mToken = token;
+        }
+
+        @Override
+        public void run() {
+            try {
+                FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
+
+                if (DEBUG) {
+                    Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
+                }
+                BackupManagerService.writeAppManifest(
+                        mPackage, mPackageManager, mManifestFile, false, false);
+                FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
+                        mDataDir.getAbsolutePath(),
+                        mManifestFile.getAbsolutePath(),
+                        output);
+                mManifestFile.delete();
+
+                if (DEBUG) {
+                    Slog.d(TAG, "Writing key-value package payload" + mPackage.packageName);
+                }
+                FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
+                        mDataDir.getAbsolutePath(),
+                        mBackupDataName.getAbsolutePath(),
+                        output);
+
+                // Write EOD marker
+                try {
+                    FileOutputStream out = new FileOutputStream(mPipe.getFileDescriptor());
+                    byte[] buf = new byte[4];
+                    out.write(buf);
+                } catch (IOException e) {
+                    Slog.e(TAG, "Unable to finalize backup stream!");
+                }
+
+                try {
+                    mBackupManagerService.mBackupManagerBinder.opComplete(mToken, 0);
+                } catch (RemoteException e) {
+                    // we'll time out anyway, so we're safe
+                }
+
+            } catch (IOException e) {
+                Slog.e(TAG, "Error running full backup for " + mPackage.packageName + ". " + e);
+            } finally {
+                IoUtils.closeQuietly(mPipe);
+            }
+        }
+    }
+
+    private void writeBackupData() throws IOException {
+
+        int token = mBackupManagerService.generateToken();
+
+        ParcelFileDescriptor[] pipes = null;
+        try {
+            pipes = ParcelFileDescriptor.createPipe();
+
+            mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
+                    OP_TYPE_BACKUP_WAIT);
+
+            // We will have to create a runnable that will read the manifest and backup data we
+            // created, such that we can pipe the data into mOutput. The reason we do this is that
+            // internally FullBackup.backupToTar is used, which will create the necessary file
+            // header, but will also chunk the data. The method routeSocketDataToOutput in
+            // BackupManagerService will dechunk the data, and append it to the TAR outputstream.
+            KeyValueAdbBackupDataCopier runner = new KeyValueAdbBackupDataCopier(mCurrentPackage, pipes[1],
+                    token);
+            pipes[1].close();   // the runner has dup'd it
+            pipes[1] = null;
+            Thread t = new Thread(runner, "key-value-app-data-runner");
+            t.start();
+
+            // Now pull data from the app and stuff it into the output
+            BackupManagerService.routeSocketDataToOutput(pipes[0], mOutput);
+
+            if (!mBackupManagerService.waitUntilOperationComplete(token)) {
+                Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName);
+            } else {
+                if (DEBUG) {
+                    Slog.d(TAG, "Full package backup success: " + mCurrentPackage.packageName);
+                }
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Error backing up " + mCurrentPackage.packageName + ": " + e);
+        } finally {
+            // flush after every package
+            mOutput.flush();
+            if (pipes != null) {
+                IoUtils.closeQuietly(pipes[0]);
+                IoUtils.closeQuietly(pipes[1]);
+            }
+        }
+    }
+
+    private void cleanup() {
+        mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo);
+        mBlankStateName.delete();
+        mNewStateName.delete();
+        mBackupDataName.delete();
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
new file mode 100644 (file)
index 0000000..6fb9355
--- /dev/null
@@ -0,0 +1,148 @@
+package com.android.server.backup;
+
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+
+import android.app.IBackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.FullBackup;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.backup.BackupManagerService.FileMetadata;
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Used by BackupManagerService to perform adb restore for key-value packages. At the moment this
+ * class resembles what is done in the standard key-value code paths in BackupManagerService, and
+ * should be unified later.
+ *
+ * TODO: We should create unified backup/restore engines that can be used for both transport and
+ * adb backup/restore, and for fullbackup and key-value backup.
+ */
+class KeyValueAdbRestoreEngine implements Runnable {
+    private static final String TAG = "KeyValueAdbRestoreEngine";
+    private static final boolean DEBUG = false;
+
+    private final BackupManagerService mBackupManagerService;
+    private final File mDataDir;
+
+    FileMetadata mInfo;
+    BackupManagerService.PerformAdbRestoreTask mRestoreTask;
+    ParcelFileDescriptor mInFD;
+    IBackupAgent mAgent;
+    int mToken;
+
+    KeyValueAdbRestoreEngine(BackupManagerService backupManagerService, File dataDir,
+            FileMetadata info, ParcelFileDescriptor inFD, IBackupAgent agent, int token) {
+        mBackupManagerService = backupManagerService;
+        mDataDir = dataDir;
+        mInfo = info;
+        mInFD = inFD;
+        mAgent = agent;
+        mToken = token;
+    }
+
+    @Override
+    public void run() {
+        try {
+            File restoreData = prepareRestoreData(mInfo, mInFD);
+
+            // TODO: version ?
+            invokeAgentForAdbRestore(mAgent, mInfo, restoreData, 0);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private File prepareRestoreData(FileMetadata info, ParcelFileDescriptor inFD) throws IOException {
+        String pkg = info.packageName;
+        File restoreDataName = new File(mDataDir, pkg + ".restore");
+        File sortedDataName = new File(mDataDir, pkg + ".sorted");
+
+        FullBackup.restoreFile(inFD, info.size, info.type, info.mode, info.mtime, restoreDataName);
+
+        // Sort the keys, as the BackupAgent expect them to come in lexicographical order
+        sortKeyValueData(restoreDataName, sortedDataName);
+        return sortedDataName;
+    }
+
+    private void invokeAgentForAdbRestore(IBackupAgent agent, FileMetadata info, File restoreData,
+            int versionCode) throws IOException {
+        String pkg = info.packageName;
+        File newStateName = new File(mDataDir, pkg + ".new");
+        try {
+            ParcelFileDescriptor backupData =
+                    ParcelFileDescriptor.open(restoreData, MODE_READ_ONLY);
+            ParcelFileDescriptor newState = ParcelFileDescriptor.open(newStateName,
+                    MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+
+            if (DEBUG) {
+                Slog.i(TAG, "Starting restore of package " + pkg + " for version code "
+                        + versionCode);
+            }
+            agent.doRestore(backupData, versionCode, newState, mToken,
+                    mBackupManagerService.mBackupManagerBinder);
+        } catch (IOException e) {
+            Slog.e(TAG, "Exception opening file. " + e);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Exception calling doRestore on agent: " + e);
+        }
+    }
+
+    private void sortKeyValueData (File restoreData, File sortedData) throws IOException {
+        FileInputStream inputStream = null;
+        FileOutputStream outputStream = null;
+        try {
+            inputStream = new FileInputStream(restoreData);
+            outputStream = new FileOutputStream(sortedData);
+            BackupDataInput reader = new BackupDataInput(inputStream.getFD());
+            BackupDataOutput writer = new BackupDataOutput(outputStream.getFD());
+            copyKeysInLexicalOrder(reader, writer);
+        } finally {
+            if (inputStream != null) {
+                IoUtils.closeQuietly(inputStream);
+            }
+            if (outputStream != null) {
+                IoUtils.closeQuietly(outputStream);
+            }
+        }
+    }
+
+    private void copyKeysInLexicalOrder(BackupDataInput in, BackupDataOutput out)
+            throws IOException {
+        Map<String, byte[]> data = new HashMap<>();
+        while (in.readNextHeader()) {
+            String key = in.getKey();
+            int size = in.getDataSize();
+            if (size < 0) {
+                in.skipEntityData();
+                continue;
+            }
+            byte[] value = new byte[size];
+            in.readEntityData(value, 0, size);
+            data.put(key, value);
+        }
+        List<String> keys = new ArrayList<>(data.keySet());
+        Collections.sort(keys);
+        for (String key : keys) {
+            byte[] value = data.get(key);
+            out.writeEntityHeader(key, value.length);
+            out.writeEntityData(value, value.length);
+        }
+    }
+}
index 8855661..c40f2ca 100644 (file)
@@ -227,14 +227,14 @@ public class Trampoline extends IBackupManager.Stub {
     }
 
     @Override
-    public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+    public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
             boolean includeShared, boolean doWidgets, boolean allApps,
-            boolean allIncludesSystem, boolean doCompress, String[] packageNames)
+            boolean allIncludesSystem, boolean doCompress, boolean doKeyValue, String[] packageNames)
                     throws RemoteException {
         BackupManagerService svc = mService;
         if (svc != null) {
-            svc.fullBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
-                    allApps, allIncludesSystem, doCompress, packageNames);
+            svc.adbBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
+                    allApps, allIncludesSystem, doCompress, doKeyValue, packageNames);
         }
     }
 
@@ -247,10 +247,10 @@ public class Trampoline extends IBackupManager.Stub {
     }
 
     @Override
-    public void fullRestore(ParcelFileDescriptor fd) throws RemoteException {
+    public void adbRestore(ParcelFileDescriptor fd) throws RemoteException {
         BackupManagerService svc = mService;
         if (svc != null) {
-            svc.fullRestore(fd);
+            svc.adbRestore(fd);
         }
     }
 
@@ -260,7 +260,7 @@ public class Trampoline extends IBackupManager.Stub {
                     throws RemoteException {
         BackupManagerService svc = mService;
         if (svc != null) {
-            svc.acknowledgeFullBackupOrRestore(token, allow,
+            svc.acknowledgeAdbBackupOrRestore(token, allow,
                     curPassword, encryptionPassword, observer);
         }
     }