2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.app.backup;
19 import android.app.IBackupAgent;
20 import android.app.QueuedWork;
21 import android.content.Context;
22 import android.content.ContextWrapper;
23 import android.content.pm.ApplicationInfo;
24 import android.os.Binder;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.ParcelFileDescriptor;
29 import android.os.Process;
30 import android.os.RemoteException;
31 import android.system.ErrnoException;
32 import android.system.Os;
33 import android.system.OsConstants;
34 import android.system.StructStat;
35 import android.util.ArraySet;
36 import android.util.Log;
38 import libcore.io.IoUtils;
40 import org.xmlpull.v1.XmlPullParserException;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.util.Collection;
46 import java.util.LinkedList;
49 import java.util.concurrent.CountDownLatch;
52 * Provides the central interface between an
53 * application and Android's data backup infrastructure. An application that wishes
54 * to participate in the backup and restore mechanism will declare a subclass of
55 * {@link android.app.backup.BackupAgent}, implement the
56 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()}
57 * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods,
58 * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via
60 * <a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code>
61 * tag's {@code android:backupAgent} attribute.
63 * <div class="special reference">
64 * <h3>Developer Guides</h3>
65 * <p>For more information about using BackupAgent, read the
66 * <a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a> developer guide.</p></div>
68 * <h3>Basic Operation</h3>
70 * When the application makes changes to data that it wishes to keep backed up,
72 * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method.
73 * This notifies the Android Backup Manager that the application needs an opportunity
74 * to update its backup image. The Backup Manager, in turn, schedules a
75 * backup pass to be performed at an opportune time.
77 * Restore operations are typically performed only when applications are first
78 * installed on a device. At that time, the operating system checks to see whether
79 * there is a previously-saved data set available for the application being installed, and if so,
80 * begins an immediate restore pass to deliver the backup data as part of the installation
83 * When a backup or restore pass is run, the application's process is launched
84 * (if not already running), the manifest-declared backup agent class (in the {@code
85 * android:backupAgent} attribute) is instantiated within
86 * that process, and the agent's {@link #onCreate()} method is invoked. This prepares the
87 * agent instance to run the actual backup or restore logic. At this point the
89 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or
90 * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be
91 * invoked as appropriate for the operation being performed.
93 * A backup data set consists of one or more "entities," flattened binary data
94 * records that are each identified with a key string unique within the data set. Adding a
95 * record to the active data set or updating an existing record is done by simply
96 * writing new entity data under the desired key. Deleting an entity from the data set
97 * is done by writing an entity under that key with header specifying a negative data
98 * size, and no actual entity data.
100 * <b>Helper Classes</b>
102 * An extensible agent based on convenient helper classes is available in
103 * {@link android.app.backup.BackupAgentHelper}. That class is particularly
104 * suited to handling of simple file or {@link android.content.SharedPreferences}
105 * backup and restore.
107 * @see android.app.backup.BackupManager
108 * @see android.app.backup.BackupAgentHelper
109 * @see android.app.backup.BackupDataInput
110 * @see android.app.backup.BackupDataOutput
112 public abstract class BackupAgent extends ContextWrapper {
113 private static final String TAG = "BackupAgent";
114 private static final boolean DEBUG = false;
117 public static final int TYPE_EOF = 0;
120 * During a full restore, indicates that the file system object being restored
121 * is an ordinary file.
123 public static final int TYPE_FILE = 1;
126 * During a full restore, indicates that the file system object being restored
129 public static final int TYPE_DIRECTORY = 2;
132 public static final int TYPE_SYMLINK = 3;
134 Handler mHandler = null;
136 Handler getHandler() {
137 if (mHandler == null) {
138 mHandler = new Handler(Looper.getMainLooper());
143 class SharedPrefsSynchronizer implements Runnable {
144 public final CountDownLatch mLatch = new CountDownLatch(1);
148 QueuedWork.waitToFinish();
153 // Syncing shared preferences deferred writes needs to happen on the main looper thread
154 private void waitForSharedPrefs() {
155 Handler h = getHandler();
156 final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer();
157 h.postAtFrontOfQueue(s);
160 } catch (InterruptedException e) { /* ignored */ }
164 public BackupAgent() {
169 * Provided as a convenience for agent implementations that need an opportunity
170 * to do one-time initialization before the actual backup or restore operation
174 public void onCreate() {
178 * Provided as a convenience for agent implementations that need to do some
179 * sort of shutdown process after backup or restore is completed.
181 * Agents do not need to override this method.
183 public void onDestroy() {
187 * The application is being asked to write any data changed since the last
188 * time it performed a backup operation. The state data recorded during the
189 * last backup pass is provided in the <code>oldState</code> file
190 * descriptor. If <code>oldState</code> is <code>null</code>, no old state
191 * is available and the application should perform a full backup. In both
192 * cases, a representation of the final backup state after this pass should
193 * be written to the file pointed to by the file descriptor wrapped in
194 * <code>newState</code>.
196 * Each entity written to the {@link android.app.backup.BackupDataOutput}
197 * <code>data</code> stream will be transmitted
198 * over the current backup transport and stored in the remote data set under
199 * the key supplied as part of the entity. Writing an entity with a negative
200 * data size instructs the transport to delete whatever entity currently exists
201 * under that key from the remote data set.
203 * @param oldState An open, read-only ParcelFileDescriptor pointing to the
204 * last backup state provided by the application. May be
205 * <code>null</code>, in which case no prior state is being
206 * provided and the application should perform a full backup.
207 * @param data A structured wrapper around an open, read/write
208 * file descriptor pointing to the backup data destination.
209 * Typically the application will use backup helper classes to
210 * write to this file.
211 * @param newState An open, read/write ParcelFileDescriptor pointing to an
212 * empty file. The application should record the final backup
213 * state here after writing the requested data to the <code>data</code>
216 public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
217 ParcelFileDescriptor newState) throws IOException;
220 * The application is being restored from backup and should replace any
221 * existing data with the contents of the backup. The backup data is
222 * provided through the <code>data</code> parameter. Once
223 * the restore is finished, the application should write a representation of
224 * the final state to the <code>newState</code> file descriptor.
226 * The application is responsible for properly erasing its old data and
227 * replacing it with the data supplied to this method. No "clear user data"
228 * operation will be performed automatically by the operating system. The
229 * exception to this is in the case of a failed restore attempt: if
230 * onRestore() throws an exception, the OS will assume that the
231 * application's data may now be in an incoherent state, and will clear it
234 * @param data A structured wrapper around an open, read-only
235 * file descriptor pointing to a full snapshot of the
236 * application's data. The application should consume every
237 * entity represented in this data stream.
238 * @param appVersionCode The value of the <a
239 * href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code
240 * android:versionCode}</a> manifest attribute,
241 * from the application that backed up this particular data set. This
242 * makes it possible for an application's agent to distinguish among any
243 * possible older data versions when asked to perform the restore
245 * @param newState An open, read/write ParcelFileDescriptor pointing to an
246 * empty file. The application should record the final backup
247 * state here after restoring its data from the <code>data</code> stream.
248 * When a full-backup dataset is being restored, this will be <code>null</code>.
250 public abstract void onRestore(BackupDataInput data, int appVersionCode,
251 ParcelFileDescriptor newState) throws IOException;
254 * The application is having its entire file system contents backed up. {@code data}
255 * points to the backup destination, and the app has the opportunity to choose which
256 * files are to be stored. To commit a file as part of the backup, call the
257 * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method. After all file
258 * data is written to the output, the agent returns from this method and the backup
259 * operation concludes.
261 * <p>Certain parts of the app's data are never backed up even if the app explicitly
262 * sends them to the output:
265 * <li>The contents of the {@link #getCacheDir()} directory</li>
266 * <li>The contents of the {@link #getCodeCacheDir()} directory</li>
267 * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li>
268 * <li>The contents of the app's shared library directory</li>
271 * <p>The default implementation of this method backs up the entirety of the
272 * application's "owned" file system trees to the output other than the few exceptions
273 * listed above. Apps only need to override this method if they need to impose special
274 * limitations on which files are being stored beyond the control that
275 * {@link #getNoBackupFilesDir()} offers.
276 * Alternatively they can provide an xml resource to specify what data to include or exclude.
279 * @param data A structured wrapper pointing to the backup destination.
280 * @throws IOException
282 * @see Context#getNoBackupFilesDir()
283 * @see ApplicationInfo#fullBackupContent
284 * @see #fullBackupFile(File, FullBackupDataOutput)
285 * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
287 public void onFullBackup(FullBackupDataOutput data) throws IOException {
288 FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
289 if (!backupScheme.isFullBackupContentEnabled()) {
293 Map<String, Set<String>> manifestIncludeMap;
294 ArraySet<String> manifestExcludeSet;
297 backupScheme.maybeParseAndGetCanonicalIncludePaths();
298 manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths();
299 } catch (IOException | XmlPullParserException e) {
300 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
301 Log.v(FullBackup.TAG_XML_PARSER,
302 "Exception trying to parse fullBackupContent xml file!"
303 + " Aborting full backup.", e);
308 final String packageName = getPackageName();
309 final ApplicationInfo appInfo = getApplicationInfo();
311 // System apps have control over where their default storage context
312 // is pointed, so we're always explicit when building paths.
313 final Context ceContext = createCredentialProtectedStorageContext();
314 final String rootDir = ceContext.getDataDir().getCanonicalPath();
315 final String filesDir = ceContext.getFilesDir().getCanonicalPath();
316 final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
317 final String databaseDir = ceContext.getDatabasePath("foo").getParentFile()
319 final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile()
321 final String cacheDir = ceContext.getCacheDir().getCanonicalPath();
322 final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
324 final Context deContext = createDeviceProtectedStorageContext();
325 final String deviceRootDir = deContext.getDataDir().getCanonicalPath();
326 final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
327 final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath();
328 final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile()
330 final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo")
331 .getParentFile().getCanonicalPath();
332 final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
333 final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
335 final String libDir = (appInfo.nativeLibraryDir != null)
336 ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
339 // Maintain a set of excluded directories so that as we traverse the tree we know we're not
340 // going places we don't expect, and so the manifest includes can't take precedence over
341 // what the framework decides is not to be included.
342 final ArraySet<String> traversalExcludeSet = new ArraySet<String>();
344 // Add the directories we always exclude.
345 traversalExcludeSet.add(filesDir);
346 traversalExcludeSet.add(noBackupDir);
347 traversalExcludeSet.add(databaseDir);
348 traversalExcludeSet.add(sharedPrefsDir);
349 traversalExcludeSet.add(cacheDir);
350 traversalExcludeSet.add(codeCacheDir);
352 traversalExcludeSet.add(deviceFilesDir);
353 traversalExcludeSet.add(deviceNoBackupDir);
354 traversalExcludeSet.add(deviceDatabaseDir);
355 traversalExcludeSet.add(deviceSharedPrefsDir);
356 traversalExcludeSet.add(deviceCacheDir);
357 traversalExcludeSet.add(deviceCodeCacheDir);
359 if (libDir != null) {
360 traversalExcludeSet.add(libDir);
364 applyXmlFiltersAndDoFullBackupForDomain(
365 packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
366 manifestExcludeSet, traversalExcludeSet, data);
367 traversalExcludeSet.add(rootDir);
369 applyXmlFiltersAndDoFullBackupForDomain(
370 packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap,
371 manifestExcludeSet, traversalExcludeSet, data);
372 traversalExcludeSet.add(deviceRootDir);
375 traversalExcludeSet.remove(filesDir);
376 applyXmlFiltersAndDoFullBackupForDomain(
377 packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap,
378 manifestExcludeSet, traversalExcludeSet, data);
379 traversalExcludeSet.add(filesDir);
381 traversalExcludeSet.remove(deviceFilesDir);
382 applyXmlFiltersAndDoFullBackupForDomain(
383 packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap,
384 manifestExcludeSet, traversalExcludeSet, data);
385 traversalExcludeSet.add(deviceFilesDir);
387 // Database directory.
388 traversalExcludeSet.remove(databaseDir);
389 applyXmlFiltersAndDoFullBackupForDomain(
390 packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap,
391 manifestExcludeSet, traversalExcludeSet, data);
392 traversalExcludeSet.add(databaseDir);
394 traversalExcludeSet.remove(deviceDatabaseDir);
395 applyXmlFiltersAndDoFullBackupForDomain(
396 packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap,
397 manifestExcludeSet, traversalExcludeSet, data);
398 traversalExcludeSet.add(deviceDatabaseDir);
401 traversalExcludeSet.remove(sharedPrefsDir);
402 applyXmlFiltersAndDoFullBackupForDomain(
403 packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
404 manifestExcludeSet, traversalExcludeSet, data);
405 traversalExcludeSet.add(sharedPrefsDir);
407 traversalExcludeSet.remove(deviceSharedPrefsDir);
408 applyXmlFiltersAndDoFullBackupForDomain(
409 packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
410 manifestExcludeSet, traversalExcludeSet, data);
411 traversalExcludeSet.add(deviceSharedPrefsDir);
413 // getExternalFilesDir() location associated with this app. Technically there should
414 // not be any files here if the app does not properly have permission to access
415 // external storage, but edge cases happen. fullBackupFileTree() catches
416 // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and
417 // we know a priori that processes running as the system UID are not permitted to
418 // access external storage, so we check for that as well to avoid nastygrams in
420 if (Process.myUid() != Process.SYSTEM_UID) {
421 File efLocation = getExternalFilesDir(null);
422 if (efLocation != null) {
423 applyXmlFiltersAndDoFullBackupForDomain(
424 packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap,
425 manifestExcludeSet, traversalExcludeSet, data);
432 * Notification that the application's current backup operation causes it to exceed
433 * the maximum size permitted by the transport. The ongoing backup operation is
434 * halted and rolled back: any data that had been stored by a previous backup operation
435 * is still intact. Typically the quota-exceeded state will be detected before any data
436 * is actually transmitted over the network.
438 * <p>The {@code quotaBytes} value is the total data size currently permitted for this
439 * application. If desired, the application can use this as a hint for determining
440 * how much data to store. For example, a messaging application might choose to
441 * store only the newest messages, dropping enough older content to stay under
444 * <p class="note">Note that the maximum quota for the application can change over
445 * time. In particular, in the future the quota may grow. Applications that adapt
446 * to the quota when deciding what data to store should be aware of this and implement
447 * their data storage mechanisms in a way that can take advantage of additional
450 * @param backupDataBytes The amount of data measured while initializing the backup
451 * operation, if the total exceeds the app's alloted quota. If initial measurement
452 * suggested that the data would fit but then too much data was actually submitted
453 * as part of the operation, then this value is the amount of data that had been
454 * streamed into the transport at the time the quota was reached.
455 * @param quotaBytes The maximum data size that the transport currently permits
456 * this application to store as a backup.
458 public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
462 * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
463 * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
466 private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
467 Map<String, Set<String>> includeMap,
468 ArraySet<String> filterSet,
469 ArraySet<String> traversalExcludeSet,
470 FullBackupDataOutput data)
472 if (includeMap == null || includeMap.size() == 0) {
473 // Do entire sub-tree for the provided token.
474 fullBackupFileTree(packageName, domainToken,
475 FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
476 filterSet, traversalExcludeSet, data);
477 } else if (includeMap.get(domainToken) != null) {
478 // This will be null if the xml parsing didn't yield any rules for
479 // this domain (there may still be rules for other domains).
480 for (String includeFile : includeMap.get(domainToken)) {
481 fullBackupFileTree(packageName, domainToken, includeFile, filterSet,
482 traversalExcludeSet, data);
488 * Write an entire file as part of a full-backup operation. The file's contents
489 * will be delivered to the backup destination along with the metadata necessary
490 * to place it with the proper location and permissions on the device where the
493 * <p class="note">Attempting to back up files in directories that are ignored by
494 * the backup system will have no effect. For example, if the app calls this method
495 * with a file inside the {@link #getNoBackupFilesDir()} directory, it will be ignored.
496 * See {@link #onFullBackup(FullBackupDataOutput)} for details on what directories
497 * are excluded from backups.
499 * @param file The file to be backed up. The file must exist and be readable by
501 * @param output The destination to which the backed-up file data will be sent.
503 public final void fullBackupFile(File file, FullBackupDataOutput output) {
504 // Look up where all of our various well-defined dir trees live on this device
505 final String rootDir;
506 final String filesDir;
507 final String nbFilesDir;
510 final String cacheDir;
511 final String codeCacheDir;
512 final String deviceRootDir;
513 final String deviceFilesDir;
514 final String deviceNbFilesDir;
515 final String deviceDbDir;
516 final String deviceSpDir;
517 final String deviceCacheDir;
518 final String deviceCodeCacheDir;
524 ApplicationInfo appInfo = getApplicationInfo();
527 // System apps have control over where their default storage context
528 // is pointed, so we're always explicit when building paths.
529 final Context ceContext = createCredentialProtectedStorageContext();
530 rootDir = ceContext.getDataDir().getCanonicalPath();
531 filesDir = ceContext.getFilesDir().getCanonicalPath();
532 nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
533 dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
534 spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath();
535 cacheDir = ceContext.getCacheDir().getCanonicalPath();
536 codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
538 final Context deContext = createDeviceProtectedStorageContext();
539 deviceRootDir = deContext.getDataDir().getCanonicalPath();
540 deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
541 deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath();
542 deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
543 deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile()
545 deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
546 deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
548 libDir = (appInfo.nativeLibraryDir == null)
550 : new File(appInfo.nativeLibraryDir).getCanonicalPath();
552 // may or may not have external files access to attempt backup/restore there
553 if (Process.myUid() != Process.SYSTEM_UID) {
554 File efLocation = getExternalFilesDir(null);
555 if (efLocation != null) {
556 efDir = efLocation.getCanonicalPath();
560 // Now figure out which well-defined tree the file is placed in, working from
561 // most to least specific. We also specifically exclude the lib, cache,
562 // and code_cache dirs.
563 filePath = file.getCanonicalPath();
564 } catch (IOException e) {
565 Log.w(TAG, "Unable to obtain canonical paths");
569 if (filePath.startsWith(cacheDir)
570 || filePath.startsWith(codeCacheDir)
571 || filePath.startsWith(nbFilesDir)
572 || filePath.startsWith(deviceCacheDir)
573 || filePath.startsWith(deviceCodeCacheDir)
574 || filePath.startsWith(deviceNbFilesDir)
575 || filePath.startsWith(libDir)) {
576 Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
581 String rootpath = null;
582 if (filePath.startsWith(dbDir)) {
583 domain = FullBackup.DATABASE_TREE_TOKEN;
585 } else if (filePath.startsWith(spDir)) {
586 domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
588 } else if (filePath.startsWith(filesDir)) {
589 domain = FullBackup.FILES_TREE_TOKEN;
591 } else if (filePath.startsWith(rootDir)) {
592 domain = FullBackup.ROOT_TREE_TOKEN;
594 } else if (filePath.startsWith(deviceDbDir)) {
595 domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN;
596 rootpath = deviceDbDir;
597 } else if (filePath.startsWith(deviceSpDir)) {
598 domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
599 rootpath = deviceSpDir;
600 } else if (filePath.startsWith(deviceFilesDir)) {
601 domain = FullBackup.DEVICE_FILES_TREE_TOKEN;
602 rootpath = deviceFilesDir;
603 } else if (filePath.startsWith(deviceRootDir)) {
604 domain = FullBackup.DEVICE_ROOT_TREE_TOKEN;
605 rootpath = deviceRootDir;
606 } else if ((efDir != null) && filePath.startsWith(efDir)) {
607 domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
610 Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
614 // And now that we know where it lives, semantically, back it up appropriately
615 // In the measurement case, backupToTar() updates the size in output and returns
616 // without transmitting any file data.
617 if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
618 + " rootpath=" + rootpath);
620 FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output);
624 * Scan the dir tree (if it actually exists) and process each entry we find. If the
625 * 'excludes' parameters are non-null, they are consulted each time a new file system entity
626 * is visited to see whether that entity (and its subtree, if appropriate) should be
627 * omitted from the backup process.
629 * @param systemExcludes An optional list of excludes.
632 protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
633 ArraySet<String> manifestExcludes,
634 ArraySet<String> systemExcludes,
635 FullBackupDataOutput output) {
636 // Pull out the domain and set it aside to use when making the tarball.
637 String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
638 if (domainPath == null) {
639 // Should never happen.
643 File rootFile = new File(startingPath);
644 if (rootFile.exists()) {
645 LinkedList<File> scanQueue = new LinkedList<File>();
646 scanQueue.add(rootFile);
648 while (scanQueue.size() > 0) {
649 File file = scanQueue.remove(0);
652 // Ignore things that aren't "real" files or dirs
653 StructStat stat = Os.lstat(file.getPath());
654 if (!OsConstants.S_ISREG(stat.st_mode)
655 && !OsConstants.S_ISDIR(stat.st_mode)) {
656 if (DEBUG) Log.i(TAG, "Not a file/dir (skipping)!: " + file);
660 // For all other verification, look at the canonicalized path
661 filePath = file.getCanonicalPath();
663 // prune this subtree?
664 if (manifestExcludes != null && manifestExcludes.contains(filePath)) {
667 if (systemExcludes != null && systemExcludes.contains(filePath)) {
671 // If it's a directory, enqueue its contents for scanning.
672 if (OsConstants.S_ISDIR(stat.st_mode)) {
673 File[] contents = file.listFiles();
674 if (contents != null) {
675 for (File entry : contents) {
676 scanQueue.add(0, entry);
680 } catch (IOException e) {
681 if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
682 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
683 Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file);
686 } catch (ErrnoException e) {
687 if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
688 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
689 Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e);
694 // Finally, back this file up (or measure it) before proceeding
695 FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output);
701 * Handle the data delivered via the given file descriptor during a full restore
702 * operation. The agent is given the path to the file's original location as well
703 * as its size and metadata.
705 * The file descriptor can only be read for {@code size} bytes; attempting to read
706 * more data has undefined behavior.
708 * The default implementation creates the destination file/directory and populates it
709 * with the data from the file descriptor, then sets the file's access mode and
710 * modification time to match the restore arguments.
712 * @param data A read-only file descriptor from which the agent can read {@code size}
713 * bytes of file data.
714 * @param size The number of bytes of file content to be restored to the given
715 * destination. If the file system object being restored is a directory, {@code size}
717 * @param destination The File on disk to be restored with the given data.
718 * @param type The kind of file system object being restored. This will be either
719 * {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
720 * @param mode The access mode to be assigned to the destination after its data is
721 * written. This is in the standard format used by {@code chmod()}.
722 * @param mtime The modification time of the file when it was backed up, suitable to
723 * be assigned to the file after its data is written.
724 * @throws IOException
726 public void onRestoreFile(ParcelFileDescriptor data, long size,
727 File destination, int type, long mode, long mtime)
730 final boolean accept = isFileEligibleForRestore(destination);
731 // If we don't accept the file, consume the bytes from the pipe anyway.
732 FullBackup.restoreFile(data, size, type, mode, mtime, accept ? destination : null);
735 private boolean isFileEligibleForRestore(File destination) throws IOException {
736 FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
737 if (!bs.isFullBackupContentEnabled()) {
738 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
739 Log.v(FullBackup.TAG_XML_PARSER,
740 "onRestoreFile \"" + destination.getCanonicalPath()
741 + "\" : fullBackupContent not enabled for " + getPackageName());
746 Map<String, Set<String>> includes = null;
747 ArraySet<String> excludes = null;
748 final String destinationCanonicalPath = destination.getCanonicalPath();
750 includes = bs.maybeParseAndGetCanonicalIncludePaths();
751 excludes = bs.maybeParseAndGetCanonicalExcludePaths();
752 } catch (XmlPullParserException e) {
753 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
754 Log.v(FullBackup.TAG_XML_PARSER,
755 "onRestoreFile \"" + destinationCanonicalPath
756 + "\" : Exception trying to parse fullBackupContent xml file!"
757 + " Aborting onRestoreFile.", e);
762 if (excludes != null &&
763 isFileSpecifiedInPathList(destination, excludes)) {
764 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
765 Log.v(FullBackup.TAG_XML_PARSER,
766 "onRestoreFile: \"" + destinationCanonicalPath + "\": listed in"
767 + " excludes; skipping.");
772 if (includes != null && !includes.isEmpty()) {
773 // Rather than figure out the <include/> domain based on the path (a lot of code, and
774 // it's a small list), we'll go through and look for it.
775 boolean explicitlyIncluded = false;
776 for (Set<String> domainIncludes : includes.values()) {
777 explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes);
778 if (explicitlyIncluded) {
782 if (!explicitlyIncluded) {
783 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
784 Log.v(FullBackup.TAG_XML_PARSER,
785 "onRestoreFile: Trying to restore \""
786 + destinationCanonicalPath + "\" but it isn't specified"
787 + " in the included files; skipping.");
796 * @return True if the provided file is either directly in the provided list, or the provided
797 * file is within a directory in the list.
799 private boolean isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList)
801 for (String canonicalPath : canonicalPathList) {
802 File fileFromList = new File(canonicalPath);
803 if (fileFromList.isDirectory()) {
804 if (file.isDirectory()) {
805 // If they are both directories check exact equals.
806 return file.equals(fileFromList);
808 // O/w we have to check if the file is within the directory from the list.
809 return file.getCanonicalPath().startsWith(canonicalPath);
812 if (file.equals(fileFromList)) {
813 // Need to check the explicit "equals" so we don't end up with substrings.
822 * Only specialized platform agents should overload this entry point to support
823 * restores to crazy non-app locations.
826 protected void onRestoreFile(ParcelFileDescriptor data, long size,
827 int type, String domain, String path, long mode, long mtime)
829 String basePath = null;
831 if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
832 + " domain=" + domain + " relpath=" + path + " mode=" + mode
833 + " mtime=" + mtime);
835 basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
836 if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
837 mode = -1; // < 0 is a token to skip attempting a chmod()
840 // Now that we've figured out where the data goes, send it on its way
841 if (basePath != null) {
842 // Canonicalize the nominal path and verify that it lies within the stated domain
843 File outFile = new File(basePath, path);
844 String outPath = outFile.getCanonicalPath();
845 if (outPath.startsWith(basePath + File.separatorChar)) {
846 if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath);
847 onRestoreFile(data, size, outFile, type, mode, mtime);
850 // Attempt to restore to a path outside the file's nominal domain.
852 Log.e(TAG, "Cross-domain restore attempt: " + outPath);
857 // Not a supported output location, or bad path: we need to consume the data
858 // anyway, so just use the default "copy the data out" implementation
859 // with a null destination.
860 if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]");
861 FullBackup.restoreFile(data, size, type, mode, mtime, null);
865 * The application's restore operation has completed. This method is called after
866 * all available data has been delivered to the application for restore (via either
867 * the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or
868 * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()}
869 * callbacks). This provides the app with a stable end-of-restore opportunity to
870 * perform any appropriate post-processing on the data that was just delivered.
872 * @see #onRestore(BackupDataInput, int, ParcelFileDescriptor)
873 * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
875 public void onRestoreFinished() {
878 // ----- Core implementation -----
881 public final IBinder onBind() {
885 private final IBinder mBinder = new BackupServiceBinder().asBinder();
888 public void attach(Context context) {
889 attachBaseContext(context);
892 // ----- IBackupService binder interface -----
893 private class BackupServiceBinder extends IBackupAgent.Stub {
894 private static final String TAG = "BackupServiceBinder";
897 public void doBackup(ParcelFileDescriptor oldState,
898 ParcelFileDescriptor data,
899 ParcelFileDescriptor newState,
900 long quotaBytes, int token, IBackupManager callbackBinder) throws RemoteException {
901 // Ensure that we're running with the app's normal permission level
902 long ident = Binder.clearCallingIdentity();
904 if (DEBUG) Log.v(TAG, "doBackup() invoked");
905 BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor(), quotaBytes);
908 BackupAgent.this.onBackup(oldState, output, newState);
909 } catch (IOException ex) {
910 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
911 throw new RuntimeException(ex);
912 } catch (RuntimeException ex) {
913 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
916 // Ensure that any SharedPreferences writes have landed after the backup,
917 // in case the app code has side effects (since apps cannot provide this
918 // guarantee themselves).
919 waitForSharedPrefs();
921 Binder.restoreCallingIdentity(ident);
923 callbackBinder.opComplete(token, 0);
924 } catch (RemoteException e) {
925 // we'll time out anyway, so we're safe
928 // Don't close the fd out from under the system service if this was local
929 if (Binder.getCallingPid() != Process.myPid()) {
930 IoUtils.closeQuietly(oldState);
931 IoUtils.closeQuietly(data);
932 IoUtils.closeQuietly(newState);
938 public void doRestore(ParcelFileDescriptor data, int appVersionCode,
939 ParcelFileDescriptor newState,
940 int token, IBackupManager callbackBinder) throws RemoteException {
941 // Ensure that we're running with the app's normal permission level
942 long ident = Binder.clearCallingIdentity();
944 if (DEBUG) Log.v(TAG, "doRestore() invoked");
945 BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
947 BackupAgent.this.onRestore(input, appVersionCode, newState);
948 } catch (IOException ex) {
949 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
950 throw new RuntimeException(ex);
951 } catch (RuntimeException ex) {
952 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
955 // Ensure that any side-effect SharedPreferences writes have landed
956 waitForSharedPrefs();
958 Binder.restoreCallingIdentity(ident);
960 callbackBinder.opComplete(token, 0);
961 } catch (RemoteException e) {
962 // we'll time out anyway, so we're safe
965 if (Binder.getCallingPid() != Process.myPid()) {
966 IoUtils.closeQuietly(data);
967 IoUtils.closeQuietly(newState);
973 public void doFullBackup(ParcelFileDescriptor data,
974 long quotaBytes, int token, IBackupManager callbackBinder) {
975 // Ensure that we're running with the app's normal permission level
976 long ident = Binder.clearCallingIdentity();
978 if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
980 // Ensure that any SharedPreferences writes have landed *before*
981 // we potentially try to back up the underlying files directly.
982 waitForSharedPrefs();
985 BackupAgent.this.onFullBackup(new FullBackupDataOutput(data, quotaBytes));
986 } catch (IOException ex) {
987 Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
988 throw new RuntimeException(ex);
989 } catch (RuntimeException ex) {
990 Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
993 // ... and then again after, as in the doBackup() case
994 waitForSharedPrefs();
996 // Send the EOD marker indicating that there is no more data
997 // forthcoming from this agent.
999 FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
1000 byte[] buf = new byte[4];
1002 } catch (IOException e) {
1003 Log.e(TAG, "Unable to finalize backup stream!");
1006 Binder.restoreCallingIdentity(ident);
1008 callbackBinder.opComplete(token, 0);
1009 } catch (RemoteException e) {
1010 // we'll time out anyway, so we're safe
1013 if (Binder.getCallingPid() != Process.myPid()) {
1014 IoUtils.closeQuietly(data);
1019 public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder) {
1020 // Ensure that we're running with the app's normal permission level
1021 final long ident = Binder.clearCallingIdentity();
1022 FullBackupDataOutput measureOutput = new FullBackupDataOutput(quotaBytes);
1024 waitForSharedPrefs();
1026 BackupAgent.this.onFullBackup(measureOutput);
1027 } catch (IOException ex) {
1028 Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1029 throw new RuntimeException(ex);
1030 } catch (RuntimeException ex) {
1031 Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1034 Binder.restoreCallingIdentity(ident);
1036 callbackBinder.opComplete(token, measureOutput.getSize());
1037 } catch (RemoteException e) {
1038 // timeout, so we're safe
1044 public void doRestoreFile(ParcelFileDescriptor data, long size,
1045 int type, String domain, String path, long mode, long mtime,
1046 int token, IBackupManager callbackBinder) throws RemoteException {
1047 long ident = Binder.clearCallingIdentity();
1049 BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
1050 } catch (IOException e) {
1051 Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e);
1052 throw new RuntimeException(e);
1054 // Ensure that any side-effect SharedPreferences writes have landed
1055 waitForSharedPrefs();
1057 Binder.restoreCallingIdentity(ident);
1059 callbackBinder.opComplete(token, 0);
1060 } catch (RemoteException e) {
1061 // we'll time out anyway, so we're safe
1064 if (Binder.getCallingPid() != Process.myPid()) {
1065 IoUtils.closeQuietly(data);
1071 public void doRestoreFinished(int token, IBackupManager callbackBinder) {
1072 long ident = Binder.clearCallingIdentity();
1074 BackupAgent.this.onRestoreFinished();
1075 } catch (Exception e) {
1076 Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e);
1079 // Ensure that any side-effect SharedPreferences writes have landed
1080 waitForSharedPrefs();
1082 Binder.restoreCallingIdentity(ident);
1084 callbackBinder.opComplete(token, 0);
1085 } catch (RemoteException e) {
1086 // we'll time out anyway, so we're safe
1092 public void fail(String message) {
1093 getHandler().post(new FailRunnable(message));
1097 public void doQuotaExceeded(long backupDataBytes, long quotaBytes) {
1098 long ident = Binder.clearCallingIdentity();
1100 BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes);
1101 } catch (Exception e) {
1102 Log.d(TAG, "onQuotaExceeded(" + BackupAgent.this.getClass().getName() + ") threw",
1106 waitForSharedPrefs();
1107 Binder.restoreCallingIdentity(ident);
1112 static class FailRunnable implements Runnable {
1113 private String mMessage;
1115 FailRunnable(String message) {
1121 throw new IllegalStateException(mMessage);