1 /* 2 * Copyright (C) 2009 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 android.app.backup; 18 19 import android.app.IBackupAgent; 20 import android.app.QueuedWork; 21 import android.app.backup.IBackupManager; 22 import android.content.Context; 23 import android.content.ContextWrapper; 24 import android.content.pm.ApplicationInfo; 25 import android.os.Binder; 26 import android.os.Handler; 27 import android.os.IBinder; 28 import android.os.Looper; 29 import android.os.ParcelFileDescriptor; 30 import android.os.Process; 31 import android.os.RemoteException; 32 import android.system.ErrnoException; 33 import android.system.Os; 34 import android.system.OsConstants; 35 import android.system.StructStat; 36 import android.util.Log; 37 38 import java.io.File; 39 import java.io.FileOutputStream; 40 import java.io.IOException; 41 import java.util.HashSet; 42 import java.util.LinkedList; 43 import java.util.concurrent.CountDownLatch; 44 45 /** 46 * Provides the central interface between an 47 * application and Android's data backup infrastructure. An application that wishes 48 * to participate in the backup and restore mechanism will declare a subclass of 49 * {@link android.app.backup.BackupAgent}, implement the 50 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} 51 * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods, 52 * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via 53 * the <code> 54 * <a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> 55 * tag's {@code android:backupAgent} attribute. 56 * 57 * <div class="special reference"> 58 * <h3>Developer Guides</h3> 59 * <p>For more information about using BackupAgent, read the 60 * <a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a> developer guide.</p></div> 61 * 62 * <h3>Basic Operation</h3> 63 * <p> 64 * When the application makes changes to data that it wishes to keep backed up, 65 * it should call the 66 * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method. 67 * This notifies the Android Backup Manager that the application needs an opportunity 68 * to update its backup image. The Backup Manager, in turn, schedules a 69 * backup pass to be performed at an opportune time. 70 * <p> 71 * Restore operations are typically performed only when applications are first 72 * installed on a device. At that time, the operating system checks to see whether 73 * there is a previously-saved data set available for the application being installed, and if so, 74 * begins an immediate restore pass to deliver the backup data as part of the installation 75 * process. 76 * <p> 77 * When a backup or restore pass is run, the application's process is launched 78 * (if not already running), the manifest-declared backup agent class (in the {@code 79 * android:backupAgent} attribute) is instantiated within 80 * that process, and the agent's {@link #onCreate()} method is invoked. This prepares the 81 * agent instance to run the actual backup or restore logic. At this point the 82 * agent's 83 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or 84 * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be 85 * invoked as appropriate for the operation being performed. 86 * <p> 87 * A backup data set consists of one or more "entities," flattened binary data 88 * records that are each identified with a key string unique within the data set. Adding a 89 * record to the active data set or updating an existing record is done by simply 90 * writing new entity data under the desired key. Deleting an entity from the data set 91 * is done by writing an entity under that key with header specifying a negative data 92 * size, and no actual entity data. 93 * <p> 94 * <b>Helper Classes</b> 95 * <p> 96 * An extensible agent based on convenient helper classes is available in 97 * {@link android.app.backup.BackupAgentHelper}. That class is particularly 98 * suited to handling of simple file or {@link android.content.SharedPreferences} 99 * backup and restore. 100 * 101 * @see android.app.backup.BackupManager 102 * @see android.app.backup.BackupAgentHelper 103 * @see android.app.backup.BackupDataInput 104 * @see android.app.backup.BackupDataOutput 105 */ 106 public abstract class BackupAgent extends ContextWrapper { 107 private static final String TAG = "BackupAgent"; 108 private static final boolean DEBUG = true; 109 110 /** @hide */ 111 public static final int TYPE_EOF = 0; 112 113 /** 114 * During a full restore, indicates that the file system object being restored 115 * is an ordinary file. 116 */ 117 public static final int TYPE_FILE = 1; 118 119 /** 120 * During a full restore, indicates that the file system object being restored 121 * is a directory. 122 */ 123 public static final int TYPE_DIRECTORY = 2; 124 125 /** @hide */ 126 public static final int TYPE_SYMLINK = 3; 127 128 Handler mHandler = null; 129 getHandler()130 Handler getHandler() { 131 if (mHandler == null) { 132 mHandler = new Handler(Looper.getMainLooper()); 133 } 134 return mHandler; 135 } 136 137 class SharedPrefsSynchronizer implements Runnable { 138 public final CountDownLatch mLatch = new CountDownLatch(1); 139 140 @Override run()141 public void run() { 142 QueuedWork.waitToFinish(); 143 mLatch.countDown(); 144 } 145 }; 146 147 // Syncing shared preferences deferred writes needs to happen on the main looper thread waitForSharedPrefs()148 private void waitForSharedPrefs() { 149 Handler h = getHandler(); 150 final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer(); 151 h.postAtFrontOfQueue(s); 152 try { 153 s.mLatch.await(); 154 } catch (InterruptedException e) { /* ignored */ } 155 } 156 157 BackupAgent()158 public BackupAgent() { 159 super(null); 160 } 161 162 /** 163 * Provided as a convenience for agent implementations that need an opportunity 164 * to do one-time initialization before the actual backup or restore operation 165 * is begun. 166 * <p> 167 * Agents do not need to override this method. 168 */ onCreate()169 public void onCreate() { 170 } 171 172 /** 173 * Provided as a convenience for agent implementations that need to do some 174 * sort of shutdown process after backup or restore is completed. 175 * <p> 176 * Agents do not need to override this method. 177 */ onDestroy()178 public void onDestroy() { 179 } 180 181 /** 182 * The application is being asked to write any data changed since the last 183 * time it performed a backup operation. The state data recorded during the 184 * last backup pass is provided in the <code>oldState</code> file 185 * descriptor. If <code>oldState</code> is <code>null</code>, no old state 186 * is available and the application should perform a full backup. In both 187 * cases, a representation of the final backup state after this pass should 188 * be written to the file pointed to by the file descriptor wrapped in 189 * <code>newState</code>. 190 * <p> 191 * Each entity written to the {@link android.app.backup.BackupDataOutput} 192 * <code>data</code> stream will be transmitted 193 * over the current backup transport and stored in the remote data set under 194 * the key supplied as part of the entity. Writing an entity with a negative 195 * data size instructs the transport to delete whatever entity currently exists 196 * under that key from the remote data set. 197 * 198 * @param oldState An open, read-only ParcelFileDescriptor pointing to the 199 * last backup state provided by the application. May be 200 * <code>null</code>, in which case no prior state is being 201 * provided and the application should perform a full backup. 202 * @param data A structured wrapper around an open, read/write 203 * file descriptor pointing to the backup data destination. 204 * Typically the application will use backup helper classes to 205 * write to this file. 206 * @param newState An open, read/write ParcelFileDescriptor pointing to an 207 * empty file. The application should record the final backup 208 * state here after writing the requested data to the <code>data</code> 209 * output stream. 210 */ onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)211 public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, 212 ParcelFileDescriptor newState) throws IOException; 213 214 /** 215 * The application is being restored from backup and should replace any 216 * existing data with the contents of the backup. The backup data is 217 * provided through the <code>data</code> parameter. Once 218 * the restore is finished, the application should write a representation of 219 * the final state to the <code>newState</code> file descriptor. 220 * <p> 221 * The application is responsible for properly erasing its old data and 222 * replacing it with the data supplied to this method. No "clear user data" 223 * operation will be performed automatically by the operating system. The 224 * exception to this is in the case of a failed restore attempt: if 225 * onRestore() throws an exception, the OS will assume that the 226 * application's data may now be in an incoherent state, and will clear it 227 * before proceeding. 228 * 229 * @param data A structured wrapper around an open, read-only 230 * file descriptor pointing to a full snapshot of the 231 * application's data. The application should consume every 232 * entity represented in this data stream. 233 * @param appVersionCode The value of the <a 234 * href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code 235 * android:versionCode}</a> manifest attribute, 236 * from the application that backed up this particular data set. This 237 * makes it possible for an application's agent to distinguish among any 238 * possible older data versions when asked to perform the restore 239 * operation. 240 * @param newState An open, read/write ParcelFileDescriptor pointing to an 241 * empty file. The application should record the final backup 242 * state here after restoring its data from the <code>data</code> stream. 243 * When a full-backup dataset is being restored, this will be <code>null</code>. 244 */ onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)245 public abstract void onRestore(BackupDataInput data, int appVersionCode, 246 ParcelFileDescriptor newState) throws IOException; 247 248 /** 249 * The application is having its entire file system contents backed up. {@code data} 250 * points to the backup destination, and the app has the opportunity to choose which 251 * files are to be stored. To commit a file as part of the backup, call the 252 * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method. After all file 253 * data is written to the output, the agent returns from this method and the backup 254 * operation concludes. 255 * 256 * <p>Certain parts of the app's data are never backed up even if the app explicitly 257 * sends them to the output: 258 * 259 * <ul> 260 * <li>The contents of the {@link #getCacheDir()} directory</li> 261 * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li> 262 * <li>The contents of the app's shared library directory</li> 263 * </ul> 264 * 265 * <p>The default implementation of this method backs up the entirety of the 266 * application's "owned" file system trees to the output other than the few exceptions 267 * listed above. Apps only need to override this method if they need to impose special 268 * limitations on which files are being stored beyond the control that 269 * {@link #getNoBackupFilesDir()} offers. 270 * 271 * @param data A structured wrapper pointing to the backup destination. 272 * @throws IOException 273 * 274 * @see Context#getNoBackupFilesDir() 275 * @see #fullBackupFile(File, FullBackupDataOutput) 276 * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) 277 */ onFullBackup(FullBackupDataOutput data)278 public void onFullBackup(FullBackupDataOutput data) throws IOException { 279 ApplicationInfo appInfo = getApplicationInfo(); 280 281 // Note that we don't need to think about the no_backup dir because it's outside 282 // all of the ones we will be traversing 283 String rootDir = new File(appInfo.dataDir).getCanonicalPath(); 284 String filesDir = getFilesDir().getCanonicalPath(); 285 String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath(); 286 String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath(); 287 String cacheDir = getCacheDir().getCanonicalPath(); 288 String libDir = (appInfo.nativeLibraryDir != null) 289 ? new File(appInfo.nativeLibraryDir).getCanonicalPath() 290 : null; 291 292 // Filters, the scan queue, and the set of resulting entities 293 HashSet<String> filterSet = new HashSet<String>(); 294 String packageName = getPackageName(); 295 296 // Okay, start with the app's root tree, but exclude all of the canonical subdirs 297 if (libDir != null) { 298 filterSet.add(libDir); 299 } 300 filterSet.add(cacheDir); 301 filterSet.add(databaseDir); 302 filterSet.add(sharedPrefsDir); 303 filterSet.add(filesDir); 304 fullBackupFileTree(packageName, FullBackup.ROOT_TREE_TOKEN, rootDir, filterSet, data); 305 306 // Now do the same for the files dir, db dir, and shared prefs dir 307 filterSet.add(rootDir); 308 filterSet.remove(filesDir); 309 fullBackupFileTree(packageName, FullBackup.DATA_TREE_TOKEN, filesDir, filterSet, data); 310 311 filterSet.add(filesDir); 312 filterSet.remove(databaseDir); 313 fullBackupFileTree(packageName, FullBackup.DATABASE_TREE_TOKEN, databaseDir, filterSet, data); 314 315 filterSet.add(databaseDir); 316 filterSet.remove(sharedPrefsDir); 317 fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir, filterSet, data); 318 319 // getExternalFilesDir() location associated with this app. Technically there should 320 // not be any files here if the app does not properly have permission to access 321 // external storage, but edge cases happen. fullBackupFileTree() catches 322 // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and 323 // we know a priori that processes running as the system UID are not permitted to 324 // access external storage, so we check for that as well to avoid nastygrams in 325 // the log. 326 if (Process.myUid() != Process.SYSTEM_UID) { 327 File efLocation = getExternalFilesDir(null); 328 if (efLocation != null) { 329 fullBackupFileTree(packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, 330 efLocation.getCanonicalPath(), null, data); 331 } 332 } 333 } 334 335 /** 336 * Write an entire file as part of a full-backup operation. The file's contents 337 * will be delivered to the backup destination along with the metadata necessary 338 * to place it with the proper location and permissions on the device where the 339 * data is restored. 340 * 341 * <p class="note">It is safe to explicitly back up files underneath your application's 342 * {@link #getNoBackupFilesDir()} directory, and they will be restored to that 343 * location correctly. 344 * 345 * @param file The file to be backed up. The file must exist and be readable by 346 * the caller. 347 * @param output The destination to which the backed-up file data will be sent. 348 */ fullBackupFile(File file, FullBackupDataOutput output)349 public final void fullBackupFile(File file, FullBackupDataOutput output) { 350 // Look up where all of our various well-defined dir trees live on this device 351 String mainDir; 352 String filesDir; 353 String nbFilesDir; 354 String dbDir; 355 String spDir; 356 String cacheDir; 357 String libDir; 358 String efDir = null; 359 String filePath; 360 361 ApplicationInfo appInfo = getApplicationInfo(); 362 363 try { 364 mainDir = new File(appInfo.dataDir).getCanonicalPath(); 365 filesDir = getFilesDir().getCanonicalPath(); 366 nbFilesDir = getNoBackupFilesDir().getCanonicalPath(); 367 dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath(); 368 spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath(); 369 cacheDir = getCacheDir().getCanonicalPath(); 370 libDir = (appInfo.nativeLibraryDir == null) 371 ? null 372 : new File(appInfo.nativeLibraryDir).getCanonicalPath(); 373 374 // may or may not have external files access to attempt backup/restore there 375 if (Process.myUid() != Process.SYSTEM_UID) { 376 File efLocation = getExternalFilesDir(null); 377 if (efLocation != null) { 378 efDir = efLocation.getCanonicalPath(); 379 } 380 } 381 382 // Now figure out which well-defined tree the file is placed in, working from 383 // most to least specific. We also specifically exclude the lib and cache dirs. 384 filePath = file.getCanonicalPath(); 385 } catch (IOException e) { 386 Log.w(TAG, "Unable to obtain canonical paths"); 387 return; 388 } 389 390 if (filePath.startsWith(cacheDir) 391 || filePath.startsWith(libDir) 392 || filePath.startsWith(nbFilesDir)) { 393 Log.w(TAG, "lib, cache, and no_backup files are not backed up"); 394 return; 395 } 396 397 final String domain; 398 String rootpath = null; 399 if (filePath.startsWith(dbDir)) { 400 domain = FullBackup.DATABASE_TREE_TOKEN; 401 rootpath = dbDir; 402 } else if (filePath.startsWith(spDir)) { 403 domain = FullBackup.SHAREDPREFS_TREE_TOKEN; 404 rootpath = spDir; 405 } else if (filePath.startsWith(filesDir)) { 406 domain = FullBackup.DATA_TREE_TOKEN; 407 rootpath = filesDir; 408 } else if (filePath.startsWith(mainDir)) { 409 domain = FullBackup.ROOT_TREE_TOKEN; 410 rootpath = mainDir; 411 } else if ((efDir != null) && filePath.startsWith(efDir)) { 412 domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN; 413 rootpath = efDir; 414 } else { 415 Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping"); 416 return; 417 } 418 419 // And now that we know where it lives, semantically, back it up appropriately 420 Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain 421 + " rootpath=" + rootpath); 422 FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, 423 output.getData()); 424 } 425 426 /** 427 * Scan the dir tree (if it actually exists) and process each entry we find. If the 428 * 'excludes' parameter is non-null, it is consulted each time a new file system entity 429 * is visited to see whether that entity (and its subtree, if appropriate) should be 430 * omitted from the backup process. 431 * 432 * @hide 433 */ fullBackupFileTree(String packageName, String domain, String rootPath, HashSet<String> excludes, FullBackupDataOutput output)434 protected final void fullBackupFileTree(String packageName, String domain, String rootPath, 435 HashSet<String> excludes, FullBackupDataOutput output) { 436 File rootFile = new File(rootPath); 437 if (rootFile.exists()) { 438 LinkedList<File> scanQueue = new LinkedList<File>(); 439 scanQueue.add(rootFile); 440 441 while (scanQueue.size() > 0) { 442 File file = scanQueue.remove(0); 443 String filePath; 444 try { 445 filePath = file.getCanonicalPath(); 446 447 // prune this subtree? 448 if (excludes != null && excludes.contains(filePath)) { 449 continue; 450 } 451 452 // If it's a directory, enqueue its contents for scanning. 453 StructStat stat = Os.lstat(filePath); 454 if (OsConstants.S_ISLNK(stat.st_mode)) { 455 if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file); 456 continue; 457 } else if (OsConstants.S_ISDIR(stat.st_mode)) { 458 File[] contents = file.listFiles(); 459 if (contents != null) { 460 for (File entry : contents) { 461 scanQueue.add(0, entry); 462 } 463 } 464 } 465 } catch (IOException e) { 466 if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file); 467 continue; 468 } catch (ErrnoException e) { 469 if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e); 470 continue; 471 } 472 473 // Finally, back this file up before proceeding 474 FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, 475 output.getData()); 476 } 477 } 478 } 479 480 /** 481 * Handle the data delivered via the given file descriptor during a full restore 482 * operation. The agent is given the path to the file's original location as well 483 * as its size and metadata. 484 * <p> 485 * The file descriptor can only be read for {@code size} bytes; attempting to read 486 * more data has undefined behavior. 487 * <p> 488 * The default implementation creates the destination file/directory and populates it 489 * with the data from the file descriptor, then sets the file's access mode and 490 * modification time to match the restore arguments. 491 * 492 * @param data A read-only file descriptor from which the agent can read {@code size} 493 * bytes of file data. 494 * @param size The number of bytes of file content to be restored to the given 495 * destination. If the file system object being restored is a directory, {@code size} 496 * will be zero. 497 * @param destination The File on disk to be restored with the given data. 498 * @param type The kind of file system object being restored. This will be either 499 * {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}. 500 * @param mode The access mode to be assigned to the destination after its data is 501 * written. This is in the standard format used by {@code chmod()}. 502 * @param mtime The modification time of the file when it was backed up, suitable to 503 * be assigned to the file after its data is written. 504 * @throws IOException 505 */ onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime)506 public void onRestoreFile(ParcelFileDescriptor data, long size, 507 File destination, int type, long mode, long mtime) 508 throws IOException { 509 FullBackup.restoreFile(data, size, type, mode, mtime, destination); 510 } 511 512 /** 513 * Only specialized platform agents should overload this entry point to support 514 * restores to crazy non-app locations. 515 * @hide 516 */ onRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime)517 protected void onRestoreFile(ParcelFileDescriptor data, long size, 518 int type, String domain, String path, long mode, long mtime) 519 throws IOException { 520 String basePath = null; 521 522 if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type 523 + " domain=" + domain + " relpath=" + path + " mode=" + mode 524 + " mtime=" + mtime); 525 526 // Parse out the semantic domains into the correct physical location 527 if (domain.equals(FullBackup.DATA_TREE_TOKEN)) { 528 basePath = getFilesDir().getCanonicalPath(); 529 } else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) { 530 basePath = getDatabasePath("foo").getParentFile().getCanonicalPath(); 531 } else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) { 532 basePath = new File(getApplicationInfo().dataDir).getCanonicalPath(); 533 } else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) { 534 basePath = getSharedPrefsFile("foo").getParentFile().getCanonicalPath(); 535 } else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) { 536 basePath = getCacheDir().getCanonicalPath(); 537 } else if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) { 538 // make sure we can try to restore here before proceeding 539 if (Process.myUid() != Process.SYSTEM_UID) { 540 File efLocation = getExternalFilesDir(null); 541 if (efLocation != null) { 542 basePath = getExternalFilesDir(null).getCanonicalPath(); 543 mode = -1; // < 0 is a token to skip attempting a chmod() 544 } 545 } 546 } else if (domain.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) { 547 basePath = getNoBackupFilesDir().getCanonicalPath(); 548 } else { 549 // Not a supported location 550 Log.i(TAG, "Unrecognized domain " + domain); 551 } 552 553 // Now that we've figured out where the data goes, send it on its way 554 if (basePath != null) { 555 // Canonicalize the nominal path and verify that it lies within the stated domain 556 File outFile = new File(basePath, path); 557 String outPath = outFile.getCanonicalPath(); 558 if (outPath.startsWith(basePath + File.separatorChar)) { 559 if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath); 560 onRestoreFile(data, size, outFile, type, mode, mtime); 561 return; 562 } else { 563 // Attempt to restore to a path outside the file's nominal domain. 564 if (DEBUG) { 565 Log.e(TAG, "Cross-domain restore attempt: " + outPath); 566 } 567 } 568 } 569 570 // Not a supported output location, or bad path: we need to consume the data 571 // anyway, so just use the default "copy the data out" implementation 572 // with a null destination. 573 if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]"); 574 FullBackup.restoreFile(data, size, type, mode, mtime, null); 575 } 576 577 /** 578 * The application's restore operation has completed. This method is called after 579 * all available data has been delivered to the application for restore (via either 580 * the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or 581 * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()} 582 * callbacks). This provides the app with a stable end-of-restore opportunity to 583 * perform any appropriate post-processing on the data that was just delivered. 584 * 585 * @see #onRestore(BackupDataInput, int, ParcelFileDescriptor) 586 * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) 587 */ onRestoreFinished()588 public void onRestoreFinished() { 589 } 590 591 // ----- Core implementation ----- 592 593 /** @hide */ onBind()594 public final IBinder onBind() { 595 return mBinder; 596 } 597 598 private final IBinder mBinder = new BackupServiceBinder().asBinder(); 599 600 /** @hide */ attach(Context context)601 public void attach(Context context) { 602 attachBaseContext(context); 603 } 604 605 // ----- IBackupService binder interface ----- 606 private class BackupServiceBinder extends IBackupAgent.Stub { 607 private static final String TAG = "BackupServiceBinder"; 608 609 @Override doBackup(ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)610 public void doBackup(ParcelFileDescriptor oldState, 611 ParcelFileDescriptor data, 612 ParcelFileDescriptor newState, 613 int token, IBackupManager callbackBinder) throws RemoteException { 614 // Ensure that we're running with the app's normal permission level 615 long ident = Binder.clearCallingIdentity(); 616 617 if (DEBUG) Log.v(TAG, "doBackup() invoked"); 618 BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor()); 619 620 try { 621 BackupAgent.this.onBackup(oldState, output, newState); 622 } catch (IOException ex) { 623 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); 624 throw new RuntimeException(ex); 625 } catch (RuntimeException ex) { 626 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); 627 throw ex; 628 } finally { 629 // Ensure that any SharedPreferences writes have landed after the backup, 630 // in case the app code has side effects (since apps cannot provide this 631 // guarantee themselves). 632 waitForSharedPrefs(); 633 634 Binder.restoreCallingIdentity(ident); 635 try { 636 callbackBinder.opComplete(token); 637 } catch (RemoteException e) { 638 // we'll time out anyway, so we're safe 639 } 640 } 641 } 642 643 @Override doRestore(ParcelFileDescriptor data, int appVersionCode, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)644 public void doRestore(ParcelFileDescriptor data, int appVersionCode, 645 ParcelFileDescriptor newState, 646 int token, IBackupManager callbackBinder) throws RemoteException { 647 // Ensure that we're running with the app's normal permission level 648 long ident = Binder.clearCallingIdentity(); 649 650 if (DEBUG) Log.v(TAG, "doRestore() invoked"); 651 BackupDataInput input = new BackupDataInput(data.getFileDescriptor()); 652 try { 653 BackupAgent.this.onRestore(input, appVersionCode, newState); 654 } catch (IOException ex) { 655 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex); 656 throw new RuntimeException(ex); 657 } catch (RuntimeException ex) { 658 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex); 659 throw ex; 660 } finally { 661 // Ensure that any side-effect SharedPreferences writes have landed 662 waitForSharedPrefs(); 663 664 Binder.restoreCallingIdentity(ident); 665 try { 666 callbackBinder.opComplete(token); 667 } catch (RemoteException e) { 668 // we'll time out anyway, so we're safe 669 } 670 } 671 } 672 673 @Override doFullBackup(ParcelFileDescriptor data, int token, IBackupManager callbackBinder)674 public void doFullBackup(ParcelFileDescriptor data, 675 int token, IBackupManager callbackBinder) { 676 // Ensure that we're running with the app's normal permission level 677 long ident = Binder.clearCallingIdentity(); 678 679 if (DEBUG) Log.v(TAG, "doFullBackup() invoked"); 680 681 // Ensure that any SharedPreferences writes have landed *before* 682 // we potentially try to back up the underlying files directly. 683 waitForSharedPrefs(); 684 685 try { 686 BackupAgent.this.onFullBackup(new FullBackupDataOutput(data)); 687 } catch (IOException ex) { 688 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); 689 throw new RuntimeException(ex); 690 } catch (RuntimeException ex) { 691 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); 692 throw ex; 693 } finally { 694 // ... and then again after, as in the doBackup() case 695 waitForSharedPrefs(); 696 697 // Send the EOD marker indicating that there is no more data 698 // forthcoming from this agent. 699 try { 700 FileOutputStream out = new FileOutputStream(data.getFileDescriptor()); 701 byte[] buf = new byte[4]; 702 out.write(buf); 703 } catch (IOException e) { 704 Log.e(TAG, "Unable to finalize backup stream!"); 705 } 706 707 Binder.restoreCallingIdentity(ident); 708 try { 709 callbackBinder.opComplete(token); 710 } catch (RemoteException e) { 711 // we'll time out anyway, so we're safe 712 } 713 } 714 } 715 716 @Override doRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime, int token, IBackupManager callbackBinder)717 public void doRestoreFile(ParcelFileDescriptor data, long size, 718 int type, String domain, String path, long mode, long mtime, 719 int token, IBackupManager callbackBinder) throws RemoteException { 720 long ident = Binder.clearCallingIdentity(); 721 try { 722 BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime); 723 } catch (IOException e) { 724 throw new RuntimeException(e); 725 } finally { 726 // Ensure that any side-effect SharedPreferences writes have landed 727 waitForSharedPrefs(); 728 729 Binder.restoreCallingIdentity(ident); 730 try { 731 callbackBinder.opComplete(token); 732 } catch (RemoteException e) { 733 // we'll time out anyway, so we're safe 734 } 735 } 736 } 737 738 @Override doRestoreFinished(int token, IBackupManager callbackBinder)739 public void doRestoreFinished(int token, IBackupManager callbackBinder) { 740 long ident = Binder.clearCallingIdentity(); 741 try { 742 BackupAgent.this.onRestoreFinished(); 743 } finally { 744 // Ensure that any side-effect SharedPreferences writes have landed 745 waitForSharedPrefs(); 746 747 Binder.restoreCallingIdentity(ident); 748 try { 749 callbackBinder.opComplete(token); 750 } catch (RemoteException e) { 751 // we'll time out anyway, so we're safe 752 } 753 } 754 } 755 756 @Override fail(String message)757 public void fail(String message) { 758 getHandler().post(new FailRunnable(message)); 759 } 760 } 761 762 static class FailRunnable implements Runnable { 763 private String mMessage; 764 FailRunnable(String message)765 FailRunnable(String message) { 766 mMessage = message; 767 } 768 769 @Override run()770 public void run() { 771 throw new IllegalStateException(mMessage); 772 } 773 } 774 } 775