Make the BackupHelperDispatcher properly handle multiple helpers.
diff --git a/core/java/android/app/BackupAgent.java b/core/java/android/app/BackupAgent.java
index e810775..0ac8a1e 100644
--- a/core/java/android/app/BackupAgent.java
+++ b/core/java/android/app/BackupAgent.java
@@ -67,7 +67,7 @@
      *                 here after writing the requested data to dataFd.
      */
     public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
-             ParcelFileDescriptor newState);
+             ParcelFileDescriptor newState) throws IOException;
     
     /**
      * The application is being restored from backup, and should replace any
@@ -120,6 +120,9 @@
             BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
             try {
                 BackupAgent.this.onBackup(oldState, output, newState);
+            } catch (IOException ex) {
+                Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+                throw new RuntimeException(ex);
             } catch (RuntimeException ex) {
                 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
                 throw ex;
diff --git a/core/java/android/backup/BackupHelperAgent.java b/core/java/android/backup/BackupHelperAgent.java
index 3720d50..5d0c4a2 100644
--- a/core/java/android/backup/BackupHelperAgent.java
+++ b/core/java/android/backup/BackupHelperAgent.java
@@ -34,7 +34,7 @@
 
     @Override
     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
-             ParcelFileDescriptor newState) {
+             ParcelFileDescriptor newState) throws IOException {
         mDispatcher.performBackup(oldState, data, newState);
     }
 
diff --git a/core/java/android/backup/BackupHelperDispatcher.java b/core/java/android/backup/BackupHelperDispatcher.java
index b25c3e3..6ccb83e 100644
--- a/core/java/android/backup/BackupHelperDispatcher.java
+++ b/core/java/android/backup/BackupHelperDispatcher.java
@@ -19,7 +19,10 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.IOException;
+import java.io.FileDescriptor;
 import java.util.TreeMap;
 import java.util.Map;
 
@@ -27,6 +30,11 @@
 public class BackupHelperDispatcher {
     private static final String TAG = "BackupHelperDispatcher";
 
+    private static class Header {
+        int chunkSize; // not including the header
+        String keyPrefix;
+    }
+
     TreeMap<String,BackupHelper> mHelpers = new TreeMap<String,BackupHelper>();
     
     public BackupHelperDispatcher() {
@@ -36,13 +44,63 @@
         mHelpers.put(keyPrefix, helper);
     }
 
-    /** TODO: Make this save and restore the key prefix. */
     public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
-             ParcelFileDescriptor newState) {
-        // Write out the state files -- mHelpers is a TreeMap, so the order is well defined.
-        for (Map.Entry<String,BackupHelper> entry: mHelpers.entrySet()) {
-            data.setKeyPrefix(entry.getKey());
-            entry.getValue().performBackup(oldState, data, newState);
+             ParcelFileDescriptor newState) throws IOException {
+        // First, do the helpers that we've already done, since they're already in the state
+        // file.
+        int err;
+        Header header = new Header();
+        TreeMap<String,BackupHelper> helpers = (TreeMap<String,BackupHelper>)mHelpers.clone();
+        FileDescriptor oldStateFD = null;
+        FileDescriptor newStateFD = newState.getFileDescriptor();
+
+        if (oldState != null) {
+            oldStateFD = oldState.getFileDescriptor();
+            while ((err = readHeader_native(header, oldStateFD)) >= 0) {
+                if (err == 0) {
+                    BackupHelper helper = helpers.get(header.keyPrefix);
+                    Log.d(TAG, "handling existing helper '" + header.keyPrefix + "' " + helper);
+                    if (helper != null) {
+                        doOneBackup(oldState, data, newState, header, helper);
+                        helpers.remove(header.keyPrefix);
+                    } else {
+                        skipChunk_native(oldStateFD, header.chunkSize);
+                    }
+                }
+            }
+        }
+
+        // Then go through and do the rest that we haven't done.
+        for (Map.Entry<String,BackupHelper> entry: helpers.entrySet()) {
+            header.keyPrefix = entry.getKey();
+            Log.d(TAG, "handling new helper '" + header.keyPrefix + "'");
+            BackupHelper helper = entry.getValue();
+            doOneBackup(oldState, data, newState, header, helper);
+        }
+    }
+
+    private void doOneBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+            ParcelFileDescriptor newState, Header header, BackupHelper helper) 
+            throws IOException {
+        int err;
+        FileDescriptor newStateFD = newState.getFileDescriptor();
+
+        // allocate space for the header in the file
+        int pos = allocateHeader_native(header, newStateFD);
+        if (pos < 0) {
+            throw new IOException("allocateHeader_native failed (error " + pos + ")");
+        }
+
+        data.setKeyPrefix(header.keyPrefix);
+
+        // do the backup
+        helper.performBackup(oldState, data, newState);
+
+        // fill in the header (seeking back to pos).  The file pointer will be returned to
+        // where it was at the end of performBackup.  Header.chunkSize will not be filled in.
+        err = writeHeader_native(header, newStateFD, pos);
+        if (err != 0) {
+            throw new IOException("writeHeader_native failed (error " + err + ")");
         }
     }
 
