BackupAgent.java revision cebb55ea9d98dc7ba93eed520b874ac24ff96800
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.app.backup; 18 19import android.app.IBackupAgent; 20import android.app.QueuedWork; 21import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags; 22import android.content.Context; 23import android.content.ContextWrapper; 24import android.content.pm.ApplicationInfo; 25import android.os.Binder; 26import android.os.Handler; 27import android.os.IBinder; 28import android.os.Looper; 29import android.os.ParcelFileDescriptor; 30import android.os.Process; 31import android.os.RemoteException; 32import android.system.ErrnoException; 33import android.system.Os; 34import android.system.OsConstants; 35import android.system.StructStat; 36import android.util.ArraySet; 37import android.util.Log; 38 39import libcore.io.IoUtils; 40 41import org.xmlpull.v1.XmlPullParserException; 42 43import java.io.File; 44import java.io.FileOutputStream; 45import java.io.IOException; 46import java.util.Collection; 47import java.util.LinkedList; 48import java.util.Map; 49import java.util.Set; 50import java.util.concurrent.CountDownLatch; 51 52/** 53 * Provides the central interface between an 54 * application and Android's data backup infrastructure. An application that wishes 55 * to participate in the backup and restore mechanism will declare a subclass of 56 * {@link android.app.backup.BackupAgent}, implement the 57 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} 58 * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods, 59 * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via 60 * the <code> 61 * <a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> 62 * tag's {@code android:backupAgent} attribute. 63 * 64 * <div class="special reference"> 65 * <h3>Developer Guides</h3> 66 * <p>For more information about using BackupAgent, read the 67 * <a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a> developer guide.</p></div> 68 * 69 * <h3>Basic Operation</h3> 70 * <p> 71 * When the application makes changes to data that it wishes to keep backed up, 72 * it should call the 73 * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method. 74 * This notifies the Android Backup Manager that the application needs an opportunity 75 * to update its backup image. The Backup Manager, in turn, schedules a 76 * backup pass to be performed at an opportune time. 77 * <p> 78 * Restore operations are typically performed only when applications are first 79 * installed on a device. At that time, the operating system checks to see whether 80 * there is a previously-saved data set available for the application being installed, and if so, 81 * begins an immediate restore pass to deliver the backup data as part of the installation 82 * process. 83 * <p> 84 * When a backup or restore pass is run, the application's process is launched 85 * (if not already running), the manifest-declared backup agent class (in the {@code 86 * android:backupAgent} attribute) is instantiated within 87 * that process, and the agent's {@link #onCreate()} method is invoked. This prepares the 88 * agent instance to run the actual backup or restore logic. At this point the 89 * agent's 90 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or 91 * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be 92 * invoked as appropriate for the operation being performed. 93 * <p> 94 * A backup data set consists of one or more "entities," flattened binary data 95 * records that are each identified with a key string unique within the data set. Adding a 96 * record to the active data set or updating an existing record is done by simply 97 * writing new entity data under the desired key. Deleting an entity from the data set 98 * is done by writing an entity under that key with header specifying a negative data 99 * size, and no actual entity data. 100 * <p> 101 * <b>Helper Classes</b> 102 * <p> 103 * An extensible agent based on convenient helper classes is available in 104 * {@link android.app.backup.BackupAgentHelper}. That class is particularly 105 * suited to handling of simple file or {@link android.content.SharedPreferences} 106 * backup and restore. 107 * <p> 108 * <b>Threading</b> 109 * <p> 110 * The constructor, as well as {@link #onCreate()} and {@link #onDestroy()} lifecycle callbacks run 111 * on the main thread (UI thread) of the application that implements the BackupAgent. 112 * The data-handling callbacks: 113 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()}, 114 * {@link #onFullBackup(FullBackupDataOutput)}, 115 * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()}, 116 * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()}, 117 * {@link #onRestoreFinished()}, and {@link #onQuotaExceeded(long, long) onQuotaExceeded()} 118 * run on binder pool threads. 119 * 120 * @see android.app.backup.BackupManager 121 * @see android.app.backup.BackupAgentHelper 122 * @see android.app.backup.BackupDataInput 123 * @see android.app.backup.BackupDataOutput 124 */ 125public abstract class BackupAgent extends ContextWrapper { 126 private static final String TAG = "BackupAgent"; 127 private static final boolean DEBUG = false; 128 129 /** @hide */ 130 public static final int TYPE_EOF = 0; 131 132 /** 133 * During a full restore, indicates that the file system object being restored 134 * is an ordinary file. 135 */ 136 public static final int TYPE_FILE = 1; 137 138 /** 139 * During a full restore, indicates that the file system object being restored 140 * is a directory. 141 */ 142 public static final int TYPE_DIRECTORY = 2; 143 144 /** @hide */ 145 public static final int TYPE_SYMLINK = 3; 146 147 /** 148 * Flag for {@link BackupDataOutput#getTransportFlags()} and 149 * {@link FullBackupDataOutput#getTransportFlags()} only. 150 * 151 * <p>The transport has client-side encryption enabled. i.e., the user's backup is encrypted 152 * with a key known only to the device, and not to the remote storage solution where the backup 153 * data is stored. The key may be synced to a remote trusted hardware module if it has 154 * protections equivalent to those described in the 155 * <a href="https://developer.android.com/preview/features/security/ckv-whitepaper.html">Google 156 * Cloud Key Vault Service whitepaper</a>. Having direct access to the trusted hardware module 157 * must be insufficient to decrypt the user's backup data. 158 * 159 * <p>The backup data itself must be encrypted using an AES/GCM/NoPadding cipher. The key 160 * material must be randomly generated using {@link java.security.SecureRandom}, and must have 161 * at least 256 bits of entropy. 162 */ 163 public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1; 164 165 /** 166 * Flag for {@link BackupDataOutput#getTransportFlags()} and 167 * {@link FullBackupDataOutput#getTransportFlags()} only. 168 * 169 * <p>The transport is for a device-to-device transfer. There is no third party or intermediate 170 * storage. The user's backup data is sent directly to another device over e.g., USB or WiFi. 171 */ 172 public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2; 173 174 /** 175 * Flag for {@link BackupDataOutput#getTransportFlags()} and 176 * {@link FullBackupDataOutput#getTransportFlags()} only. 177 * 178 * <p>Used for internal testing only. Do not check this flag in production code. 179 * 180 * @hide 181 */ 182 public static final int FLAG_FAKE_CLIENT_SIDE_ENCRYPTION_ENABLED = 1 << 31; 183 184 Handler mHandler = null; 185 186 Handler getHandler() { 187 if (mHandler == null) { 188 mHandler = new Handler(Looper.getMainLooper()); 189 } 190 return mHandler; 191 } 192 193 class SharedPrefsSynchronizer implements Runnable { 194 public final CountDownLatch mLatch = new CountDownLatch(1); 195 196 @Override 197 public void run() { 198 QueuedWork.waitToFinish(); 199 mLatch.countDown(); 200 } 201 }; 202 203 // Syncing shared preferences deferred writes needs to happen on the main looper thread 204 private void waitForSharedPrefs() { 205 Handler h = getHandler(); 206 final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer(); 207 h.postAtFrontOfQueue(s); 208 try { 209 s.mLatch.await(); 210 } catch (InterruptedException e) { /* ignored */ } 211 } 212 213 214 public BackupAgent() { 215 super(null); 216 } 217 218 /** 219 * Provided as a convenience for agent implementations that need an opportunity 220 * to do one-time initialization before the actual backup or restore operation 221 * is begun. 222 * <p> 223 */ 224 public void onCreate() { 225 } 226 227 /** 228 * Provided as a convenience for agent implementations that need to do some 229 * sort of shutdown process after backup or restore is completed. 230 * <p> 231 * Agents do not need to override this method. 232 */ 233 public void onDestroy() { 234 } 235 236 /** 237 * The application is being asked to write any data changed since the last 238 * time it performed a backup operation. The state data recorded during the 239 * last backup pass is provided in the <code>oldState</code> file 240 * descriptor. If <code>oldState</code> is <code>null</code>, no old state 241 * is available and the application should perform a full backup. In both 242 * cases, a representation of the final backup state after this pass should 243 * be written to the file pointed to by the file descriptor wrapped in 244 * <code>newState</code>. 245 * <p> 246 * Each entity written to the {@link android.app.backup.BackupDataOutput} 247 * <code>data</code> stream will be transmitted 248 * over the current backup transport and stored in the remote data set under 249 * the key supplied as part of the entity. Writing an entity with a negative 250 * data size instructs the transport to delete whatever entity currently exists 251 * under that key from the remote data set. 252 * 253 * @param oldState An open, read-only ParcelFileDescriptor pointing to the 254 * last backup state provided by the application. May be 255 * <code>null</code>, in which case no prior state is being 256 * provided and the application should perform a full backup. 257 * @param data A structured wrapper around an open, read/write 258 * file descriptor pointing to the backup data destination. 259 * Typically the application will use backup helper classes to 260 * write to this file. 261 * @param newState An open, read/write ParcelFileDescriptor pointing to an 262 * empty file. The application should record the final backup 263 * state here after writing the requested data to the <code>data</code> 264 * output stream. 265 */ 266 public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, 267 ParcelFileDescriptor newState) throws IOException; 268 269 /** 270 * The application is being restored from backup and should replace any 271 * existing data with the contents of the backup. The backup data is 272 * provided through the <code>data</code> parameter. Once 273 * the restore is finished, the application should write a representation of 274 * the final state to the <code>newState</code> file descriptor. 275 * <p> 276 * The application is responsible for properly erasing its old data and 277 * replacing it with the data supplied to this method. No "clear user data" 278 * operation will be performed automatically by the operating system. The 279 * exception to this is in the case of a failed restore attempt: if 280 * onRestore() throws an exception, the OS will assume that the 281 * application's data may now be in an incoherent state, and will clear it 282 * before proceeding. 283 * 284 * @param data A structured wrapper around an open, read-only 285 * file descriptor pointing to a full snapshot of the 286 * application's data. The application should consume every 287 * entity represented in this data stream. 288 * @param appVersionCode The value of the <a 289 * href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code 290 * android:versionCode}</a> manifest attribute, 291 * from the application that backed up this particular data set. This 292 * makes it possible for an application's agent to distinguish among any 293 * possible older data versions when asked to perform the restore 294 * operation. 295 * @param newState An open, read/write ParcelFileDescriptor pointing to an 296 * empty file. The application should record the final backup 297 * state here after restoring its data from the <code>data</code> stream. 298 * When a full-backup dataset is being restored, this will be <code>null</code>. 299 */ 300 public abstract void onRestore(BackupDataInput data, int appVersionCode, 301 ParcelFileDescriptor newState) throws IOException; 302 303 /** 304 * New version of {@link #onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor)} 305 * that handles a long app version code. Default implementation casts the version code to 306 * an int and calls {@link #onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor)}. 307 */ 308 public void onRestore(BackupDataInput data, long appVersionCode, 309 ParcelFileDescriptor newState) 310 throws IOException { 311 onRestore(data, (int) appVersionCode, newState); 312 } 313 314 /** 315 * The application is having its entire file system contents backed up. {@code data} 316 * points to the backup destination, and the app has the opportunity to choose which 317 * files are to be stored. To commit a file as part of the backup, call the 318 * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method. After all file 319 * data is written to the output, the agent returns from this method and the backup 320 * operation concludes. 321 * 322 * <p>Certain parts of the app's data are never backed up even if the app explicitly 323 * sends them to the output: 324 * 325 * <ul> 326 * <li>The contents of the {@link #getCacheDir()} directory</li> 327 * <li>The contents of the {@link #getCodeCacheDir()} directory</li> 328 * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li> 329 * <li>The contents of the app's shared library directory</li> 330 * </ul> 331 * 332 * <p>The default implementation of this method backs up the entirety of the 333 * application's "owned" file system trees to the output other than the few exceptions 334 * listed above. Apps only need to override this method if they need to impose special 335 * limitations on which files are being stored beyond the control that 336 * {@link #getNoBackupFilesDir()} offers. 337 * Alternatively they can provide an xml resource to specify what data to include or exclude. 338 * 339 * 340 * @param data A structured wrapper pointing to the backup destination. 341 * @throws IOException 342 * 343 * @see Context#getNoBackupFilesDir() 344 * @see ApplicationInfo#fullBackupContent 345 * @see #fullBackupFile(File, FullBackupDataOutput) 346 * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) 347 */ 348 public void onFullBackup(FullBackupDataOutput data) throws IOException { 349 FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this); 350 if (!backupScheme.isFullBackupContentEnabled()) { 351 return; 352 } 353 354 Map<String, Set<PathWithRequiredFlags>> manifestIncludeMap; 355 ArraySet<PathWithRequiredFlags> manifestExcludeSet; 356 try { 357 manifestIncludeMap = 358 backupScheme.maybeParseAndGetCanonicalIncludePaths(); 359 manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths(); 360 } catch (IOException | XmlPullParserException e) { 361 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { 362 Log.v(FullBackup.TAG_XML_PARSER, 363 "Exception trying to parse fullBackupContent xml file!" 364 + " Aborting full backup.", e); 365 } 366 return; 367 } 368 369 final String packageName = getPackageName(); 370 final ApplicationInfo appInfo = getApplicationInfo(); 371 372 // System apps have control over where their default storage context 373 // is pointed, so we're always explicit when building paths. 374 final Context ceContext = createCredentialProtectedStorageContext(); 375 final String rootDir = ceContext.getDataDir().getCanonicalPath(); 376 final String filesDir = ceContext.getFilesDir().getCanonicalPath(); 377 final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath(); 378 final String databaseDir = ceContext.getDatabasePath("foo").getParentFile() 379 .getCanonicalPath(); 380 final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile() 381 .getCanonicalPath(); 382 final String cacheDir = ceContext.getCacheDir().getCanonicalPath(); 383 final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath(); 384 385 final Context deContext = createDeviceProtectedStorageContext(); 386 final String deviceRootDir = deContext.getDataDir().getCanonicalPath(); 387 final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath(); 388 final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath(); 389 final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile() 390 .getCanonicalPath(); 391 final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo") 392 .getParentFile().getCanonicalPath(); 393 final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath(); 394 final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath(); 395 396 final String libDir = (appInfo.nativeLibraryDir != null) 397 ? new File(appInfo.nativeLibraryDir).getCanonicalPath() 398 : null; 399 400 // Maintain a set of excluded directories so that as we traverse the tree we know we're not 401 // going places we don't expect, and so the manifest includes can't take precedence over 402 // what the framework decides is not to be included. 403 final ArraySet<String> traversalExcludeSet = new ArraySet<String>(); 404 405 // Add the directories we always exclude. 406 traversalExcludeSet.add(filesDir); 407 traversalExcludeSet.add(noBackupDir); 408 traversalExcludeSet.add(databaseDir); 409 traversalExcludeSet.add(sharedPrefsDir); 410 traversalExcludeSet.add(cacheDir); 411 traversalExcludeSet.add(codeCacheDir); 412 413 traversalExcludeSet.add(deviceFilesDir); 414 traversalExcludeSet.add(deviceNoBackupDir); 415 traversalExcludeSet.add(deviceDatabaseDir); 416 traversalExcludeSet.add(deviceSharedPrefsDir); 417 traversalExcludeSet.add(deviceCacheDir); 418 traversalExcludeSet.add(deviceCodeCacheDir); 419 420 if (libDir != null) { 421 traversalExcludeSet.add(libDir); 422 } 423 424 // Root dir first. 425 applyXmlFiltersAndDoFullBackupForDomain( 426 packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap, 427 manifestExcludeSet, traversalExcludeSet, data); 428 traversalExcludeSet.add(rootDir); 429 430 applyXmlFiltersAndDoFullBackupForDomain( 431 packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap, 432 manifestExcludeSet, traversalExcludeSet, data); 433 traversalExcludeSet.add(deviceRootDir); 434 435 // Data dir next. 436 traversalExcludeSet.remove(filesDir); 437 applyXmlFiltersAndDoFullBackupForDomain( 438 packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap, 439 manifestExcludeSet, traversalExcludeSet, data); 440 traversalExcludeSet.add(filesDir); 441 442 traversalExcludeSet.remove(deviceFilesDir); 443 applyXmlFiltersAndDoFullBackupForDomain( 444 packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap, 445 manifestExcludeSet, traversalExcludeSet, data); 446 traversalExcludeSet.add(deviceFilesDir); 447 448 // Database directory. 449 traversalExcludeSet.remove(databaseDir); 450 applyXmlFiltersAndDoFullBackupForDomain( 451 packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap, 452 manifestExcludeSet, traversalExcludeSet, data); 453 traversalExcludeSet.add(databaseDir); 454 455 traversalExcludeSet.remove(deviceDatabaseDir); 456 applyXmlFiltersAndDoFullBackupForDomain( 457 packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap, 458 manifestExcludeSet, traversalExcludeSet, data); 459 traversalExcludeSet.add(deviceDatabaseDir); 460 461 // SharedPrefs. 462 traversalExcludeSet.remove(sharedPrefsDir); 463 applyXmlFiltersAndDoFullBackupForDomain( 464 packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap, 465 manifestExcludeSet, traversalExcludeSet, data); 466 traversalExcludeSet.add(sharedPrefsDir); 467 468 traversalExcludeSet.remove(deviceSharedPrefsDir); 469 applyXmlFiltersAndDoFullBackupForDomain( 470 packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap, 471 manifestExcludeSet, traversalExcludeSet, data); 472 traversalExcludeSet.add(deviceSharedPrefsDir); 473 474 // getExternalFilesDir() location associated with this app. Technically there should 475 // not be any files here if the app does not properly have permission to access 476 // external storage, but edge cases happen. fullBackupFileTree() catches 477 // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and 478 // we know a priori that processes running as the system UID are not permitted to 479 // access external storage, so we check for that as well to avoid nastygrams in 480 // the log. 481 if (Process.myUid() != Process.SYSTEM_UID) { 482 File efLocation = getExternalFilesDir(null); 483 if (efLocation != null) { 484 applyXmlFiltersAndDoFullBackupForDomain( 485 packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap, 486 manifestExcludeSet, traversalExcludeSet, data); 487 } 488 489 } 490 } 491 492 /** 493 * Notification that the application's current backup operation causes it to exceed 494 * the maximum size permitted by the transport. The ongoing backup operation is 495 * halted and rolled back: any data that had been stored by a previous backup operation 496 * is still intact. Typically the quota-exceeded state will be detected before any data 497 * is actually transmitted over the network. 498 * 499 * <p>The {@code quotaBytes} value is the total data size currently permitted for this 500 * application. If desired, the application can use this as a hint for determining 501 * how much data to store. For example, a messaging application might choose to 502 * store only the newest messages, dropping enough older content to stay under 503 * the quota. 504 * 505 * <p class="note">Note that the maximum quota for the application can change over 506 * time. In particular, in the future the quota may grow. Applications that adapt 507 * to the quota when deciding what data to store should be aware of this and implement 508 * their data storage mechanisms in a way that can take advantage of additional 509 * quota. 510 * 511 * @param backupDataBytes The amount of data measured while initializing the backup 512 * operation, if the total exceeds the app's alloted quota. If initial measurement 513 * suggested that the data would fit but then too much data was actually submitted 514 * as part of the operation, then this value is the amount of data that had been 515 * streamed into the transport at the time the quota was reached. 516 * @param quotaBytes The maximum data size that the transport currently permits 517 * this application to store as a backup. 518 */ 519 public void onQuotaExceeded(long backupDataBytes, long quotaBytes) { 520 } 521 522 /** 523 * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>. 524 * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path 525 * is a directory, but only if all the required flags of the include rule are satisfied by 526 * the transport. 527 */ 528 private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken, 529 Map<String, Set<PathWithRequiredFlags>> includeMap, 530 ArraySet<PathWithRequiredFlags> filterSet, ArraySet<String> traversalExcludeSet, 531 FullBackupDataOutput data) throws IOException { 532 if (includeMap == null || includeMap.size() == 0) { 533 // Do entire sub-tree for the provided token. 534 fullBackupFileTree(packageName, domainToken, 535 FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken), 536 filterSet, traversalExcludeSet, data); 537 } else if (includeMap.get(domainToken) != null) { 538 // This will be null if the xml parsing didn't yield any rules for 539 // this domain (there may still be rules for other domains). 540 for (PathWithRequiredFlags includeFile : includeMap.get(domainToken)) { 541 if (areIncludeRequiredTransportFlagsSatisfied(includeFile.getRequiredFlags(), 542 data.getTransportFlags())) { 543 fullBackupFileTree(packageName, domainToken, includeFile.getPath(), filterSet, 544 traversalExcludeSet, data); 545 } 546 } 547 } 548 } 549 550 private boolean areIncludeRequiredTransportFlagsSatisfied(int includeFlags, 551 int transportFlags) { 552 // all bits that are set in includeFlags must also be set in transportFlags 553 return (transportFlags & includeFlags) == includeFlags; 554 } 555 556 /** 557 * Write an entire file as part of a full-backup operation. The file's contents 558 * will be delivered to the backup destination along with the metadata necessary 559 * to place it with the proper location and permissions on the device where the 560 * data is restored. 561 * 562 * <p class="note">Attempting to back up files in directories that are ignored by 563 * the backup system will have no effect. For example, if the app calls this method 564 * with a file inside the {@link #getNoBackupFilesDir()} directory, it will be ignored. 565 * See {@link #onFullBackup(FullBackupDataOutput)} for details on what directories 566 * are excluded from backups. 567 * 568 * @param file The file to be backed up. The file must exist and be readable by 569 * the caller. 570 * @param output The destination to which the backed-up file data will be sent. 571 */ 572 public final void fullBackupFile(File file, FullBackupDataOutput output) { 573 // Look up where all of our various well-defined dir trees live on this device 574 final String rootDir; 575 final String filesDir; 576 final String nbFilesDir; 577 final String dbDir; 578 final String spDir; 579 final String cacheDir; 580 final String codeCacheDir; 581 final String deviceRootDir; 582 final String deviceFilesDir; 583 final String deviceNbFilesDir; 584 final String deviceDbDir; 585 final String deviceSpDir; 586 final String deviceCacheDir; 587 final String deviceCodeCacheDir; 588 final String libDir; 589 590 String efDir = null; 591 String filePath; 592 593 ApplicationInfo appInfo = getApplicationInfo(); 594 595 try { 596 // System apps have control over where their default storage context 597 // is pointed, so we're always explicit when building paths. 598 final Context ceContext = createCredentialProtectedStorageContext(); 599 rootDir = ceContext.getDataDir().getCanonicalPath(); 600 filesDir = ceContext.getFilesDir().getCanonicalPath(); 601 nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath(); 602 dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath(); 603 spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath(); 604 cacheDir = ceContext.getCacheDir().getCanonicalPath(); 605 codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath(); 606 607 final Context deContext = createDeviceProtectedStorageContext(); 608 deviceRootDir = deContext.getDataDir().getCanonicalPath(); 609 deviceFilesDir = deContext.getFilesDir().getCanonicalPath(); 610 deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath(); 611 deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath(); 612 deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile() 613 .getCanonicalPath(); 614 deviceCacheDir = deContext.getCacheDir().getCanonicalPath(); 615 deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath(); 616 617 libDir = (appInfo.nativeLibraryDir == null) 618 ? null 619 : new File(appInfo.nativeLibraryDir).getCanonicalPath(); 620 621 // may or may not have external files access to attempt backup/restore there 622 if (Process.myUid() != Process.SYSTEM_UID) { 623 File efLocation = getExternalFilesDir(null); 624 if (efLocation != null) { 625 efDir = efLocation.getCanonicalPath(); 626 } 627 } 628 629 // Now figure out which well-defined tree the file is placed in, working from 630 // most to least specific. We also specifically exclude the lib, cache, 631 // and code_cache dirs. 632 filePath = file.getCanonicalPath(); 633 } catch (IOException e) { 634 Log.w(TAG, "Unable to obtain canonical paths"); 635 return; 636 } 637 638 if (filePath.startsWith(cacheDir) 639 || filePath.startsWith(codeCacheDir) 640 || filePath.startsWith(nbFilesDir) 641 || filePath.startsWith(deviceCacheDir) 642 || filePath.startsWith(deviceCodeCacheDir) 643 || filePath.startsWith(deviceNbFilesDir) 644 || filePath.startsWith(libDir)) { 645 Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up"); 646 return; 647 } 648 649 final String domain; 650 String rootpath = null; 651 if (filePath.startsWith(dbDir)) { 652 domain = FullBackup.DATABASE_TREE_TOKEN; 653 rootpath = dbDir; 654 } else if (filePath.startsWith(spDir)) { 655 domain = FullBackup.SHAREDPREFS_TREE_TOKEN; 656 rootpath = spDir; 657 } else if (filePath.startsWith(filesDir)) { 658 domain = FullBackup.FILES_TREE_TOKEN; 659 rootpath = filesDir; 660 } else if (filePath.startsWith(rootDir)) { 661 domain = FullBackup.ROOT_TREE_TOKEN; 662 rootpath = rootDir; 663 } else if (filePath.startsWith(deviceDbDir)) { 664 domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN; 665 rootpath = deviceDbDir; 666 } else if (filePath.startsWith(deviceSpDir)) { 667 domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN; 668 rootpath = deviceSpDir; 669 } else if (filePath.startsWith(deviceFilesDir)) { 670 domain = FullBackup.DEVICE_FILES_TREE_TOKEN; 671 rootpath = deviceFilesDir; 672 } else if (filePath.startsWith(deviceRootDir)) { 673 domain = FullBackup.DEVICE_ROOT_TREE_TOKEN; 674 rootpath = deviceRootDir; 675 } else if ((efDir != null) && filePath.startsWith(efDir)) { 676 domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN; 677 rootpath = efDir; 678 } else { 679 Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping"); 680 return; 681 } 682 683 // And now that we know where it lives, semantically, back it up appropriately 684 // In the measurement case, backupToTar() updates the size in output and returns 685 // without transmitting any file data. 686 if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain 687 + " rootpath=" + rootpath); 688 689 FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output); 690 } 691 692 /** 693 * Scan the dir tree (if it actually exists) and process each entry we find. If the 694 * 'excludes' parameters are non-null, they are consulted each time a new file system entity 695 * is visited to see whether that entity (and its subtree, if appropriate) should be 696 * omitted from the backup process. 697 * 698 * @param systemExcludes An optional list of excludes. 699 * @hide 700 */ 701 protected final void fullBackupFileTree(String packageName, String domain, String startingPath, 702 ArraySet<PathWithRequiredFlags> manifestExcludes, 703 ArraySet<String> systemExcludes, 704 FullBackupDataOutput output) { 705 // Pull out the domain and set it aside to use when making the tarball. 706 String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain); 707 if (domainPath == null) { 708 // Should never happen. 709 return; 710 } 711 712 File rootFile = new File(startingPath); 713 if (rootFile.exists()) { 714 LinkedList<File> scanQueue = new LinkedList<File>(); 715 scanQueue.add(rootFile); 716 717 while (scanQueue.size() > 0) { 718 File file = scanQueue.remove(0); 719 String filePath; 720 try { 721 // Ignore things that aren't "real" files or dirs 722 StructStat stat = Os.lstat(file.getPath()); 723 if (!OsConstants.S_ISREG(stat.st_mode) 724 && !OsConstants.S_ISDIR(stat.st_mode)) { 725 if (DEBUG) Log.i(TAG, "Not a file/dir (skipping)!: " + file); 726 continue; 727 } 728 729 // For all other verification, look at the canonicalized path 730 filePath = file.getCanonicalPath(); 731 732 // prune this subtree? 733 if (manifestExcludes != null 734 && manifestExcludesContainFilePath(manifestExcludes, filePath)) { 735 continue; 736 } 737 if (systemExcludes != null && systemExcludes.contains(filePath)) { 738 continue; 739 } 740 741 // If it's a directory, enqueue its contents for scanning. 742 if (OsConstants.S_ISDIR(stat.st_mode)) { 743 File[] contents = file.listFiles(); 744 if (contents != null) { 745 for (File entry : contents) { 746 scanQueue.add(0, entry); 747 } 748 } 749 } 750 } catch (IOException e) { 751 if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file); 752 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { 753 Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file); 754 } 755 continue; 756 } catch (ErrnoException e) { 757 if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e); 758 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { 759 Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e); 760 } 761 continue; 762 } 763 764 // Finally, back this file up (or measure it) before proceeding 765 FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output); 766 } 767 } 768 } 769 770 private boolean manifestExcludesContainFilePath( 771 ArraySet<PathWithRequiredFlags> manifestExcludes, String filePath) { 772 for (PathWithRequiredFlags exclude : manifestExcludes) { 773 String excludePath = exclude.getPath(); 774 if (excludePath != null && excludePath.equals(filePath)) { 775 return true; 776 } 777 } 778 return false; 779 } 780 781 /** 782 * Handle the data delivered via the given file descriptor during a full restore 783 * operation. The agent is given the path to the file's original location as well 784 * as its size and metadata. 785 * <p> 786 * The file descriptor can only be read for {@code size} bytes; attempting to read 787 * more data has undefined behavior. 788 * <p> 789 * The default implementation creates the destination file/directory and populates it 790 * with the data from the file descriptor, then sets the file's access mode and 791 * modification time to match the restore arguments. 792 * 793 * @param data A read-only file descriptor from which the agent can read {@code size} 794 * bytes of file data. 795 * @param size The number of bytes of file content to be restored to the given 796 * destination. If the file system object being restored is a directory, {@code size} 797 * will be zero. 798 * @param destination The File on disk to be restored with the given data. 799 * @param type The kind of file system object being restored. This will be either 800 * {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}. 801 * @param mode The access mode to be assigned to the destination after its data is 802 * written. This is in the standard format used by {@code chmod()}. 803 * @param mtime The modification time of the file when it was backed up, suitable to 804 * be assigned to the file after its data is written. 805 * @throws IOException 806 */ 807 public void onRestoreFile(ParcelFileDescriptor data, long size, 808 File destination, int type, long mode, long mtime) 809 throws IOException { 810 811 final boolean accept = isFileEligibleForRestore(destination); 812 // If we don't accept the file, consume the bytes from the pipe anyway. 813 FullBackup.restoreFile(data, size, type, mode, mtime, accept ? destination : null); 814 } 815 816 private boolean isFileEligibleForRestore(File destination) throws IOException { 817 FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this); 818 if (!bs.isFullBackupContentEnabled()) { 819 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { 820 Log.v(FullBackup.TAG_XML_PARSER, 821 "onRestoreFile \"" + destination.getCanonicalPath() 822 + "\" : fullBackupContent not enabled for " + getPackageName()); 823 } 824 return false; 825 } 826 827 Map<String, Set<PathWithRequiredFlags>> includes = null; 828 ArraySet<PathWithRequiredFlags> excludes = null; 829 final String destinationCanonicalPath = destination.getCanonicalPath(); 830 try { 831 includes = bs.maybeParseAndGetCanonicalIncludePaths(); 832 excludes = bs.maybeParseAndGetCanonicalExcludePaths(); 833 } catch (XmlPullParserException e) { 834 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { 835 Log.v(FullBackup.TAG_XML_PARSER, 836 "onRestoreFile \"" + destinationCanonicalPath 837 + "\" : Exception trying to parse fullBackupContent xml file!" 838 + " Aborting onRestoreFile.", e); 839 } 840 return false; 841 } 842 843 if (excludes != null && 844 isFileSpecifiedInPathList(destination, excludes)) { 845 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { 846 Log.v(FullBackup.TAG_XML_PARSER, 847 "onRestoreFile: \"" + destinationCanonicalPath + "\": listed in" 848 + " excludes; skipping."); 849 } 850 return false; 851 } 852 853 if (includes != null && !includes.isEmpty()) { 854 // Rather than figure out the <include/> domain based on the path (a lot of code, and 855 // it's a small list), we'll go through and look for it. 856 boolean explicitlyIncluded = false; 857 for (Set<PathWithRequiredFlags> domainIncludes : includes.values()) { 858 explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes); 859 if (explicitlyIncluded) { 860 break; 861 } 862 } 863 if (!explicitlyIncluded) { 864 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { 865 Log.v(FullBackup.TAG_XML_PARSER, 866 "onRestoreFile: Trying to restore \"" 867 + destinationCanonicalPath + "\" but it isn't specified" 868 + " in the included files; skipping."); 869 } 870 return false; 871 } 872 } 873 return true; 874 } 875 876 /** 877 * @return True if the provided file is either directly in the provided list, or the provided 878 * file is within a directory in the list. 879 */ 880 private boolean isFileSpecifiedInPathList(File file, 881 Collection<PathWithRequiredFlags> canonicalPathList) throws IOException { 882 for (PathWithRequiredFlags canonical : canonicalPathList) { 883 String canonicalPath = canonical.getPath(); 884 File fileFromList = new File(canonicalPath); 885 if (fileFromList.isDirectory()) { 886 if (file.isDirectory()) { 887 // If they are both directories check exact equals. 888 return file.equals(fileFromList); 889 } else { 890 // O/w we have to check if the file is within the directory from the list. 891 return file.getCanonicalPath().startsWith(canonicalPath); 892 } 893 } else { 894 if (file.equals(fileFromList)) { 895 // Need to check the explicit "equals" so we don't end up with substrings. 896 return true; 897 } 898 } 899 } 900 return false; 901 } 902 903 /** 904 * Only specialized platform agents should overload this entry point to support 905 * restores to crazy non-app locations. 906 * @hide 907 */ 908 protected void onRestoreFile(ParcelFileDescriptor data, long size, 909 int type, String domain, String path, long mode, long mtime) 910 throws IOException { 911 String basePath = null; 912 913 if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type 914 + " domain=" + domain + " relpath=" + path + " mode=" + mode 915 + " mtime=" + mtime); 916 917 basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain); 918 if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) { 919 mode = -1; // < 0 is a token to skip attempting a chmod() 920 } 921 922 // Now that we've figured out where the data goes, send it on its way 923 if (basePath != null) { 924 // Canonicalize the nominal path and verify that it lies within the stated domain 925 File outFile = new File(basePath, path); 926 String outPath = outFile.getCanonicalPath(); 927 if (outPath.startsWith(basePath + File.separatorChar)) { 928 if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath); 929 onRestoreFile(data, size, outFile, type, mode, mtime); 930 return; 931 } else { 932 // Attempt to restore to a path outside the file's nominal domain. 933 if (DEBUG) { 934 Log.e(TAG, "Cross-domain restore attempt: " + outPath); 935 } 936 } 937 } 938 939 // Not a supported output location, or bad path: we need to consume the data 940 // anyway, so just use the default "copy the data out" implementation 941 // with a null destination. 942 if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]"); 943 FullBackup.restoreFile(data, size, type, mode, mtime, null); 944 } 945 946 /** 947 * The application's restore operation has completed. This method is called after 948 * all available data has been delivered to the application for restore (via either 949 * the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or 950 * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()} 951 * callbacks). This provides the app with a stable end-of-restore opportunity to 952 * perform any appropriate post-processing on the data that was just delivered. 953 * 954 * @see #onRestore(BackupDataInput, int, ParcelFileDescriptor) 955 * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) 956 */ 957 public void onRestoreFinished() { 958 } 959 960 // ----- Core implementation ----- 961 962 /** @hide */ 963 public final IBinder onBind() { 964 return mBinder; 965 } 966 967 private final IBinder mBinder = new BackupServiceBinder().asBinder(); 968 969 /** @hide */ 970 public void attach(Context context) { 971 attachBaseContext(context); 972 } 973 974 // ----- IBackupService binder interface ----- 975 private class BackupServiceBinder extends IBackupAgent.Stub { 976 private static final String TAG = "BackupServiceBinder"; 977 978 @Override 979 public void doBackup(ParcelFileDescriptor oldState, 980 ParcelFileDescriptor data, 981 ParcelFileDescriptor newState, 982 long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) 983 throws RemoteException { 984 // Ensure that we're running with the app's normal permission level 985 long ident = Binder.clearCallingIdentity(); 986 987 if (DEBUG) Log.v(TAG, "doBackup() invoked"); 988 BackupDataOutput output = new BackupDataOutput( 989 data.getFileDescriptor(), quotaBytes, transportFlags); 990 991 try { 992 BackupAgent.this.onBackup(oldState, output, newState); 993 } catch (IOException ex) { 994 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); 995 throw new RuntimeException(ex); 996 } catch (RuntimeException ex) { 997 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); 998 throw ex; 999 } finally { 1000 // Ensure that any SharedPreferences writes have landed after the backup, 1001 // in case the app code has side effects (since apps cannot provide this 1002 // guarantee themselves). 1003 waitForSharedPrefs(); 1004 1005 Binder.restoreCallingIdentity(ident); 1006 try { 1007 callbackBinder.opComplete(token, 0); 1008 } catch (RemoteException e) { 1009 // we'll time out anyway, so we're safe 1010 } 1011 1012 // Don't close the fd out from under the system service if this was local 1013 if (Binder.getCallingPid() != Process.myPid()) { 1014 IoUtils.closeQuietly(oldState); 1015 IoUtils.closeQuietly(data); 1016 IoUtils.closeQuietly(newState); 1017 } 1018 } 1019 } 1020 1021 @Override 1022 public void doRestore(ParcelFileDescriptor data, long appVersionCode, 1023 ParcelFileDescriptor newState, 1024 int token, IBackupManager callbackBinder) throws RemoteException { 1025 // Ensure that we're running with the app's normal permission level 1026 long ident = Binder.clearCallingIdentity(); 1027 1028 if (DEBUG) Log.v(TAG, "doRestore() invoked"); 1029 1030 // Ensure that any side-effect SharedPreferences writes have landed *before* 1031 // we may be about to rewrite the file out from underneath 1032 waitForSharedPrefs(); 1033 1034 BackupDataInput input = new BackupDataInput(data.getFileDescriptor()); 1035 try { 1036 BackupAgent.this.onRestore(input, appVersionCode, newState); 1037 } catch (IOException ex) { 1038 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex); 1039 throw new RuntimeException(ex); 1040 } catch (RuntimeException ex) { 1041 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex); 1042 throw ex; 1043 } finally { 1044 // And bring live SharedPreferences instances up to date 1045 reloadSharedPreferences(); 1046 1047 Binder.restoreCallingIdentity(ident); 1048 try { 1049 callbackBinder.opComplete(token, 0); 1050 } catch (RemoteException e) { 1051 // we'll time out anyway, so we're safe 1052 } 1053 1054 if (Binder.getCallingPid() != Process.myPid()) { 1055 IoUtils.closeQuietly(data); 1056 IoUtils.closeQuietly(newState); 1057 } 1058 } 1059 } 1060 1061 @Override 1062 public void doFullBackup(ParcelFileDescriptor data, 1063 long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) { 1064 // Ensure that we're running with the app's normal permission level 1065 long ident = Binder.clearCallingIdentity(); 1066 1067 if (DEBUG) Log.v(TAG, "doFullBackup() invoked"); 1068 1069 // Ensure that any SharedPreferences writes have landed *before* 1070 // we potentially try to back up the underlying files directly. 1071 waitForSharedPrefs(); 1072 1073 try { 1074 BackupAgent.this.onFullBackup(new FullBackupDataOutput( 1075 data, quotaBytes, transportFlags)); 1076 } catch (IOException ex) { 1077 Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); 1078 throw new RuntimeException(ex); 1079 } catch (RuntimeException ex) { 1080 Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); 1081 throw ex; 1082 } finally { 1083 // ... and then again after, as in the doBackup() case 1084 waitForSharedPrefs(); 1085 1086 // Send the EOD marker indicating that there is no more data 1087 // forthcoming from this agent. 1088 try { 1089 FileOutputStream out = new FileOutputStream(data.getFileDescriptor()); 1090 byte[] buf = new byte[4]; 1091 out.write(buf); 1092 } catch (IOException e) { 1093 Log.e(TAG, "Unable to finalize backup stream!"); 1094 } 1095 1096 Binder.restoreCallingIdentity(ident); 1097 try { 1098 callbackBinder.opComplete(token, 0); 1099 } catch (RemoteException e) { 1100 // we'll time out anyway, so we're safe 1101 } 1102 1103 if (Binder.getCallingPid() != Process.myPid()) { 1104 IoUtils.closeQuietly(data); 1105 } 1106 } 1107 } 1108 1109 public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder, 1110 int transportFlags) { 1111 // Ensure that we're running with the app's normal permission level 1112 final long ident = Binder.clearCallingIdentity(); 1113 FullBackupDataOutput measureOutput = 1114 new FullBackupDataOutput(quotaBytes, transportFlags); 1115 1116 waitForSharedPrefs(); 1117 try { 1118 BackupAgent.this.onFullBackup(measureOutput); 1119 } catch (IOException ex) { 1120 Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex); 1121 throw new RuntimeException(ex); 1122 } catch (RuntimeException ex) { 1123 Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex); 1124 throw ex; 1125 } finally { 1126 Binder.restoreCallingIdentity(ident); 1127 try { 1128 callbackBinder.opComplete(token, measureOutput.getSize()); 1129 } catch (RemoteException e) { 1130 // timeout, so we're safe 1131 } 1132 } 1133 } 1134 1135 @Override 1136 public void doRestoreFile(ParcelFileDescriptor data, long size, 1137 int type, String domain, String path, long mode, long mtime, 1138 int token, IBackupManager callbackBinder) throws RemoteException { 1139 long ident = Binder.clearCallingIdentity(); 1140 try { 1141 BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime); 1142 } catch (IOException e) { 1143 Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e); 1144 throw new RuntimeException(e); 1145 } finally { 1146 // Ensure that any side-effect SharedPreferences writes have landed 1147 waitForSharedPrefs(); 1148 // And bring live SharedPreferences instances up to date 1149 reloadSharedPreferences(); 1150 1151 Binder.restoreCallingIdentity(ident); 1152 try { 1153 callbackBinder.opComplete(token, 0); 1154 } catch (RemoteException e) { 1155 // we'll time out anyway, so we're safe 1156 } 1157 1158 if (Binder.getCallingPid() != Process.myPid()) { 1159 IoUtils.closeQuietly(data); 1160 } 1161 } 1162 } 1163 1164 @Override 1165 public void doRestoreFinished(int token, IBackupManager callbackBinder) { 1166 long ident = Binder.clearCallingIdentity(); 1167 try { 1168 BackupAgent.this.onRestoreFinished(); 1169 } catch (Exception e) { 1170 Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e); 1171 throw e; 1172 } finally { 1173 // Ensure that any side-effect SharedPreferences writes have landed 1174 waitForSharedPrefs(); 1175 1176 Binder.restoreCallingIdentity(ident); 1177 try { 1178 callbackBinder.opComplete(token, 0); 1179 } catch (RemoteException e) { 1180 // we'll time out anyway, so we're safe 1181 } 1182 } 1183 } 1184 1185 @Override 1186 public void fail(String message) { 1187 getHandler().post(new FailRunnable(message)); 1188 } 1189 1190 @Override 1191 public void doQuotaExceeded(long backupDataBytes, long quotaBytes) { 1192 long ident = Binder.clearCallingIdentity(); 1193 try { 1194 BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes); 1195 } catch (Exception e) { 1196 Log.d(TAG, "onQuotaExceeded(" + BackupAgent.this.getClass().getName() + ") threw", 1197 e); 1198 throw e; 1199 } finally { 1200 waitForSharedPrefs(); 1201 Binder.restoreCallingIdentity(ident); 1202 } 1203 } 1204 } 1205 1206 static class FailRunnable implements Runnable { 1207 private String mMessage; 1208 1209 FailRunnable(String message) { 1210 mMessage = message; 1211 } 1212 1213 @Override 1214 public void run() { 1215 throw new IllegalStateException(mMessage); 1216 } 1217 } 1218} 1219