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 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 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 588 public void onRestoreFinished() { 589 } 590 591 // ----- Core implementation ----- 592 593 /** @hide */ 594 public final IBinder onBind() { 595 return mBinder; 596 } 597 598 private final IBinder mBinder = new BackupServiceBinder().asBinder(); 599 600 /** @hide */ 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 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 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 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 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 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 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 765 FailRunnable(String message) { 766 mMessage = message; 767 } 768 769 @Override 770 public void run() { 771 throw new IllegalStateException(mMessage); 772 } 773 } 774 } 775