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