@@ -83,5 +141,11 @@
             helper.writeRestoreSnapshot(newState);
         }
     }
+
+    private static native int readHeader_native(Header h, FileDescriptor fd);
+    private static native int skipChunk_native(FileDescriptor fd, int bytesToSkip);
+
+    private static native int allocateHeader_native(Header h, FileDescriptor fd);
+    private static native int writeHeader_native(Header h, FileDescriptor fd, int pos);
 }
 
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index b328869..888cb11 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -120,7 +120,8 @@
 	com_android_internal_graphics_NativeUtils.cpp \
 	android_backup_BackupDataInput.cpp \
 	android_backup_BackupDataOutput.cpp \
-	android_backup_FileBackupHelperBase.cpp
+	android_backup_FileBackupHelperBase.cpp \
+	android_backup_BackupHelperDispatcher.cpp
 
 LOCAL_C_INCLUDES += \
 	$(JNI_H_INCLUDE) \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 7350348..c815301 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -158,6 +158,7 @@
 extern int register_android_backup_BackupDataInput(JNIEnv *env);
 extern int register_android_backup_BackupDataOutput(JNIEnv *env);
 extern int register_android_backup_FileBackupHelperBase(JNIEnv *env);
+extern int register_android_backup_BackupHelperDispatcher(JNIEnv *env);
 
 static AndroidRuntime* gCurRuntime = NULL;
 
@@ -1131,6 +1132,7 @@
     REG_JNI(register_android_backup_BackupDataInput),
     REG_JNI(register_android_backup_BackupDataOutput),
     REG_JNI(register_android_backup_FileBackupHelperBase),
