1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.backuprestore; 18 19 import android.app.Activity; 20 import android.app.backup.BackupManager; 21 import android.app.backup.RestoreObserver; 22 import android.os.Bundle; 23 import android.util.Log; 24 import android.view.View; 25 import android.widget.CheckBox; 26 import android.widget.CompoundButton; 27 import android.widget.RadioGroup; 28 29 import java.io.File; 30 import java.io.IOException; 31 import java.io.RandomAccessFile; 32 33 /** 34 * This example is intended to demonstrate a few approaches that an Android 35 * application developer can take when implementing a 36 * {@link android.app.backup.BackupAgent BackupAgent}. This feature, added 37 * to the Android platform with API version 8, allows the application to 38 * back up its data to a device-provided storage location, transparently to 39 * the user. If the application is uninstalled and then reinstalled, or if 40 * the user starts using a new Android device, the backed-up information 41 * can be provided automatically when the application is reinstalled. 42 * 43 * <p>Participating in the backup/restore mechanism is simple. The application 44 * provides a class that extends {@link android.app.backup.BackupAgent}, and 45 * overrides the two core callback methods 46 * {@link android.app.backup.BackupAgent#onBackup(android.os.ParcelFileDescriptor, android.app.backup.BackupDataOutput, android.os.ParcelFileDescriptor) onBackup()} 47 * and 48 * {@link android.app.backup.BackupAgent#onRestore(android.app.backup.BackupDataInput, int, android.os.ParcelFileDescriptor) onRestore()}. 49 * It also publishes the agent class to the operating system by naming the class 50 * with the <code>android:backupAgent</code> attribute of the 51 * <code><application></code> tag in the application's manifest. 52 * When a backup or restore operation is performed, the application's agent class 53 * is instantiated within the application's execution context and the corresponding 54 * method invoked. Please see the documentation on the 55 * {@link android.app.backup.BackupAgent BackupAgent} class for details about the 56 * data interchange between the agent and the backup mechanism. 57 * 58 * <p>This example application maintains a few pieces of simple data, and provides 59 * three different sample agent implementations, each illustrating an alternative 60 * approach. The three sample agent classes are: 61 * 62 * <p><ol type="1"> 63 * <li>{@link ExampleAgent} - this agent backs up the application's data in a single 64 * record. It illustrates the direct "by hand" processes of saving backup state for 65 * future reference, sending data to the backup transport, and reading it from a restore 66 * dataset.</li> 67 * <li>{@link FileHelperExampleAgent} - this agent takes advantage of the suite of 68 * helper classes provided along with the core BackupAgent API. By extending 69 * {@link android.app.backup.BackupHelperAgent} and using the targeted 70 * {link android.app.backup.FileBackupHelper FileBackupHelper} class, it achieves 71 * the same result as {@link ExampleAgent} - backing up the application's saved 72 * data file in a single chunk, and restoring it upon request -- in only a few lines 73 * of code.</li> 74 * <li>{@link MultiRecordExampleAgent} - this agent stores each separate bit of data 75 * managed by the UI in separate records within the backup dataset. It illustrates 76 * how an application's backup agent can do selective updates of only what information 77 * has changed since the last backup.</li></ol> 78 * 79 * <p>You can build the application to use any of these agent implementations simply by 80 * changing the class name supplied in the <code>android:backupAgent</code> manifest 81 * attribute to indicate the agent you wish to use. <strong>Note:</strong> the backed-up 82 * data and backup-state tracking of these agents are not compatible! If you change which 83 * agent the application uses, you should also wipe the backup state associated with 84 * the application on your handset. The 'bmgr' shell application on the device can 85 * do this; simply run the following command from your desktop computer while attached 86 * to the device via adb: 87 * 88 * <p><code>adb shell bmgr wipe com.example.android.backuprestore</code> 89 * 90 * <p>You can then install the new version of the application, and its next backup pass 91 * will start over from scratch with the new agent. 92 */ 93 public class BackupRestoreActivity extends Activity { 94 static final String TAG = "BRActivity"; 95 96 /** 97 * We serialize access to our persistent data through a global static 98 * object. This ensures that in the unlikely event of the our backup/restore 99 * agent running to perform a backup while our UI is updating the file, the 100 * agent will not accidentally read partially-written data. 101 * 102 * <p>Curious but true: a zero-length array is slightly lighter-weight than 103 * merely allocating an Object, and can still be synchronized on. 104 */ 105 static final Object[] sDataLock = new Object[0]; 106 107 /** Also supply a global standard file name for everyone to use */ 108 static final String DATA_FILE_NAME = "saved_data"; 109 110 /** The various bits of UI that the user can manipulate */ 111 RadioGroup mFillingGroup; 112 CheckBox mAddMayoCheckbox; 113 CheckBox mAddTomatoCheckbox; 114 115 /** Cache a reference to our persistent data file */ 116 File mDataFile; 117 118 /** Also cache a reference to the Backup Manager */ 119 BackupManager mBackupManager; 120 121 /** Set up the activity and populate its UI from the persistent data. */ 122 @Override onCreate(Bundle savedInstanceState)123 public void onCreate(Bundle savedInstanceState) { 124 super.onCreate(savedInstanceState); 125 126 /** Establish the activity's UI */ 127 setContentView(R.layout.backup_restore); 128 129 /** Once the UI has been inflated, cache the controls for later */ 130 mFillingGroup = (RadioGroup) findViewById(R.id.filling_group); 131 mAddMayoCheckbox = (CheckBox) findViewById(R.id.mayo); 132 mAddTomatoCheckbox = (CheckBox) findViewById(R.id.tomato); 133 134 /** Set up our file bookkeeping */ 135 mDataFile = new File(getFilesDir(), BackupRestoreActivity.DATA_FILE_NAME); 136 137 /** It is handy to keep a BackupManager cached */ 138 mBackupManager = new BackupManager(this); 139 140 /** 141 * Finally, build the UI from the persistent store 142 */ 143 populateUI(); 144 } 145 146 /** 147 * Configure the UI based on our persistent data, creating the 148 * data file and establishing defaults if necessary. 149 */ populateUI()150 void populateUI() { 151 RandomAccessFile file; 152 153 // Default values in case there's no data file yet 154 int whichFilling = R.id.pastrami; 155 boolean addMayo = false; 156 boolean addTomato = false; 157 158 /** Hold the data-access lock around access to the file */ 159 synchronized (BackupRestoreActivity.sDataLock) { 160 boolean exists = mDataFile.exists(); 161 try { 162 file = new RandomAccessFile(mDataFile, "rw"); 163 if (exists) { 164 Log.v(TAG, "datafile exists"); 165 whichFilling = file.readInt(); 166 addMayo = file.readBoolean(); 167 addTomato = file.readBoolean(); 168 Log.v(TAG, " mayo=" + addMayo 169 + " tomato=" + addTomato 170 + " filling=" + whichFilling); 171 } else { 172 // The default values were configured above: write them 173 // to the newly-created file. 174 Log.v(TAG, "creating default datafile"); 175 writeDataToFileLocked(file, 176 addMayo, addTomato, whichFilling); 177 178 // We also need to perform an initial backup; ask for one 179 mBackupManager.dataChanged(); 180 } 181 } catch (IOException ioe) { 182 183 } 184 } 185 186 /** Now that we've processed the file, build the UI outside the lock */ 187 mFillingGroup.check(whichFilling); 188 mAddMayoCheckbox.setChecked(addMayo); 189 mAddTomatoCheckbox.setChecked(addTomato); 190 191 /** 192 * We also want to record the new state when the user makes changes, 193 * so install simple observers that do this 194 */ 195 mFillingGroup.setOnCheckedChangeListener( 196 new RadioGroup.OnCheckedChangeListener() { 197 public void onCheckedChanged(RadioGroup group, 198 int checkedId) { 199 // As with the checkbox listeners, rewrite the 200 // entire state file 201 Log.v(TAG, "New radio item selected: " + checkedId); 202 recordNewUIState(); 203 } 204 }); 205 206 CompoundButton.OnCheckedChangeListener checkListener 207 = new CompoundButton.OnCheckedChangeListener() { 208 public void onCheckedChanged(CompoundButton buttonView, 209 boolean isChecked) { 210 // Whichever one is altered, we rewrite the entire UI state 211 Log.v(TAG, "Checkbox toggled: " + buttonView); 212 recordNewUIState(); 213 } 214 }; 215 mAddMayoCheckbox.setOnCheckedChangeListener(checkListener); 216 mAddTomatoCheckbox.setOnCheckedChangeListener(checkListener); 217 } 218 219 /** 220 * Handy helper routine to write the UI data to a file. 221 */ writeDataToFileLocked(RandomAccessFile file, boolean addMayo, boolean addTomato, int whichFilling)222 void writeDataToFileLocked(RandomAccessFile file, 223 boolean addMayo, boolean addTomato, int whichFilling) 224 throws IOException { 225 file.setLength(0L); 226 file.writeInt(whichFilling); 227 file.writeBoolean(addMayo); 228 file.writeBoolean(addTomato); 229 Log.v(TAG, "NEW STATE: mayo=" + addMayo 230 + " tomato=" + addTomato 231 + " filling=" + whichFilling); 232 } 233 234 /** 235 * Another helper; this one reads the current UI state and writes that 236 * to the persistent store, then tells the backup manager that we need 237 * a backup. 238 */ recordNewUIState()239 void recordNewUIState() { 240 boolean addMayo = mAddMayoCheckbox.isChecked(); 241 boolean addTomato = mAddTomatoCheckbox.isChecked(); 242 int whichFilling = mFillingGroup.getCheckedRadioButtonId(); 243 try { 244 synchronized (BackupRestoreActivity.sDataLock) { 245 RandomAccessFile file = new RandomAccessFile(mDataFile, "rw"); 246 writeDataToFileLocked(file, addMayo, addTomato, whichFilling); 247 } 248 } catch (IOException e) { 249 Log.e(TAG, "Unable to record new UI state"); 250 } 251 252 mBackupManager.dataChanged(); 253 } 254 255 /** 256 * Click handler, designated in the layout, that runs a restore of the app's 257 * most recent data when the button is pressed. 258 */ onRestoreButtonClick(View v)259 public void onRestoreButtonClick(View v) { 260 Log.v(TAG, "Requesting restore of our most recent data"); 261 mBackupManager.requestRestore( 262 new RestoreObserver() { 263 public void restoreFinished(int error) { 264 /** Done with the restore! Now draw the new state of our data */ 265 Log.v(TAG, "Restore finished, error = " + error); 266 populateUI(); 267 } 268 } 269 ); 270 } 271 } 272