Full local backup infrastructure

This is the basic infrastructure for pulling a full(*) backup of the
device's data over an adb(**) connection to the local device.  The
basic process consists of these interacting pieces:

1. The framework's BackupManagerService, which coordinates the
   collection of app data and routing to the destination.

2. A new framework-provided BackupAgent implementation called
   FullBackupAgent, which is instantiated in the target applications'
   processes in turn, and knows how to emit a datastream that contains
   all of the app's saved data files.

3. A new shell-level program called "bu" that is used to bridge from
   adb to the framework's Backup Manager.

4. adb itself, which now knows how to use 'bu' to kick off a backup
   operation and pull the resulting data stream to the desktop host.

5. A system-provided application that verifies with the user that
   an attempted backup/restore operation is in fact expected and to
   be allowed.

The full agent implementation is not used during normal operation of
the delta-based app-customized remote backup process.  Instead it's
used during user-confirmed *full* backup of applications and all their
data to a local destination, e.g. via the adb connection.

The output format is 'tar'.  This makes it very easy for the end
user to examine the resulting dataset, e.g. for purpose of extracting
files for debug purposes; as well as making it easy to contemplate
adding things like a direct gzip stage to the data pipeline during
backup/restore.  It also makes it convenient to construct and maintain
synthetic backup datasets for testing purposes.

Within the tar format, certain artificial conventions are used.
All files are stored within top-level directories according to
their semantic origin:

apps/pkgname/a/  : Application .apk file itself
apps/pkgname/obb/: The application's associated .obb containers
apps/pkgname/f/  : The subtree rooted at the getFilesDir() location
apps/pkgname/db/ : The subtree rooted at the getDatabasePath() parent
apps/pkgname/sp/ : The subtree rooted at the getSharedPrefsFile() parent
apps/pkgname/r/  : Files stored relative to the root of the app's file tree
apps/pkgname/c/  : Reserved for the app's getCacheDir() tree; not stored.

For each package, the first entry in the tar stream is a file called
"_manifest", nominally rooted at apps/pkgname.  This file contains some
metadata about the package whose data is stored in the archive.

The contents of shared storage can optionally be included in the tar
stream. It is placed in the synthetic location:

shared/...

uid/gid are ignored; app uids are assigned at install time, and the
app's data is handled from within its own execution environment, so
will automatically have the app's correct uid.

Forward-locked .apk files are never backed up.  System-partition
.apk files are not backed up unless they have been overridden by a
post-factory upgrade, in which case the current .apk *is* backed up --
i.e. the .apk that matches the on-disk data.  The manifest preceding
each application's portion of the tar stream provides version numbers
and signature blocks for version checking, as well as an indication
of whether the restore logic should expect to install the .apk before
extracting the data.

System packages can designate their own full backup agents.  This is
to manage things like the settings provider which (a) cannot be shut
down on the fly in order to do a clean snapshot of their file trees,
and (b) manage data that is not only irrelevant but actively hostile
to non-identical devices -- CDMA telephony settings would seriously
mess up a GSM device if emplaced there blind, for example.

When a full backup or restore is initiated from adb, the system will
present a confirmation UI that the user must explicitly respond to
within a short [~ 30 seconds] timeout.  This is to avoid the
possibility of malicious desktop-side software secretly grabbing a copy
of all the user's data for nefarious purposes.

(*) The backup is not strictly a full mirror.  In particular, the
    settings database is not cloned; it is handled the same way that
    it is in cloud backup/restore.  This is because some settings
    are actively destructive if cloned onto a different (or
    especially a different-model) device: telephony settings and
    AndroidID are good examples of this.

(**) On the framework side it doesn't care that it's adb; it just
    sends the tar stream to a file descriptor.  This can easily be
    retargeted around whatever transport we might decide to use
    in the future.

KNOWN ISSUES:

* the security UI is desperately ugly; no proper designs have yet
  been done for it
* restore is not yet implemented
* shared storage backup is not yet implemented
* symlinks aren't yet handled, though some infrastructure for
  dealing with them has been put in place.

Change-Id: Ia8347611e23b398af36ea22c36dff0a276b1ce91
diff --git a/Android.mk b/Android.mk
index 2329be2..9bd30fe 100644
--- a/Android.mk
+++ b/Android.mk
@@ -82,6 +82,7 @@
 	core/java/android/app/IWallpaperManagerCallback.aidl \
 	core/java/android/app/admin/IDevicePolicyManager.aidl \
 	core/java/android/app/backup/IBackupManager.aidl \
+	core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
 	core/java/android/app/backup/IRestoreObserver.aidl \
 	core/java/android/app/backup/IRestoreSession.aidl \
 	core/java/android/bluetooth/IBluetooth.aidl \
diff --git a/api/current.txt b/api/current.txt
index 2eda26f..12fa844 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -445,6 +445,7 @@
     field public static final int fromXScale = 16843202; // 0x10101c2
     field public static final int fromYDelta = 16843208; // 0x10101c8
     field public static final int fromYScale = 16843204; // 0x10101c4
