Flesh out restore interface on manager; work up most of LocalTransport
diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java
index c3b6a02..8df7eae 100644
--- a/core/java/android/backup/BackupManager.java
+++ b/core/java/android/backup/BackupManager.java
@@ -45,7 +45,7 @@
     /**
      * Defined backup transports understood by {@link IBackupManager.selectBackupTransport}.
      */
-    public static final int TRANSPORT_ADB = 1;
+    public static final int TRANSPORT_LOCAL = 1;
     public static final int TRANSPORT_GOOGLE = 2;
 
     /**
diff --git a/core/java/com/android/internal/backup/AdbTransport.java b/core/java/com/android/internal/backup/AdbTransport.java
deleted file mode 100644
index 8d3bd1c..0000000
--- a/core/java/com/android/internal/backup/AdbTransport.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.android.internal.backup;
-
-import android.backup.RestoreSet;
-import android.content.pm.PackageInfo;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-
-/**
- * Backup transport for full backup over adb.  This transport pipes everything to
- * a file in a known location in /cache, which 'adb backup' then pulls to the desktop
- * (deleting it afterwards).
- */
-
-public class AdbTransport extends IBackupTransport.Stub {
-
-    public long requestBackupTime() throws RemoteException {
-        return 0;
-    }
-
-    public int startSession() throws RemoteException {
-        // TODO Auto-generated method stub
-        return 0;
-    }
-
-    public int endSession() throws RemoteException {
-        // TODO Auto-generated method stub
-        return 0;
-    }
-
-    public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
-            throws RemoteException {
-        // TODO Auto-generated method stub
-        return 0;
-    }
-
-    // Restore handling
-    public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
-        RestoreSet[] set = new RestoreSet[1];
-        set[0].device = "USB";
-        set[0].name = "adb";
-        set[0].token = 0;
-        return set;
-    }
-
-    public PackageInfo[] getAppSet(int token) throws android.os.RemoteException {
-        // !!! TODO: real implementation
-        return new PackageInfo[0];
-    }
-
-    public int getRestoreData(int token, PackageInfo packageInfo, ParcelFileDescriptor data)
-            throws android.os.RemoteException {
-        // !!! TODO: real implementation
-        return 0;
-    }
-}
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
new file mode 100644
index 0000000..62fba4a
--- /dev/null
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -0,0 +1,140 @@
+package com.android.internal.backup;
+
+import android.backup.RestoreSet;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Backup transport for stashing stuff into a known location on disk, and
+ * later restoring from there.  For testing only.
+ */
+
+public class LocalTransport extends IBackupTransport.Stub {
+    private static final String TAG = "LocalTransport";
+    private static final String DATA_FILE_NAME = "data";
+
+    private Context mContext;
+    private PackageManager mPackageManager;
+    private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
+    private FileFilter mDirFileFilter = new FileFilter() {
+        public boolean accept(File f) {
+            return f.isDirectory();
+        }
+    };
+
+
+    public LocalTransport(Context context) {
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+    }
+
+    public long requestBackupTime() throws RemoteException {
+        // any time is a good time for local backup
+        return 0;
+    }
+
+    public int startSession() throws RemoteException {
+        return 0;
+    }
+
+    public int endSession() throws RemoteException {
+        return 0;
+    }
+
+    public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
+            throws RemoteException {
+        File packageDir = new File(mDataDir, packageInfo.packageName);
+        File imageFileName = new File(packageDir, DATA_FILE_NAME);
+
+        //!!! TODO: process the (partial) update into the persistent restore set:
+        
+        // Parse out the existing image file into the key/value map
+
+        // Parse out the backup data into the key/value updates
+
+        // Apply the backup key/value updates to the image
+
+        // Write out the image in the canonical format
+
+        return -1;
+    }
+
+    // Restore handling
+    public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
+        // one hardcoded restore set
+        RestoreSet[] set = new RestoreSet[1];
+        set[0].device = "flash";
+        set[0].name = "Local disk image";
+        set[0].token = 0;
+        return set;
+    }
+
+    public PackageInfo[] getAppSet(int token) throws android.os.RemoteException {
+        // the available packages are the extant subdirs of mDatadir
+        File[] packageDirs = mDataDir.listFiles(mDirFileFilter);
+        ArrayList<PackageInfo> packages = new ArrayList<PackageInfo>();
+        for (File dir : packageDirs) {
+            try {
+                PackageInfo pkg = mPackageManager.getPackageInfo(dir.getName(),
+                        PackageManager.GET_SIGNATURES);
+                if (pkg != null) {
+                    packages.add(pkg);
+                }
+            } catch (NameNotFoundException e) {
+                // restore set contains data for a package not installed on the
+                // phone -- just ignore it.
+            }
+        }
+
+        Log.v(TAG, "Built app set of " + packages.size() + " entries:");
+        for (PackageInfo p : packages) {
+            Log.v(TAG, "    + " + p.packageName);
+        }
+
+        PackageInfo[] result = new PackageInfo[packages.size()];
+        return packages.toArray(result);
+    }
+
+    public int getRestoreData(int token, PackageInfo packageInfo, ParcelFileDescriptor output)
+            throws android.os.RemoteException {
+        // we only support one hardcoded restore set
+        if (token != 0) return -1;
+
+        // the data for a given package is at a known location
+        File packageDir = new File(mDataDir, packageInfo.packageName);
+        File imageFile = new File(packageDir, DATA_FILE_NAME);
+
+        // restore is relatively easy: we already maintain the full data set in
+        // the canonical form understandable to the BackupAgent
+        return copyFileToFD(imageFile, output);
+    }
+
+    private int copyFileToFD(File source, ParcelFileDescriptor dest) {
+        try {
+            FileInputStream in = new FileInputStream(source);
+            FileOutputStream out = new FileOutputStream(dest.getFileDescriptor());
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = in.read(buffer)) >= 0) {
+                out.write(buffer, 0, bytesRead);
+            }
+        } catch (IOException e) {
+            // something went wrong; claim failure
+            return -1;
+        }
+        return 0;
+    }
+}
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index f871496..d3067ec 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -47,7 +47,7 @@
 import android.backup.BackupManager;
 import android.backup.RestoreSet;
 
