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