/* * 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.app.backup.RestoreObserver; import android.os.Bundle; import android.util.Log; import android.view.View; 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><application></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(); } /** * Click handler, designated in the layout, that runs a restore of the app's * most recent data when the button is pressed. */ public void onRestoreButtonClick(View v) { Log.v(TAG, "Requesting restore of our most recent data"); mBackupManager.requestRestore( new RestoreObserver() { public void restoreFinished(int error) { /** Done with the restore! Now draw the new state of our data */ Log.v(TAG, "Restore finished, error = " + error); populateUI(); } } ); } }