Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 42864c3b authored by Nextbit's avatar Nextbit Committed by Steve Kondik
Browse files

Provide non-interactive APIs to BackupManagerService

- Add return codes for backup and restore in FullBackupRestoreObserver
- Add non-interactive backup and restore methods in BackupManagerService
- Add per-file filters for backup in BackupAgent

This is a roll-up CR from cm-11.0 and also contains the following Change-Ids:
- I808cdc1c3b374e29d8438c7051e37c24a7a9d991 fixes adb restore
- I54983d8f270285942db20eaff1e6de4229ec781e fixes result code-passing

Change-Id: Ic1dfd5d144507263baad76f7088a52eab2fb283a
parent 2dea8e3e
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -147,4 +147,28 @@ oneway interface IBackupAgent {
     * @param message The message to be passed to the agent's application in an exception.
     */
    void fail(String message);

    /**
     * Perform a backup for all the files specified by domainTokens parameter. The output file
     * should be a socket or other non-seekable, write-only data sink.  When this method is
     * called, the app should write all of its files to the output.
     *
     * @param data Write-only file to receive the backed-up content stream.
     *        The data must be formatted correctly for the resulting archive to be
     *        legitimate and restorable.
     *
     * @param token Opaque token identifying this transaction.  This must
     *        be echoed back to the backup service binder once the agent
     *        completes restoring the application based on the restore data
     *        contents.
     *
     * @param domainTokens File paths of the files (represented as domains) to include.
     *
     * @param excludeFilesRegex Files to exclude specified as a Java-compatible pattern.
     *
     * @param callbackBinder Binder on which to indicate operation completion,
     *        passed here as a convenience to the agent.
     */
    void doBackupFiles(in ParcelFileDescriptor data, int token, in String[] domainTokens,
            in String excludeFilesRegex, IBackupManager callbackBinder);
}
+166 −10
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Pattern;

/**
 * Provides the central interface between an
@@ -105,7 +106,14 @@ import java.util.concurrent.CountDownLatch;
 */
public abstract class BackupAgent extends ContextWrapper {
    private static final String TAG = "BackupAgent";
    private static final boolean DEBUG = true;
    private static final boolean DEBUG = false;

    /**
     * To lookup an application's database and shared preferences directories (usually
     * /data/data/com.application.name), this generic name is used to invoke the
     * getSharedPrefsFile(String) and getDatabasePath(String) APIs.
     */
    private static final String GENERIC_FILE_NAME = "foo";

