More backup tests
diff --git a/api/current.xml b/api/current.xml
index c222d42..39e9b4d 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -21795,6 +21795,184 @@
 </field>
 </class>
 </package>
+<package name="android.backup"
+>
+<class name="BackupDataOutput"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="BackupDataOutput"
+ type="android.backup.BackupDataOutput"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="fd" type="java.io.FileDescriptor">
+</parameter>
+</constructor>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="flush"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="write"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="buffer" type="byte[]">
+</parameter>
+</method>
+<method name="write"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="oneByte" type="int">
+</parameter>
+</method>
+<method name="write"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="buffer" type="byte[]">
+</parameter>
+<parameter name="offset" type="int">
+</parameter>
+<parameter name="count" type="int">
+</parameter>
+</method>
+<method name="writeKey"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="key" type="java.lang.String">
+</parameter>
+</method>
+</class>
+<class name="FileBackupHelper"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="FileBackupHelper"
+ type="android.backup.FileBackupHelper"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="performBackup"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="oldSnapshot" type="android.os.ParcelFileDescriptor">
+</parameter>
+<parameter name="newSnapshot" type="android.os.ParcelFileDescriptor">
+</parameter>
+<parameter name="data" type="android.backup.BackupDataOutput">
+</parameter>
+<parameter name="files" type="java.lang.String[]">
+</parameter>
+</method>
+</class>
+<class name="SharedPreferencesBackupHelper"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SharedPreferencesBackupHelper"
+ type="android.backup.SharedPreferencesBackupHelper"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="performBackup"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="oldSnapshot" type="android.os.ParcelFileDescriptor">
+</parameter>
+<parameter name="newSnapshot" type="android.os.ParcelFileDescriptor">
+</parameter>
+<parameter name="data" type="android.backup.BackupDataOutput">
+</parameter>
+<parameter name="prefGroups" type="java.lang.String[]">
+</parameter>
+</method>
+</class>
+</package>
 <package name="android.content"
 >
 <class name="ActivityNotFoundException"
diff --git a/core/java/android/backup/BackupDataOutput.java b/core/java/android/backup/BackupDataOutput.java
new file mode 100644
index 0000000..6c47f7e
--- /dev/null
+++ b/core/java/android/backup/BackupDataOutput.java
@@ -0,0 +1,44 @@
+/*
+ * 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.backup;
+
+import android.content.Context;
+
+import java.io.FileDescriptor;
+
+public class BackupDataOutput {
+    /* package */ FileDescriptor fd;
+
+    public static final int OP_UPDATE = 1;
+    public static final int OP_DELETE = 2;
+
+    public BackupDataOutput(Context context, FileDescriptor fd) {
+        this.fd = fd;
+    }
+
+    public void close() {
+        // do we close the fd?
+    }
+    public native void flush();
+    public native void write(byte[] buffer);
+    public native void write(int oneByte);
+    public native void write(byte[] buffer, int offset, int count);
+
+    public native void writeOperation(int op);
+    public native void writeKey(String key);
+}
+
diff --git a/core/java/android/backup/FileBackupHelper.java b/core/java/android/backup/FileBackupHelper.java
new file mode 100644
index 0000000..3b2122c
--- /dev/null
+++ b/core/java/android/backup/FileBackupHelper.java
@@ -0,0 +1,68 @@
+/*
+ * 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.backup;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileDescriptor;
+
+public class FileBackupHelper {
+    /**
+     * Based on oldSnapshot, determine which of the files from the application's data directory
+     * need to be backed up, write them to the data stream, and fill in newSnapshot with the
+     * state as it exists now.
+     */
+    public static void performBackup(Context context,
+            ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot,
+            BackupDataOutput data, String[] files) {
+        String basePath = context.getFilesDir().getAbsolutePath();
+        performBackup_checked(basePath, oldSnapshot, newSnapshot, data, files);
+    }
+
+    /**
+     * Check the parameters so the native code doens't have to throw all the exceptions
+     * since it's easier to do that from java.
+     */
+    static void performBackup_checked(String basePath,
+            ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot,
+            BackupDataOutput data, String[] files) {
+        if (newSnapshot == null) {
+            throw new NullPointerException("newSnapshot==null");
+        }
+        if (data == null) {
+            throw new NullPointerException("data==null");
+        }
+        if (data.fd == null) {
+            throw new NullPointerException("data.fd==null");
+        }
+        if (files == null) {
+            throw new NullPointerException("files==null");
+        }
+
+        int err = performBackup_native(basePath, oldSnapshot.getFileDescriptor(),
+                newSnapshot.getFileDescriptor(), data.fd, files);
+
+        if (err != 0) {
+            throw new RuntimeException("Backup failed"); // TODO: more here
+        }
+    }
+
+    native private static int performBackup_native(String basePath,
+            FileDescriptor oldSnapshot, FileDescriptor newSnapshot,
+            FileDescriptor data, String[] files);
+}
diff --git a/core/java/android/backup/SharedPreferencesBackupHelper.java b/core/java/android/backup/SharedPreferencesBackupHelper.java
new file mode 100644
index 0000000..e839bb4
--- /dev/null
+++ b/core/java/android/backup/SharedPreferencesBackupHelper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.backup;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileDescriptor;
+
+public class SharedPreferencesBackupHelper {
+    public static void performBackup(Context context,
+            ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot,
+            BackupDataOutput data, String[] prefGroups) {
+        String basePath = "/xxx"; //context.getPreferencesDir();
+
+        // make filenames for the prefGroups
+        final int N = prefGroups.length;
+        String[] files = new String[N];
+        for (int i=0; i<N; i++) {
+            files[i] = prefGroups[i] + ".xml";
+        }
+
+        FileBackupHelper.performBackup_checked(basePath, oldSnapshot, newSnapshot, data, files);
+    }
+}
+
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ac35459..4839b6f 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -116,7 +116,8 @@
 	android_ddm_DdmHandleNativeHeap.cpp \
 	android_location_GpsLocationProvider.cpp \
 	com_android_internal_os_ZygoteInit.cpp \