-import com.android.internal.backup.AdbTransport;
+import com.android.internal.backup.LocalTransport;
 import com.android.internal.backup.GoogleTransport;
 import com.android.internal.backup.IBackupTransport;
 
@@ -72,6 +72,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;
 
     // Timeout interval for deciding that a bind or clear-data has taken too long
     static final long TIMEOUT_INTERVAL = 10 * 1000;
@@ -131,7 +132,9 @@
         mStateDir = new File(Environment.getDataDirectory(), "backup");
         mStateDir.mkdirs();
         mDataDir = Environment.getDownloadCacheDirectory();
-        mTransportId = BackupManager.TRANSPORT_GOOGLE;
+
+        //!!! TODO: default to cloud transport, not local
+        mTransportId = BackupManager.TRANSPORT_LOCAL;
         
         // Build our mapping of uid to backup client services
         synchronized (mBackupParticipants) {
@@ -212,6 +215,14 @@
 
             case MSG_RUN_FULL_BACKUP:
                 break;
+
+            case MSG_RUN_RESTORE:
+            {
+                int token = msg.arg1;
+                IBackupTransport transport = (IBackupTransport)msg.obj;
+                (new PerformRestoreThread(transport, token)).run();
+                break;
+            }
             }
         }
     }
@@ -331,9 +342,9 @@
     private IBackupTransport createTransport(int transportID) {
         IBackupTransport transport = null;
         switch (transportID) {
-        case BackupManager.TRANSPORT_ADB:
-            if (DEBUG) Log.v(TAG, "Initializing adb transport");
-            transport = new AdbTransport();
+        case BackupManager.TRANSPORT_LOCAL:
+            if (DEBUG) Log.v(TAG, "Initializing local transport");
+            transport = new LocalTransport(mContext);
             break;
 
         case BackupManager.TRANSPORT_GOOGLE:
@@ -585,10 +596,12 @@
 
     class PerformRestoreThread extends Thread {
         private IBackupTransport mTransport;
+        private int mToken;
         private RestoreSet mImage;
 
-        PerformRestoreThread(IBackupTransport transport) {
+        PerformRestoreThread(IBackupTransport transport, int restoreSetToken) {
             mTransport = transport;
+            mToken = restoreSetToken;
         }
 
         @Override
@@ -622,7 +635,7 @@
                 try {
                     RestoreSet[] images = mTransport.getAvailableRestoreSets();
                     if (images.length > 0) {
-                        // !!! for now we always take the first set
+                        // !!! TODO: pick out the set for this token
                         mImage = images[0];
 
                         // build the set of apps we will attempt to restore
@@ -870,6 +883,9 @@
 
         // --- Binder interface ---
         public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
+            mContext.enforceCallingPermission("android.permission.BACKUP",
+                    "getAvailableRestoreSets");
+
             synchronized(this) {
                 if (mRestoreSets == null) {
                     mRestoreSets = mRestoreTransport.getAvailableRestoreSets();
@@ -879,10 +895,26 @@
         }
 
         public int performRestore(int token) throws android.os.RemoteException {
+            mContext.enforceCallingPermission("android.permission.BACKUP", "performRestore");
+
+            if (mRestoreSets != null) {
+                for (int i = 0; i < mRestoreSets.length; i++) {
+                    if (token == mRestoreSets[i].token) {
+                        Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE,
+                                mRestoreTransport);
+                        msg.arg1 = token;
+                        mBackupHandler.sendMessage(msg);
+                        return 0;
+                    }
+                }
+            }
             return -1;
         }
 
         public void endRestoreSession() throws android.os.RemoteException {
+            mContext.enforceCallingPermission("android.permission.BACKUP",
+                    "endRestoreSession");
+
             mRestoreTransport.endSession();
             mRestoreTransport = null;
         }