+    field public static final int fullBackupAgent = 16843628; // 0x101036c
     field public static final int fullBright = 16842954; // 0x10100ca
     field public static final int fullDark = 16842950; // 0x10100c6
     field public static final int functionalTest = 16842787; // 0x1010023
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index ac0e410..38d0d2a 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -150,20 +150,13 @@
     }
 
     private void doBackup() {
-        boolean isFull = false;
         String pkg = nextArg();
-        if ("-f".equals(pkg)) {
-            isFull = true;
-            pkg = nextArg();
-        }
-
-        if (pkg == null || pkg.startsWith("-")) {
+        if (pkg == null) {
             showUsage();
             return;
         }
 
         try {
-            // !!! TODO: handle full backup
             mBmgr.dataChanged(pkg);
         } catch (RemoteException e) {
             System.err.println(e.toString());
diff --git a/cmds/bu/Android.mk b/cmds/bu/Android.mk
new file mode 100644
index 0000000..4fd5fec
--- /dev/null
+++ b/cmds/bu/Android.mk
@@ -0,0 +1,18 @@
+# Copyright 2011 The Android Open Source Project
+#
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_MODULE := bu
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := bu
+LOCAL_SRC_FILES := bu
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_PREBUILT)
+
+
diff --git a/cmds/bu/NOTICE b/cmds/bu/NOTICE
new file mode 100644
index 0000000..becc120
--- /dev/null
+++ b/cmds/bu/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2011, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/cmds/bu/bu b/cmds/bu/bu
new file mode 100755
index 0000000..e8dbc31
--- /dev/null
+++ b/cmds/bu/bu
@@ -0,0 +1,6 @@
+# Script to start "bu" on the device
+#
+base=/system
+export CLASSPATH=$base/framework/bu.jar
+exec app_process $base/bin com.android.commands.bu.Backup "$@"
+
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
new file mode 100644
index 0000000..2b8d6aa
--- /dev/null
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.bu;
+
+import android.app.backup.IBackupManager;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public final class Backup {
+    static final String TAG = "Backup";
+
+    private static String[] mArgs;
+    private int mNextArg;
+
+    public static void main(String[] args) {
+        mArgs = args;
+        try {
+            new Backup().run();
+        } catch (Exception e) {
+            Log.e(TAG, "Error running backup", e);
+        }
+        Log.d(TAG, "Finished.");
+    }
+
+    public void run() {
+        IBackupManager bmgr = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
+        if (bmgr == null) {
+            System.err.println("ERROR: could not contact backup manager");
+            return;
+        }
+
+        ArrayList<String> packages = new ArrayList<String>();
+        boolean saveApks = false;
+        boolean saveShared = false;
+        boolean doEverything = false;
+
+        String arg;
+        while ((arg = nextArg()) != null) {
+            
+            if (arg.startsWith("-")) {
+                if ("-apk".equals(arg)) {
+                    saveApks = true;
+                } else if ("-noapk".equals(arg)) {
+                    saveApks = false;
+                } else if ("-shared".equals(arg)) {
+                    saveShared = true;
+                } else if ("-noshared".equals(arg)) {
+                    saveShared = false;
+                } else if ("-all".equals(arg)) {
+                    doEverything = true;
+                } else {
+                    System.err.println("ERROR: unknown flag " + arg);
+                    return;
+                }
+            } else {
+                // Not a flag; treat as a package name
+                packages.add(arg);
+            }
+        }
+
+        if (doEverything && packages.size() > 0) {
+            System.err.println("ERROR: -all used with specific package set");
+            return;
+        }
+
+        if (!doEverything && packages.size() == 0) {
+            System.err.println("ERROR: no packages supplied and -all not used");
+            return;
+        }
+
+        try {
+            ParcelFileDescriptor fd = ParcelFileDescriptor.dup(FileDescriptor.out);
+            String[] packArray = new String[packages.size()];
+            bmgr.fullBackup(fd, saveApks, saveShared, doEverything, packages.toArray(packArray));
+        } catch (IOException e) {
+            System.err.println("ERROR: cannot dup System.out");
+        } catch (RemoteException e) {
+            System.err.println("ERROR: unable to invoke backup manager service");
+        }
+    }
+
+    private String nextArg() {
+        if (mNextArg >= mArgs.length) {
+            return null;
+        }
+        String arg = mArgs[mNextArg];
+        mNextArg++;
+        return arg;
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f0e7e98..85e59b3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -129,7 +129,7 @@
     /** @hide */
     public static final boolean DEBUG_BROADCAST = false;
     private static final boolean DEBUG_RESULTS = false;
-    private static final boolean DEBUG_BACKUP = false;
+    private static final boolean DEBUG_BACKUP = true;
     private static final boolean DEBUG_CONFIGURATION = false;
     private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
     private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
@@ -1979,24 +1979,26 @@
 
         BackupAgent agent = null;
         String classname = data.appInfo.backupAgentName;
-        if (classname == null) {
-            if (data.backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL) {
-                Slog.e(TAG, "Attempted incremental backup but no defined agent for "
-                        + packageName);
-                return;
+
+        if (data.backupMode == IApplicationThread.BACKUP_MODE_FULL) {
+            classname = "android.app.backup.FullBackupAgent";
+            if ((data.appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                // system packages can supply their own full-backup agent
+                if (data.appInfo.fullBackupAgentName != null) {
+                    classname = data.appInfo.fullBackupAgentName;
+                }
             }
-            classname = "android.app.FullBackupAgent";
         }
+
         try {
             IBinder binder = null;
             try {
+                if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname);
+
                 java.lang.ClassLoader cl = packageInfo.getClassLoader();
-                agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance();
+                agent = (BackupAgent) cl.loadClass(classname).newInstance();
 
                 // set up the agent's context
-                if (DEBUG_BACKUP) Slog.v(TAG, "Initializing BackupAgent "
-                        + data.appInfo.backupAgentName);
-
                 ContextImpl context = new ContextImpl();
                 context.init(packageInfo, null, this);
                 context.setOuterContext(agent);
@@ -2023,7 +2025,7 @@
             }
         } catch (Exception e) {
             throw new RuntimeException("Unable to create BackupAgent "
-                    + data.appInfo.backupAgentName + ": " + e.toString(), e);
+                    + classname + ": " + e.toString(), e);
         }
     }
 
diff --git a/core/java/android/app/FullBackupAgent.java b/core/java/android/app/FullBackupAgent.java
deleted file mode 100644
index acd20bd..0000000
--- a/core/java/android/app/FullBackupAgent.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app;
-
-import android.app.backup.BackupAgent;
-import android.app.backup.BackupDataInput;
-import android.app.backup.BackupDataOutput;
-import android.app.backup.FileBackupHelper;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.LinkedList;
-
-/**
- * Backs up an application's entire /data/data/&lt;package&gt;/... file system.  This
- * class is used by the desktop full backup mechanism and is not intended for direct
- * use by applications.
- * 
- * {@hide}
- */
-
-public class FullBackupAgent extends BackupAgent {
-    // !!! TODO: turn off debugging
-    private static final String TAG = "FullBackupAgent";
-    private static final boolean DEBUG = true;
-
-    @Override
-    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
-            ParcelFileDescriptor newState) {
-        LinkedList<File> dirsToScan = new LinkedList<File>();
-        ArrayList<String> allFiles = new ArrayList<String>();
-
-        // build the list of files in the app's /data/data tree
-        dirsToScan.add(getFilesDir());
-        if (DEBUG) Log.v(TAG, "Backing up dir tree @ " + getFilesDir().getAbsolutePath() + " :");
-        while (dirsToScan.size() > 0) {
-            File dir = dirsToScan.removeFirst();
-            File[] contents = dir.listFiles();
-            if (contents != null) {
-                for (File f : contents) {
-                    if (f.isDirectory()) {
-                        dirsToScan.add(f);
-                    } else if (f.isFile()) {
-                        if (DEBUG) Log.v(TAG, "    " + f.getAbsolutePath());
-                        allFiles.add(f.getAbsolutePath());
-                    }
-                }
-            }
-        }
-
-        // That's the file set; now back it all up
-        FileBackupHelper helper = new FileBackupHelper(this,
-                allFiles.toArray(new String[allFiles.size()]));
-        helper.performBackup(oldState, data, newState);
-    }
-
-    @Override
-    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) {
-    }
-}
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index fed2bc5..52fc623 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -51,6 +51,7 @@
     void doBackup(in ParcelFileDescriptor oldState,
             in ParcelFileDescriptor data,
             in ParcelFileDescriptor newState,
+            boolean storeApk,
             int token, IBackupManager callbackBinder);
 
     /**
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index cb4e0e78..dc60e24 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -85,7 +85,7 @@
  */
 public abstract class BackupAgent extends ContextWrapper {
     private static final String TAG = "BackupAgent";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
 
     public BackupAgent() {
         super(null);
@@ -172,11 +172,18 @@
      * @param newState An open, read/write ParcelFileDescriptor pointing to an
      *            empty file. The application should record the final backup
      *            state here after restoring its data from the <code>data</code> stream.
+     *            When a full-backup dataset is being restored, this will be <code>null</code>.
      */
     public abstract void onRestore(BackupDataInput data, int appVersionCode,
             ParcelFileDescriptor newState)
             throws IOException;
 
+    /**
+     * Package-private, used only for dispatching an extra step during full backup
+     */
+    void onSaveApk(BackupDataOutput data) {
+        if (DEBUG) Log.v(TAG, "--- base onSaveApk() ---");
+    }
 
     // ----- Core implementation -----
 
@@ -199,12 +206,18 @@
         public void doBackup(ParcelFileDescriptor oldState,
                 ParcelFileDescriptor data,
                 ParcelFileDescriptor newState,
+                boolean storeApk,
                 int token, IBackupManager callbackBinder) throws RemoteException {
             // Ensure that we're running with the app's normal permission level
             long ident = Binder.clearCallingIdentity();
 
             if (DEBUG) Log.v(TAG, "doBackup() invoked");
             BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
+
+            if (storeApk) {
+                onSaveApk(output);
+            }
+
             try {
                 BackupAgent.this.onBackup(oldState, output, newState);
             } catch (IOException ex) {
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
new file mode 100644
index 0000000..9850566
--- /dev/null
+++ b/core/java/android/app/backup/FullBackup.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+/**
+ * Global constant definitions et cetera related to the full-backup-to-fd
+ * binary format.
+ *
+ * @hide
+ */
+public class FullBackup {
+    public static String APK_TREE_TOKEN = "a";
+    public static String OBB_TREE_TOKEN = "obb";
+    public static String ROOT_TREE_TOKEN = "r";
+    public static String DATA_TREE_TOKEN = "f";
+    public static String DATABASE_TREE_TOKEN = "db";
+    public static String SHAREDPREFS_TREE_TOKEN = "sp";
+    public static String CACHE_TREE_TOKEN = "c";
+
+    public static String FULL_BACKUP_INTENT_ACTION = "fullback";
+    public static String FULL_RESTORE_INTENT_ACTION = "fullrest";
+    public static String CONF_TOKEN_INTENT_EXTRA = "conftoken";
+
+    static public native int backupToTar(String packageName, String domain,
+            String linkdomain, String rootpath, String path, BackupDataOutput output);
+}
diff --git a/core/java/android/app/backup/FullBackupAgent.java b/core/java/android/app/backup/FullBackupAgent.java
new file mode 100644
index 0000000..f0a1f2a
--- /dev/null
+++ b/core/java/android/app/backup/FullBackupAgent.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import libcore.io.Libcore;
+import libcore.io.ErrnoException;
+import libcore.io.OsConstants;
+import libcore.io.StructStat;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.LinkedList;
+
+/**
+ * Backs up an application's entire /data/data/&lt;package&gt;/... file system.  This
+ * class is used by the desktop full backup mechanism and is not intended for direct
+ * use by applications.
+ * 
+ * {@hide}
+ */
+
+public class FullBackupAgent extends BackupAgent {
+    // !!! TODO: turn off debugging
+    private static final String TAG = "FullBackupAgent";
+    private static final boolean DEBUG = true;
+
+    PackageManager mPm;
+
+    private String mMainDir;
+    private String mFilesDir;
+    private String mDatabaseDir;
+    private String mSharedPrefsDir;
+    private String mCacheDir;
+    private String mLibDir;
+
+    @Override
+    public void onCreate() {
+        mPm = getPackageManager();
+        try {
+            ApplicationInfo appInfo = mPm.getApplicationInfo(getPackageName(), 0);
+            mMainDir = new File(appInfo.dataDir).getAbsolutePath();
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Unable to find package " + getPackageName());
+            throw new RuntimeException(e);
+        }
+
+        mFilesDir = getFilesDir().getAbsolutePath();
+        mDatabaseDir = getDatabasePath("foo").getParentFile().getAbsolutePath();
+        mSharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getAbsolutePath();
+        mCacheDir = getCacheDir().getAbsolutePath();
+
+        ApplicationInfo app = getApplicationInfo();
+        mLibDir = (app.nativeLibraryDir != null)
+                ? new File(app.nativeLibraryDir).getAbsolutePath()
+                : null;
+    }
+
+    @Override
+    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+            ParcelFileDescriptor newState) {
+        // Filters, the scan queue, and the set of resulting entities
+        HashSet<String> filterSet = new HashSet<String>();
+
+        // Okay, start with the app's root tree, but exclude all of the canonical subdirs
+        if (mLibDir != null) {
+            filterSet.add(mLibDir);
+        }
+        filterSet.add(mCacheDir);
+        filterSet.add(mDatabaseDir);
+        filterSet.add(mSharedPrefsDir);
+        filterSet.add(mFilesDir);
+        processTree(FullBackup.ROOT_TREE_TOKEN, mMainDir, filterSet, data);
+
+        // Now do the same for the files dir, db dir, and shared prefs dir
+        filterSet.add(mMainDir);
+        filterSet.remove(mFilesDir);
+        processTree(FullBackup.DATA_TREE_TOKEN, mFilesDir, filterSet, data);
+
+        filterSet.add(mFilesDir);
+        filterSet.remove(mDatabaseDir);
+        processTree(FullBackup.DATABASE_TREE_TOKEN, mDatabaseDir, filterSet, data);
+
+        filterSet.add(mDatabaseDir);
+        filterSet.remove(mSharedPrefsDir);
+        processTree(FullBackup.SHAREDPREFS_TREE_TOKEN, mSharedPrefsDir, filterSet, data);
+    }
+
+    private void processTree(String domain, String rootPath,
+            HashSet<String> excludes, BackupDataOutput data) {
+        // Scan the dir tree (if it actually exists) and process each entry we find
+        File rootFile = new File(rootPath);
+        if (rootFile.exists()) {
+            LinkedList<File> scanQueue = new LinkedList<File>();
+            scanQueue.add(rootFile);
+
+            while (scanQueue.size() > 0) {
+                File file = scanQueue.remove(0);
+                String filePath = file.getAbsolutePath();
+
+                // prune this subtree?
+                if (excludes.contains(filePath)) {
+                    continue;
+                }
+
+                // If it's a directory, enqueue its contents for scanning.
+                try {
+                    StructStat stat = Libcore.os.lstat(filePath);
+                    if (OsConstants.S_ISLNK(stat.st_mode)) {
+                        if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
+                        continue;
+                    } else if (OsConstants.S_ISDIR(stat.st_mode)) {
+                        File[] contents = file.listFiles();
+                        if (contents != null) {
+                            for (File entry : contents) {
+                                scanQueue.add(0, entry);
+                            }
+                        }
+                    }
+                } catch (ErrnoException e) {
+                    if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
+                    continue;
+                }
+
+                // Finally, back this file up before proceeding
+                FullBackup.backupToTar(getPackageName(), domain, null, rootPath, filePath, data);
+            }
+        }
+    }
+
+    @Override
+    void onSaveApk(BackupDataOutput data) {
+        ApplicationInfo app = getApplicationInfo();
+        if (DEBUG) Log.i(TAG, "APK flags: system=" + ((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0)
+                + " updated=" + ((app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)
+                + " locked=" + ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) );
+        if (DEBUG) Log.i(TAG, "codepath: " + getPackageCodePath());
+
+        // Forward-locked apps, system-bundled .apks, etc are filtered out before we get here
+        final String pkgName = getPackageName();
+        final String apkDir = new File(getPackageCodePath()).getParent();
+        FullBackup.backupToTar(pkgName, FullBackup.APK_TREE_TOKEN, null,
+                apkDir, getPackageCodePath(), data);
+
+        // Save associated .obb content if it exists and we did save the apk
+        // check for .obb and save those too
+        final File obbDir = Environment.getExternalStorageAppObbDirectory(pkgName);
+        if (obbDir != null) {
+            if (DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath());
+            File[] obbFiles = obbDir.listFiles();
+            if (obbFiles != null) {
+                final String obbDirName = obbDir.getAbsolutePath();
+                for (File obb : obbFiles) {
+                    FullBackup.backupToTar(pkgName, FullBackup.OBB_TREE_TOKEN, null,
+                            obbDirName, obb.getAbsolutePath(), data);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) {
+    }
+}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index b315b3a..94e31a8 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -16,7 +16,9 @@
 
 package android.app.backup;
 
+import android.app.backup.IFullBackupRestoreObserver;
 import android.app.backup.IRestoreSession;
+import android.os.ParcelFileDescriptor;
 import android.content.Intent;
 
 /**
@@ -121,6 +123,42 @@
     void backupNow();
 
     /**
+     * Write a full 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.
+     *
+     * <p>This method is <i>synchronous</i> -- it does not return until the backup has
+     * completed.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @param fd The file descriptor to which a 'tar' file stream is to be written
+     * @param includeApks If <code>true</code>, the resulting tar stream will include the
+     *     application .apk files themselves as well as their data.
+     * @param includeShared If <code>true</code>, the resulting tar stream will include
+     *     the contents of the device's shared storage (SD card or equivalent).
+     * @param allApps If <code>true</code>, the resulting tar stream will include all
+     *     installed applications' data, not just those named in the <code>packageNames</code>
+     *     parameter.
+     * @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 includeShared,
+            boolean allApps, in String[] packageNames);
+
+    /**
+     * Confirm that the requested full backup/restore operation can proceed.  The system will
+     * not actually perform the operation described to fullBackup() / fullRestore() unless the
+     * UI calls back into the Backup Manager to confirm, passing the correct token.  At
+     * the same time, the UI supplies a callback Binder for progress notifications during
+     * the operation.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     */
+    void acknowledgeFullBackupOrRestore(int token, boolean allow,
+            IFullBackupRestoreObserver observer);
+
+    /**
      * Identify the currently selected transport.  Callers must hold the
      * android.permission.BACKUP permission to use this method.
      */
diff --git a/core/java/android/app/backup/IFullBackupRestoreObserver.aidl b/core/java/android/app/backup/IFullBackupRestoreObserver.aidl
new file mode 100644
index 0000000..3e0b73d
--- /dev/null
+++ b/core/java/android/app/backup/IFullBackupRestoreObserver.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+/**
+ * Observer of a full backup or restore process.  The observer is told "interesting"
+ * information about an ongoing full backup or restore action.
+ *
+ * {@hide}
+ */
+
+oneway interface IFullBackupRestoreObserver {
+    /**
+     * Notification: a full backup operation has begun.
+     */
+    void onStartBackup();
+
+    /**
+     * Notification: the system has begun backing up the given package.
+     *
+     * @param name The name of the application being saved.  This will typically be a
+     *     user-meaningful name such as "Browser" rather than a package name such as
+     *     "com.android.browser", though this is not guaranteed.
+     */
+    void onBackupPackage(String name);
+
+    /**
+     * Notification: the full backup operation has ended.
+     */
+    void onEndBackup();
+
+    /**
+     * Notification: a restore-from-full-backup operation has begun.
+     */
+    void onStartRestore();
+
+    /**
+     * Notification: the system has begun restore of the given package.
+     *
+     * @param name The name of the application being saved.  This will typically be a
+     *     user-meaningful name such as "Browser" rather than a package name such as
+     *     "com.android.browser", though this is not guaranteed.
+     */
+    void onRestorePackage(String name);
+
+    /**
+     * Notification: the restore-from-full-backup operation has ended.
+     */
+    void onEndRestore();
+
+    /**
+     * The user's window of opportunity for confirming the operation has timed out.
+     */
+    void onTimeout();
+}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 92b2c3b..4b38d48 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -89,7 +89,16 @@
      * <p>If android:allowBackup is set to false, this attribute is ignored.
      */
     public String backupAgentName;
-    
+
+    /**
+     * Class implementing the package's *full* backup functionality.  This
+     * is not usable except by system-installed packages.  It can be the same
+     * as the backupAgent.
+     *
+     * @hide
+     */
+    public String fullBackupAgentName;
+
     /**
      * Value for {@link #flags}: if set, this application is installed in the
      * device's system image.
@@ -505,6 +514,7 @@
         dest.writeInt(installLocation);
         dest.writeString(manageSpaceActivityName);
         dest.writeString(backupAgentName);
+        dest.writeString(fullBackupAgentName);
         dest.writeInt(descriptionRes);
     }
 
@@ -538,6 +548,7 @@
         installLocation = source.readInt();
         manageSpaceActivityName = source.readString();
         backupAgentName = source.readString();
+        fullBackupAgentName = source.readString();
         descriptionRes = source.readInt();
     }
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 564f4f4..b8cb165 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1506,7 +1506,17 @@
                 }
             }
         }
-        
+
+        name = sa.getNonConfigurationString(
+                com.android.internal.R.styleable.AndroidManifestApplication_fullBackupAgent, 0);
+        if (name != null) {
+            ai.fullBackupAgentName = buildClassName(pkgName, name, outError);
+            if (true) {
+                Log.v(TAG, "android:fullBackupAgent=" + ai.fullBackupAgentName
+                        + " from " + pkgName + "+" + name);
+            }
+        }
+
         TypedValue v = sa.peekValue(
                 com.android.internal.R.styleable.AndroidManifestApplication_label);
         if (v != null && (ai.labelRes=v.resourceId) == 0) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 8beb94b..223008c 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -151,6 +151,7 @@
 	android_backup_BackupDataOutput.cpp \
 	android_backup_FileBackupHelperBase.cpp \
 	android_backup_BackupHelperDispatcher.cpp \
+	android_app_backup_FullBackup.cpp \
 	android_content_res_ObbScanner.cpp \
 	android_content_res_Configuration.cpp \
     android_animation_PropertyValuesHolder.cpp
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 17f9246..b787e9f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -165,6 +165,7 @@
 extern int register_android_backup_BackupDataOutput(JNIEnv *env);
 extern int register_android_backup_FileBackupHelperBase(JNIEnv *env);
 extern int register_android_backup_BackupHelperDispatcher(JNIEnv *env);
+extern int register_android_app_backup_FullBackup(JNIEnv *env);
 extern int register_android_app_ActivityThread(JNIEnv *env);
 extern int register_android_app_NativeActivity(JNIEnv *env);
 extern int register_android_view_InputChannel(JNIEnv* env);
@@ -1208,7 +1209,7 @@
     REG_JNI(register_android_backup_BackupDataOutput),
     REG_JNI(register_android_backup_FileBackupHelperBase),
     REG_JNI(register_android_backup_BackupHelperDispatcher),
-
+    REG_JNI(register_android_app_backup_FullBackup),
     REG_JNI(register_android_app_ActivityThread),
     REG_JNI(register_android_app_NativeActivity),
     REG_JNI(register_android_view_InputChannel),
diff --git a/core/jni/android_app_backup_FullBackup.cpp b/core/jni/android_app_backup_FullBackup.cpp
new file mode 100644
index 0000000..ecfe5ff
--- /dev/null
+++ b/core/jni/android_app_backup_FullBackup.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "FullBackup_native"
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include "JNIHelp.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include <utils/BackupHelpers.h>
+
+#include <string.h>
+
+namespace android
+{
+
+// android.app.backup.BackupDataOutput
+static struct {
+    // This is actually a native pointer to the underlying BackupDataWriter instance
+    jfieldID mBackupWriter;
+} sBackupDataOutput;
+
+/*
+ * Write files to the given output.  This implementation does *not* create
+ * a standalone archive suitable for restore on its own.  In particular, the identification of
+ * the application's name etc is not in-band here; it's assumed that the calling code has
+ * taken care of supplying that information previously in the output stream.
+ *
+ * The file format is 'tar's, with special semantics applied by use of a "fake" directory
+ * hierarchy within the tar stream:
+ *
+ * apps/packagename/a/Filename.apk - this is an actual application binary, which will be
+ *   installed on the target device at restore time.  These need to appear first in the tar
+ *   stream.
+ * apps/packagename/obb/[relpath] - OBB containers belonging the app
+ * apps/packagename/r/[relpath] - these are files at the root of the app's data tree
+ * apps/packagename/f/[relpath] - this is a file within the app's getFilesDir() tree, stored
+ *   at [relpath] relative to the top of that tree.
+ * apps/packagename/db/[relpath] - as with "files" but for the getDatabasePath() tree
+ * apps/packagename/sp/[relpath] - as with "files" but for the getSharedPrefsFile() tree
+ * apps/packagename/c/[relpath] - as with "files" but for the getCacheDir() tree
+ *
+ * and for the shared storage hierarchy:
+ *
+ * shared/[relpaths] - files belonging in the device's shared storage location.  This will
+ *    *not* include .obb files; those are saved with their owning apps.
+ *
+ * This method writes one file data block.  'domain' is the name of the appropriate pseudo-
+ * directory to be applied for this file; 'linkdomain' is the pseudo-dir for a relative
+ * symlink's antecedent.
+ *
+ * packagename: the package name to use as the top level directory tag
+ * domain:      which semantic name the file is to be stored under (a, r, f, db, etc)
+ * linkdomain:  where a symlink points for purposes of rewriting; current unused
+ * rootpath:    prefix to be snipped from full path when encoding in tar
+ * path:        absolute path to the file to be saved
+ * dataOutput:  the BackupDataOutput object that we're saving into
+ */
+static int backupToTar(JNIEnv* env, jobject clazz, jstring packageNameObj,
+        jstring domainObj, jstring linkdomain,
+        jstring rootpathObj, jstring pathObj, jobject dataOutputObj) {
+    // Extract the various strings, allowing for null object pointers
+    const char* packagenamechars = env->GetStringUTFChars(packageNameObj, NULL);
+    const char* rootchars = env->GetStringUTFChars(rootpathObj, NULL);
+    const char* pathchars = env->GetStringUTFChars(pathObj, NULL);
+    const char* domainchars = env->GetStringUTFChars(domainObj, NULL);
+
+    String8 packageName(packagenamechars ? packagenamechars : "");
+    String8 rootpath(rootchars ? rootchars : "");
+    String8 path(pathchars ? pathchars : "");
+    String8 domain(domainchars ? domainchars : "");
+
+    if (domainchars) env->ReleaseStringUTFChars(domainObj, domainchars);
+    if (pathchars) env->ReleaseStringUTFChars(pathObj, pathchars);
+    if (rootchars) env->ReleaseStringUTFChars(rootpathObj, rootchars);
+    if (packagenamechars) env->ReleaseStringUTFChars(packageNameObj, packagenamechars);
+
+    // Extract the data output fd
+    BackupDataWriter* writer = (BackupDataWriter*) env->GetIntField(dataOutputObj,
+            sBackupDataOutput.mBackupWriter);
+
+    // Validate
+    if (!writer) {
+        LOGE("No output stream provided [%s]", path.string());
+        return -1;
+    }
+
+    if (path.length() < rootpath.length()) {
+        LOGE("file path [%s] shorter than root path [%s]",
+                path.string(), rootpath.string());
+        return -1;
+    }
+
+    return write_tarfile(packageName, domain, rootpath, path, writer);
+}
+
+static const JNINativeMethod g_methods[] = {
+    { "backupToTar",
+            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/app/backup/BackupDataOutput;)I",
+            (void*)backupToTar },
+};
+
+int register_android_app_backup_FullBackup(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/app/backup/BackupDataOutput");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.app.backup.BackupDataOutput");
+
+    sBackupDataOutput.mBackupWriter = env->GetFieldID(clazz, "mBackupWriter", "I");
+    LOG_FATAL_IF(sBackupDataOutput.mBackupwriter == NULL,
+            "Unable to find mBackupWriter field in android.app.backup.BackupDataOutput");
+
+    return AndroidRuntime::registerNativeMethods(env, "android/app/backup/FullBackup",
+            g_methods, NELEM(g_methods));
+}
+
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1c4bffdc..2d85ccc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1241,6 +1241,14 @@
         android:description="@string/permdesc_backup"
         android:protectionLevel="signatureOrSystem" />
 
+    <!-- Allows a package to launch the secure full-backup confirmation UI.
+         ONLY the system process may hold this permission.
+         @hide -->
+    <permission android:name="android.permission.CONFIRM_FULL_BACKUP"
+        android:label="@string/permlab_confirm_full_backup"
+        android:description="@string/permdesc_confirm_full_backup"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by a {@link android.widget.RemoteViewsService},
          to ensure that only the system can bind to it. -->
     <permission android:name="android.permission.BIND_REMOTEVIEWS"
@@ -1329,12 +1337,17 @@
           android:protectionLevel="signature" />
     <uses-permission android:name="android.intent.category.MASTER_CLEAR.permission.C2D_MESSAGE"/>
 
+    <!-- The system process is explicitly the only one allowed to launch the
+         confirmation UI for full backup/restore -->
+    <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
                  android:label="@string/android_system_label"
                  android:allowClearUserData="false"
                  android:backupAgent="com.android.server.SystemBackupAgent"
+                 android:fullBackupAgent="com.android.server.SystemBackupAgent"
                  android:killAfterRestore="false"
                  android:icon="@drawable/ic_launcher_android">
         <activity android:name="com.android.internal.app.ChooserActivity"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 9b04f78..1463bc7 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -685,6 +685,13 @@
          <p>The default value of this attribute is <code>false</code>. -->
     <attr name="restoreAnyVersion" format="boolean" />
 
+    <!-- The agent to use for a *full* backup of the package.  Only system applications
+         can use this to override the ordinary FullBackupAgent with a custom implementation.
+         It's needed strictly for packages with strongly device-specific data, such as the
+         Settings provider.
+         -->
+    <attr name="fullBackupAgent" format="string" />
+
     <!-- The default install location defined by an application. -->
     <attr name="installLocation">
         <!-- Let the system decide ideal install location -->
@@ -782,6 +789,7 @@
         <attr name="killAfterRestore" />
         <attr name="restoreNeedsApplication" />
         <attr name="restoreAnyVersion" />
+        <attr name="fullBackupAgent" />
         <attr name="neverEncrypt" />
         <!-- Request that your application's processes be created with
              a large Dalvik heap.  This applies to <em>all</em> processes
@@ -790,7 +798,7 @@
              to allow multiple applications to use a process, they all must
              use this option consistently or will get unpredictable results. -->
         <attr name="largeHeap" format="boolean" />
-        <!-- Declare that this applicationn can't participate in the normal
+        <!-- Declare that this application can't participate in the normal
              state save/restore mechanism.  Since it is not able to save and
              restore its state on demand,
              it can not participate in the normal activity lifecycle.  It will
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 7ca5e98..652d791 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1668,4 +1668,6 @@
   <public type="attr" name="textEditSuggestionsTopWindowLayout" />
   <public type="attr" name="textEditSuggestionItemLayout" />
 
+  <public type="attr" name="fullBackupAgent" />
+
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 358e0e0..3951623 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -631,6 +631,11 @@
     <string name="permdesc_backup">Allows the application to control the system\'s backup and restore mechanism.  Not for use by normal applications.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_confirm_full_backup">confirm a full backup or restore operation</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_confirm_full_backup">Allows the application to launch the full backup confirmation UI.  Not to be used by any application.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_internalSystemWindow">display unauthorized windows</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_internalSystemWindow">Allows the creation of
diff --git a/include/utils/BackupHelpers.h b/include/utils/BackupHelpers.h
index b1f5045..1bb04a7 100644
--- a/include/utils/BackupHelpers.h
+++ b/include/utils/BackupHelpers.h
@@ -70,6 +70,14 @@
     ~BackupDataWriter();
 
     status_t WriteEntityHeader(const String8& key, size_t dataSize);
+
+    /* Note: WriteEntityData will write arbitrary data into the file without
+     * validation or a previously-supplied header.  The full backup implementation
+     * uses it this way to generate a controlled binary stream that is not
+     * entity-structured.  If the implementation here is changed, either this
+     * use case must remain valid, or the full backup implementation should be
+     * adjusted to use some other appropriate mechanism.
+     */
     status_t WriteEntityData(const void* data, size_t size);
 
     void SetKeyPrefix(const String8& keyPrefix);
@@ -103,7 +111,7 @@
 
     bool HasEntities();
     status_t ReadEntityHeader(String8* key, size_t* dataSize);
-    status_t SkipEntityData(); // must be called with the pointer at the begining of the data.
+    status_t SkipEntityData(); // must be called with the pointer at the beginning of the data.
     ssize_t ReadEntityData(void* data, size_t size);
 
 private:
@@ -126,6 +134,9 @@
 int back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD,
         char const* const* files, char const* const *keys, int fileCount);
 
+int write_tarfile(const String8& packageName, const String8& domain,
+        const String8& rootPath, const String8& filePath, BackupDataWriter* outputStream);
+
 class RestoreHelperBase
 {
 public:
diff --git a/libs/utils/BackupData.cpp b/libs/utils/BackupData.cpp
index adb3174..f963058 100644
--- a/libs/utils/BackupData.cpp
+++ b/libs/utils/BackupData.cpp
@@ -20,12 +20,15 @@
 #include <utils/ByteOrder.h>
 
 #include <stdio.h>
+#include <string.h>
 #include <unistd.h>
 
 #include <cutils/log.h>
 
 namespace android {
 
+static const bool DEBUG = false;
+
 /*
  * File Format (v1):
  *
@@ -75,6 +78,7 @@
     paddingSize = padding_extra(n);
     if (paddingSize > 0) {
         uint32_t padding = 0xbcbcbcbc;
+        if (DEBUG) LOGI("writing %d padding bytes for %d", paddingSize, n);
         amt = write(m_fd, &padding, paddingSize);
         if (amt != paddingSize) {
             m_status = errno;
@@ -107,8 +111,8 @@
     } else {
         k = key;
     }
-    if (false) {
-        LOGD("Writing entity: prefix='%s' key='%s' dataSize=%d", m_keyPrefix.string(), key.string(),
+    if (DEBUG) {
+        LOGD("Writing header: prefix='%s' key='%s' dataSize=%d", m_keyPrefix.string(), key.string(),
                 dataSize);
     }
 
@@ -121,6 +125,7 @@
     header.keyLen = tolel(keyLen);
     header.dataSize = tolel(dataSize);
 
+    if (DEBUG) LOGI("writing entity header, %d bytes", sizeof(entity_header_v1));
     amt = write(m_fd, &header, sizeof(entity_header_v1));
     if (amt != sizeof(entity_header_v1)) {
         m_status = errno;
@@ -128,6 +133,7 @@
     }
     m_pos += amt;
 
+    if (DEBUG) LOGI("writing entity header key, %d bytes", keyLen+1);
     amt = write(m_fd, k.string(), keyLen+1);
     if (amt != keyLen+1) {
         m_status = errno;
@@ -145,7 +151,12 @@
 status_t
 BackupDataWriter::WriteEntityData(const void* data, size_t size)
 {
+    if (DEBUG) LOGD("Writing data: size=%lu", (unsigned long) size);
+
     if (m_status != NO_ERROR) {
+        if (DEBUG) {
+            LOGD("Not writing data - stream in error state %d (%s)", m_status, strerror(m_status));
+        }
         return m_status;
     }
 
@@ -155,6 +166,7 @@
     ssize_t amt = write(m_fd, data, size);
     if (amt != (ssize_t)size) {
         m_status = errno;
+        if (DEBUG) LOGD("write returned error %d (%s)", m_status, strerror(m_status));
         return m_status;
     }
     m_pos += amt;
diff --git a/libs/utils/BackupHelpers.cpp b/libs/utils/BackupHelpers.cpp
index 4ad9b51..ad4a308 100644
--- a/libs/utils/BackupHelpers.cpp
+++ b/libs/utils/BackupHelpers.cpp
@@ -442,6 +442,184 @@
     return 0;
 }
 
+// Utility function, equivalent to stpcpy(): perform a strcpy, but instead of
+// returning the initial dest, return a pointer to the trailing NUL.
+static char* strcpy_ptr(char* dest, const char* str) {
+    if (dest && str) {
+        while ((*dest = *str) != 0) {
+            dest++;
+            str++;
+        }
+    }
+    return dest;
+}
+
+int write_tarfile(const String8& packageName, const String8& domain,
+        const String8& rootpath, const String8& filepath, BackupDataWriter* writer)
+{
+    // In the output stream everything is stored relative to the root
+    const char* relstart = filepath.string() + rootpath.length();
+    if (*relstart == '/') relstart++;     // won't be true when path == rootpath
+    String8 relpath(relstart);
+
+    // Too long a name for the ustar format?
+    //    "apps/" + packagename + '/' + domainpath < 155 chars
+    //    relpath < 100 chars
+    if ((5 + packageName.length() + 1 + domain.length() >= 155) || (relpath.length() >= 100)) {
+        LOGE("Filename [%s] too long, skipping", relpath.string());
+        return -1;
+    }
+
+    int err = 0;
+    struct stat64 s;
+    if (lstat64(filepath.string(), &s) != 0) {
+        err = errno;
+        LOGE("Error %d (%s) from lstat64(%s)", err, strerror(err), filepath.string());
+        return err;
+    }
+
+    const int isdir = S_ISDIR(s.st_mode);
+
+    // !!! TODO: use mmap when possible to avoid churning the buffer cache
+    // !!! TODO: this will break with symlinks; need to use readlink(2)
+    int fd = open(filepath.string(), O_RDONLY);
+    if (fd < 0) {
+        err = errno;
+        LOGE("Error %d (%s) from open(%s)", err, strerror(err), filepath.string());
+        return err;
+    }
+
+    // read/write up to this much at a time.
+    const size_t BUFSIZE = 32 * 1024;
+
+    char* buf = new char[BUFSIZE];
+    if (buf == NULL) {
+        LOGE("Out of mem allocating transfer buffer");
+        err = ENOMEM;
+        goto done;
+    }
+
+    // Good to go -- first construct the standard tar header at the start of the buffer
+    memset(buf, 0, 512);    // tar header is 512 bytes
+
+    // Magic fields for the ustar file format
+    strcat(buf + 257, "ustar");
+    strcat(buf + 263, "00");
+
+    {
+        // Prefix and main relative path.  Path lengths have been preflighted.
+
+        // [ 345 : 155 ] filename path prefix [ustar]
+        //
+        // packagename and domain can each be empty.
+        char* cp = buf + 345;
+        if (packageName.length() > 0) {
+            // it's an app; so prefix with "apps/packagename/"
+            cp = strcpy_ptr(cp, "apps/");
+            cp = strcpy_ptr(cp, packageName.string());
+        }
+
+        if (domain.length() > 0) {
+            // only need a / if there was a package name
+            if (packageName.length() > 0) *cp++ = '/';
+            cp = strcpy_ptr(cp, domain.string());
+        }
+
+        // [   0 : 100 ]; file name/path
+        strncpy(buf, relpath.string(), 100);
+
+        LOGI("   Name: %s/%s", buf + 345, buf);
+    }
+
+    // [ 100 :   8 ] file mode
+    snprintf(buf + 100, 8, "0%o", s.st_mode);
+
+    // [ 108 :   8 ] uid -- ignored in Android format; uids are remapped at restore time
+    // [ 116 :   8 ] gid -- ignored in Android format
+    snprintf(buf + 108, 8, "0%lo", s.st_uid);
+    snprintf(buf + 116, 8, "0%lo", s.st_gid);
+
+    // [ 124 :  12 ] file size in bytes
+    snprintf(buf + 124, 12, "0%llo", s.st_size);
+
+    // [ 136 :  12 ] last mod time as a UTC time_t
+    snprintf(buf + 136, 12, "%0lo", s.st_mtime);
+
+    // [ 148 :   8 ] checksum -- to be calculated with this field as space chars
+    memset(buf + 148, ' ', 8);
+
+    // [ 156 :   1 ] link/file type
+    uint8_t type;
+    if (isdir) {
+        type = '5';     // tar magic: '5' == directory
+    } else if (S_ISREG(s.st_mode)) {
+        type = '0';     // tar magic: '0' == normal file
+    } else {
+        LOGW("Error: unknown file mode 0%o [%s]", s.st_mode, filepath.string());
+        goto cleanup;
+    }
+    buf[156] = type;
+
+    // [ 157 : 100 ] name of linked file [not implemented]
+
+    // Now go back and calculate the header checksum
+    {
+        uint16_t sum = 0;
+        for (uint8_t* p = (uint8_t*) buf; p < ((uint8_t*)buf) + 512; p++) {
+            sum += *p;
+        }
+
+        // Now write the real checksum value:
+        // [ 148 :   8 ]  checksum: 6 octal digits [leading zeroes], NUL, SPC
+        sprintf(buf + 148, "%06o", sum); // the trailing space is already in place
+    }
+
+    // Write the 512-byte tar file header block to the output
+    writer->WriteEntityData(buf, 512);
+
+    // Now write the file data itself, for real files.  We honor tar's convention that
+    // only full 512-byte blocks are sent to write().
+    if (!isdir) {
+        off64_t toWrite = s.st_size;
+        while (toWrite > 0) {
+            size_t toRead = (toWrite < BUFSIZE) ? toWrite : BUFSIZE;
+            ssize_t nRead = read(fd, buf, toRead);
+            if (nRead < 0) {
+                err = errno;
+                LOGE("Unable to read file [%s], err=%d (%s)", filepath.string(),
+                        err, strerror(err));
+                break;
+            } else if (nRead == 0) {
+                LOGE("EOF but expect %lld more bytes in [%s]", (long long) toWrite,
+                        filepath.string());
+                err = EIO;
+                break;
+            }
+
+            // At EOF we might have a short block; NUL-pad that to a 512-byte multiple.  This
+            // depends on the OS guarantee that for ordinary files, read() will never return
+            // less than the number of bytes requested.
+            ssize_t partial = (nRead+512) % 512;
+            if (partial > 0) {
+                ssize_t remainder = 512 - partial;
+                memset(buf + nRead, 0, remainder);
+                nRead += remainder;
+            }
+            writer->WriteEntityData(buf, nRead);
+            toWrite -= nRead;
+        }
+    }
+
+cleanup:
+    delete [] buf;
+done:
+    close(fd);
+    return err;
+}
+// end tarfile
+
+
+
 #define RESTORE_BUF_SIZE (8*1024)
 
 RestoreHelperBase::RestoreHelperBase()
diff --git a/packages/BackupRestoreConfirmation/Android.mk b/packages/BackupRestoreConfirmation/Android.mk
new file mode 100644
index 0000000..e775b44
--- /dev/null
+++ b/packages/BackupRestoreConfirmation/Android.mk
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := BackupRestoreConfirmation
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
+########################
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/packages/BackupRestoreConfirmation/AndroidManifest.xml b/packages/BackupRestoreConfirmation/AndroidManifest.xml
new file mode 100644
index 0000000..ed9a519
--- /dev/null
+++ b/packages/BackupRestoreConfirmation/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.backupconfirm" >
+
+    <uses-permission android:name="android.permission.BACKUP" />
+
+    <application android:allowClearUserData="false"
+                 android:killAfterRestore="false"
+                 android:permission="android.permission.CONFIRM_FULL_BACKUP" >
+
+        <activity android:name=".BackupRestoreConfirmation" 
+                  android:windowSoftInputMode="stateAlwaysHidden"
+                  android:excludeFromRecents="true"
+                  android:exported="true" >
+        </activity>
+    </application>
+</manifest> 
diff --git a/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml b/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml
new file mode 100644
index 0000000..109cfff
--- /dev/null
+++ b/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+/*
+ * Copyright (C) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="fill_parent" 
+                android:layout_height="wrap_content"
+                android:padding="10dp" >
+
+    <TextView android:id="@+id/confirm_text"
+              android:layout_width="match_parent" 
+              android:layout_height="wrap_content"
+              android:layout_marginBottom="30dp"
+              android:text="@string/backup_confirm_text" />
+
+    <TextView android:id="@+id/package_name"
+              android:layout_width="match_parent"
+              android:layout_height="20dp"
+              android:layout_marginLeft="30dp"
+              android:layout_below="@id/confirm_text"
+              android:layout_marginBottom="30dp" />
+
+    <Button android:id="@+id/button_allow"
+            android:text="@string/allow_backup_button_label"
+            android:layout_below="@id/package_name"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+
+    <Button android:id="@+id/button_deny"
+            android:text="@string/deny_backup_button_label"
+            android:layout_below="@id/package_name"
+            android:layout_toRightOf="@id/button_allow"
+            android:layout_alignTop="@id/button_allow"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+
+</RelativeLayout>
diff --git a/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml b/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml
new file mode 100644
index 0000000..a1f9a4a
--- /dev/null
+++ b/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+/*
+ * Copyright (C) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="fill_parent" 
+                android:layout_height="wrap_content"
+                android:padding="10dp" >
+
+    <TextView android:id="@+id/confirm_text"
+              android:layout_width="match_parent" 
+              android:layout_height="wrap_content"
+              android:layout_marginBottom="30dp"
+              android:text="@string/restore_confirm_text" />
+
+    <TextView android:id="@+id/package_name"
+              android:layout_width="match_parent"
+              android:layout_height="20dp"
+              android:layout_marginLeft="30dp"
+              android:layout_below="@id/confirm_text"
+              android:layout_marginBottom="30dp" />
+
+    <Button android:id="@+id/button_allow"
+            android:text="@string/allow_restore_button_label"
+            android:layout_below="@id/package_name"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+
+    <Button android:id="@+id/button_deny"
+            android:text="@string/deny_restore_button_label"
+            android:layout_below="@id/package_name"
+            android:layout_toRightOf="@id/button_allow"
+            android:layout_alignTop="@id/button_allow"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+
+</RelativeLayout>
diff --git a/packages/BackupRestoreConfirmation/res/values/strings.xml b/packages/BackupRestoreConfirmation/res/values/strings.xml
new file mode 100644
index 0000000..3d85e86
--- /dev/null
+++ b/packages/BackupRestoreConfirmation/res/values/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Text for message to user that a full backup has been requested, and must be confirmed. -->
+    <string name="backup_confirm_text">A full backup of all data to a connected desktop computer has been requested.  Do you want to allow this to happen\?\n\nIf you did not request the backup yourself, do not allow the operation to proceed.</string>
+    <!-- Button to allow a requested full backup to occur -->
+    <string name="allow_backup_button_label">Back up my data</string>
+    <!-- Button to refuse to allow the requested full backup -->
+    <string name="deny_backup_button_label">Do not back up</string>
+
+    <!-- Text for message to user that a full restore has been requested, and must be confirmed. -->
+    <string name="restore_confirm_text">A full restore of all data from a connected desktop computer has been requested.  Do you want to allow this to happen\?\n\nIf you did not request the restore yourself, do not allow the operation to proceed.  This will replace any data currently on the device!</string>
+    <!-- Button to allow a requested full restore to occur -->
+    <string name="allow_restore_button_label">Restore my data</string>
+    <!-- Button to refuse to allow the requested full restore -->
+    <string name="deny_restore_button_label">Do not restore</string>
+
+</resources>
diff --git a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
new file mode 100644
index 0000000..805b905
--- /dev/null
+++ b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.backupconfirm;
+
+import android.app.Activity;
+import android.app.backup.FullBackup;
+import android.app.backup.IBackupManager;
+import android.app.backup.IFullBackupRestoreObserver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Confirm with the user that a requested full backup/restore operation is legitimate.
+ * Any attempt to perform a full backup/restore will launch this UI and wait for a
+ * designated timeout interval (nominally 30 seconds) for the user to confirm.  If the
+ * user fails to respond within the timeout period, or explicitly refuses the operation
+ * within the UI presented here, no data will be transferred off the device.
+ *
+ * Note that the fully scoped name of this class is baked into the backup manager service.
+ *
+ * @hide
+ */
+public class BackupRestoreConfirmation extends Activity {
+    static final String TAG = "BackupRestoreConfirmation";
+    static final boolean DEBUG = true;
+
+    static final int MSG_START_BACKUP = 1;
+    static final int MSG_BACKUP_PACKAGE = 2;
+    static final int MSG_END_BACKUP = 3;
+    static final int MSG_START_RESTORE = 11;
+    static final int MSG_RESTORE_PACKAGE = 12;
+    static final int MSG_END_RESTORE = 13;
+    static final int MSG_TIMEOUT = 100;
+
+    Handler mHandler;
+    IBackupManager mBackupManager;
+    FullObserver mObserver;
+    int mToken;
+
+    TextView mStatusView;
+    Button mAllowButton;
+    Button mDenyButton;
+
+    // Handler for dealing with observer callbacks on the main thread
+    class ObserverHandler extends Handler {
+        Context mContext;
+        ObserverHandler(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_START_BACKUP: {
+                    Toast.makeText(mContext, "!!! Backup starting !!!", Toast.LENGTH_LONG);
+                }
+                break;
+
+                case MSG_BACKUP_PACKAGE: {
+                    String name = (String) msg.obj;
+                    mStatusView.setText(name);
+                }
+                break;
+
+                case MSG_END_BACKUP: {
+                    Toast.makeText(mContext, "!!! Backup ended !!!", Toast.LENGTH_SHORT);
+                }
+                break;
+
+                case MSG_START_RESTORE: {
+                    Toast.makeText(mContext, "!!! Restore starting !!!", Toast.LENGTH_LONG);
+                }
+                break;
+
+                case MSG_RESTORE_PACKAGE: {
+                }
+                break;
+
+                case MSG_END_RESTORE: {
+                    Toast.makeText(mContext, "!!! Restore ended !!!", Toast.LENGTH_SHORT);
+                }
+                break;
+
+                case MSG_TIMEOUT: {
+                }
+                break;
+            }
+        }
+    }
+    
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        final Intent intent = getIntent();
+        final String action = intent.getAction();
+
+        int layoutId;
+        if (action.equals(FullBackup.FULL_BACKUP_INTENT_ACTION)) {
+            layoutId = R.layout.confirm_backup;
+        } else if (action.equals(FullBackup.FULL_RESTORE_INTENT_ACTION)) {
+            layoutId = R.layout.confirm_restore;
+        } else {
+            Slog.w(TAG, "Backup/restore confirmation activity launched with invalid action!");
+            finish();
+            return;
+        }
+
+        mToken = intent.getIntExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, -1);
+        if (mToken < 0) {
+            Slog.e(TAG, "Backup/restore confirmation requested but no token passed!");
+            finish();
+            return;
+        }
+
+        mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
+
+        mHandler = new ObserverHandler(getApplicationContext());
+        mObserver = new FullObserver();
+
+        setContentView(layoutId);
+
+        // Same resource IDs for each layout variant (backup / restore)
+        mStatusView = (TextView) findViewById(R.id.package_name);
+        mAllowButton = (Button) findViewById(R.id.button_allow);
+        mDenyButton = (Button) findViewById(R.id.button_deny);
+
+        mAllowButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                try {
+                    mBackupManager.acknowledgeFullBackupOrRestore(mToken, true, mObserver);
+                } catch (RemoteException e) {
+                    // TODO: bail gracefully if we can't contact the backup manager
+                }
+                mAllowButton.setEnabled(false);
+                mDenyButton.setEnabled(false);
+            }
+        });
+
+        mDenyButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                try {
+                    mBackupManager.acknowledgeFullBackupOrRestore(mToken, false, mObserver);
+                } catch (RemoteException e) {
+                    // TODO: bail gracefully if we can't contact the backup manager
+                }
+                mAllowButton.setEnabled(false);
+                mDenyButton.setEnabled(false);
+            }
+        });
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+
+        // We explicitly equate departure from the UI with refusal.  This includes the
+        // implicit configuration-changed stop/restart cycle.
+        try {
+            mBackupManager.acknowledgeFullBackupOrRestore(mToken, false, null);
+        } catch (RemoteException e) {
+            // if this fails we'll still time out with no acknowledgment
+        }
+        finish();
+    }
+
+    /**
+     * The observer binder for showing backup/restore progress.  This binder just bounces
+     * the notifications onto the main thread.
+     */
+    class FullObserver extends IFullBackupRestoreObserver.Stub {
+        //
+        // IFullBackupRestoreObserver implementation
+        //
+        @Override
+        public void onStartBackup() throws RemoteException {
+            mHandler.sendEmptyMessage(MSG_START_BACKUP);
+        }
+
+        @Override
+        public void onBackupPackage(String name) throws RemoteException {
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_BACKUP_PACKAGE, name));
+        }
+
+        @Override
+        public void onEndBackup() throws RemoteException {
+            mHandler.sendEmptyMessage(MSG_END_BACKUP);
+        }
+
+        @Override
+        public void onStartRestore() throws RemoteException {
+            mHandler.sendEmptyMessage(MSG_START_RESTORE);
+        }
+
+        @Override
+        public void onRestorePackage(String name) throws RemoteException {
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_RESTORE_PACKAGE, name));
+        }
+
+        @Override
+        public void onEndRestore() throws RemoteException {
+            mHandler.sendEmptyMessage(MSG_END_RESTORE);
+        }        
+
+        @Override
+        public void onTimeout() throws RemoteException {
+            mHandler.sendEmptyMessage(MSG_TIMEOUT);
+        }
+    }
+}
diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml
index dd0d064..e5f52e2 100644
--- a/packages/SettingsProvider/AndroidManifest.xml
+++ b/packages/SettingsProvider/AndroidManifest.xml
@@ -6,6 +6,7 @@
                  android:label="@string/app_label"
                  android:process="system"
                  android:backupAgent="SettingsBackupAgent"
