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