Implement shared-storage full backup/restore

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

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

Change-Id: I380019da8d0bb5b3699bd7c11eeff621a88e78c3
diff --git a/packages/SharedStorageBackup/Android.mk b/packages/SharedStorageBackup/Android.mk
new file mode 100644
index 0000000..1d4f4da7
--- /dev/null
+++ b/packages/SharedStorageBackup/Android.mk
@@ -0,0 +1,33 @@
+#
+# 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_PROGUARD_FLAG_FILES := proguard.flags
+
+LOCAL_PACKAGE_NAME := SharedStorageBackup
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
+########################
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/packages/SharedStorageBackup/AndroidManifest.xml b/packages/SharedStorageBackup/AndroidManifest.xml
new file mode 100644
index 0000000..258059c
--- /dev/null
+++ b/packages/SharedStorageBackup/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.sharedstoragebackup" >
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
+
+    <application android:allowClearUserData="false"
+                 android:permission="android.permission.CONFIRM_FULL_BACKUP"
+                 android:fullBackupAgent=".SharedStorageAgent"
+                 android:allowBackup="false" >
+    </application>
+</manifest>
diff --git a/packages/SharedStorageBackup/proguard.flags b/packages/SharedStorageBackup/proguard.flags
new file mode 100644
index 0000000..f43cb81
--- /dev/null
+++ b/packages/SharedStorageBackup/proguard.flags
@@ -0,0 +1 @@
+-keep class com.android.sharedstoragebackup.SharedStorageAgent
diff --git a/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java
new file mode 100644
index 0000000..b02ca2e
--- /dev/null
+++ b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java
@@ -0,0 +1,93 @@
+package com.android.sharedstoragebackup;
+
+import android.app.backup.FullBackup;
+import android.app.backup.FullBackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.content.Context;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+
+public class SharedStorageAgent extends FullBackupAgent {
+    static final String TAG = "SharedStorageAgent";
+    static final boolean DEBUG = true;
+
+    StorageVolume[] mVolumes;
+
+    @Override
+    public void onCreate() {
+        StorageManager mgr = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
+        if (mgr != null) {
+            mVolumes = mgr.getVolumeList();
+        } else {
+            Slog.e(TAG, "Unable to access Storage Manager");
+        }
+    }
+
+    @Override
+    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+            ParcelFileDescriptor newState) throws IOException {
+        // If there are shared-storage volumes available, run the inherited directory-
+        // hierarchy backup process on them.  By convention in the Storage Manager, the
+        // "primary" shared storage volume is first in the list.
+        if (mVolumes != null) {
+            for (int i = 0; i < mVolumes.length; i++) {
+                StorageVolume v = mVolumes[i];
+                // Express the contents of volume N this way in the tar stream:
+                //     shared/N/path/to/file
+                // The restore will then extract to the given volume
+                String domain = FullBackup.SHARED_PREFIX + i;
+                processTree(null, domain, v.getPath(), null, data);
+            }
+        }
+    }
+
+    /**
+     * Incremental onRestore() implementation is not used.
+     */
+    @Override
+    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+            throws IOException {
+    }
+
+    /**
+     * Full restore of one file to shared storage
+     */
+    @Override
+    public void onRestoreFile(ParcelFileDescriptor data, long size,
+            int type, String domain, String relpath, long mode, long mtime)
+            throws IOException {
+        Slog.d(TAG, "Shared restore: [ " + domain + " : " + relpath + "]");
+
+        File outFile = null;
+
+        // The file path must be in the semantic form [number]/path/to/file...
+        int slash = relpath.indexOf('/');
+        if (slash > 0) {
+            try {
+                int i = Integer.parseInt(relpath.substring(0, slash));
+                if (i <= mVolumes.length) {
+                    outFile = new File(mVolumes[i].getPath(), relpath.substring(slash + 1));
+                    if (DEBUG) Slog.i(TAG, " => " + outFile.getAbsolutePath());
+                } else {
+                    Slog.w(TAG, "Cannot restore data for unavailable volume " + i);
+                }
+            } catch (NumberFormatException e) {
+                if (DEBUG) Slog.w(TAG, "Bad volume number token: " + relpath.substring(0, slash));
+            }
+        } else {
+            if (DEBUG) Slog.i(TAG, "Can't find volume-number token");
+        }
+        if (outFile == null) {
+            Slog.e(TAG, "Skipping data with malformed path " + relpath);
+        }
+
+        FullBackup.restoreToFile(data, size, type, mode, mtime, outFile, false);
+    }
+}