+    REG_JNI(register_android_backup_BackupHelperDispatcher),
 };
 
 /*
diff --git a/core/jni/android_backup_BackupHelperDispatcher.cpp b/core/jni/android_backup_BackupHelperDispatcher.cpp
new file mode 100644
index 0000000..24d529b
--- /dev/null
+++ b/core/jni/android_backup_BackupHelperDispatcher.cpp
@@ -0,0 +1,251 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "BackupHelperDispatcher_native"
+#include <utils/Log.h>
+
+#include "JNIHelp.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+
+#define VERSION_1_HEADER 0x01706c48  // 'Hlp'1 little endian
+
+namespace android
+{
+
+struct chunk_header_v1 {
+    int headerSize;
+    int version;
+    int dataSize; // corresponds to Header.chunkSize
+    int nameLength; // not including the NULL terminator, which is not written to the file
+};
+
+// java.io.FileDescriptor
+static jfieldID s_descriptorField = 0;
+static jfieldID s_chunkSizeField = 0;
+static jfieldID s_keyPrefixField = 0;
+
+static int
+readHeader_native(JNIEnv* env, jobject clazz, jobject headerObj, jobject fdObj)
+{
+    chunk_header_v1 flattenedHeader;
+    int fd;
+    ssize_t amt;
+    String8 keyPrefix;
+    char* buf;
+
+    fd = env->GetIntField(fdObj, s_descriptorField);
+
+    amt = read(fd, &flattenedHeader.headerSize, sizeof(flattenedHeader.headerSize));
+    if (amt != sizeof(flattenedHeader.headerSize)) {
+        return -1;
+    }
+
+    int remainingHeader = flattenedHeader.headerSize - sizeof(flattenedHeader.headerSize);
+
+    if (flattenedHeader.headerSize < (int)sizeof(chunk_header_v1)) {
+        LOGW("Skipping unknown header: %d bytes", flattenedHeader.headerSize);
+        if (remainingHeader > 0) {
+            lseek(fd, remainingHeader, SEEK_CUR);
+            // >0 means skip this chunk
+            return 1;
+        }
+    }
+
+    amt = read(fd, &flattenedHeader.version,
+            sizeof(chunk_header_v1)-sizeof(flattenedHeader.headerSize));
+    if (amt <= 0) {
+        LOGW("Failed reading chunk header");
+        return -1;
+    }
+    remainingHeader -= sizeof(chunk_header_v1)-sizeof(flattenedHeader.headerSize);
+
+    if (flattenedHeader.version != VERSION_1_HEADER) {
+        LOGW("Skipping unknown header version: 0x%08x, %d bytes", flattenedHeader.version,
+                flattenedHeader.headerSize);
+        if (remainingHeader > 0) {
+            lseek(fd, remainingHeader, SEEK_CUR);
+            // >0 means skip this chunk
+            return 1;
+        }
+    }
+
+    if (flattenedHeader.dataSize < 0 || flattenedHeader.nameLength < 0 ||
+            remainingHeader < flattenedHeader.nameLength) {
+        LOGW("Malformed V1 header remainingHeader=%d dataSize=%d nameLength=%d", remainingHeader,
+                flattenedHeader.dataSize, flattenedHeader.nameLength);
+        return -1;
+    }
+
+    buf = keyPrefix.lockBuffer(flattenedHeader.nameLength);
+    if (buf == NULL) {
+        LOGW("unable to allocate %d bytes", flattenedHeader.nameLength);
+        return -1;
+    }
+
+    amt = read(fd, buf, flattenedHeader.nameLength);
+
+    keyPrefix.unlockBuffer(flattenedHeader.nameLength);
+
+    remainingHeader -= flattenedHeader.nameLength;
+
+    LOGD("remainingHeader=%d", remainingHeader);
+
+    if (remainingHeader > 0) {
+        lseek(fd, remainingHeader, SEEK_CUR);
+    }
+
+    env->SetIntField(headerObj, s_chunkSizeField, flattenedHeader.dataSize);
+    env->SetObjectField(headerObj, s_keyPrefixField, env->NewStringUTF(keyPrefix.string()));
+
+    return 0;
+}
+
+static int
+skipChunk_native(JNIEnv* env, jobject clazz, jobject fdObj, jint bytesToSkip)
+{
+    int fd;
+
+    fd = env->GetIntField(fdObj, s_descriptorField);
+
+    lseek(fd, bytesToSkip, SEEK_CUR);
+
+    return 0;
+}
+
+static int
+padding_len(int len)
+{
+    len = len % 4;
+    return len == 0 ? len : 4 - len;
+}
+
+static int
+allocateHeader_native(JNIEnv* env, jobject clazz, jobject headerObj, jobject fdObj)
+{
+    int pos;
+    jstring nameObj;
+    int nameLength;
+    int namePadding;
+    int headerSize;
+    int fd;
+
+    fd = env->GetIntField(fdObj, s_descriptorField);
+
+    nameObj = (jstring)env->GetObjectField(headerObj, s_keyPrefixField);
+
+    nameLength = env->GetStringUTFLength(nameObj);
+    namePadding = padding_len(nameLength);
+
+    headerSize = sizeof(chunk_header_v1) + nameLength + namePadding;
+
+    pos = lseek(fd, 0, SEEK_CUR);
+
+    lseek(fd, headerSize, SEEK_CUR);
+    
+    return pos;
+}
+
+static int
+writeHeader_native(JNIEnv* env, jobject clazz, jobject headerObj, jobject fdObj, jint pos)
+{
+    int err;
+    chunk_header_v1 header;
+    int fd;
+    int namePadding;
+    int prevPos;
+    jstring nameObj;
+    const char* buf;
+
+    fd = env->GetIntField(fdObj, s_descriptorField);
+    prevPos = lseek(fd, 0, SEEK_CUR);
+
+    nameObj = (jstring)env->GetObjectField(headerObj, s_keyPrefixField);
+    header.nameLength = env->GetStringUTFLength(nameObj);
+    namePadding = padding_len(header.nameLength);
+
+    header.headerSize = sizeof(chunk_header_v1) + header.nameLength + namePadding;
+    header.version = VERSION_1_HEADER;
+
+    lseek(fd, pos, SEEK_SET);
+    err = write(fd, &header, sizeof(chunk_header_v1));
+    if (err != sizeof(chunk_header_v1)) {
+        return errno;
+    }
+
+    buf = env->GetStringUTFChars(nameObj, NULL);
+    err = write(fd, buf, header.nameLength);
+    env->ReleaseStringUTFChars(nameObj, buf);
+    if (err != header.nameLength) {
+        return errno;
+    }
+
+    if (namePadding != 0) {
+        int zero = 0;
+        err = write(fd, &zero, namePadding);
+        if (err != namePadding) {
+            return errno;
+        }
+    }
+
+    lseek(fd, prevPos, SEEK_SET);
+    return 0;
+}
+
+static const JNINativeMethod g_methods[] = {
+    { "readHeader_native",
+       "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;)I",
+       (void*)readHeader_native },
+    { "skipChunk_native",
+        "(Ljava/io/FileDescriptor;I)I",
+        (void*)skipChunk_native },
+    { "allocateHeader_native",
+        "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;)I",
+        (void*)allocateHeader_native },
+    { "writeHeader_native",
+       "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;I)I",
+       (void*)writeHeader_native },
+};
+
+int register_android_backup_BackupHelperDispatcher(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");
+    
+    clazz = env->FindClass("android/backup/BackupHelperDispatcher$Header");
+    LOG_FATAL_IF(clazz == NULL,
+            "Unable to find class android.backup.BackupHelperDispatcher.Header");
+    s_chunkSizeField = env->GetFieldID(clazz, "chunkSize", "I");
+    LOG_FATAL_IF(s_chunkSizeField == NULL,
+            "Unable to find chunkSize field in android.backup.BackupHelperDispatcher.Header");
+    s_keyPrefixField = env->GetFieldID(clazz, "keyPrefix", "Ljava/lang/String;");
+    LOG_FATAL_IF(s_keyPrefixField == NULL,
+            "Unable to find keyPrefix field in android.backup.BackupHelperDispatcher.Header");
+    
+    return AndroidRuntime::registerNativeMethods(env, "android/backup/BackupHelperDispatcher",
+            g_methods, NELEM(g_methods));
+}
+
+}
diff --git a/tests/backup/src/com/android/backuptest/BackupTestAgent.java b/tests/backup/src/com/android/backuptest/BackupTestAgent.java
index c6acc66..0fdfb18 100644
--- a/tests/backup/src/com/android/backuptest/BackupTestAgent.java
+++ b/tests/backup/src/com/android/backuptest/BackupTestAgent.java
@@ -23,6 +23,7 @@
 {
     public void onCreate() {
         addHelper("data_files", new FileBackupHelper(this, BackupTestActivity.FILE_NAME));
+        addHelper("more_data_files", new FileBackupHelper(this, "another_file.txt"));
     }
 }