diff --git a/samples/BackupRestore/Android.mk b/samples/BackupRestore/Android.mk
new file mode 100644
index 000000000..c164a6c0b
--- /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 000000000..8c3772026
--- /dev/null
+++ b/samples/BackupRestore/AndroidManifest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
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 android:backupAgent attribute of the
+ * <application> 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.
+ *
+ *
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: + * + *
You can build the application to use any of these agent implementations simply by
+ * changing the class name supplied in the android:backupAgent manifest
+ * attribute to indicate the agent you wish to use. Note: 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:
+ *
+ *
adb shell bmgr wipe com.example.android.backuprestore
+ *
+ *
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. + * + *
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 000000000..3d93c1a71 --- /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. + * + *
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 is 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 true if the application's data has changed since
+ * the last backup operation; false 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 newer 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.
+ *
+ *
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 000000000..b7ec0161c --- /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. + * + *
"Backup helpers" are a general mechanism that an agent implementation + * uses by extending {@link BackupAgentHelper} rather than the basic + * {@link BackupAgent} class. + * + *
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 000000000..47359c729 --- /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); + } +}