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 getHandler()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 run()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 waitForSharedPrefs()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 BackupAgent()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 */ onCreate()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 */ onDestroy()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 */ onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)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 */ onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)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 */ onFullBackup(FullBackupDataOutput data)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 */ applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken, Map<String, Set<String>> includeMap, ArraySet<String> filterSet, ArraySet<String> traversalExcludeSet, FullBackupDataOutput data)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 */ fullBackupFile(File file, FullBackupDataOutput output)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 */ fullBackupFileTree(String packageName, String domain, String startingPath, ArraySet<String> manifestExcludes, ArraySet<String> systemExcludes, FullBackupDataOutput output)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 */ onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime)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 isFileEligibleForRestore(File destination)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 */ isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList)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 */ onRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime)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 */ onRestoreFinished()754 public void onRestoreFinished() { 755 } 756 757 // ----- Core implementation ----- 758 759 /** @hide */ onBind()760 public final IBinder onBind() { 761 return mBinder; 762 } 763 764 private final IBinder mBinder = new BackupServiceBinder().asBinder(); 765 766 /** @hide */ attach(Context context)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 doBackup(ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)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 doRestore(ParcelFileDescriptor data, int appVersionCode, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)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 doFullBackup(ParcelFileDescriptor data, int token, IBackupManager callbackBinder)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 doMeasureFullBackup(int token, IBackupManager callbackBinder)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 doRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime, int token, IBackupManager callbackBinder)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 doRestoreFinished(int token, IBackupManager callbackBinder)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 fail(String message)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 FailRunnable(String message)959 FailRunnable(String message) { 960 mMessage = message; 961 } 962 963 @Override run()964 public void run() { 965 throw new IllegalStateException(mMessage); 966 } 967 } 968 } 969