+                 android:fullBackupAgent="SettingsBackupAgent"
                  android:killAfterRestore="false"
                  android:icon="@drawable/ic_launcher_settings">
                  
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 744798e..45bb2b6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -36,6 +36,7 @@
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupAgentHelper;
+import android.app.backup.FullBackup;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -99,6 +100,10 @@
     private static final String KEY_WIFI_SUPPLICANT = "\uffedWIFI";
     private static final String KEY_WIFI_CONFIG = "\uffedCONFIG_WIFI";
 
+    // Name of the temporary file we use during full backup/restore.  This is
+    // stored in the full-backup tarfile as well, so should not be changed.
+    private static final String STAGE_FILE = "flattened-data";
+
     private SettingsHelper mSettingsHelper;
     private WifiManager mWfm;
     private static String mWifiConfigFile;
@@ -121,22 +126,52 @@
         byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
         byte[] wifiConfigData = getFileData(mWifiConfigFile);
 
-        long[] stateChecksums = readOldChecksums(oldState);
+        // This same agent class is used for both full and incremental backups.  A full
+        // backup is flagged by a 'null' oldState argument.  In the case of a full backup,
+        // the output is structured as tarfile contents.
+        if (oldState != null) {
+            long[] stateChecksums = readOldChecksums(oldState);
 
-        stateChecksums[STATE_SYSTEM] =
+            stateChecksums[STATE_SYSTEM] =
                 writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data);
