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