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