-        stateChecksums[STATE_SECURE] =
+            stateChecksums[STATE_SECURE] =
                 writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data);
-        stateChecksums[STATE_LOCALE] =
+            stateChecksums[STATE_LOCALE] =
                 writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data);
-        stateChecksums[STATE_WIFI_SUPPLICANT] =
+            stateChecksums[STATE_WIFI_SUPPLICANT] =
                 writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT,
-                wifiSupplicantData, data);
-        stateChecksums[STATE_WIFI_CONFIG] =
+                        wifiSupplicantData, data);
+            stateChecksums[STATE_WIFI_CONFIG] =
                 writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData,
-                data);
+                        data);
 
-        writeNewChecksums(stateChecksums, newState);
+            writeNewChecksums(stateChecksums, newState);
+        } else {
+            // Write the data to the staging file, then emit that as our tarfile
+            // representation of the backed-up settings.
+            String root = getFilesDir().getAbsolutePath();
+            File stage = new File(root, STAGE_FILE);
+            FileOutputStream filestream = new FileOutputStream(stage);
+            BufferedOutputStream bufstream = new BufferedOutputStream(filestream);
+            DataOutputStream out = new DataOutputStream(bufstream);
+
+            out.writeInt(systemSettingsData.length);
+            out.write(systemSettingsData);
+            out.writeInt(secureSettingsData.length);
+            out.write(secureSettingsData);
+            out.writeInt(locale.length);
+            out.write(locale);
+            out.writeInt(wifiSupplicantData.length);
+            out.write(wifiSupplicantData);
+            out.writeInt(wifiConfigData.length);
+            out.write(wifiConfigData);
+
+            out.flush();    // also flushes downstream
+
+            // now we're set to emit the tar stream
+            FullBackup.backupToTar(getPackageName(), FullBackup.DATA_TREE_TOKEN, null,
+                    root, stage.getAbsolutePath(), data);
+        }
     }
 
     @Override
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 6e76331..a334dbb 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -23,10 +23,14 @@
 import android.app.IApplicationThread;
 import android.app.IBackupAgent;
 import android.app.PendingIntent;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.FullBackup;
 import android.app.backup.RestoreSet;
 import android.app.backup.IBackupManager;
