FileDocCategorySizeDatePackage
BackupAgent.javaAPI DocAndroid 5.1 API34595Thu Mar 12 22:22:10 GMT 2015android.app.backup

BackupAgent

public abstract class BackupAgent extends android.content.ContextWrapper
Provides the central interface between an application and Android's data backup infrastructure. An application that wishes to participate in the backup and restore mechanism will declare a subclass of {@link android.app.backup.BackupAgent}, implement the {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods, and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via the <application> tag's {@code android:backupAgent} attribute.

Developer Guides

For more information about using BackupAgent, read the Data Backup developer guide.

Basic Operation

When the application makes changes to data that it wishes to keep backed up, it should call the {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method. This notifies the Android Backup Manager that the application needs an opportunity to update its backup image. The Backup Manager, in turn, schedules a backup pass to be performed at an opportune time.

Restore operations are typically performed only when applications are first installed on a device. At that time, the operating system checks to see whether there is a previously-saved data set available for the application being installed, and if so, begins an immediate restore pass to deliver the backup data as part of the installation process.

When a backup or restore pass is run, the application's process is launched (if not already running), the manifest-declared backup agent class (in the {@code android:backupAgent} attribute) is instantiated within that process, and the agent's {@link #onCreate()} method is invoked. This prepares the agent instance to run the actual backup or restore logic. At this point the agent's {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be invoked as appropriate for the operation being performed.

A backup data set consists of one or more "entities," flattened binary data records that are each identified with a key string unique within the data set. Adding a record to the active data set or updating an existing record is done by simply writing new entity data under the desired key. Deleting an entity from the data set is done by writing an entity under that key with header specifying a negative data size, and no actual entity data.

Helper Classes

An extensible agent based on convenient helper classes is available in {@link android.app.backup.BackupAgentHelper}. That class is particularly suited to handling of simple file or {@link android.content.SharedPreferences} backup and restore.

see
android.app.backup.BackupManager
see
android.app.backup.BackupAgentHelper
see
android.app.backup.BackupDataInput
see
android.app.backup.BackupDataOutput

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
public static final int
TYPE_EOF
public static final int
TYPE_FILE
During a full restore, indicates that the file system object being restored is an ordinary file.
public static final int
TYPE_DIRECTORY
During a full restore, indicates that the file system object being restored is a directory.
public static final int
TYPE_SYMLINK
android.os.Handler
mHandler
private final android.os.IBinder
mBinder
Constructors Summary
public BackupAgent()

        super(null);
    
Methods Summary
public voidattach(android.content.Context context)

hide


      
        
        attachBaseContext(context);
    
public final voidfullBackupFile(java.io.File file, FullBackupDataOutput output)
Write an entire file as part of a full-backup operation. The file's contents will be delivered to the backup destination along with the metadata necessary to place it with the proper location and permissions on the device where the data is restored.

It is safe to explicitly back up files underneath your application's {@link #getNoBackupFilesDir()} directory, and they will be restored to that location correctly.

param
file The file to be backed up. The file must exist and be readable by the caller.
param
output The destination to which the backed-up file data will be sent.

        // Look up where all of our various well-defined dir trees live on this device
        String mainDir;
        String filesDir;
        String nbFilesDir;
        String dbDir;
        String spDir;
        String cacheDir;
        String libDir;
        String efDir = null;
        String filePath;

        ApplicationInfo appInfo = getApplicationInfo();

        try {
            mainDir = new File(appInfo.dataDir).getCanonicalPath();
            filesDir = getFilesDir().getCanonicalPath();
            nbFilesDir = getNoBackupFilesDir().getCanonicalPath();
            dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
            spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
            cacheDir = getCacheDir().getCanonicalPath();
            libDir = (appInfo.nativeLibraryDir == null)
                    ? null
                    : new File(appInfo.nativeLibraryDir).getCanonicalPath();

            // may or may not have external files access to attempt backup/restore there
            if (Process.myUid() != Process.SYSTEM_UID) {
                File efLocation = getExternalFilesDir(null);
                if (efLocation != null) {
                    efDir = efLocation.getCanonicalPath();
                }
            }

            // Now figure out which well-defined tree the file is placed in, working from
            // most to least specific.  We also specifically exclude the lib and cache dirs.
            filePath = file.getCanonicalPath();
        } catch (IOException e) {
            Log.w(TAG, "Unable to obtain canonical paths");
            return;
        }

        if (filePath.startsWith(cacheDir)
                || filePath.startsWith(libDir)
                || filePath.startsWith(nbFilesDir)) {
            Log.w(TAG, "lib, cache, and no_backup files are not backed up");
            return;
        }

        final String domain;
        String rootpath = null;
        if (filePath.startsWith(dbDir)) {
            domain = FullBackup.DATABASE_TREE_TOKEN;
            rootpath = dbDir;
        } else if (filePath.startsWith(spDir)) {
            domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
            rootpath = spDir;
        } else if (filePath.startsWith(filesDir)) {
            domain = FullBackup.DATA_TREE_TOKEN;
            rootpath = filesDir;
        } else if (filePath.startsWith(mainDir)) {
            domain = FullBackup.ROOT_TREE_TOKEN;
            rootpath = mainDir;
        } else if ((efDir != null) && filePath.startsWith(efDir)) {
            domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
            rootpath = efDir;
        } else {
            Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
            return;
        }

        // And now that we know where it lives, semantically, back it up appropriately
        Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
                + " rootpath=" + rootpath);
        FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath,
                output.getData());
    
