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

Commit 2e40d115 authored by Christopher Tate's avatar Christopher Tate Committed by Christopher Tate
Browse files

Add BackupAgent.onRestoreFinished() callback

The agent's onRestoreFinished() method is called after all available
data has been delivered to the app, whether via the key/value restore
API or the full-data file-at-a-time API.  This gives the app a stable
opportunity to do any postprocessing that might be appropriate.

Also fixes a lingering bug in the framework's handling of backup
agent lifetimes.  In cases where an existing agent instances was
being rebound, the framework was forgetting to notify the dependent
that the agent was available.  This was causing timeouts and restore
failure.

Bug 16241004

Change-Id: I3f52b299312d30d38b0cba63a2cfaeb934991ef2
parent 738177ca
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -5454,6 +5454,7 @@ package android.app.backup {
    method public void onFullBackup(android.app.backup.FullBackupDataOutput) throws java.io.IOException;
    method public abstract void onRestore(android.app.backup.BackupDataInput, int, android.os.ParcelFileDescriptor) throws java.io.IOException;
    method public void onRestoreFile(android.os.ParcelFileDescriptor, long, java.io.File, int, long, long) throws java.io.IOException;
    method public void onRestoreFinished();
    field public static final int TYPE_DIRECTORY = 2; // 0x2
    field public static final int TYPE_FILE = 1; // 0x1
  }