+import android.app.backup.IFullBackupRestoreObserver;
 import android.app.backup.IRestoreObserver;
 import android.app.backup.IRestoreSession;
+import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -60,6 +64,9 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.util.StringBuilderPrinter;
+
+import libcore.io.Libcore;
 
 import com.android.internal.backup.BackupConstants;
 import com.android.internal.backup.IBackupTransport;
@@ -81,10 +88,15 @@
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 class BackupManagerService extends IBackupManager.Stub {
     private static final String TAG = "BackupManagerService";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
+
+    // Name and current contents version of the full-backup manifest file
+    static final String BACKUP_MANIFEST_FILENAME = "_manifest";
+    static final int BACKUP_MANIFEST_VERSION = 1;
 
     // How often we perform a backup pass.  Privileged external callers can
     // trigger an immediate pass.
@@ -108,14 +120,20 @@
     private static final int MSG_RUN_GET_RESTORE_SETS = 6;
     private static final int MSG_TIMEOUT = 7;
     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;
 
     // Timeout interval for deciding that a bind or clear-data has taken too long
     static final long TIMEOUT_INTERVAL = 10 * 1000;
 
     // 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_RESTORE_INTERVAL = 60 * 1000;
 
+    // User confirmation timeout for a full backup/restore operation
+    static final long TIMEOUT_FULL_CONFIRMATION = 30 * 1000;
+
     private Context mContext;
     private PackageManager mPackageManager;
     IPackageManager mPackageManagerBinder;
@@ -138,15 +156,13 @@
     // set of backup services that have pending changes
     class BackupRequest {
         public ApplicationInfo appInfo;
-        public boolean fullBackup;
 
-        BackupRequest(ApplicationInfo app, boolean isFull) {
+        BackupRequest(ApplicationInfo app) {
             appInfo = app;
-            fullBackup = isFull;
         }
 
         public String toString() {
-            return "BackupRequest{app=" + appInfo + " full=" + fullBackup + "}";
+            return "BackupRequest{app=" + appInfo + "}";
         }
     }
     // Backups that we haven't started yet.  Keys are package names.
@@ -232,6 +248,38 @@
         }
     }
 