protected final voidfullBackupFileTree(java.lang.String packageName, java.lang.String domain, java.lang.String rootPath, java.util.HashSet excludes, FullBackupDataOutput output)
Scan the dir tree (if it actually exists) and process each entry we find. If the 'excludes' parameter is non-null, it is consulted each time a new file system entity is visited to see whether that entity (and its subtree, if appropriate) should be omitted from the backup process.

hide

        File rootFile = new File(rootPath);
        if (rootFile.exists()) {
            LinkedList<File> scanQueue = new LinkedList<File>();
            scanQueue.add(rootFile);

            while (scanQueue.size() > 0) {
                File file = scanQueue.remove(0);
                String filePath;
                try {
                    filePath = file.getCanonicalPath();

                    // prune this subtree?
                    if (excludes != null && excludes.contains(filePath)) {
                        continue;
                    }

                    // If it's a directory, enqueue its contents for scanning.
                    StructStat stat = Os.lstat(filePath);
                    if (OsConstants.S_ISLNK(stat.st_mode)) {
                        if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
                        continue;
                    } else if (OsConstants.S_ISDIR(stat.st_mode)) {
                        File[] contents = file.listFiles();
                        if (contents != null) {
                            for (File entry : contents) {
                                scanQueue.add(0, entry);
                            }
                        }
                    }
                } catch (IOException e) {
                    if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
                    continue;
                } catch (ErrnoException e) {
                    if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
                    continue;
                }

                // Finally, back this file up before proceeding
                FullBackup.backupToTar(packageName, domain, null, rootPath, filePath,
                        output.getData());
            }
        }
    
android.os.HandlergetHandler()


      
        if (mHandler == null) {
            mHandler = new Handler(Looper.getMainLooper());
        }
        return mHandler;
    
public abstract voidonBackup(android.os.ParcelFileDescriptor oldState, BackupDataOutput data, android.os.ParcelFileDescriptor newState)
The application is being asked to write any data changed since the last time it performed a backup operation. The state data recorded during the last backup pass is provided in the oldState file descriptor. If oldState is null, no old state is available and the application should perform a full backup. In both cases, a representation of the final backup state after this pass should be written to the file pointed to by the file descriptor wrapped in newState.

Each entity written to the {@link android.app.backup.BackupDataOutput} data stream will be transmitted over the current backup transport and stored in the remote data set under the key supplied as part of the entity. Writing an entity with a negative data size instructs the transport to delete whatever entity currently exists under that key from the remote data set.

param
oldState An open, read-only ParcelFileDescriptor pointing to the last backup state provided by the application. May be null, in which case no prior state is being provided and the application should perform a full backup.
param
data A structured wrapper around an open, read/write file descriptor pointing to the backup data destination. Typically the application will use backup helper classes to write to this file.
param
newState An open, read/write ParcelFileDescriptor pointing to an empty file. The application should record the final backup state here after writing the requested data to the data output stream.

public final android.os.IBinderonBind()

hide

        return mBinder;
    
