Backup/Restore sample application

A very simple application: one activity, with a few bits of persistent data that
are updated live by the activity.  To that are added a few alternative agent
implementations, to illustrate various approaches and tradeoffs when implementing
a backup/restore agent.  In particular, there are example agents that do everything
"by hand," either record-by-record or by directly backing up the app's entire
persistent data file; plus an example agent that illustrates how very easy it
is to implement the latter using the OS-provided helpers.

Part of bug #2545514

Change-Id: Iaca33a5113406360c23d6e2e59eb012f9f1e9400
diff --git a/samples/BackupRestore/Android.mk b/samples/BackupRestore/Android.mk
new file mode 100644
index 0000000..c164a6c
--- /dev/null
+++ b/samples/BackupRestore/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := BackupRestore
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/samples/BackupRestore/AndroidManifest.xml b/samples/BackupRestore/AndroidManifest.xml
new file mode 100644
index 0000000..8c37720
--- /dev/null
+++ b/samples/BackupRestore/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<!-- Declare the contents of this Android application.  The namespace
+     attribute brings in the Android platform namespace, and the package
+     supplies a unique name for the application.  When writing your
+     own application, the package name must be changed from "com.example.*"
+     to come from a domain that you own or have control over. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.backuprestore"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <!-- The backup/restore mechanism was introduced in API version 8 -->
+    <uses-sdk android:minSdkVersion="8"/>
+
+    <application android:label="Backup/Restore"
+        android:backupAgent="ExampleAgent">
+
+      <activity android:name="BackupRestoreActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+</manifest>
diff --git a/samples/BackupRestore/res/layout/backup_restore.xml b/samples/BackupRestore/res/layout/backup_restore.xml
new file mode 100644
index 0000000..1459d42
--- /dev/null
+++ b/samples/BackupRestore/res/layout/backup_restore.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<!-- Layout description of the BackupRestore sample's main activity -->
+
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <TextView android:text="@string/filling_text"
+            android:textSize="20dp"
+            android:layout_marginTop="20dp"
+            android:layout_marginBottom="10dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+
+        <RadioGroup android:id="@+id/filling_group"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="20dp"
+            android:orientation="vertical">
+
+            <RadioButton android:id="@+id/bacon"
+                android:text="@string/bacon_label"/>
+            <RadioButton android:id="@+id/pastrami"
+                android:text="@string/pastrami_label"/>
+            <RadioButton android:id="@+id/hummus"
+                android:text="@string/hummus_label"/>
+
+        </RadioGroup>
+
+        <TextView android:text="@string/extras_text"
+            android:textSize="20dp"
+            android:layout_marginTop="20dp"
+            android:layout_marginBottom="10dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+
+        <CheckBox android:id="@+id/mayo"
+            android:text="@string/mayo_text"
+            android:layout_marginLeft="20dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+
+        <CheckBox android:id="@+id/tomato"
+            android:text="@string/tomato_text"
+            android:layout_marginLeft="20dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/samples/BackupRestore/res/values/strings.xml b/samples/BackupRestore/res/values/strings.xml
new file mode 100644
index 0000000..64cd7a2
--- /dev/null
+++ b/samples/BackupRestore/res/values/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<resources>
+  <string name="filling_text">Choose a sandwich filling:</string>
+  <string name="bacon_label">Bacon</string>
+  <string name="pastrami_label">Pastrami</string>
+  <string name="hummus_label">Hummus</string>
+
+  <string name="extras_text">Extras:</string>
+  <string name="mayo_text">Mayonnaise\?</string>
+  <string name="tomato_text">Tomato\?</string>
+</resources>
diff --git a/samples/BackupRestore/src/com/example/android/backuprestore/BackupRestoreActivity.java b/samples/BackupRestore/src/com/example/android/backuprestore/BackupRestoreActivity.java
new file mode 100644
index 0000000..01c10ae
--- /dev/null
+++ b/samples/BackupRestore/src/com/example/android/backuprestore/BackupRestoreActivity.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2010 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.example.android.backuprestore;
+
+import android.app.Activity;
+import android.app.backup.BackupManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.RadioGroup;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * This example is intended to demonstrate a few approaches that an Android
+ * application developer can take when implementing a
+ * {@link android.app.backup.BackupAgent BackupAgent}.  This feature, added
+ * to the Android platform with API version 8, allows the application to
+ * back up its data to a device-provided storage location, transparently to
+ * the user.  If the application is uninstalled and then reinstalled, or if
+ * the user starts using a new Android device, the backed-up information
+ * can be provided automatically when the application is reinstalled.
+ *
+ * <p>Participating in the backup/restore mechanism is simple.  The application
+ * provides a class that extends {@link android.app.backup.BackupAgent}, and
+ * overrides the two core callback methods
+ * {@link android.app.backup.BackupAgent#onBackup(android.os.ParcelFileDescriptor, android.app.backup.BackupDataOutput, android.os.ParcelFileDescriptor) onBackup()}
+ * and
+ * {@link android.app.backup.BackupAgent#onRestore(android.app.backup.BackupDataInput, int, android.os.ParcelFileDescriptor) onRestore()}.
+ * It also publishes the agent class to the operating system by naming the class
+ * with the <code>android:backupAgent</code> attribute of the
+ * <code>&lt;application&gt;</code> tag in the application's manifest.
+ * When a backup or restore operation is performed, the application's agent class
+ * is instantiated within the application's execution context and the corresponding
+ * method invoked.  Please see the documentation on the
+ * {@link android.app.backup.BackupAgent BackupAgent} class for details about the
+ * data interchange between the agent and the backup mechanism.
+ *
+ * <p>This example application maintains a few pieces of simple data, and provides
+ * three different sample agent implementations, each illustrating an alternative
+ * approach.  The three sample agent classes are:
+ *
+ * <p><ol type="1">
+ * <li>{@link ExampleAgent} - this agent backs up the application's data in a single
+ *     record.  It illustrates the direct "by hand" processes of saving backup state for
+ *     future reference, sending data to the backup transport, and reading it from a restore
+ *     dataset.</li>
+ * <li>{@link FileHelperExampleAgent} - this agent takes advantage of the suite of
+ *     helper classes provided along with the core BackupAgent API.  By extending
+ *     {@link android.app.backup.BackupHelperAgent} and using the targeted
+ *     {link android.app.backup.FileBackupHelper FileBackupHelper} class, it achieves
+ *     the same result as {@link ExampleAgent} - backing up the application's saved
+ *     data file in a single chunk, and restoring it upon request -- in only a few lines
+ *     of code.</li>
+ * <li>{@link MultiRecordExampleAgent} - this agent stores each separate bit of data
+ *     managed by the UI in separate records within the backup dataset.  It illustrates
+ *     how an application's backup agent can do selective updates of only what information
+ *     has changed since the last backup.</li></ol>
+ *
+ * <p>You can build the application to use any of these agent implementations simply by
+ * changing the class name supplied in the <code>android:backupAgent</code> manifest
+ * attribute to indicate the agent you wish to use.  <strong>Note:</strong> the backed-up
+ * data and backup-state tracking of these agents are not compatible!  If you change which
+ * agent the application uses, you should also wipe the backup state associated with
+ * the application on your handset.  The 'bmgr' shell application on the device can
+ * do this; simply run the following command from your desktop computer while attached
+ * to the device via adb:
+ *
+ * <p><code>adb shell bmgr wipe com.example.android.backuprestore</code>
+ *
+ * <p>You can then install the new version of the application, and its next backup pass
+ * will start over from scratch with the new agent.
+ */
+public class BackupRestoreActivity extends Activity {
+    static final String TAG = "BRActivity";
+
+    /**
+     * We serialize access to our persistent data through a global static
+     * object.  This ensures that in the unlikely event of the our backup/restore
+     * agent running to perform a backup while our UI is updating the file, the
+     * agent will not accidentally read partially-written data.
+     *
+     * <p>Curious but true: a zero-length array is slightly lighter-weight than
+     * merely allocating an Object, and can still be synchronized on.
+     */
+    static final Object[] sDataLock = new Object[0];
+
+    /** Also supply a global standard file name for everyone to use */
+    static final String DATA_FILE_NAME = "saved_data";
+
+    /** The various bits of UI that the user can manipulate */
+    RadioGroup mFillingGroup;
+    CheckBox mAddMayoCheckbox;
+    CheckBox mAddTomatoCheckbox;
+
+    /** Cache a reference to our persistent data file */
+    File mDataFile;
+
+    /** Also cache a reference to the Backup Manager */
+    BackupManager mBackupManager;
+
+    /** Set up the activity and populate its UI from the persistent data. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        /** Establish the activity's UI */
+        setContentView(R.layout.backup_restore);
+
+        /** Once the UI has been inflated, cache the controls for later */
+        mFillingGroup = (RadioGroup) findViewById(R.id.filling_group);
+        mAddMayoCheckbox = (CheckBox) findViewById(R.id.mayo);
+        mAddTomatoCheckbox = (CheckBox) findViewById(R.id.tomato);
+
+        /** Set up our file bookkeeping */
+        mDataFile = new File(getFilesDir(), BackupRestoreActivity.DATA_FILE_NAME);
+
+        /** It is handy to keep a BackupManager cached */
+        mBackupManager = new BackupManager(this);
+
+        /**
+         * Finally, build the UI from the persistent store
+         */
+        populateUI();
+    }
+
+    /**
+     * Configure the UI based on our persistent data, creating the
+     * data file and establishing defaults if necessary.
+     */
+    void populateUI() {
+        RandomAccessFile file;
+
+        // Default values in case there's no data file yet
+        int whichFilling = R.id.pastrami;
+        boolean addMayo = false;
+        boolean addTomato = false;
+
+        /** Hold the data-access lock around access to the file */
+        synchronized (BackupRestoreActivity.sDataLock) {
+            boolean exists = mDataFile.exists();
+            try {
+                file = new RandomAccessFile(mDataFile, "rw");
+                if (exists) {
+                    Log.v(TAG, "datafile exists");
+                    whichFilling = file.readInt();
+                    addMayo = file.readBoolean();
+                    addTomato = file.readBoolean();
+                    Log.v(TAG, "  mayo=" + addMayo
+                            + " tomato=" + addTomato
+                            + " filling=" + whichFilling);
+                } else {
+                    // The default values were configured above: write them
+                    // to the newly-created file.
+                    Log.v(TAG, "creating default datafile");
+                    writeDataToFileLocked(file,
+                            addMayo, addTomato, whichFilling);
+
+                    // We also need to perform an initial backup; ask for one
+                    mBackupManager.dataChanged();
+                }
+            } catch (IOException ioe) {
+                
+            }
+        }
+
+        /** Now that we've processed the file, build the UI outside the lock */
+        mFillingGroup.check(whichFilling);
+        mAddMayoCheckbox.setChecked(addMayo);
+        mAddTomatoCheckbox.setChecked(addTomato);
+
+        /**
+         * We also want to record the new state when the user makes changes,
+         * so install simple observers that do this
+         */
+        mFillingGroup.setOnCheckedChangeListener(
+                new RadioGroup.OnCheckedChangeListener() {
+                    public void onCheckedChanged(RadioGroup group,
+                            int checkedId) {
+                        // As with the checkbox listeners, rewrite the
+                        // entire state file
+                        Log.v(TAG, "New radio item selected: " + checkedId);
+                        recordNewUIState();
+                    }
+                });
+
+        CompoundButton.OnCheckedChangeListener checkListener
+                = new CompoundButton.OnCheckedChangeListener() {
+            public void onCheckedChanged(CompoundButton buttonView,
+                    boolean isChecked) {
+                // Whichever one is altered, we rewrite the entire UI state
+                Log.v(TAG, "Checkbox toggled: " + buttonView);
+                recordNewUIState();
+            }
+        };
+        mAddMayoCheckbox.setOnCheckedChangeListener(checkListener);
+        mAddTomatoCheckbox.setOnCheckedChangeListener(checkListener);
+    }
+
+    /**
+     * Handy helper routine to write the UI data to a file.
+     */
+    void writeDataToFileLocked(RandomAccessFile file,
+            boolean addMayo, boolean addTomato, int whichFilling)
+        throws IOException {
+            file.setLength(0L);
+            file.writeInt(whichFilling);
+            file.writeBoolean(addMayo);
+            file.writeBoolean(addTomato);
+            Log.v(TAG, "NEW STATE: mayo=" + addMayo
+                    + " tomato=" + addTomato
+                    + " filling=" + whichFilling);
+    }
+
+    /**
+     * Another helper; this one reads the current UI state and writes that
+     * to the persistent store, then tells the backup manager that we need
+     * a backup.
+     */
+    void recordNewUIState() {
+        boolean addMayo = mAddMayoCheckbox.isChecked();
+        boolean addTomato = mAddTomatoCheckbox.isChecked();
+        int whichFilling = mFillingGroup.getCheckedRadioButtonId();
+        try {
+            synchronized (BackupRestoreActivity.sDataLock) {
+                RandomAccessFile file = new RandomAccessFile(mDataFile, "rw");
+                writeDataToFileLocked(file, addMayo, addTomato, whichFilling);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to record new UI state");
+        }
+
+        mBackupManager.dataChanged();
+    }
+}
\ No newline at end of file
diff --git a/samples/BackupRestore/src/com/example/android/backuprestore/ExampleAgent.java b/samples/BackupRestore/src/com/example/android/backuprestore/ExampleAgent.java
new file mode 100644
index 0000000..3d93c1a
--- /dev/null
+++ b/samples/BackupRestore/src/com/example/android/backuprestore/ExampleAgent.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2010 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.example.android.backuprestore;
+
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.os.ParcelFileDescriptor;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * This is the backup/restore agent class for the BackupRestore sample
+ * application.  This particular agent illustrates using the backup and
+ * restore APIs directly, without taking advantage of any helper classes.
+ */
+public class ExampleAgent extends BackupAgent {
+    /**
+     * We put a simple version number into the state files so that we can
+     * tell properly how to read "old" versions if at some point we want
+     * to change what data we back up and how we store the state blob.
+     */
+    static final int AGENT_VERSION = 1;
+
+    /**
+     * Pick an arbitrary string to use as the "key" under which the
+     * data is backed up.  This key identifies different data records
+     * within this one application's data set.  Since we only maintain
+     * one piece of data we don't need to distinguish, so we just pick
+     * some arbitrary tag to use. 
+     */
+    static final String APP_DATA_KEY = "alldata";
+
+    /** The app's current data, read from the live disk file */
+    boolean mAddMayo;
+    boolean mAddTomato;
+    int mFilling;
+
+    /** The location of the application's persistent data file */
+    File mDataFile;
+
+    /** For convenience, we set up the File object for the app's data on creation */
+    @Override
+    public void onCreate() {
+        mDataFile = new File(getFilesDir(), BackupRestoreActivity.DATA_FILE_NAME);
+    }
+
+    /**
+     * The set of data backed up by this application is very small: just
+     * two booleans and an integer.  With such a simple dataset, it's
+     * easiest to simply store a copy of the backed-up data as the state
+     * blob describing the last dataset backed up.  The state file
+     * contents can be anything; it is private to the agent class, and
+     * is never stored off-device.
+     *
+     * <p>One thing that an application may wish to do is tag the state
+     * blob contents with a version number.  This is so that if the
+     * application is upgraded, the next time it attempts to do a backup,
+     * it can detect that the last backup operation was performed by an
+     * older version of the agent, and might therefore require different
+     * handling.
+     */
+    @Override
+    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+            ParcelFileDescriptor newState) throws IOException {
+        // First, get the current data from the application's file.  This
+        // may throw an IOException, but in that case something has gone
+        // badly wrong with the app's data on disk, and we do not want
+        // to back up garbage data.  If we just let the exception go, the
+        // Backup Manager will handle it and simply skip the current
+        // backup operation.
+        synchronized (BackupRestoreActivity.sDataLock) {
+            RandomAccessFile file = new RandomAccessFile(mDataFile, "r");
+            mFilling = file.readInt();
+            mAddMayo = file.readBoolean();
+            mAddTomato = file.readBoolean();
+        }
+
+        // If the new state file descriptor is null, this is the first time
+        // a backup is being performed, so we know we have to write the
+        // data.  If there <em>is</em> a previous state blob, we want to
+        // double check whether the current data is actually different from
+        // our last backup, so that we can avoid transmitting redundant
+        // data to the storage backend.
+        boolean doBackup = (oldState == null);
+        if (!doBackup) {
+            doBackup = compareStateFile(oldState);
+        }
+
+        // If we decided that we do in fact need to write our dataset, go
+        // ahead and do that.  The way this agent backs up the data is to
+        // flatten it into a single buffer, then write that to the backup
+        // transport under the single key string.
+        if (doBackup) {
+            ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
+
+            // We use a DataOutputStream to write structured data into
+            // the buffering stream
+            DataOutputStream outWriter = new DataOutputStream(bufStream);
+            outWriter.writeInt(mFilling);
+            outWriter.writeBoolean(mAddMayo);
+            outWriter.writeBoolean(mAddTomato);
+
+            // Okay, we've flattened the data for transmission.  Pull it
+            // out of the buffering stream object and send it off.
+            byte[] buffer = bufStream.toByteArray();
+            int len = buffer.length;
+            data.writeEntityHeader(APP_DATA_KEY, len);
+            data.writeEntityData(buffer, len);
+        }
+
+        // Finally, in all cases, we need to write the new state blob
+        writeStateFile(newState);
+    }
+
+    /**
+     * Helper routine - read a previous state file and decide whether to
+     * perform a backup based on its contents.
+     *
+     * @return <code>true</code> if the application's data has changed since
+     *   the last backup operation; <code>false</code> otherwise.
+     */
+    boolean compareStateFile(ParcelFileDescriptor oldState) {
+        FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
+        DataInputStream in = new DataInputStream(instream);
+
+        try {
+            int stateVersion = in.readInt();
+            if (stateVersion > AGENT_VERSION) {
+                // Whoops; the last version of the app that backed up
+                // data on this device was <em>newer</em> than the current
+                // version -- the user has downgraded.  That's problematic.
+                // In this implementation, we recover by simply rewriting
+                // the backup.
+                return true;
+            }
+
+            // The state data we store is just a mirror of the app's data;
+            // read it from the state file then return 'true' if any of
+            // it differs from the current data.
+            int lastFilling = in.readInt();
+            boolean lastMayo = in.readBoolean();
+            boolean lastTomato = in.readBoolean();
+
+            return (lastFilling != mFilling)
+                    || (lastTomato != mAddTomato)
+                    || (lastMayo != mAddMayo);
+        } catch (IOException e) {
+            // If something went wrong reading the state file, be safe
+            // and back up the data again.
+            return true;
+        }
+    }
+
+    /**
+     * Write out the new state file:  the version number, followed by the
+     * three bits of data as we sent them off to the backup transport.
+     */
+    void writeStateFile(ParcelFileDescriptor stateFile) throws IOException {
+        FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
+        DataOutputStream out = new DataOutputStream(outstream);
+
+        out.writeInt(AGENT_VERSION);
+        out.writeInt(mFilling);
+        out.writeBoolean(mAddMayo);
+        out.writeBoolean(mAddTomato);
+    }
+
+    /**
+     * This application does not do any "live" restores of its own data,
+     * so the only time a restore will happen is when the application is
+     * installed.  This means that the activity itself is not going to
+     * be running while we change its data out from under it.  That, in
+     * turn, means that there is no need to send out any sort of notification
+     * of the new data:  we only need to read the data from the stream
+     * provided here, build the application's new data file, and then
+     * write our new backup state blob that will be consulted at the next
+     * backup operation.
+     * 
+     * <p>We don't bother checking the versionCode of the app who originated
+     * the data because we have never revised the backup data format.  If
+     * we had, the 'appVersionCode' parameter would tell us how we should
+     * interpret the data we're about to read.
+     */
+    @Override
+    public void onRestore(BackupDataInput data, int appVersionCode,
+            ParcelFileDescriptor newState) throws IOException {
+        // We should only see one entity in the data stream, but the safest
+        // way to consume it is using a while() loop
+        while (data.readNextHeader()) {
+            String key = data.getKey();
+            int dataSize = data.getDataSize();
+
+            if (APP_DATA_KEY.equals(key)) {
+                // It's our saved data, a flattened chunk of data all in
+                // one buffer.  Use some handy structured I/O classes to
+                // extract it.
+                byte[] dataBuf = new byte[dataSize];
+                data.readEntityData(dataBuf, 0, dataSize);
+                ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
+                DataInputStream in = new DataInputStream(baStream);
+
+                mFilling = in.readInt();
+                mAddMayo = in.readBoolean();
+                mAddTomato = in.readBoolean();
+
+                // Now we are ready to construct the app's data file based
+                // on the data we are restoring from.
+                synchronized (BackupRestoreActivity.sDataLock) {
+                    RandomAccessFile file = new RandomAccessFile(mDataFile, "rw");
+                    file.setLength(0L);
+                    file.writeInt(mFilling);
+                    file.writeBoolean(mAddMayo);
+                    file.writeBoolean(mAddTomato);
+                }
+            } else {
+                // Curious!  This entity is data under a key we do not
+                // understand how to process.  Just skip it.
+                data.skipEntityData();
+            }
+        }
+
+        // The last thing to do is write the state blob that describes the
+        // app's data as restored from backup.
+        writeStateFile(newState);
+    }
+}
diff --git a/samples/BackupRestore/src/com/example/android/backuprestore/FileHelperExampleAgent.java b/samples/BackupRestore/src/com/example/android/backuprestore/FileHelperExampleAgent.java
new file mode 100644
index 0000000..b7ec016
--- /dev/null
+++ b/samples/BackupRestore/src/com/example/android/backuprestore/FileHelperExampleAgent.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2010 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.example.android.backuprestore;
+
+import java.io.IOException;
+
+import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.FileBackupHelper;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * This agent backs up the application's data using the BackupAgentHelper
+ * infrastructure.  In this application's case, the backup data is merely
+ * a duplicate of the stored data file; that makes it a perfect candidate
+ * for backing up using the {@link android.app.backup.FileBackupHelper} class
+ * provided by the Android operating system.
+ *
+ * <p>"Backup helpers" are a general mechanism that an agent implementation
+ * uses by extending {@link BackupAgentHelper} rather than the basic
+ * {@link BackupAgent} class.
+ *
+ * <p>By itself, the FileBackupHelper is properly handling the backup and
+ * restore of the datafile that we've configured it with, but it does
+ * not know about the potential need to use locking around its access
+ * to it.  However, it is straightforward to override
+ * {@link #onBackup()} and {@link #onRestore()} to supply the necessary locking
+ * around the helper's operation.
+ */
+public class FileHelperExampleAgent extends BackupAgentHelper {
+    /**
+     * The "key" string passed when adding a helper is a token used to
+     * disambiguate between entities supplied by multiple different helper
+     * objects.  They only need to be unique among the helpers within this
+     * one agent class, not globally unique.
+     */
+    static final String FILE_HELPER_KEY = "the_file";
+
+    /**
+     * The {@link android.app.backup.FileBackupHelper FileBackupHelper} class
+     * does nearly all of the work for our use case:  backup and restore of a
+     * file stored within our application's getFilesDir() location.  It will
+     * also handle files stored at any subpath within that location.  All we
+     * need to do is a bit of one-time configuration: installing the helper
+     * when this agent object is created.
+     */
+    @Override
+    public void onCreate() {
+        // All we need to do when working within the BackupAgentHelper mechanism
+        // is to install the helper that will process and back up the files we
+        // care about.  In this case, it's just one file.
+        FileBackupHelper helper = new FileBackupHelper(this, BackupRestoreActivity.DATA_FILE_NAME);
+        addHelper(FILE_HELPER_KEY, helper);
+    }
+
+    /**
+     * We want to ensure that the UI is not trying to rewrite the data file
+     * while we're reading it for backup, so we override this method to
+     * supply the necessary locking.
+     */
+    @Override
+    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+             ParcelFileDescriptor newState) throws IOException {
+        // Hold the lock while the FileBackupHelper performs the backup operation
+        synchronized (BackupRestoreActivity.sDataLock) {
+            super.onBackup(oldState, data, newState);
+        }
+    }
+
+    /**
+     * Adding locking around the file rewrite that happens during restore is
+     * similarly straightforward.
+     */
+    @Override
+    public void onRestore(BackupDataInput data, int appVersionCode,
+            ParcelFileDescriptor newState) throws IOException {
+        // Hold the lock while the FileBackupHelper restores the file from
+        // the data provided here.
+        synchronized (BackupRestoreActivity.sDataLock) {
+            super.onRestore(data, appVersionCode, newState);
+        }
+    }
+}
diff --git a/samples/BackupRestore/src/com/example/android/backuprestore/MultiRecordExampleAgent.java b/samples/BackupRestore/src/com/example/android/backuprestore/MultiRecordExampleAgent.java
new file mode 100644
index 0000000..47359c7
--- /dev/null
+++ b/samples/BackupRestore/src/com/example/android/backuprestore/MultiRecordExampleAgent.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2010 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.example.android.backuprestore;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * This agent implementation is similar to the {@link ExampleAgent} one, but
+ * stores each distinct piece of application data in a separate record within
+ * the backup data set.  These records are updated independently: if the user
+ * changes the state of one of the UI's checkboxes, for example, only that
+ * datum's backup record is updated, not the entire data file.
+ */
+public class MultiRecordExampleAgent extends BackupAgent {
+    // Key strings for each record in the backup set
+    static final String FILLING_KEY = "filling";
+    static final String MAYO_KEY = "mayo";
+    static final String TOMATO_KEY = "tomato";
+
+    // Current live data, read from the application's data file
+    int mFilling;
+    boolean mAddMayo;
+    boolean mAddTomato;
+
+    /** The location of the application's persistent data file */
+    File mDataFile;
+
+    @Override
+    public void onCreate() {
+        // Cache a File for the app's data
+        mDataFile = new File(getFilesDir(), BackupRestoreActivity.DATA_FILE_NAME);
+    }
+
+    @Override
+    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+            ParcelFileDescriptor newState) throws IOException {
+        // First, get the current data from the application's file.  This
+        // may throw an IOException, but in that case something has gone
+        // badly wrong with the app's data on disk, and we do not want
+        // to back up garbage data.  If we just let the exception go, the
+        // Backup Manager will handle it and simply skip the current
+        // backup operation.
+        synchronized (BackupRestoreActivity.sDataLock) {
+            RandomAccessFile file = new RandomAccessFile(mDataFile, "r");
+            mFilling = file.readInt();
+            mAddMayo = file.readBoolean();
+            mAddTomato = file.readBoolean();
+        }
+
+        // If this is the first backup ever, we have to back up everything
+        boolean forceBackup = (oldState == null);
+
+        // Now read the state as of the previous backup pass, if any
+        int lastFilling = 0;
+        boolean lastMayo = false;
+        boolean lastTomato = false;
+
+        if (!forceBackup) {
+
+            FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
+            DataInputStream in = new DataInputStream(instream);
+
+            try {
+                // Read the state as of the last backup
+                lastFilling = in.readInt();
+                lastMayo = in.readBoolean();
+                lastTomato = in.readBoolean();
+            } catch (IOException e) {
+                // If something went wrong reading the state file, be safe and
+                // force a backup of all the data again.
+                forceBackup = true;
+            }
+        }
+
+        // Okay, now check each datum to see whether we need to back up a new value.  We'll
+        // reuse the bytearray buffering stream for each datum.  We also use a little
+        // helper routine to avoid some code duplication when writing the two boolean
+        // records.
+        ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
+        DataOutputStream out = new DataOutputStream(bufStream);
+
+        if (forceBackup || (mFilling != lastFilling)) {
+            // bufStream.reset();   // not necessary the first time, but good to remember
+            out.writeInt(mFilling);
+            writeBackupEntity(data, bufStream, FILLING_KEY);
+        }
+
+        if (forceBackup || (mAddMayo != lastMayo)) {
+            bufStream.reset();
+            out.writeBoolean(mAddMayo);
+            writeBackupEntity(data, bufStream, MAYO_KEY);
+        }
+
+        if (forceBackup || (mAddTomato != lastTomato)) {
+            bufStream.reset();
+            out.writeBoolean(mAddTomato);
+            writeBackupEntity(data, bufStream, TOMATO_KEY);
+        }
+
+        // Finally, write the state file that describes our data as of this backup pass
+        writeStateFile(newState);
+    }
+
+    /**
+     * Write out the new state file:  the version number, followed by the
+     * three bits of data as we sent them off to the backup transport.
+     */
+    void writeStateFile(ParcelFileDescriptor stateFile) throws IOException {
+        FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
+        DataOutputStream out = new DataOutputStream(outstream);
+
+        out.writeInt(mFilling);
+        out.writeBoolean(mAddMayo);
+        out.writeBoolean(mAddTomato);
+    }
+
+    // Helper: write the boolean 'value' as a backup record under the given 'key',
+    // reusing the given buffering stream & data writer objects to do so.
+    void writeBackupEntity(BackupDataOutput data, ByteArrayOutputStream bufStream, String key)
+            throws IOException {
+        byte[] buf = bufStream.toByteArray();
+        data.writeEntityHeader(key, buf.length);
+        data.writeEntityData(buf, buf.length);
+    }
+
+    /**
+     * On restore, we pull the various bits of data out of the restore stream,
+     * then reconstruct the application's data file inside the shared lock.  A
+     * restore data set will always be the full set of records supplied by the
+     * application's backup operations.
+     */
+    @Override
+    public void onRestore(BackupDataInput data, int appVersionCode,
+            ParcelFileDescriptor newState) throws IOException {
+
+        // Consume the restore data set, remembering each bit of application state
+        // that we see along the way
+        while (data.readNextHeader()) {
+            String key = data.getKey();
+            int dataSize = data.getDataSize();
+
+            // In this implementation, we trust that we won't see any record keys
+            // that we don't understand.  Since we expect to handle them all, we
+            // go ahead and extract the data for each record before deciding how
+            // it will be handled.
+            byte[] dataBuf = new byte[dataSize];
+            data.readEntityData(dataBuf, 0, dataSize);
+            ByteArrayInputStream instream = new ByteArrayInputStream(dataBuf);
+            DataInputStream in = new DataInputStream(instream);
+
+            if (FILLING_KEY.equals(key)) {
+                mFilling = in.readInt();
+            } else if (MAYO_KEY.equals(key)) {
+                mAddMayo = in.readBoolean();
+            } else if (TOMATO_KEY.equals(key)) {
+                mAddTomato = in.readBoolean();
+            }
+        }
+
+        // Now we're ready to write out a full new dataset for the application.  Note that
+        // the restore process is intended to *replace* any existing or default data, so
+        // we can just go ahead and overwrite it all.
+        synchronized (BackupRestoreActivity.sDataLock) {
+            RandomAccessFile file = new RandomAccessFile(mDataFile, "rw");
+            file.setLength(0L);
+            file.writeInt(mFilling);
+            file.writeBoolean(mAddMayo);
+            file.writeBoolean(mAddTomato);
+        }
+
+        // Finally, write the state file that describes our data as of this restore pass.
+        writeStateFile(newState);
+    }
+}