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

Commit df01deaa authored by Christopher Tate's avatar Christopher Tate
Browse files

More restore plumbing, plus add suggested-backoff to transport API

Adds most of the code for a background-thread restore process, structured much
like the backup thread.  Broke some common functionality out into a helper
function for doing a synchronous wait for a requested agent to attach.

Added a method to IBackupTransport whereby the transport will be asked for
an opinion on whether this is a good time for a backup to happen.  It will
reply with the results of its policymaking around backoff intervals, time-of-day
selection, etc.
parent 8f094ca7
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -13,6 +13,10 @@ import android.os.RemoteException;

public class AdbTransport extends IBackupTransport.Stub {

    public long requestBackupTime() throws RemoteException {
        return 0;
    }

    public int startSession() throws RemoteException {
        // TODO Auto-generated method stub
        return 0;
+4 −0
Original line number Diff line number Diff line
@@ -11,6 +11,10 @@ import android.os.RemoteException;

public class GoogleTransport extends IBackupTransport.Stub {

    public long requestBackupTime() throws RemoteException {
        return 0;       // !!! TODO: implement real backoff policy
    }

    public int startSession() throws RemoteException {
        // TODO Auto-generated method stub
        return 0;
+16 −9
Original line number Diff line number Diff line
@@ -40,6 +40,16 @@ interface IBackupTransport {
        - cloud: tear down connection etc
        - adb: close the file
*/
    /**
     * Verify that this is a suitable time for a backup pass.  This should return zero
     * if a backup is reasonable right now, false otherwise.  This method will be called
     * outside of the {@link #startSession}/{@link #endSession} pair.
     *
     * <p>If this is not a suitable time for a backup, the transport should suggest a
     * backoff delay, in milliseconds, after which the Backup Manager should try again.
     */
    long requestBackupTime();

    /**
     * Establish a connection to the back-end data repository, if necessary.  If the transport
     * needs to initialize state that is not tied to individual applications' backup operations,
@@ -64,33 +74,30 @@ interface IBackupTransport {
    /**
     * Get the set of backups currently available over this transport.
     *
     * @return A bundle containing two elements:  an int array under the key
     *   "tokens" whose entries are a transport-private identifier for each backup set;
     *   and a String array under the key "names" whose entries are the user-meaningful
     *   names corresponding to the backup sets at each index in the tokens array.
     * @return Descriptions of the set of restore images available for this device.
     **/
    RestoreSet[] getAvailableRestoreSets();

    /**
     * Get the set of applications from a given backup image.
     * Get the set of applications from a given restore image.
     *
     * @param token A backup token as returned by {@link availableBackups}.
     * @param token A backup token as returned by {@link #getAvailableRestoreSets}.
     * @return An array of PackageInfo objects describing all of the applications
     *   available for restore from the given backup set.  This should include the list
     *   available for restore from this restore image.  This should include the list
     *   of signatures for each package so that the Backup Manager can filter using that
     *   information.
     */
    PackageInfo[] getAppSet(int token);

    /**
     * Retrieve one application's data from the backup destination.
     * Retrieve one application's data from the backing store.
     *
     * @param token The backup record from which a restore is being requested.
     * @param packageInfo The identity of the application whose data is being restored.
     *   This must include the signature list for the package; it is up to the transport
     *   to verify that the requested app's signatures match the saved backup record
     *   because the transport cannot necessarily trust the client device.
     * @param data An open, writeable file into which the backup image should be stored.
     * @param data An open, writable file into which the backup image should be stored.
     * @return Zero on success; a nonzero error code on failure.
     */
    int getRestoreData(int token, in PackageInfo packageInfo, in ParcelFileDescriptor data);
+174 −43
Original line number Diff line number Diff line
@@ -215,7 +215,8 @@ class BackupManagerService extends IBackupManager.Stub {
            // Look up the package info & signatures.  This is first so that if it
            // throws an exception, there's no file setup yet that would need to
            // be unraveled.
            PackageInfo packInfo = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
            PackageInfo packInfo = mPackageManager.getPackageInfo(packageName,
                    PackageManager.GET_SIGNATURES);

            // !!! TODO: get the state file dir from the transport
            File savedStateName = new File(mStateDir, packageName);
@@ -367,7 +368,8 @@ class BackupManagerService extends IBackupManager.Stub {
        if (N > 0) {
            for (int a = N-1; a >= 0; a--) {
                ApplicationInfo app = allApps.get(a);
                if (app.backupAgentName == null) {
                if (((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0)
                        || app.backupAgentName == null) {
                    allApps.remove(a);
                }
            }
@@ -411,6 +413,40 @@ class BackupManagerService extends IBackupManager.Stub {
        return transport;
    }

    // fire off a backup agent, blocking until it attaches or times out
    IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
        IBackupAgent agent = null;
        synchronized(mAgentConnectLock) {
            mConnecting = true;
            mConnectedAgent = null;
            try {
                if (mActivityManager.bindBackupAgent(app, mode)) {
                    Log.d(TAG, "awaiting agent for " + app);

                    // success; wait for the agent to arrive
                    while (mConnecting && mConnectedAgent == null) {
                        try {
                            mAgentConnectLock.wait(10000);
                        } catch (InterruptedException e) {
                            // just retry
                            return null;
                        }
                    }

                    // if we timed out with no connect, abort and move on
                    if (mConnecting == true) {
                        Log.w(TAG, "Timeout waiting for agent " + app);
                        return null;
                    }
                    agent = mConnectedAgent;
                }
            } catch (RemoteException e) {
                // can't happen
            }
        }
        return agent;
    }

    // ----- Back up a set of applications via a worker thread -----

    class PerformBackupThread extends Thread {
@@ -425,15 +461,6 @@ class BackupManagerService extends IBackupManager.Stub {

        @Override
        public void run() {
            /*
             * 1. start up the current transport
             * 2. for each item in the queue:
             *      2a. bind the agent [wait for async attach]
             *      2b. set up the files and call doBackup()
             *      2c. unbind the agent
             * 3. tear down the transport
             * 4. done!
             */
            if (DEBUG) Log.v(TAG, "Beginning backup of " + mQueue.size() + " targets");

            // stand up the current transport
@@ -442,6 +469,15 @@ class BackupManagerService extends IBackupManager.Stub {
                return;
            }

            // start up the transport
            try {
                transport.startSession();
            } catch (Exception e) {
                Log.e(TAG, "Error session transport");
                e.printStackTrace();
                return;
            }

            // The transport is up and running; now run all the backups in our queue
            doQueuedBackups(transport);

@@ -456,60 +492,153 @@ class BackupManagerService extends IBackupManager.Stub {

        private void doQueuedBackups(IBackupTransport transport) {
            for (BackupRequest request : mQueue) {
                Log.d(TAG, "starting agent for " + request);
                // !!! TODO: need to handle the restore case?
                Log.d(TAG, "starting agent for backup of " + request);

                IBackupAgent agent = null;
                int mode = (request.fullBackup)
                        ? IApplicationThread.BACKUP_MODE_FULL
                        : IApplicationThread.BACKUP_MODE_INCREMENTAL;
                try {
                    synchronized(mAgentConnectLock) {
                        mConnecting = true;
                        mConnectedAgent = null;
                        if (mActivityManager.bindBackupAgent(request.appInfo, mode)) {
                            Log.d(TAG, "awaiting agent for " + request);
                    agent = bindToAgentSynchronous(request.appInfo, mode);
                    if (agent != null) {
                        processOneBackup(request, agent, transport);
                    }

                            // success; wait for the agent to arrive
                            while (mConnecting && mConnectedAgent == null) {
                    // unbind even on timeout, just in case
                    mActivityManager.unbindBackupAgent(request.appInfo);
                } catch (SecurityException ex) {
                    // Try for the next one.
                    Log.d(TAG, "error in bind", ex);
                } catch (RemoteException e) {
                    // can't happen
                }

            }
        }
    }


    // ----- Restore handling -----

    // Is the given package restorable on this device?  Returns the on-device app's
    // ApplicationInfo struct if it is; null if not.
    //
    // !!! TODO: also consider signatures
    ApplicationInfo isRestorable(PackageInfo packageInfo) {
        if (packageInfo.packageName != null) {
            try {
                                    mAgentConnectLock.wait(10000);
                                } catch (InterruptedException e) {
                                    // just retry
                                    continue;
                ApplicationInfo app = mPackageManager.getApplicationInfo(packageInfo.packageName,
                        PackageManager.GET_SIGNATURES);
                if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
                    return app;
                }
            } catch (Exception e) {
                // doesn't exist on this device, or other error -- just ignore it.
            }
        }
        return null;
    }

                            // if we timed out with no connect, abort and move on
                            if (mConnecting == true) {
                                Log.w(TAG, "Timeout waiting for agent " + request);
                                continue;
    class PerformRestoreThread extends Thread {
        private IBackupTransport mTransport;

        PerformRestoreThread(IBackupTransport transport) {
            mTransport = transport;
        }
                            agent = mConnectedAgent;

        @Override
        public void run() {
            /**
             * Restore sequence:
             *
             * 1. start up the transport session
             * 2. get the restore set description for our identity
             * 3. for each app in the restore set:
             *    3.a. if it's restorable on this device, add it to the restore queue
             * 4. for each app in the restore queue:
             *    4.b. get the restore data for the app from the transport
             *    4.c. launch the backup agent for the app
             *    4.d. agent.doRestore() with the data from the server
             *    4.e. unbind the agent [and kill the app?]
             * 5. shut down the transport
             */

            int err = -1;
            try {
                err = mTransport.startSession();
            } catch (Exception e) {
                Log.e(TAG, "Error starting transport for restore");
                e.printStackTrace();
            }

            if (err == 0) {
                // build the set of apps to restore
                try {
                    RestoreSet[] images = mTransport.getAvailableRestoreSets();
                    if (images.length > 0) {
                        // !!! for now we always take the first set
                        RestoreSet image = images[0];

                        // build the set of apps we will attempt to restore
                        PackageInfo[] packages = mTransport.getAppSet(image.token);
                        HashSet<ApplicationInfo> appsToRestore = new HashSet<ApplicationInfo>();
                        for (PackageInfo pkg: packages) {
                            ApplicationInfo app = isRestorable(pkg);
                            if (app != null) {
                                appsToRestore.add(app);
                            }
                        }

                        // now run the restore queue
                        doQueuedRestores(appsToRestore);
                    }
                } catch (RemoteException e) {
                    // can't happen; activity manager is local
                } catch (SecurityException ex) {
                    // Try for the next one.
                    Log.d(TAG, "error in bind", ex);
                    // can't happen; transports run locally
                }

                // successful bind? run the backup for this agent
                if (agent != null) {
                    processOneBackup(request, agent, transport);
                // done; shut down the transport
                try {
                    mTransport.endSession();
                } catch (Exception e) {
                    Log.e(TAG, "Error ending transport for restore");
                    e.printStackTrace();
                }
            }

                // send the unbind even on timeout, just in case
            // even if the initial session startup failed, report that we're done here
        }

        // restore each app in the queue
        void doQueuedRestores(HashSet<ApplicationInfo> appsToRestore) {
            for (ApplicationInfo app : appsToRestore) {
                Log.d(TAG, "starting agent for restore of " + app);

                IBackupAgent agent = null;
                try {
                    mActivityManager.unbindBackupAgent(request.appInfo);
                    agent = bindToAgentSynchronous(app, IApplicationThread.BACKUP_MODE_RESTORE);
                    if (agent != null) {
                        processOneRestore(app, agent);
                    }

                    // unbind even on timeout, just in case
                    mActivityManager.unbindBackupAgent(app);
                } catch (SecurityException ex) {
                    // Try for the next one.
                    Log.d(TAG, "error in bind", ex);
                } catch (RemoteException e) {
                    // can't happen
                }

            }
        }

        // do the guts of a restore
        void processOneRestore(ApplicationInfo app, IBackupAgent agent) {
            // !!! TODO: actually run the restore through mTransport
        }
    }


    // ----- IBackupManager binder interface -----

    public void dataChanged(String packageName) throws RemoteException {
@@ -548,6 +677,8 @@ class BackupManagerService extends IBackupManager.Stub {
                mBackupHandler.removeMessages(MSG_RUN_BACKUP);
                mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
            }
        } else {
            Log.w(TAG, "dataChanged but no participant pkg " + packageName);
        }
    }