public voidonCreate()
Provided as a convenience for agent implementations that need an opportunity to do one-time initialization before the actual backup or restore operation is begun.

Agents do not need to override this method.

    
public voidonDestroy()
Provided as a convenience for agent implementations that need to do some sort of shutdown process after backup or restore is completed.

Agents do not need to override this method.

    
public voidonFullBackup(FullBackupDataOutput data)
The application is having its entire file system contents backed up. {@code data} points to the backup destination, and the app has the opportunity to choose which files are to be stored. To commit a file as part of the backup, call the {@link #fullBackupFile(File, FullBackupDataOutput)} helper method. After all file data is written to the output, the agent returns from this method and the backup operation concludes.

Certain parts of the app's data are never backed up even if the app explicitly sends them to the output:

  • The contents of the {@link #getCacheDir()} directory
  • The contents of the {@link #getNoBackupFilesDir()} directory
  • The contents of the app's shared library directory

The default implementation of this method backs up the entirety of the application's "owned" file system trees to the output other than the few exceptions listed above. Apps only need to override this method if they need to impose special limitations on which files are being stored beyond the control that {@link #getNoBackupFilesDir()} offers.

param
data A structured wrapper pointing to the backup destination.
throws
IOException
see
Context#getNoBackupFilesDir()
see
#fullBackupFile(File, FullBackupDataOutput)
see
#onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)

        ApplicationInfo appInfo = getApplicationInfo();

        // Note that we don't need to think about the no_backup dir because it's outside
        // all of the ones we will be traversing
        String rootDir = new File(appInfo.dataDir).getCanonicalPath();
        String filesDir = getFilesDir().getCanonicalPath();
        String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
        String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
        String cacheDir = getCacheDir().getCanonicalPath();
        String libDir = (appInfo.nativeLibraryDir != null)
                ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
                : null;

        // Filters, the scan queue, and the set of resulting entities
        HashSet<String> filterSet = new HashSet<String>();
        String packageName = getPackageName();

        // Okay, start with the app's root tree, but exclude all of the canonical subdirs
        if (libDir != null) {
            filterSet.add(libDir);
        }
        filterSet.add(cacheDir);
        filterSet.add(databaseDir);
        filterSet.add(sharedPrefsDir);
        filterSet.add(filesDir);
        fullBackupFileTree(packageName, FullBackup.ROOT_TREE_TOKEN, rootDir, filterSet, data);

        // Now do the same for the files dir, db dir, and shared prefs dir
        filterSet.add(rootDir);
        filterSet.remove(filesDir);
        fullBackupFileTree(packageName, FullBackup.DATA_TREE_TOKEN, filesDir, filterSet, data);

        filterSet.add(filesDir);
        filterSet.remove(databaseDir);
        fullBackupFileTree(packageName, FullBackup.DATABASE_TREE_TOKEN, databaseDir, filterSet, data);

        filterSet.add(databaseDir);
        filterSet.remove(sharedPrefsDir);
        fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir, filterSet, data);

        // getExternalFilesDir() location associated with this app.  Technically there should
        // not be any files here if the app does not properly have permission to access
        // external storage, but edge cases happen. fullBackupFileTree() catches
        // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and
        // we know a priori that processes running as the system UID are not permitted to
        // access external storage, so we check for that as well to avoid nastygrams in
        // the log.
        if (Process.myUid() != Process.SYSTEM_UID) {
            File efLocation = getExternalFilesDir(null);
            if (efLocation != null) {
                fullBackupFileTree(packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN,
                        efLocation.getCanonicalPath(), null, data);
            }
        }
    
public abstract voidonRestore(BackupDataInput data, int appVersionCode, android.os.ParcelFileDescriptor newState)
The application is being restored from backup and should replace any existing data with the contents of the backup. The backup data is provided through the data parameter. Once the restore is finished, the application should write a representation of the final state to the newState file descriptor.

The application is responsible for properly erasing its old data and replacing it with the data supplied to this method. No "clear user data" operation will be performed automatically by the operating system. The exception to this is in the case of a failed restore attempt: if onRestore() throws an exception, the OS will assume that the application's data may now be in an incoherent state, and will clear it before proceeding.

param
data A structured wrapper around an open, read-only file descriptor pointing to a full snapshot of the application's data. The application should consume every entity represented in this data stream.
param
appVersionCode The value of the {@code android:versionCode} manifest attribute, from the application that backed up this particular data set. This makes it possible for an application's agent to distinguish among any possible older data versions when asked to perform the restore operation.
param
newState An open, read/write ParcelFileDescriptor pointing to an empty file. The application should record the final backup state here after restoring its data from the data stream. When a full-backup dataset is being restored, this will be null.

public voidonRestoreFile(android.os.ParcelFileDescriptor data, long size, java.io.File destination, int type, long mode, long mtime)
Handle the data delivered via the given file descriptor during a full restore operation. The agent is given the path to the file's original location as well as its size and metadata.

The file descriptor can only be read for {@code size} bytes; attempting to read more data has undefined behavior.

The default implementation creates the destination file/directory and populates it with the data from the file descriptor, then sets the file's access mode and modification time to match the restore arguments.

param
data A read-only file descriptor from which the agent can read {@code size} bytes of file data.
param
size The number of bytes of file content to be restored to the given destination. If the file system object being restored is a directory, {@code size} will be zero.
param
destination The File on disk to be restored with the given data.
param
type The kind of file system object being restored. This will be either {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
param
mode The access mode to be assigned to the destination after its data is written. This is in the standard format used by {@code chmod()}.
param
mtime The modification time of the file when it was backed up, suitable to be assigned to the file after its data is written.
throws
IOException

        FullBackup.restoreFile(data, size, type, mode, mtime, destination);
    
protected voidonRestoreFile(android.os.ParcelFileDescriptor data, long size, int type, java.lang.String domain, java.lang.String path, long mode, long mtime)
Only specialized platform agents should overload this entry point to support restores to crazy non-app locations.

hide

        String basePath = null;

        if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
                + " domain=" + domain + " relpath=" + path + " mode=" + mode
                + " mtime=" + mtime);

        // Parse out the semantic domains into the correct physical location
        if (domain.equals(FullBackup.DATA_TREE_TOKEN)) {
            basePath = getFilesDir().getCanonicalPath();
        } else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) {
            basePath = getDatabasePath("foo").getParentFile().getCanonicalPath();
        } else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) {
            basePath = new File(getApplicationInfo().dataDir).getCanonicalPath();
        } else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) {
            basePath = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
        } else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
            basePath = getCacheDir().getCanonicalPath();
        } else if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
            // make sure we can try to restore here before proceeding
            if (Process.myUid() != Process.SYSTEM_UID) {
                File efLocation = getExternalFilesDir(null);
                if (efLocation != null) {
                    basePath = getExternalFilesDir(null).getCanonicalPath();
                    mode = -1;  // < 0 is a token to skip attempting a chmod()
                }
            }
        } else if (domain.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
            basePath = getNoBackupFilesDir().getCanonicalPath();
        } else {
            // Not a supported location
            Log.i(TAG, "Unrecognized domain " + domain);
        }

        // Now that we've figured out where the data goes, send it on its way
        if (basePath != null) {
            // Canonicalize the nominal path and verify that it lies within the stated domain
            File outFile = new File(basePath, path);
            String outPath = outFile.getCanonicalPath();
            if (outPath.startsWith(basePath + File.separatorChar)) {
                if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath);
                onRestoreFile(data, size, outFile, type, mode, mtime);
                return;
            } else {
                // Attempt to restore to a path outside the file's nominal domain.
                if (DEBUG) {
                    Log.e(TAG, "Cross-domain restore attempt: " + outPath);
                }
            }
        }

        // Not a supported output location, or bad path:  we need to consume the data
        // anyway, so just use the default "copy the data out" implementation
        // with a null destination.
        if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]");
        FullBackup.restoreFile(data, size, type, mode, mtime, null);
    
public voidonRestoreFinished()
The application's restore operation has completed. This method is called after all available data has been delivered to the application for restore (via either the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()} callbacks). This provides the app with a stable end-of-restore opportunity to perform any appropriate post-processing on the data that was just delivered.

see
#onRestore(BackupDataInput, int, ParcelFileDescriptor)
see
#onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)

    
private voidwaitForSharedPrefs()

        Handler h = getHandler();
        final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer();
        h.postAtFrontOfQueue(s);
        try {
            s.mLatch.await();
        } catch (InterruptedException e) { /* ignored */ }