+    class FullParams {
+        public ParcelFileDescriptor fd;
+        public final AtomicBoolean latch;
+        public IFullBackupRestoreObserver observer;
+
+        FullParams() {
+            latch = new AtomicBoolean(false);
+        }
+    }
+
+    class FullBackupParams extends FullParams {
+        public boolean includeApks;
+        public boolean includeShared;
+        public boolean allApps;
+        public String[] packages;
+
+        FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveShared,
+                boolean doAllApps, String[] pkgList) {
+            fd = output;
+            includeApks = saveApks;
+            includeShared = saveShared;
+            allApps = doAllApps;
+            packages = pkgList;
+        }
+    }
+
+    class FullRestoreParams extends FullParams {
+        FullRestoreParams(ParcelFileDescriptor input) {
+            fd = input;
+        }
+    }
+
     // Bookkeeping of in-flight operations for timeout etc. purposes.  The operation
     // token is the index of the entry in the pending-operations list.
     static final int OP_PENDING = 0;
@@ -242,6 +290,8 @@
     final Object mCurrentOpLock = new Object();
     final Random mTokenGenerator = new Random();
 
+    final SparseArray<FullParams> mFullConfirmations = new SparseArray<FullParams>();
+
     // Where we keep our journal files and other bookkeeping
     File mBaseStateDir;
     File mDataDir;