+28 −27
Original line number Diff line number Diff line
@@ -2610,15 +2610,7 @@ public final class ActivityThread {
            return;
        }

        if (mBackupAgents.get(packageName) != null) {
            Slog.d(TAG, "BackupAgent " + "  for " + packageName
                    + " already exists");
            return;
        }

        BackupAgent agent = null;
        String classname = data.appInfo.backupAgentName;

        // full backup operation but no app-supplied agent?  use the default implementation
        if (classname == null && (data.backupMode == IApplicationThread.BACKUP_MODE_FULL
                || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL)) {
@@ -2627,6 +2619,14 @@ public final class ActivityThread {

        try {
            IBinder binder = null;
            BackupAgent agent = mBackupAgents.get(packageName);
            if (agent != null) {
                // reusing the existing instance
                if (DEBUG_BACKUP) {
                    Slog.v(TAG, "Reusing existing agent instance");
                }
                binder = agent.onBind();
            } else {
                try {
                    if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname);

@@ -2651,6 +2651,7 @@ public final class ActivityThread {
                    }
                    // falling through with 'binder' still null
                }
            }

            // tell the OS that we're live now
            try {
+15 −0
Original line number Diff line number Diff line
@@ -124,6 +124,21 @@ oneway interface IBackupAgent {
            int type, String domain, String path, long mode, long mtime,
            int token, IBackupManager callbackBinder);

    /**
     * Provide the app with a canonical "all data has been delivered" end-of-restore
     * callback so that it can do any postprocessing of the restored data that might
     * be appropriate.  This is issued after both key/value and full data restore
     * operations have completed.
     *
     * @param token Opaque token identifying this transaction.  This must
     *        be echoed back to the backup service binder once the agent is
     *        finished restoring the application based on the restore data
     *        contents.
     * @param callbackBinder Binder on which to indicate operation completion,
     *        passed here as a convenience to the agent.
     */
    void doRestoreFinished(int token, IBackupManager callbackBinder);

    /**
     * Out of band: instruct the agent to crash within the client process.  This is used
     * when the backup infrastructure detects a semantic error post-hoc and needs to
+34 −3
Original line number Diff line number Diff line
@@ -243,8 +243,7 @@ public abstract class BackupAgent extends ContextWrapper {
     *            When a full-backup dataset is being restored, this will be <code>null</code>.
     */
    public abstract void onRestore(BackupDataInput data, int appVersionCode,
            ParcelFileDescriptor newState)
            throws IOException;
            ParcelFileDescriptor newState) throws IOException;

    /**
     * The application is having its entire file system contents backed up.  {@code data}
@@ -575,6 +574,20 @@ public abstract class BackupAgent extends ContextWrapper {
        FullBackup.restoreFile(data, size, type, mode, mtime, null);
    }

    /**
     * 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)
     */
    public void onRestoreFinished() {
    }

    // ----- Core implementation -----

    /** @hide */
@@ -722,6 +735,24 @@ public abstract class BackupAgent extends ContextWrapper {
            }
        }

        @Override
        public void doRestoreFinished(int token, IBackupManager callbackBinder) {
            long ident = Binder.clearCallingIdentity();
            try {
                BackupAgent.this.onRestoreFinished();
            } finally {
                // Ensure that any side-effect SharedPreferences writes have landed
                waitForSharedPrefs();

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

        @Override
        public void fail(String message) {
            getHandler().post(new FailRunnable(message));
+93 −19
Original line number Diff line number Diff line
@@ -233,6 +233,7 @@ public class BackupManagerService extends IBackupManager.Stub {
    static final long TIMEOUT_FULL_BACKUP_INTERVAL = 5 * 60 * 1000;
    static final long TIMEOUT_SHARED_BACKUP_INTERVAL = 30 * 60 * 1000;
    static final long TIMEOUT_RESTORE_INTERVAL = 60 * 1000;
    static final long TIMEOUT_RESTORE_FINISHED_INTERVAL = 30 * 1000;

    // User confirmation timeout for a full backup/restore operation.  It's this long in
    // order to give them time to enter the backup password.
@@ -6309,6 +6310,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
        RUNNING_QUEUE,
        RESTORE_KEYVALUE,
        RESTORE_FULL,
        RESTORE_FINISHED,
        FINAL
    }

@@ -6343,6 +6345,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
        // Our bookkeeping about the ancestral dataset
        private PackageManagerBackupAgent mPmAgent;

        // Currently-bound backup agent for restore + restoreFinished purposes
        private IBackupAgent mAgent;

        // What sort of restore we're doing now
        private RestoreDescription mRestoreDescription;

@@ -6441,6 +6446,13 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
                    // this one is always valid too
                }
            }

            if (MORE_DEBUG) {
                Slog.v(TAG, "Restore; accept set size is " + mAcceptSet.size());
                for (PackageInfo info : mAcceptSet) {
                    Slog.v(TAG, "   " + info.packageName);
                }
            }
        }

        private String[] packagesToNames(List<PackageInfo> apps) {
@@ -6473,6 +6485,10 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
                    restoreFull();
                    break;

                case RESTORE_FINISHED:
                    restoreFinished();
                    break;

                case FINAL:
                    if (!mFinished) finalizeRestore();
                    else {
@@ -6529,7 +6545,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
        private  void startRestore() {
            sendStartRestore(mAcceptSet.size());

            UnifiedRestoreState nextState = UnifiedRestoreState.RUNNING_QUEUE;
            UnifiedRestoreState nextState = UnifiedRestoreState.RESTORE_FINISHED;
            try {
                // If we don't yet have PM metadata for this token, synthesize an
                // entry for the PM pseudopackage and make it the first to be
@@ -6544,6 +6560,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
                    mPmAgent = new PackageManagerBackupAgent(metadataFile);
                } catch (IOException e) {
                    // Nope, we need to get it via restore
                    if (MORE_DEBUG) Slog.v(TAG, "Need to restore @pm@");
                    PackageInfo pmPackage = new PackageInfo();
                    pmPackage.packageName = PACKAGE_MANAGER_SENTINEL;
                    mAcceptSet.add(0, pmPackage);
@@ -6581,8 +6598,11 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
                    mCurrentPackage = new PackageInfo();
                    mCurrentPackage.packageName = PACKAGE_MANAGER_SENTINEL;
                    mPmAgent = new PackageManagerBackupAgent(mPackageManager, null);
                    initiateOneRestore(mCurrentPackage, 0,
                            IBackupAgent.Stub.asInterface(mPmAgent.onBind()));
                    mAgent = IBackupAgent.Stub.asInterface(mPmAgent.onBind());
                    if (MORE_DEBUG) {
                        Slog.v(TAG, "initiating restore for PMBA");
                    }
                    initiateOneRestore(mCurrentPackage, 0);
                    // The PM agent called operationComplete() already, because our invocation
                    // of it is process-local and therefore synchronous.  That means that a
                    // RUNNING_QUEUE message is already enqueued.  Only if we're unable to
@@ -6621,6 +6641,11 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
                        nextState = UnifiedRestoreState.FINAL;
                        return;
                    }
                } else {
                    // We have the PMBA already, so we can proceed directly to
                    // the RUNNING_QUEUE state ourselves.
                    if (MORE_DEBUG) Slog.v(TAG, "PMBA from cache; proceeding to run queue");
                    nextState = UnifiedRestoreState.RUNNING_QUEUE;
                }
            } catch (RemoteException e) {
                // If we lost the transport at any time, halt
@@ -6762,10 +6787,10 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
            }

            // Good to go!  Set up and bind the agent...
            IBackupAgent agent = bindToAgentSynchronous(
            mAgent = bindToAgentSynchronous(
                    mCurrentPackage.applicationInfo,
                    IApplicationThread.BACKUP_MODE_INCREMENTAL);
            if (agent == null) {
            if (mAgent == null) {
                Slog.w(TAG, "Can't find backup agent for " + packageName);
                EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
                        "Restore agent missing");
@@ -6775,7 +6800,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF

            // And then finally start the restore on this agent
            try {
                initiateOneRestore(mCurrentPackage, metaInfo.versionCode, agent);
                initiateOneRestore(mCurrentPackage, metaInfo.versionCode);
                ++mCount;
            } catch (Exception e) {
                Slog.e(TAG, "Error when attempting restore: " + e.toString());
@@ -6785,7 +6810,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
        }

        // Guts of a key/value restore operation
        void initiateOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent) {
        void initiateOneRestore(PackageInfo app, int appVersionCode) {
            final String packageName = app.packageName;

            if (DEBUG) Slog.d(TAG, "initiateOneRestore packageName=" + packageName);
@@ -6881,7 +6906,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
                // the operationComplete() callback will schedule the next step,
                // so we do not do that here.
                prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, this);
                agent.doRestore(mBackupData, appVersionCode, mNewState,
                mAgent.doRestore(mBackupData, appVersionCode, mNewState,
                        token, mBackupManagerBinder);
            } catch (Exception e) {
                Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
@@ -6926,6 +6951,20 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
            }
        }

        // state RESTORE_FINISHED : provide the "no more data" signpost callback at the end
        private void restoreFinished() {
            try {
                final int token = generateToken();
                prepareOperationTimeout(token, TIMEOUT_RESTORE_FINISHED_INTERVAL, this);
                mAgent.doRestoreFinished(token, mBackupManagerBinder);
                // If we get this far, the callback or timeout will schedule the
                // next restore state, so we're done
            } catch (Exception e) {
                Slog.e(TAG, "Unable to finalize restore of " + mCurrentPackage.packageName);
                executeNextState(UnifiedRestoreState.FINAL);
            }
        }

        class StreamFeederThread extends RestoreEngine implements Runnable {
            final String TAG = "StreamFeederThread";
            FullRestoreEngine mEngine;
@@ -7202,23 +7241,58 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF

        @Override
        public void operationComplete() {
            if (MORE_DEBUG) {
                Slog.i(TAG, "operationComplete() during restore: target="
                        + mCurrentPackage.packageName
                        + " state=" + mState);
            }

            final UnifiedRestoreState nextState;
            switch (mState) {
                case RESTORE_KEYVALUE:
                case RESTORE_FULL: {
                    // Okay, we've just heard back from the agent that it's done with
                    // the restore itself.  We now have to send the same agent its
                    // doRestoreFinished() callback, so roll into that state.
                    nextState = UnifiedRestoreState.RESTORE_FINISHED;
                    break;
                }

                case RESTORE_FINISHED: {
                    // Okay, we're done with this package.  Tidy up and go on to the next
                    // app in the queue.
                    int size = (int) mBackupDataName.length();
            EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, mCurrentPackage.packageName, size);
                    EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE,
                            mCurrentPackage.packageName, size);

                    // Just go back to running the restore queue
                    keyValueAgentCleanup();

                    // If there was widget state associated with this app, get the OS to
                    // incorporate it into current bookeeping and then pass that along to
            // the app as part of the restore operation.
                    // the app as part of the restore-time work.
                    if (mWidgetData != null) {
                        restoreWidgetData(mCurrentPackage.packageName, mWidgetData);
                    }

            executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
                    nextState = UnifiedRestoreState.RUNNING_QUEUE;
                    break;
                }

                default: {
                    // Some kind of horrible semantic error; we're in an unexpected state.
                    // Back off hard and wind up.
                    Slog.e(TAG, "Unexpected restore callback into state " + mState);
                    keyValueAgentErrorCleanup();
                    nextState = UnifiedRestoreState.FINAL;
                    break;
                }
            }

            executeNextState(nextState);
        }

        // A call to agent.doRestore() has timed out
        // A call to agent.doRestore() or agent.doRestoreFinished() has timed out
        @Override
        public void handleTimeout() {
            Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);