-	com_android_internal_graphics_NativeUtils.cpp
+	com_android_internal_graphics_NativeUtils.cpp \
+	android_backup_FileBackupHelper.cpp
 
 LOCAL_C_INCLUDES += \
 	$(JNI_H_INCLUDE) \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 7c9f457..aa6450d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -155,6 +155,7 @@
 extern int register_com_android_internal_os_ZygoteInit(JNIEnv* env);
 extern int register_android_util_Base64(JNIEnv* env);
 extern int register_android_location_GpsLocationProvider(JNIEnv* env);
+extern int register_android_backup_FileBackupHelper(JNIEnv *env);
 
 static AndroidRuntime* gCurRuntime = NULL;
 
@@ -1125,6 +1126,7 @@
     REG_JNI(register_android_ddm_DdmHandleNativeHeap),
     REG_JNI(register_android_util_Base64),
     REG_JNI(register_android_location_GpsLocationProvider),
+    REG_JNI(register_android_backup_FileBackupHelper),
 };
 
 /*
diff --git a/core/jni/android_backup_FileBackupHelper.cpp b/core/jni/android_backup_FileBackupHelper.cpp
new file mode 100644
index 0000000..e8d60a0
--- /dev/null
+++ b/core/jni/android_backup_FileBackupHelper.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#include "JNIHelp.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include <utils/backup_helpers.h>
+
+namespace android
+{
+
+static jfieldID s_descriptorField;
+
+static int
+performBackup_native(JNIEnv* env, jstring basePath,
+            jobject oldSnapshot, jobject newSnapshot,
+            jobject data, jobjectArray files)
+{
+    int err;
+
+    // all parameters have already been checked against null
+
+    int oldSnapshotFD = env->GetIntField(oldSnapshot, s_descriptorField);
+    int newSnapshotFD = env->GetIntField(newSnapshot, s_descriptorField);
+    int dataFD = env->GetIntField(data, s_descriptorField);
+
+    char const* basePathUTF = env->GetStringUTFChars(basePath, NULL);
+    const int fileCount = env->GetArrayLength(files);
+    char const** filesUTF = (char const**)malloc(sizeof(char*)*fileCount);
+    for (int i=0; i<fileCount; i++) {
+        filesUTF[i] = env->GetStringUTFChars((jstring)env->GetObjectArrayElement(files, i), NULL);
+    }
+
+    err = back_up_files(oldSnapshotFD, newSnapshotFD, dataFD, basePathUTF, filesUTF, fileCount);
+
+    for (int i=0; i<fileCount; i++) {
+        env->ReleaseStringUTFChars((jstring)env->GetObjectArrayElement(files, i), filesUTF[i]);
+    }
+    free(filesUTF);
+    env->ReleaseStringUTFChars(basePath, basePathUTF);
+
+    return err;
+}
+
+static const JNINativeMethod g_methods[] = {
+    { "performBackup_native",
+        "(Ljava/lang/String;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;"
+        "Ljava/io/FileDescriptor;[Ljava/lang/String;)I",
+        (void*)performBackup_native },
+};
+
+int register_android_backup_FileBackupHelper(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass("java/io/FileDescriptor");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");
+    s_descriptorField = env->GetFieldID(clazz, "descriptor", "I");
+    LOG_FATAL_IF(s_descriptorField == NULL,
+            "Unable to find descriptor field in java.io.FileDescriptor");
+    
+    return AndroidRuntime::registerNativeMethods(env, "android/backup/FileBackupHelper",
+            g_methods, NELEM(g_methods));
+}
+
+}
diff --git a/core/jni/android_os_ParcelFileDescriptor.cpp b/core/jni/android_os_ParcelFileDescriptor.cpp
index 971f87c..848a57a 100644
--- a/core/jni/android_os_ParcelFileDescriptor.cpp
+++ b/core/jni/android_os_ParcelFileDescriptor.cpp
@@ -1,19 +1,18 @@
-/* //device/libs/android_runtime/android_os_ParcelFileDescriptor.cpp
-**
-** Copyright 2008, 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.
-*/
+/*
+ * Copyright (C) 2008 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_NDEBUG 0
 
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 63fc871..e582fb15 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -42,7 +42,9 @@
 import android.backup.IBackupManager;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
+import java.io.PrintWriter;
 import java.lang.String;
 import java.util.HashSet;
 import java.util.List;
@@ -51,7 +53,8 @@
     private static final String TAG = "BackupManagerService";
     private static final boolean DEBUG = true;
     
-    private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
+    private static final long COLLECTION_INTERVAL = 1000;
+    //private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
 
     private static final int MSG_RUN_BACKUP = 1;
     
@@ -338,8 +341,11 @@
         // Record that we need a backup pass for the caller.  Since multiple callers
         // may share a uid, we need to note all candidates within that uid and schedule
         // a backup pass for each of them.
+
+        Log.d(TAG, "dataChanged packageName=" + packageName);
         
         HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
+        Log.d(TAG, "targets=" + targets);
         if (targets != null) {
             synchronized (mQueueLock) {
                 // Note that this client has made data changes that need to be backed up
@@ -354,6 +360,7 @@
                     }
                 }
 
+                Log.d(TAG, "Scheduling backup for " + mPendingBackups.size() + " participants");
                 // Schedule a backup pass in a few minutes.  As backup-eligible data
                 // keeps changing, continue to defer the backup pass until things
                 // settle down, to avoid extra overhead.
@@ -380,4 +387,23 @@
             }
         }
     }
+
+    
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        synchronized (mQueueLock) {
+            int N = mBackupParticipants.size();
+            pw.println("Participants:");
+            for (int i=0; i<N; i++) {
+                int uid = mBackupParticipants.keyAt(i);
+                pw.print("  uid: ");
+                pw.println(uid);
+                HashSet<ServiceInfo> services = mBackupParticipants.valueAt(i);
+                for (ServiceInfo s: services) {
+                    pw.print("    ");
+                    pw.println(s.toString());
+                }
+            }
+        }
+    }
 }
diff --git a/tests/backup/Android.mk b/tests/backup/Android.mk
index 35c05df..2e3385f8 100644
--- a/tests/backup/Android.mk
+++ b/tests/backup/Android.mk
@@ -21,7 +21,7 @@
 LOCAL_SRC_FILES := \
     backup_helper_test.cpp
  
-LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_TAGS := user
 LOCAL_MODULE := backup_helper_test
 LOCAL_SHARED_LIBRARIES := libutils
 
@@ -31,7 +31,7 @@
 # ========================================
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_TAGS := user
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
diff --git a/tests/backup/AndroidManifest.xml b/tests/backup/AndroidManifest.xml
index c26078b..eaeb5b7 100644
--- a/tests/backup/AndroidManifest.xml
+++ b/tests/backup/AndroidManifest.xml
@@ -10,7 +10,7 @@
         </activity>
         <service android:name="BackupTestService">
            <intent-filter>
-               <action android:name="android.backup.BackupService" />
+               <action android:name="android.backup.BackupService.SERVICE" />
            </intent-filter>
         </service>
     </application>
diff --git a/tests/backup/src/com/android/backuptest/BackupTestActivity.java b/tests/backup/src/com/android/backuptest/BackupTestActivity.java
index 31aec39..de68cb7 100644
--- a/tests/backup/src/com/android/backuptest/BackupTestActivity.java
+++ b/tests/backup/src/com/android/backuptest/BackupTestActivity.java
@@ -31,14 +31,58 @@
 import android.widget.ListView;
 import android.widget.Toast;
 
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.text.DateFormat;
+import java.util.Date;
+
 public class BackupTestActivity extends ListActivity
 {
     static final String TAG = "BackupTestActivity";
 
     static final String PREF_GROUP_SETTINGS = "settings";
     static final String PREF_KEY = "pref";
+    static final String FILE_NAME = "file.txt";
 
     Test[] mTests = new Test[] {
+        new Test("Show File") {
+            void run() {
+                StringBuffer str = new StringBuffer();
+                str.append("Text is:");
+                BufferedReader reader = null;
+                try {
+                    reader = new BufferedReader(new InputStreamReader(openFileInput(FILE_NAME)));
+                    while (reader.ready()) {
+                        str.append("\n");
+                        str.append(reader.readLine());
+                    }
+                } catch (IOException ex) {
+                    str.append("ERROR: ");
+                    str.append(ex.toString());
+                }
+                Log.d(TAG, str.toString());
+                Toast.makeText(BackupTestActivity.this, str, Toast.LENGTH_SHORT).show();
+            }
+        },
+        new Test("Append to File") {
+            void run() {
+                PrintStream output = null;
+                try {
+                    output = new PrintStream(openFileOutput(FILE_NAME, MODE_APPEND));
+                    DateFormat formatter = DateFormat.getDateTimeInstance();
+                    output.println(formatter.format(new Date()));
+                    output.close();
+                } catch (IOException ex) {
+                    if (output != null) {
+                        output.close();
+                    }
+                }
+                BackupManager bm = new BackupManager(BackupTestActivity.this);
+                bm.dataChanged();
+            }
+        },
         new Test("Show Shared Pref") {
             void run() {
                 SharedPreferences prefs = getSharedPreferences(PREF_GROUP_SETTINGS, MODE_PRIVATE);