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