@@ -264,6 +314,17 @@
     static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
     HashSet<String> mPendingInits = new HashSet<String>();  // transport names
 
+    // Utility: build a new random integer token
+    int generateToken() {
+        int token;
+        do {
+            synchronized (mTokenGenerator) {
+                token = mTokenGenerator.nextInt();
+            }
+        } while (token < 0);
+        return token;
+    }
+
     // ----- Asynchronous backup/restore handler thread -----
 
     private class BackupHandler extends Handler {
@@ -321,7 +382,13 @@
             }
 
             case MSG_RUN_FULL_BACKUP:
+            {
+                FullBackupParams params = (FullBackupParams)msg.obj;
+                (new PerformFullBackupTask(params.fd, params.observer, params.includeApks,
+                        params.includeShared, params.allApps, params.packages,
+                        params.latch)).run();
                 break;
+            }
 
             case MSG_RUN_RESTORE:
             {
@@ -416,6 +483,34 @@
                     }
                 }
             }
+
+            case MSG_FULL_CONFIRMATION_TIMEOUT:
+            {
+                synchronized (mFullConfirmations) {
+                    FullParams params = mFullConfirmations.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);
+
+                        // Remove the token from the set
+                        mFullConfirmations.delete(msg.arg1);
+
+                        // Report a timeout to the observer, if any
+                        if (params.observer != null) {
+                            try {
+                                params.observer.onTimeout();
+                            } catch (RemoteException e) {
+                                /* don't care if the app has gone away */
+                            }
+                        }
+                    } else {
+                        Slog.d(TAG, "couldn't find params for token " + msg.arg1);
+                    }
+                }
+                break;
+            }
             }
         }
     }
@@ -1253,9 +1348,11 @@
     void prepareOperationTimeout(int token, long interval) {
         if (DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
                 + " interval=" + interval);
-        mCurrentOperations.put(token, OP_PENDING);
-        Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0);
-        mBackupHandler.sendMessageDelayed(msg, interval);
+        synchronized (mCurrentOpLock) {
+            mCurrentOperations.put(token, OP_PENDING);
+            Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0);
+            mBackupHandler.sendMessageDelayed(msg, interval);
+        }
     }
 
     // ----- Back up a set of applications via a worker thread -----
@@ -1313,7 +1410,7 @@
                 if (status == BackupConstants.TRANSPORT_OK) {
                     PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
                             mPackageManager, allAgentPackages());
-                    BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false);
+                    BackupRequest pmRequest = new BackupRequest(new ApplicationInfo());
                     pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL;
                     status = processOneBackup(pmRequest,
                             IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
@@ -1407,12 +1504,10 @@
                 }
 
                 IBackupAgent agent = null;
-                int mode = (request.fullBackup)
-                        ? IApplicationThread.BACKUP_MODE_FULL
-                        : IApplicationThread.BACKUP_MODE_INCREMENTAL;
                 try {
                     mWakelock.setWorkSource(new WorkSource(request.appInfo.uid));
-                    agent = bindToAgentSynchronous(request.appInfo, mode);
+                    agent = bindToAgentSynchronous(request.appInfo,
+                            IApplicationThread.BACKUP_MODE_INCREMENTAL);
                     if (agent != null) {
                         int result = processOneBackup(request, agent, transport);
                         if (result != BackupConstants.TRANSPORT_OK) return result;
@@ -1446,7 +1541,7 @@
             ParcelFileDescriptor newState = null;
 
             PackageInfo packInfo;
-            int token = mTokenGenerator.nextInt();
+            final int token = generateToken();
             try {
                 // Look up the package info & signatures.  This is first so that if it
                 // throws an exception, there's no file setup yet that would need to
@@ -1461,12 +1556,11 @@
                 }
 
                 // In a full backup, we pass a null ParcelFileDescriptor as
-                // the saved-state "file"
-                if (!request.fullBackup) {
-                    savedState = ParcelFileDescriptor.open(savedStateName,
-                            ParcelFileDescriptor.MODE_READ_ONLY |
-                            ParcelFileDescriptor.MODE_CREATE);  // Make an empty file if necessary
-                }
+                // the saved-state "file". This is by definition an incremental,
+                // so we build a saved state file to pass.
+                savedState = ParcelFileDescriptor.open(savedStateName,
+                        ParcelFileDescriptor.MODE_READ_ONLY |
+                        ParcelFileDescriptor.MODE_CREATE);  // Make an empty file if necessary
 
                 backupData = ParcelFileDescriptor.open(backupDataName,
                         ParcelFileDescriptor.MODE_READ_WRITE |
@@ -1480,7 +1574,8 @@
 
                 // Initiate the target's backup pass
                 prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL);
-                agent.doBackup(savedState, backupData, newState, token, mBackupManagerBinder);
+                agent.doBackup(savedState, backupData, newState, false,
+                        token, mBackupManagerBinder);
                 boolean success = waitUntilOperationComplete(token);
 
                 if (!success) {
@@ -1552,6 +1647,224 @@
     }
 
 
+    // ----- Full backup to a file/socket -----
+
+    class PerformFullBackupTask implements Runnable {
+        ParcelFileDescriptor mOutputFile;
+        IFullBackupRestoreObserver mObserver;
+        boolean mIncludeApks;
+        boolean mIncludeShared;
+        boolean mAllApps;
+        String[] mPackages;
+        AtomicBoolean mLatchObject;
+        File mFilesDir;
+        File mManifestFile;
+
+        PerformFullBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer, 
+                boolean includeApks, boolean includeShared,
+                boolean doAllApps, String[] packages, AtomicBoolean latch) {
+            mOutputFile = fd;
+            mObserver = observer;
+            mIncludeApks = includeApks;
+            mIncludeShared = includeShared;
+            mAllApps = doAllApps;
+            mPackages = packages;
+            mLatchObject = latch;
+
+            mFilesDir = new File("/data/system");
+            mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
+        }
+
+        @Override
+        public void run() {
+            final List<PackageInfo> packagesToBackup;
+
+            sendStartBackup();
+
+            // doAllApps supersedes the package set if any
+            if (mAllApps) {
+                packagesToBackup = mPackageManager.getInstalledPackages(
+                        PackageManager.GET_SIGNATURES);
+            } else {
+                packagesToBackup = new ArrayList<PackageInfo>();
+                for (String pkgName : mPackages) {
+                    try {
+                        packagesToBackup.add(mPackageManager.getPackageInfo(pkgName,
+                                PackageManager.GET_SIGNATURES));
+                    } catch (NameNotFoundException e) {
+                        Slog.w(TAG, "Unknown package " + pkgName + ", skipping");
+                    }
+                }
+            }
+
+            // Now back up the app data via the agent mechanism
+            PackageInfo pkg = null;
+            try {
+                int N = packagesToBackup.size();
+                for (int i = 0; i < N; i++) {
+                    pkg = packagesToBackup.get(i);
+
+                    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 = mPackageManager.getApplicationInfo(
+                                    pkg.packageName, 0);
+                            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 (NameNotFoundException e) {
+                            Slog.e(TAG, "Package exists but not app info; skipping: "
+                                    + 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);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "App died during full backup");
+            } finally {
+                if (pkg != null) {
+                    tearDown(pkg);
+                }
+                try {
+                    mOutputFile.close();
+                } catch (IOException e) {
+                    /* nothing we can do about this */
+                }
+                synchronized (mCurrentOpLock) {
+                    mCurrentOperations.clear();
+                }
+                synchronized (mLatchObject) {
+                    mLatchObject.set(true);
+                    mLatchObject.notifyAll();
+                }
+                sendEndBackup();
+                mWakelock.release();
+                if (DEBUG) Slog.d(TAG, "Full backup pass complete.");
+            }
+        }
+
+        private void writeAppManifest(PackageInfo pkg, File manifestFile, boolean withApk)
+                throws IOException {
+            // Manifest format. All data are strings ending in LF:
+            //     BACKUP_MANIFEST_VERSION, currently 1
+            //
+            // Version 1:
+            //     package name
+            //     package's versionCode
+            //     boolean: "1" if archive includes .apk, "0" otherwise
+            //     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(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);
+            Libcore.os.ftruncate(outstream.getFD(), 0);
+            outstream.write(builder.toString().getBytes());
+            outstream.close();
+        }
+
+        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);
+
+                // 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");
+            }
+        }
+
+        // wrappers for observer use
+        void sendStartBackup() {
+            if (mObserver != null) {
+                try {
+                    mObserver.onStartBackup();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "full backup observer went away: startBackup");
+                    mObserver = null;
+                }
+            }
+        }
+
+        void sendOnBackupPackage(String name) {
+            if (mObserver != null) {
+                try {
+                    // TODO: use a more user-friendly name string
+                    mObserver.onBackupPackage(name);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "full backup observer went away: backupPackage");
+                    mObserver = null;
+                }
+            }
+        }
+
+        void sendEndBackup() {
+            if (mObserver != null) {
+                try {
+                    mObserver.onEndBackup();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "full backup observer went away: endBackup");
+                    mObserver = null;
+                }
+            }
+        }
+    }
+
+
     // ----- Restore handling -----
 
     private boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
