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