Add a "clear backed-up data" method to the backup mechanism

It's now possible to ask that the backup manager wipe the saved data for a given
application from the backing store.  LocalTransport implements this now but the
Google backend does not yet.  When the data is wiped, the on-device backup state
is also wiped to ensure that the next backup pushes all necessary data.

Bmgr has not yet been modified to actually call into this method, but it will
be soon.
diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl
index 1f11762..d4933ac 100644
--- a/core/java/android/backup/IBackupManager.aidl
+++ b/core/java/android/backup/IBackupManager.aidl
@@ -32,10 +32,24 @@
     /**
      * Tell the system service that the caller has made changes to its
      * data, and therefore needs to undergo an incremental backup pass.
+     *
+     * Any application can invoke this method for its own package, but
+     * only callers who hold the android.permission.BACKUP permission
+     * may invoke it for arbitrary packages.
      */
     void dataChanged(String packageName);
 
     /**
+     * Erase all backed-up data for the given package from the storage
+     * destination.
+     *
+     * Any application can invoke this method for its own package, but
+     * only callers who hold the android.permission.BACKUP permission
+     * may invoke it for arbitrary packages.
+     */
+    void clearBackupData(String packageName);
+
+    /**
      * Notifies the Backup Manager Service that an agent has become available.  This
      * method is only invoked by the Activity Manager.
      */
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 4bef265..af06965 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -83,13 +83,25 @@
     boolean performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd);
 
     /**
-     * Finish sending application data to the backup destination.  This must be
-     * called after {@link #performBackup} to ensure that all data is sent.  Only
-     * when this method returns true can the backup be assumed to have succeeded.
+     * Erase the give application's data from the backup destination.  This clears
+     * out the given package's data from the current backup set, making it as though
+     * the app had never yet been backed up.  After this is called, {@link finishBackup}
+     * must be called to ensure that the operation is recorded successfully.
      *
      * @return false if errors occurred (the backup should be aborted and rescheduled),
      *   true if everything is OK so far (but {@link #finishBackup} must be called).
      */
+    boolean clearBackupData(in PackageInfo packageInfo);
+
+    /**
+     * Finish sending application data to the backup destination.  This must be
+     * called after {@link #performBackup} or {@link clearBackupData} to ensure that
+     * all data is sent.  Only when this method returns true can a backup be assumed
+     * to have succeeded.
+     *
+     * @return false if errors occurred (the backup should be aborted and rescheduled),
+     *   true if everything is OK.
+     */
     boolean finishBackup();
 
     /**
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index c5d9d403..2facce2 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -111,6 +111,17 @@
         }
     }
 
+    public boolean clearBackupData(PackageInfo packageInfo) {
+        if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
+
+        File packageDir = new File(mDataDir, packageInfo.packageName);
+        for (File f : packageDir.listFiles()) {
+            f.delete();
+        }
+        packageDir.delete();
+        return true;
+    }
+
     public boolean finishBackup() throws RemoteException {
         if (DEBUG) Log.v(TAG, "finishBackup()");
         return true;
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 953e401..8790472 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -81,6 +81,7 @@
     private static final int MSG_RUN_BACKUP = 1;
     private static final int MSG_RUN_FULL_BACKUP = 2;
     private static final int MSG_RUN_RESTORE = 3;
+    private static final int MSG_RUN_CLEAR = 4;
 
     // Timeout interval for deciding that a bind or clear-data has taken too long
     static final long TIMEOUT_INTERVAL = 10 * 1000;
@@ -148,6 +149,16 @@
         }
     }
 
+    private class ClearParams {
+        public IBackupTransport transport;
+        public PackageInfo packageInfo;
+
+        ClearParams(IBackupTransport _transport, PackageInfo _info) {
+            transport = _transport;
+            packageInfo = _info;
+        }
+    }
+
     // Where we keep our journal files and other bookkeeping
     private File mBaseStateDir;
     private File mDataDir;
@@ -386,6 +397,13 @@
                 (new PerformRestoreThread(params.transport, params.observer, params.token)).start();
                 break;
             }
+
+            case MSG_RUN_CLEAR:
+            {
+                ClearParams params = (ClearParams)msg.obj;
+                (new PerformClearThread(params.transport, params.packageInfo)).start();
+                break;
+            }
             }
         }
     }
@@ -1071,6 +1089,37 @@
         }
     }
 
+    class PerformClearThread extends Thread {
+        IBackupTransport mTransport;
+        PackageInfo mPackage;
+
+        PerformClearThread(IBackupTransport transport, PackageInfo packageInfo) {
+            mTransport = transport;
+            mPackage = packageInfo;
+        }
+
+        @Override
+        public void run() {
+            try {
+                // Clear the on-device backup state to ensure a full backup next time
+                File stateDir = new File(mBaseStateDir, mTransport.transportDirName());
+                File stateFile = new File(stateDir, mPackage.packageName);
+                stateFile.delete();
+
+                // Tell the transport to remove all the persistent storage for the app
+                mTransport.clearBackupData(mPackage);
+            } catch (RemoteException e) {
+                // can't happen; the transport is local
+            } finally {
+                try {
+                    mTransport.finishBackup();
+                } catch (RemoteException e) {
+                    // can't happen; the transport is local
+                }
+            }
+        }
+    }
+
 
     // ----- IBackupManager binder interface -----
 
@@ -1142,6 +1191,52 @@
         }
     }
 
+    // Clear the given package's backup data from the current transport
+    public void clearBackupData(String packageName) {
+        if (DEBUG) Log.v(TAG, "clearBackupData() of " + packageName);
+        PackageInfo info;
+        try {
+            info = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+        } catch (NameNotFoundException e) {
+            Log.d(TAG, "No such package '" + packageName + "' - not clearing backup data");
+            return;
+        }
+
+        // If the caller does not hold the BACKUP permission, it can only request a
+        // wipe of its own backed-up data.
+        HashSet<ApplicationInfo> apps;
+        if ((mContext.checkPermission("android.permission.BACKUP", Binder.getCallingPid(),
+                Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) {
+            apps = mBackupParticipants.get(Binder.getCallingUid());
+        } else {
+            // a caller with full permission can ask to back up any participating app
+            // !!! TODO: allow data-clear of ANY app?
+            if (DEBUG) Log.v(TAG, "Privileged caller, allowing clear of other apps");
+            apps = new HashSet<ApplicationInfo>();
+            int N = mBackupParticipants.size();
+            for (int i = 0; i < N; i++) {
+                HashSet<ApplicationInfo> s = mBackupParticipants.valueAt(i);
+                if (s != null) {
+                    apps.addAll(s);
+                }
+            }
+        }
+
+        // now find the given package in the set of candidate apps
+        for (ApplicationInfo app : apps) {
+            if (app.packageName.equals(packageName)) {
+                if (DEBUG) Log.v(TAG, "Found the app - running clear process");
+                // found it; fire off the clear request
+                synchronized (mQueueLock) {
+                    Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR,
+                            new ClearParams(getTransport(mCurrentTransport), info));
+                    mBackupHandler.sendMessage(msg);
+                }
+                break;
+            }
+        }
+    }
+
     // Run a backup pass immediately for any applications that have declared
     // that they have pending updates.
     public void backupNow() throws RemoteException {