diff options
| -rw-r--r-- | core/java/android/app/BackupAgent.java | 34 | ||||
| -rw-r--r-- | core/java/android/app/IBackupAgent.aidl | 23 | ||||
| -rw-r--r-- | core/java/android/backup/IBackupManager.aidl | 10 | ||||
| -rw-r--r-- | core/java/android/backup/RestoreSession.java | 4 | ||||
| -rw-r--r-- | services/java/com/android/server/BackupManagerService.java | 313 | 
5 files changed, 260 insertions, 124 deletions
diff --git a/core/java/android/app/BackupAgent.java b/core/java/android/app/BackupAgent.java index 2a586777ddf3..35b6fed71014 100644 --- a/core/java/android/app/BackupAgent.java +++ b/core/java/android/app/BackupAgent.java @@ -19,6 +19,7 @@ package android.app;  import android.app.IBackupAgent;  import android.backup.BackupDataInput;  import android.backup.BackupDataOutput; +import android.backup.IBackupManager;  import android.content.Context;  import android.content.ContextWrapper;  import android.os.Binder; @@ -94,12 +95,9 @@ public abstract class BackupAgent extends ContextWrapper {      // ----- Core implementation ----- -     -    /** -     * Returns the private interface called by the backup system.  Applications will -     * not typically override this. -     */ -    public IBinder onBind() { + +    /** @hide */ +    public final IBinder onBind() {          return mBinder;      } @@ -116,9 +114,10 @@ public abstract class BackupAgent extends ContextWrapper {          public void doBackup(ParcelFileDescriptor oldState,                  ParcelFileDescriptor data, -                ParcelFileDescriptor newState) throws RemoteException { +                ParcelFileDescriptor newState, +                int token, IBackupManager callbackBinder) throws RemoteException {              // Ensure that we're running with the app's normal permission level -            long token = Binder.clearCallingIdentity(); +            long ident = Binder.clearCallingIdentity();              if (DEBUG) Log.v(TAG, "doBackup() invoked");              BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor()); @@ -131,14 +130,20 @@ public abstract class BackupAgent extends ContextWrapper {                  Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);                  throw ex;              } finally { -                Binder.restoreCallingIdentity(token); +                Binder.restoreCallingIdentity(ident); +                try { +                    callbackBinder.opComplete(token); +                } catch (RemoteException e) { +                    // we'll time out anyway, so we're safe +                }              }          }          public void doRestore(ParcelFileDescriptor data, int appVersionCode, -                ParcelFileDescriptor newState) throws RemoteException { +                ParcelFileDescriptor newState, +                int token, IBackupManager callbackBinder) throws RemoteException {              // Ensure that we're running with the app's normal permission level -            long token = Binder.clearCallingIdentity(); +            long ident = Binder.clearCallingIdentity();              if (DEBUG) Log.v(TAG, "doRestore() invoked");              BackupDataInput input = new BackupDataInput(data.getFileDescriptor()); @@ -151,7 +156,12 @@ public abstract class BackupAgent extends ContextWrapper {                  Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);                  throw ex;              } finally { -                Binder.restoreCallingIdentity(token); +                Binder.restoreCallingIdentity(ident); +                try { +                    callbackBinder.opComplete(token); +                } catch (RemoteException e) { +                    // we'll time out anyway, so we're safe +                }              }          }      } diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl index 9b0550fce64b..0de6ad9a83e2 100644 --- a/core/java/android/app/IBackupAgent.aidl +++ b/core/java/android/app/IBackupAgent.aidl @@ -16,6 +16,7 @@  package android.app; +import android.backup.IBackupManager;  import android.os.ParcelFileDescriptor;  /** @@ -25,7 +26,7 @@ import android.os.ParcelFileDescriptor;   *   * {@hide}   */  -interface IBackupAgent { +oneway interface IBackupAgent {      /**       * Request that the app perform an incremental backup.       * @@ -39,10 +40,18 @@ interface IBackupAgent {       *       * @param newState Read-write file, empty when onBackup() is called,       *        where the new state blob is to be recorded. +     * +     * @param token Opaque token identifying this transaction.  This must +     *        be echoed back to the backup service binder once the new +     *        data has been written to the data and newState files. +     * +     * @param callbackBinder Binder on which to indicate operation completion, +     *        passed here as a convenience to the agent.       */      void doBackup(in ParcelFileDescriptor oldState,              in ParcelFileDescriptor data, -            in ParcelFileDescriptor newState); +            in ParcelFileDescriptor newState, +            int token, IBackupManager callbackBinder);      /**       * Restore an entire data snapshot to the application. @@ -58,7 +67,15 @@ interface IBackupAgent {       * @param newState Read-write file, empty when onRestore() is called,       *        that is to be written with the state description that holds after       *        the restore has been 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 doRestore(in ParcelFileDescriptor data, int appVersionCode, -            in ParcelFileDescriptor newState); +            in ParcelFileDescriptor newState, int token, IBackupManager callbackBinder);  } diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl index 9d181bec49df..cb775f7504dc 100644 --- a/core/java/android/backup/IBackupManager.aidl +++ b/core/java/android/backup/IBackupManager.aidl @@ -130,4 +130,14 @@ interface IBackupManager {       * @return An interface to the restore session, or null on error.       */      IRestoreSession beginRestoreSession(String transportID); + +    /** +     * Notify the backup manager that a BackupAgent has completed the operation +     * corresponding to the given token. +     * +     * @param token The transaction token passed to a BackupAgent's doBackup() or +     *        doRestore() method. +     * {@hide} +     */ +    void opComplete(int token);  } diff --git a/core/java/android/backup/RestoreSession.java b/core/java/android/backup/RestoreSession.java index 119fc52e7433..a884793ab7af 100644 --- a/core/java/android/backup/RestoreSession.java +++ b/core/java/android/backup/RestoreSession.java @@ -109,9 +109,7 @@ public class RestoreSession {      /*       * We wrap incoming binder calls with a private class implementation that -     * redirects them into main-thread actions.  This accomplishes two things: -     * first, it ensures that the app's code is run on their own main thread, -     * never with system Binder identity; and second, it serializes the restore +     * redirects them into main-thread actions.  This serializes the restore       * progress callbacks nicely within the usual main-thread lifecycle pattern.       */      private class RestoreObserverWrapper extends IRestoreObserver.Stub { diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 72e26f8775ae..ee68a50b5321 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -43,18 +43,20 @@ import android.os.Binder;  import android.os.Bundle;  import android.os.Environment;  import android.os.Handler; +import android.os.HandlerThread;  import android.os.IBinder; +import android.os.Looper;  import android.os.Message;  import android.os.ParcelFileDescriptor;  import android.os.PowerManager;  import android.os.Process;  import android.os.RemoteException;  import android.os.SystemClock; -import android.os.SystemProperties;  import android.provider.Settings;  import android.util.EventLog;  import android.util.Log;  import android.util.SparseArray; +import android.util.SparseIntArray;  import com.android.internal.backup.BackupConstants;  import com.android.internal.backup.IBackupTransport; @@ -98,20 +100,27 @@ class BackupManagerService extends IBackupManager.Stub {      private static final int MSG_RUN_RESTORE = 3;      private static final int MSG_RUN_CLEAR = 4;      private static final int MSG_RUN_INITIALIZE = 5; +    private static final int MSG_TIMEOUT = 6;      // Timeout interval for deciding that a bind or clear-data has taken too long      static final long TIMEOUT_INTERVAL = 10 * 1000; +    // Timeout intervals for agent backup & restore operations +    static final long TIMEOUT_BACKUP_INTERVAL = 30 * 1000; +    static final long TIMEOUT_RESTORE_INTERVAL = 60 * 1000; +      private Context mContext;      private PackageManager mPackageManager;      private IActivityManager mActivityManager;      private PowerManager mPowerManager;      private AlarmManager mAlarmManager; +    IBackupManager mBackupManagerBinder;      boolean mEnabled;   // access to this is synchronized on 'this'      boolean mProvisioned;      PowerManager.WakeLock mWakelock; -    final BackupHandler mBackupHandler = new BackupHandler(); +    HandlerThread mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND); +    BackupHandler mBackupHandler;      PendingIntent mRunBackupIntent, mRunInitIntent;      BroadcastReceiver mRunBackupReceiver, mRunInitReceiver;      // map UIDs to the set of backup client services within that UID's app set @@ -185,6 +194,16 @@ class BackupManagerService extends IBackupManager.Stub {          }      } +    // Bookkeeping of in-flight operations for timeout etc. purposes.  The operation +    // token is the index of the entry in the pending-operations list. +    static final int OP_PENDING = 0; +    static final int OP_ACKNOWLEDGED = 1; +    static final int OP_TIMEOUT = -1; + +    final SparseIntArray mCurrentOperations = new SparseIntArray(); +    final Object mCurrentOpLock = new Object(); +    final Random mTokenGenerator = new Random(); +      // Where we keep our journal files and other bookkeeping      File mBaseStateDir;      File mDataDir; @@ -200,6 +219,115 @@ class BackupManagerService extends IBackupManager.Stub {      HashSet<String> mPendingInits = new HashSet<String>();  // transport names      volatile boolean mInitInProgress = false; +    // ----- Asynchronous backup/restore handler thread ----- + +    private class BackupHandler extends Handler { +        public BackupHandler(Looper looper) { +            super(looper); +        } + +        public void handleMessage(Message msg) { + +            switch (msg.what) { +            case MSG_RUN_BACKUP: +            { +                mLastBackupPass = System.currentTimeMillis(); +                mNextBackupPass = mLastBackupPass + BACKUP_INTERVAL; + +                IBackupTransport transport = getTransport(mCurrentTransport); +                if (transport == null) { +                    Log.v(TAG, "Backup requested but no transport available"); +                    synchronized (mQueueLock) { +                        mBackupOrRestoreInProgress = false; +                    } +                    mWakelock.release(); +                    break; +                } + +                // snapshot the pending-backup set and work on that +                ArrayList<BackupRequest> queue = new ArrayList<BackupRequest>(); +                synchronized (mQueueLock) { +                    // Do we have any work to do? +                    if (mPendingBackups.size() > 0) { +                        for (BackupRequest b: mPendingBackups.values()) { +                            queue.add(b); +                        } +                        if (DEBUG) Log.v(TAG, "clearing pending backups"); +                        mPendingBackups.clear(); + +                        // Start a new backup-queue journal file too +                        File oldJournal = mJournal; +                        mJournal = null; + +                        // At this point, we have started a new journal file, and the old +                        // file identity is being passed to the backup processing thread. +                        // When it completes successfully, that old journal file will be +                        // deleted.  If we crash prior to that, the old journal is parsed +                        // at next boot and the journaled requests fulfilled. +                        (new PerformBackupTask(transport, queue, oldJournal)).run(); +                    } else { +                        Log.v(TAG, "Backup requested but nothing pending"); +                        synchronized (mQueueLock) { +                            mBackupOrRestoreInProgress = false; +                        } +                        mWakelock.release(); +                    } +                } +                break; +            } + +            case MSG_RUN_FULL_BACKUP: +                break; + +            case MSG_RUN_RESTORE: +            { +                RestoreParams params = (RestoreParams)msg.obj; +                Log.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer); +                (new PerformRestoreTask(params.transport, params.observer, +                        params.token)).run(); +                break; +            } + +            case MSG_RUN_CLEAR: +            { +                ClearParams params = (ClearParams)msg.obj; +                (new PerformClearTask(params.transport, params.packageInfo)).run(); +                break; +            } + +            case MSG_RUN_INITIALIZE: +            { +                HashSet<String> queue; + +                // Snapshot the pending-init queue and work on that +                synchronized (mQueueLock) { +                    queue = new HashSet<String>(mPendingInits); +                    mPendingInits.clear(); +                } + +                (new PerformInitializeTask(queue)).run(); +                break; +            } + +            case MSG_TIMEOUT: +            { +                synchronized (mCurrentOpLock) { +                    final int token = msg.arg1; +                    int state = mCurrentOperations.get(token, OP_TIMEOUT); +                    if (state == OP_PENDING) { +                        if (DEBUG) Log.v(TAG, "TIMEOUT: token=" + token); +                        mCurrentOperations.put(token, OP_TIMEOUT); +                    } +                    mCurrentOpLock.notifyAll(); +                } +                break; +            } +            } +        } +    } + +    // ----- Main service implementation ----- +      public BackupManagerService(Context context) {          mContext = context;          mPackageManager = context.getPackageManager(); @@ -208,6 +336,13 @@ class BackupManagerService extends IBackupManager.Stub {          mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);          mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); +        mBackupManagerBinder = asInterface(asBinder()); + +        // spin up the backup/restore handler thread +        mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND); +        mHandlerThread.start(); +        mBackupHandler = new BackupHandler(mHandlerThread.getLooper()); +          // Set up our bookkeeping          boolean areEnabled = Settings.Secure.getInt(context.getContentResolver(),                  Settings.Secure.BACKUP_ENABLED, 0) != 0; @@ -593,95 +728,6 @@ class BackupManagerService extends IBackupManager.Stub {          }      }; -    // ----- Run the actual backup process asynchronously ----- - -    private class BackupHandler extends Handler { -        public void handleMessage(Message msg) { - -            switch (msg.what) { -            case MSG_RUN_BACKUP: -            { -                mLastBackupPass = System.currentTimeMillis(); -                mNextBackupPass = mLastBackupPass + BACKUP_INTERVAL; - -                IBackupTransport transport = getTransport(mCurrentTransport); -                if (transport == null) { -                    Log.v(TAG, "Backup requested but no transport available"); -                    synchronized (mQueueLock) { -                        mBackupOrRestoreInProgress = false; -                    } -                    mWakelock.release(); -                    break; -                } - -                // snapshot the pending-backup set and work on that -                ArrayList<BackupRequest> queue = new ArrayList<BackupRequest>(); -                synchronized (mQueueLock) { -                    // Do we have any work to do? -                    if (mPendingBackups.size() > 0) { -                        for (BackupRequest b: mPendingBackups.values()) { -                            queue.add(b); -                        } -                        Log.v(TAG, "clearing pending backups"); -                        mPendingBackups.clear(); - -                        // Start a new backup-queue journal file too -                        File oldJournal = mJournal; -                        mJournal = null; - -                        // At this point, we have started a new journal file, and the old -                        // file identity is being passed to the backup processing thread. -                        // When it completes successfully, that old journal file will be -                        // deleted.  If we crash prior to that, the old journal is parsed -                        // at next boot and the journaled requests fulfilled. -                        (new PerformBackupThread(transport, queue, oldJournal)).start(); -                    } else { -                        Log.v(TAG, "Backup requested but nothing pending"); -                        synchronized (mQueueLock) { -                            mBackupOrRestoreInProgress = false; -                        } -                        mWakelock.release(); -                    } -                } -                break; -            } - -            case MSG_RUN_FULL_BACKUP: -                break; - -            case MSG_RUN_RESTORE: -            { -                RestoreParams params = (RestoreParams)msg.obj; -                Log.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer); -                (new PerformRestoreThread(params.transport, params.observer, -                        params.token)).start(); -                break; -            } - -            case MSG_RUN_CLEAR: -            { -                ClearParams params = (ClearParams)msg.obj; -                (new PerformClearThread(params.transport, params.packageInfo)).start(); -                break; -            } - -            case MSG_RUN_INITIALIZE: -            { -                HashSet<String> queue; - -                // Snapshot the pending-init queue and work on that -                synchronized (mQueueLock) { -                    queue = new HashSet<String>(mPendingInits); -                    mPendingInits.clear(); -                } - -                (new PerformInitializeThread(queue)).start(); -                break; -            } -            } -        } -    } -      // Add the backup agents in the given package to our set of known backup participants.      // If 'packageName' is null, adds all backup agents in the whole system.      void addPackageParticipantsLocked(String packageName) { @@ -978,16 +1024,44 @@ class BackupManagerService extends IBackupManager.Stub {          }      } +    // ----- +    // Utility methods used by the asynchronous-with-timeout backup/restore operations +    boolean waitUntilOperationComplete(int token) { +        int finalState = OP_PENDING; +        synchronized (mCurrentOpLock) { +            try { +                while ((finalState = mCurrentOperations.get(token, OP_TIMEOUT)) == OP_PENDING) { +                    try { +                        mCurrentOpLock.wait(); +                    } catch (InterruptedException e) {} +                } +            } catch (IndexOutOfBoundsException e) { +                // the operation has been mysteriously cleared from our +                // bookkeeping -- consider this a success and ignore it. +            } +        } +        mBackupHandler.removeMessages(MSG_TIMEOUT); +        if (DEBUG) Log.v(TAG, "operation " + token + " complete: finalState=" + finalState); +        return finalState == OP_ACKNOWLEDGED; +    } + +    void prepareOperationTimeout(int token, long interval) { +        if (DEBUG) Log.v(TAG, "starting timeout: token=" + token + " interval=" + interval); +        mCurrentOperations.put(token, OP_PENDING); +        Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0); +        mBackupHandler.sendMessageDelayed(msg, interval); +    } +      // ----- Back up a set of applications via a worker thread ----- -    class PerformBackupThread extends Thread { +    class PerformBackupTask implements Runnable {          private static final String TAG = "PerformBackupThread";          IBackupTransport mTransport;          ArrayList<BackupRequest> mQueue;          File mStateDir;          File mJournal; -        public PerformBackupThread(IBackupTransport transport, ArrayList<BackupRequest> queue, +        public PerformBackupTask(IBackupTransport transport, ArrayList<BackupRequest> queue,                  File journal) {              mTransport = transport;              mQueue = queue; @@ -1000,7 +1074,6 @@ class BackupManagerService extends IBackupManager.Stub {              }          } -        @Override          public void run() {              int status = BackupConstants.TRANSPORT_OK;              long startRealtime = SystemClock.elapsedRealtime(); @@ -1158,6 +1231,7 @@ class BackupManagerService extends IBackupManager.Stub {              ParcelFileDescriptor newState = null;              PackageInfo packInfo; +            int token = mTokenGenerator.nextInt();              try {                  // 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 @@ -1189,8 +1263,16 @@ class BackupManagerService extends IBackupManager.Stub {                          ParcelFileDescriptor.MODE_CREATE |                          ParcelFileDescriptor.MODE_TRUNCATE); -                // Run the target's backup pass -                agent.doBackup(savedState, backupData, newState); +                // Initiate the target's backup pass +                prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL); +                agent.doBackup(savedState, backupData, newState, token, mBackupManagerBinder); +                boolean success = waitUntilOperationComplete(token); + +                if (!success) { +                    // timeout -- bail out into the failed-transaction logic +                    throw new RuntimeException("Backup timeout"); +                } +                  logBackupComplete(packageName);                  if (DEBUG) Log.v(TAG, "doBackup() success");              } catch (Exception e) { @@ -1204,6 +1286,9 @@ class BackupManagerService extends IBackupManager.Stub {                  try { if (backupData != null) backupData.close(); } catch (IOException e) {}                  try { if (newState != null) newState.close(); } catch (IOException e) {}                  savedState = backupData = newState = null; +                synchronized (mCurrentOpLock) { +                    mCurrentOperations.clear(); +                }              }              // Now propagate the newly-backed-up data to the transport @@ -1299,7 +1384,7 @@ class BackupManagerService extends IBackupManager.Stub {          return true;      } -    class PerformRestoreThread extends Thread { +    class PerformRestoreTask implements Runnable {          private IBackupTransport mTransport;          private IRestoreObserver mObserver;          private long mToken; @@ -1315,7 +1400,7 @@ class BackupManagerService extends IBackupManager.Stub {              }          } -        PerformRestoreThread(IBackupTransport transport, IRestoreObserver observer, +        PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,                  long restoreSetToken) {              mTransport = transport;              Log.d(TAG, "PerformRestoreThread mObserver=" + mObserver); @@ -1329,7 +1414,6 @@ class BackupManagerService extends IBackupManager.Stub {              }          } -        @Override          public void run() {              long startRealtime = SystemClock.elapsedRealtime();              if (DEBUG) Log.v(TAG, "Beginning restore process mTransport=" + mTransport @@ -1579,6 +1663,7 @@ class BackupManagerService extends IBackupManager.Stub {              ParcelFileDescriptor backupData = null;              ParcelFileDescriptor newState = null; +            int token = mTokenGenerator.nextInt();              try {                  // Run the transport's restore pass                  backupData = ParcelFileDescriptor.open(backupDataName, @@ -1602,7 +1687,14 @@ class BackupManagerService extends IBackupManager.Stub {                              ParcelFileDescriptor.MODE_CREATE |                              ParcelFileDescriptor.MODE_TRUNCATE); -                agent.doRestore(backupData, appVersionCode, newState); +                // Kick off the restore, checking for hung agents +                prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL); +                agent.doRestore(backupData, appVersionCode, newState, token, mBackupManagerBinder); +                boolean success = waitUntilOperationComplete(token); + +                if (!success) { +                    throw new RuntimeException("restore timeout"); +                }                  // if everything went okay, remember the recorded state now                  // @@ -1635,20 +1727,20 @@ class BackupManagerService extends IBackupManager.Stub {                  try { if (backupData != null) backupData.close(); } catch (IOException e) {}                  try { if (newState != null) newState.close(); } catch (IOException e) {}                  backupData = newState = null; +                mCurrentOperations.delete(token);              }          }      } -    class PerformClearThread extends Thread { +    class PerformClearTask implements Runnable {          IBackupTransport mTransport;          PackageInfo mPackage; -        PerformClearThread(IBackupTransport transport, PackageInfo packageInfo) { +        PerformClearTask(IBackupTransport transport, PackageInfo packageInfo) {              mTransport = transport;              mPackage = packageInfo;          } -        @Override          public void run() {              try {                  // Clear the on-device backup state to ensure a full backup next time @@ -1678,14 +1770,13 @@ class BackupManagerService extends IBackupManager.Stub {          }      } -    class PerformInitializeThread extends Thread { +    class PerformInitializeTask implements Runnable {          HashSet<String> mQueue; -        PerformInitializeThread(HashSet<String> transportNames) { +        PerformInitializeTask(HashSet<String> transportNames) {              mQueue = transportNames;          } -        @Override          public void run() {              try {                  for (String transportName : mQueue) { @@ -2073,6 +2164,16 @@ class BackupManagerService extends IBackupManager.Stub {          return mActiveRestoreSession;      } +    // Note that a currently-active backup agent has notified us that it has +    // completed the given outstanding asynchronous backup/restore operation. +    public void opComplete(int token) { +        synchronized (mCurrentOpLock) { +            if (DEBUG) Log.v(TAG, "opComplete: " + token); +            mCurrentOperations.put(token, OP_ACKNOWLEDGED); +            mCurrentOpLock.notifyAll(); +        } +    } +      // ----- Restore session -----      class ActiveRestoreSession extends IRestoreSession.Stub {  |