    /** @hide */
    public static final int TYPE_EOF = 0;
@@ -282,8 +290,9 @@ public abstract class BackupAgent extends ContextWrapper {
        // 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 databaseDir = getDatabasePath(GENERIC_FILE_NAME).getParentFile().getCanonicalPath();
        String sharedPrefsDir =
            getSharedPrefsFile(GENERIC_FILE_NAME).getParentFile().getCanonicalPath();
        String cacheDir = getCacheDir().getCanonicalPath();
        String libDir = (appInfo.nativeLibraryDir != null)
                ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
@@ -364,8 +373,8 @@ public abstract class BackupAgent extends ContextWrapper {
            mainDir = new File(appInfo.dataDir).getCanonicalPath();
            filesDir = getFilesDir().getCanonicalPath();
            nbFilesDir = getNoBackupFilesDir().getCanonicalPath();
            dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
            spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
            dbDir = getDatabasePath(GENERIC_FILE_NAME).getParentFile().getCanonicalPath();
            spDir = getSharedPrefsFile(GENERIC_FILE_NAME).getParentFile().getCanonicalPath();
            cacheDir = getCacheDir().getCanonicalPath();
            libDir = (appInfo.nativeLibraryDir == null)
                    ? null
@@ -432,7 +441,20 @@ public abstract class BackupAgent extends ContextWrapper {
     * @hide
     */
    protected final void fullBackupFileTree(String packageName, String domain, String rootPath,
            HashSet<String> excludes, FullBackupDataOutput output) {
            HashSet<String> excludeFullPathDir, FullBackupDataOutput output) {
        fullBackupFileTree(packageName, domain, rootPath, excludeFullPathDir, null, 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
     */
    protected final void fullBackupFileTree(String packageName, String domain, String rootPath,
            HashSet<String> excludeFullPathDir, Pattern excludeFiles, FullBackupDataOutput output) {
        File rootFile = new File(rootPath);
        if (rootFile.exists()) {
            LinkedList<File> scanQueue = new LinkedList<File>();
@@ -445,7 +467,14 @@ public abstract class BackupAgent extends ContextWrapper {
                    filePath = file.getCanonicalPath();

                    // prune this subtree?
                    if (excludes != null && excludes.contains(filePath)) {
                    if (excludeFullPathDir != null && excludeFullPathDir.contains(filePath)) {
                        if (DEBUG) Log.i(TAG, "Excluding directory path: " + filePath);
                        continue;
                    }

                    // Does this match the regex ?
                    if (excludeFiles != null && excludeFiles.matcher(filePath).find()) {
                        if (DEBUG) Log.i(TAG, "Excluding file: " + filePath);
                        continue;
                    }

@@ -466,7 +495,9 @@ public abstract class BackupAgent extends ContextWrapper {
                    if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
                    continue;
                } catch (ErrnoException e) {
                    if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
                    if (DEBUG) {
                        Log.w(TAG, "Error scanning file " + file + ", skipping this file backup");
                    }
                    continue;
                }

@@ -477,6 +508,91 @@ public abstract class BackupAgent extends ContextWrapper {
        }
    }

    /**
     * Backup files specified by domainTokens parameter. It should be one (or more) of
     * rootDir, filesDir, databaseDir, cacheDir, sharedPrefsDir, externalFilesDir
     * excludeFiles is a regex of the files to be excluded when doing the backup.
     *
     * @param domainTokens
     * @param excludeFilesRegex
     * @param data
     * @hide
     */
    private void onBackupFiles(String[] domainTokens, String excludeFilesRegex,
            FullBackupDataOutput data) throws IOException {
        String rootDir, filesDir, databaseDir, sharedPrefsDir, cacheDir, libDir, efLocationDir;
        ApplicationInfo appInfo = getApplicationInfo();

        rootDir = new File(appInfo.dataDir).getCanonicalPath();
        filesDir = getFilesDir().getCanonicalPath();
        databaseDir = getDatabasePath(GENERIC_FILE_NAME).getParentFile().getCanonicalPath();
        sharedPrefsDir = getSharedPrefsFile(GENERIC_FILE_NAME).getParentFile().getCanonicalPath();
        cacheDir = getCacheDir().getCanonicalPath();
        libDir = (appInfo.nativeLibraryDir != null)
                ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
                : null;
        efLocationDir = getExternalFilesDir(null).getCanonicalPath();

        // 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);

        Pattern excludeFiles = null;
        if (excludeFilesRegex != null) {
            excludeFiles = Pattern.compile(excludeFilesRegex, Pattern.CASE_INSENSITIVE);
        }

        for (String dToken : domainTokens) {
            if (FullBackup.ROOT_TREE_TOKEN.equals(dToken)) {
                fullBackupFileTree(packageName, FullBackup.ROOT_TREE_TOKEN, rootDir, filterSet,
                        excludeFiles, data);
            }

            if (FullBackup.DATA_TREE_TOKEN.equals(dToken)) {
                filterSet.add(rootDir);
                filterSet.remove(filesDir);
                fullBackupFileTree(packageName, FullBackup.DATA_TREE_TOKEN, filesDir, filterSet,
                        excludeFiles, data);
            }

            if (FullBackup.DATABASE_TREE_TOKEN.equals(dToken)) {
                filterSet.add(filesDir);
                filterSet.remove(databaseDir);
                fullBackupFileTree(packageName, FullBackup.DATABASE_TREE_TOKEN, databaseDir,
                        filterSet, excludeFiles, data);
            }

            if (FullBackup.SHAREDPREFS_TREE_TOKEN.equals(dToken)) {
                filterSet.add(databaseDir);
                filterSet.remove(sharedPrefsDir);
                fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir,
                        filterSet, excludeFiles, data);
            }

            if (FullBackup.MANAGED_EXTERNAL_TREE_TOKEN.equals(dToken)) {
                // 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) {
                    fullBackupFileTree(packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN,
                            efLocationDir, null, excludeFiles, data);
                }
            }
        }
    }

    /**
     * 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
@@ -527,11 +643,11 @@ public abstract class BackupAgent extends ContextWrapper {
        if (domain.equals(FullBackup.DATA_TREE_TOKEN)) {
            basePath = getFilesDir().getCanonicalPath();
        } else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) {
            basePath = getDatabasePath("foo").getParentFile().getCanonicalPath();
            basePath = getDatabasePath(GENERIC_FILE_NAME).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();
            basePath = getSharedPrefsFile(GENERIC_FILE_NAME).getParentFile().getCanonicalPath();
        } else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
            basePath = getCacheDir().getCanonicalPath();
        } else if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
@@ -713,6 +829,46 @@ public abstract class BackupAgent extends ContextWrapper {
            }
        }

        @Override
        public void doBackupFiles(ParcelFileDescriptor data, int token, String[] domainTokens,
                String excludeFilesRegex, IBackupManager callbackBinder) {
            // Ensure that we're running with the app's normal permission level
            long ident = Binder.clearCallingIdentity();

            // Ensure that any SharedPreferences writes have landed *before*
            // we potentially try to back up the underlying files directly.
            waitForSharedPrefs();

            try {
                BackupAgent.this.onBackupFiles(domainTokens, excludeFilesRegex,
                        new FullBackupDataOutput(data));
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            } catch (RuntimeException ex) {
                throw ex;
            } finally {
                // ... and then again after, as in the doBackup() case
                waitForSharedPrefs();

                // Send the EOD marker indicating that there is no more data
                // forthcoming from this agent.
                try {
                    FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
                    byte[] buf = new byte[4];
                    out.write(buf);
                } catch (IOException e) {
                    Log.e(TAG, "Unable to finalize backup stream!");
                }

                Binder.restoreCallingIdentity(ident);
                try {
                    callbackBinder.opComplete(token);
                } catch (RemoteException e) {
                    // we'll time out anyway, so we're safe
                }
            }
        }

        @Override
        public void doRestoreFile(ParcelFileDescriptor data, long size,
                int type, String domain, String path, long mode, long mtime,
+36 −0
Original line number Diff line number Diff line
@@ -178,6 +178,31 @@ interface IBackupManager {
     */
    void fullTransportBackup(in String[] packageNames);

    /**
     * Write a backup of the given package to the supplied file descriptor.
     * The fd may be a socket or other non-seekable destination.
     *
     * <p>Use this method instead of fullBackup() when non-interactive operations are needed.
     *
     * <p>This method is <i>synchronous</i> -- it does not return until the backup has
     * completed.
     *
     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
     *
     * @param fd The file descriptor to which a 'tar' file stream is to be written.
     * @param domainTokens - The specific files paths (expressed as domain tokens) that should
     *        be backedup.
     * @param excludeFilesRegex - The files that should be excluded, specified as a regex.
     * @param packageName - The name of the package.
     * @param shouldKillAfterBackup - Should the process be killed once the backup is done.
     * @param ignoreEncryptionPasswordCheck - Should encryption password check be ignored
     * @param observer - The Backup Observer which is notified of the start / finish / error state
     *        of the backup.
     */
    void fullBackupNoninteractive(in ParcelFileDescriptor fd, in String[] domainTokens,
            in String excludeFilesRegex, in String packageName, boolean shouldKillAfterBackup,
            boolean ignoreEncryptionPasswordCheck, IFullBackupRestoreObserver observer);

    /**
     * Restore device content from the data stream passed through the given socket.  The
     * data stream must be in the format emitted by fullBackup().
@@ -186,6 +211,17 @@ interface IBackupManager {
     */
    void fullRestore(in ParcelFileDescriptor fd);

    /**
     * Restore device content from the data stream passed through the given socket.  The
     * data stream must be in the format emitted by fullBackup().
     *
     * <p>Use this method instead of fullRestore() when non-interactive operations are needed.
     *
     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
     */
    void fullRestoreNoninteractive(in ParcelFileDescriptor fd,
            boolean ignoreEncryptionPasswordCheck, IFullBackupRestoreObserver observer);

    /**
     * Confirm that the requested full backup/restore operation can proceed.  The system will
     * not actually perform the operation described to fullBackup() / fullRestore() unless the
+16 −0
Original line number Diff line number Diff line
@@ -43,6 +43,14 @@ oneway interface IFullBackupRestoreObserver {
     */
    void onEndBackup();

    /**
     * Notification: the full backup operation has ended with result.
     *
     * <p>To ensure backwards-compatibility, onEndBackup() and onEndBackupWithResult()
     * are both called for the same backup operation.
     */
    void onEndBackupWithResult(int result);

    /**
     * Notification: a restore-from-full-backup operation has begun.
     */
@@ -62,6 +70,14 @@ oneway interface IFullBackupRestoreObserver {
     */
    void onEndRestore();

    /**
     * Notification: the restore-from-full-backup operation has ended with result.
     *
     * <p>To ensure backwards-compatibility, onEndBackup() and onEndBackupWithResult()
     * are both called for the same backup operation.
     */
    void onEndRestoreWithResult(int result);

    /**
     * The user's window of opportunity for confirming the operation has timed out.
     */
+13 −1
Original line number Diff line number Diff line
@@ -324,6 +324,12 @@ public class BackupRestoreConfirmation extends Activity {
            mHandler.sendEmptyMessage(MSG_END_BACKUP);
        }

        @Override
        public void onEndBackupWithResult(int result) throws RemoteException {
            // The framework calls both onEndBackup() and onEndBackupResult().
            // This observer needs to only listen to one callback.
        }

        @Override
        public void onStartRestore() throws RemoteException {
            mHandler.sendEmptyMessage(MSG_START_RESTORE);
@@ -339,6 +345,12 @@ public class BackupRestoreConfirmation extends Activity {
            mHandler.sendEmptyMessage(MSG_END_RESTORE);
        }

        @Override
        public void onEndRestoreWithResult(int result) throws RemoteException {
            // The framework calls both onEndRestore() and onEndRestoreResult().
            // This observer needs to only listen to one callback.
        }

        @Override
        public void onTimeout() throws RemoteException {
            mHandler.sendEmptyMessage(MSG_TIMEOUT);
Loading