@@ -1893,7 +2206,7 @@
             ParcelFileDescriptor backupData = null;
             ParcelFileDescriptor newState = null;
 
-            int token = mTokenGenerator.nextInt();
+            final int token = generateToken();
             try {
                 // Run the transport's restore pass
                 backupData = ParcelFileDescriptor.open(backupDataName,
@@ -1957,7 +2270,9 @@
                 try { if (backupData != null) backupData.close(); } catch (IOException e) {}
                 try { if (newState != null) newState.close(); } catch (IOException e) {}
                 backupData = newState = null;
-                mCurrentOperations.delete(token);
+                synchronized (mCurrentOperations) {
+                    mCurrentOperations.delete(token);
+                }
 
                 // If we know a priori that we'll need to perform a full post-restore backup
                 // pass, clear the new state file data.  This means we're discarding work that
@@ -2092,7 +2407,7 @@
                 if (app.packageName.equals(packageName)) {
                     // Add the caller to the set of pending backups.  If there is
                     // one already there, then overwrite it, but no harm done.
-                    BackupRequest req = new BackupRequest(app, false);
+                    BackupRequest req = new BackupRequest(app);
                     if (mPendingBackups.put(app.packageName, req) == null) {
                         // Journal this request in case of crash.  The put()
                         // operation returned null when this package was not already
@@ -2239,10 +2554,141 @@
         }
     }
 
+    // Run a *full* backup pass for the given package, 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.
+    public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeShared,
+            boolean doAllApps, String[] pkgList) {
+        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");
+
+        // Validate
+        if (!doAllApps) {
+            if (!includeShared) {
+                // If we're backing up shared data (sdcard or equivalent), then we can run
+                // without any supplied app names.  Otherwise, we'd be doing no work, so
+                // report the error.
+                if (pkgList == null || pkgList.length == 0) {
+                    throw new IllegalArgumentException(
+                            "Backup requested but neither shared nor any apps named");
+                }
+            }
+        }
+
+        if (DEBUG) Slog.v(TAG, "Requesting full backup: apks=" + includeApks
+                + " shared=" + includeShared + " all=" + doAllApps
+                + " pkgs=" + pkgList);
+
+        long oldId = Binder.clearCallingIdentity();
+        try {
+            FullBackupParams params = new FullBackupParams(fd, includeApks, includeShared,
+                    doAllApps, pkgList);
+            final int token = generateToken();
+            synchronized (mFullConfirmations) {
+                mFullConfirmations.put(token, params);
+            }
+
+            // start up the confirmation UI, making sure the screen lights up
+            if (DEBUG) Slog.d(TAG, "Starting confirmation UI, token=" + token);
+            try {
+                Intent confIntent = new Intent(FullBackup.FULL_BACKUP_INTENT_ACTION);
+                confIntent.setClassName("com.android.backupconfirm",
+                        "com.android.backupconfirm.BackupRestoreConfirmation");
+                confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token);
+                confIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivity(confIntent);
+            } catch (ActivityNotFoundException e) {
+                Slog.e(TAG, "Unable to launch full backup confirmation", e);
+                mFullConfirmations.delete(token);
+                return;
+            }
+            mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+
+            // start the confirmation countdown
+            if (DEBUG) Slog.d(TAG, "Posting conf timeout msg after "
+                    + TIMEOUT_FULL_CONFIRMATION + " millis");
+            Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
+                    token, 0, params);
+            mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION);
+
+            // wait for the backup to be performed
+            if (DEBUG) Slog.d(TAG, "Waiting for full backup completion...");
+            waitForCompletion(params);
+            if (DEBUG) Slog.d(TAG, "...Full backup operation complete!");
+        } finally {
+            Binder.restoreCallingIdentity(oldId);
+            try {
+                fd.close();
+            } catch (IOException e) {
+                // just eat it
+            }
+        }
+    }
+
+    void waitForCompletion(FullParams params) {
+        synchronized (params.latch) {
+            while (params.latch.get() == false) {
+                try {
+                    params.latch.wait();
+                } catch (InterruptedException e) { /* never interrupted */ }
+            }
+        }
+    }
+
+    void signalFullBackupRestoreCompletion(FullParams params) {
+        synchronized (params.latch) {
+            params.latch.set(true);
+            params.latch.notifyAll();
+        }
+    }
+
+    // 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,
+            IFullBackupRestoreObserver observer) {
+        if (DEBUG) Slog.d(TAG, "acknowledgeFullBackupOrRestore : 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, "fullBackup");
+
+        long oldId = Binder.clearCallingIdentity();
+        try {
+
+            FullParams params;
+            synchronized (mFullConfirmations) {
+                params = mFullConfirmations.get(token);
+                if (params != null) {
+                    mBackupHandler.removeMessages(MSG_FULL_CONFIRMATION_TIMEOUT, params);
+                    mFullConfirmations.delete(token);
+
+                    if (allow) {
+                        params.observer = observer;
+                        final int verb = params instanceof FullBackupParams
+                        ? MSG_RUN_FULL_BACKUP
+                                : MSG_RUN_FULL_RESTORE;
+
+                        mWakelock.acquire();
+                        Message msg = mBackupHandler.obtainMessage(verb, params);
+                        mBackupHandler.sendMessage(msg);
+                    } else {
+                        Slog.w(TAG, "User rejected full backup/restore operation");
+                        // indicate completion without having actually transferred any data
+                        signalFullBackupRestoreCompletion(params);
+                    }
+                } else {
+                    Slog.w(TAG, "Attempted to ack full backup/restore with invalid token");
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(oldId);
+        }
+    }
+
     // Enable/disable the backup service
     public void setBackupEnabled(boolean enable) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
-                "setBackupEnabled");
+        "setBackupEnabled");
 
         Slog.i(TAG, "Backup enabled => " + enable);
 
diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java
index 80b0174..54555bb 100644
--- a/services/java/com/android/server/SystemBackupAgent.java
+++ b/services/java/com/android/server/SystemBackupAgent.java
@@ -20,6 +20,7 @@
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupAgentHelper;
+import android.app.backup.FullBackup;
 import android.app.backup.WallpaperBackupHelper;
 import android.content.Context;
 import android.os.ParcelFileDescriptor;
@@ -37,12 +38,19 @@
     private static final String TAG = "SystemBackupAgent";
 
     // These paths must match what the WallpaperManagerService uses
-    private static final String WALLPAPER_IMAGE = "/data/data/com.android.settings/files/wallpaper";
-    private static final String WALLPAPER_INFO = "/data/system/wallpaper_info.xml";
+    private static final String WALLPAPER_IMAGE_DIR = "/data/data/com.android.settings/files";
+    private static final String WALLPAPER_IMAGE = WALLPAPER_IMAGE_DIR + "/wallpaper";
+    private static final String WALLPAPER_INFO_DIR = "/data/system";
+    private static final String WALLPAPER_INFO = WALLPAPER_INFO_DIR + "/wallpaper_info.xml";
 
     @Override
     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
             ParcelFileDescriptor newState) throws IOException {
+        if (oldState == null) {
+            runFullBackup(data);
+            return;
+        }
+
         // We only back up the data under the current "wallpaper" schema with metadata
         WallpaperManagerService wallpaper = (WallpaperManagerService)ServiceManager.getService(
                 Context.WALLPAPER_SERVICE);
@@ -57,6 +65,14 @@
         super.onBackup(oldState, data, newState);
     }
 
+    private void runFullBackup(BackupDataOutput output) {
+        // Back up the data files directly
+        FullBackup.backupToTar(getPackageName(), null, null,
+                WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output);
+        FullBackup.backupToTar(getPackageName(), null, null,
+                WALLPAPER_INFO_DIR, WALLPAPER_INFO, output);
+    }
+
     @Override
     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
             throws IOException {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index ed52dd3..586bb6b 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -164,7 +164,7 @@
     static final boolean DEBUG_URI_PERMISSION = localLOGV || false;
     static final boolean DEBUG_USER_LEAVING = localLOGV || false;
     static final boolean DEBUG_RESULTS = localLOGV || false;
-    static final boolean DEBUG_BACKUP = localLOGV || false;
+    static final boolean DEBUG_BACKUP = localLOGV || true;
     static final boolean DEBUG_CONFIGURATION = localLOGV || false;
     static final boolean DEBUG_POWER = localLOGV || false;
     static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false;
@@ -3308,7 +3308,7 @@
         if (callerUid == Process.SYSTEM_UID) {
             synchronized (this) {
                 ProcessRecord app = getProcessRecordLocked(processName, uid);
-                if (app != null) {
+                if (app != null && app.thread != null) {
                     try {
                         app.thread.scheduleSuicide();
                     } catch (RemoteException e) {
@@ -10733,7 +10733,9 @@
             }
 
             BackupRecord r = new BackupRecord(ss, app, backupMode);
-            ComponentName hostingName = new ComponentName(app.packageName, app.backupAgentName);
+            ComponentName hostingName = (backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL)
+                    ? new ComponentName(app.packageName, app.backupAgentName)
+                    : new ComponentName("android", "FullBackupAgent");
             // startProcessLocked() returns existing proc's record if it's already running
             ProcessRecord proc = startProcessLocked(app.processName, app,
                     false, 0, "backup", hostingName, false);
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index b0400af..d5ac19e 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -73,7 +73,7 @@
  */
 final class ActivityStack {
     static final String TAG = ActivityManagerService.TAG;
-    static final boolean localLOGV = ActivityManagerService.localLOGV;
+    static final boolean localLOGV = ActivityManagerService.localLOGV || true;
     static final boolean DEBUG_SWITCH = ActivityManagerService.DEBUG_SWITCH;
     static final boolean DEBUG_PAUSE = ActivityManagerService.DEBUG_PAUSE;
     static final boolean DEBUG_VISBILITY = ActivityManagerService.DEBUG_VISBILITY;