Full backup/restore now handles OBBs sensibly

OBB backup/ restore is no longer handled within the target app
process.  This is done to avoid having to require that OBB-using
apps have full read/write permission for external storage.

The new OBB backup service is a new component running in the
same app as the already-existing shared storage backup agent.
The backup infrastructure delegates backup/restore of apps'
OBB contents to this component (because the system process
may not itself read/write external storage).

From the command line, OBB backup is enabled by using new
-obb / -noobb flags with adb backup.  The default is noobb.

Finally, a couple of nit fixes:

- buffer-size mismatch between the writer and reader of chunked
  file data has been corrected; now the reading side won't be
  issuing an extra pipe read per chunk.

- bu now explicitly closes the transport socket fd after
  adopting it. This was benign but triggered a logged
  warning about leaked fds.

Bug: 6718844
Change-Id: Ie252494e2327e9ab97cf9ed87c298410a8618492
diff --git a/packages/SharedStorageBackup/AndroidManifest.xml b/packages/SharedStorageBackup/AndroidManifest.xml
index fc21df3..b8df88e 100644
--- a/packages/SharedStorageBackup/AndroidManifest.xml
+++ b/packages/SharedStorageBackup/AndroidManifest.xml
@@ -24,5 +24,12 @@
     <application android:allowClearUserData="false"
                  android:permission="android.permission.CONFIRM_FULL_BACKUP"
                  android:backupAgent="SharedStorageAgent" >
+
+        <service android:name=".ObbBackupService"
+                 android:enabled="true"
+                 android:exported="true">
+        </service>
+
     </application>
+
 </manifest>
diff --git a/packages/SharedStorageBackup/proguard.flags b/packages/SharedStorageBackup/proguard.flags
index f43cb81..6a66a47 100644
--- a/packages/SharedStorageBackup/proguard.flags
+++ b/packages/SharedStorageBackup/proguard.flags
@@ -1 +1,2 @@
 -keep class com.android.sharedstoragebackup.SharedStorageAgent
+-keep class com.android.sharedstoragebackup.ObbBackupService
diff --git a/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/ObbBackupService.java b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/ObbBackupService.java
new file mode 100644
index 0000000..7ebe096
--- /dev/null
+++ b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/ObbBackupService.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2013 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.sharedstoragebackup;
+
+import android.app.Service;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.FullBackup;
+import android.app.backup.IBackupManager;
+import android.content.Intent;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import com.android.internal.backup.IObbBackupService;
+
+/**
+ * Service that the Backup Manager Services delegates OBB backup/restore operations to,
+ * because those require accessing external storage.
+ *
+ * {@hide}
+ */
+public class ObbBackupService extends Service {
+    static final String TAG = "ObbBackupService";
+    static final boolean DEBUG = true;
+
+    /**
+     * IObbBackupService interface implementation
+     */
+    IObbBackupService mService = new IObbBackupService.Stub() {
+        /*
+         * Back up a package's OBB directory tree
+         */
+        @Override
+        public void backupObbs(String packageName, ParcelFileDescriptor data,
+                int token, IBackupManager callbackBinder) {
+            final FileDescriptor outFd = data.getFileDescriptor();
+            try {
+                File obbDir = Environment.getExternalStorageAppObbDirectory(packageName);
+                if (obbDir != null) {
+                    if (obbDir.exists()) {
+                        ArrayList<File> obbList = allFileContents(obbDir);
+                        if (obbList != null) {
+                            // okay, there's at least something there
+                            if (DEBUG) {
+                                Log.i(TAG, obbList.size() + " files to back up");
+                            }
+                            final String rootPath = obbDir.getCanonicalPath();
+                            final BackupDataOutput out = new BackupDataOutput(outFd);
+                            for (File f : obbList) {
+                                final String filePath = f.getCanonicalPath();
+                                if (DEBUG) {
+                                    Log.i(TAG, "storing: " + filePath);
+                                }
+                                FullBackup.backupToTar(packageName, FullBackup.OBB_TREE_TOKEN, null,
+                                        rootPath, filePath, out);
+                            }
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                Log.w(TAG, "Exception backing up OBBs for " + packageName, e);
+            } finally {
+                // Send the EOD marker indicating that there is no more data
+                try {
+                    FileOutputStream out = new FileOutputStream(outFd);
+                    byte[] buf = new byte[4];
+                    out.write(buf);
+                } catch (IOException e) {
+                    Log.e(TAG, "Unable to finalize obb backup stream!");
+                }
+
+                try {
+                    callbackBinder.opComplete(token);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        /*
+         * Restore an OBB file for the given package from the incoming stream
+         */
+        @Override
+        public void restoreObbFile(String packageName, ParcelFileDescriptor data,
+                long fileSize, int type, String path, long mode, long mtime,
+                int token, IBackupManager callbackBinder) {
+            try {
+                File outFile = Environment.getExternalStorageAppObbDirectory(packageName);
+                if (outFile != null) {
+                    outFile = new File(outFile, path);
+                }
+                // outFile is null here if we couldn't get access to external storage,
+                // in which case restoreFile() will discard the data cleanly and let
+                // us proceed with the next file segment in the stream.  We pass -1
+                // for the file mode to suppress attempts to chmod() on shared storage.
+                FullBackup.restoreFile(data, fileSize, type, -1, mtime, outFile);
+            } catch (IOException e) {
+                Log.i(TAG, "Exception restoring OBB " + path, e);
+            } finally {
+                try {
+                    callbackBinder.opComplete(token);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        ArrayList<File> allFileContents(File rootDir) {
+            final ArrayList<File> files = new ArrayList<File>();
+            final ArrayList<File> dirs = new ArrayList<File>();
+
+            dirs.add(rootDir);
+            while (!dirs.isEmpty()) {
+                File dir = dirs.remove(0);
+                File[] contents = dir.listFiles();
+                if (contents != null) {
+                    for (File f : contents) {
+                        if (f.isDirectory()) dirs.add(f);
+                        else if (f.isFile()) files.add(f);
+                    }
+                }
+            }
+            return files;
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mService.asBinder();
+    }
+}