Hold a wakelock during backup/restore/clear operations

We need to make sure we stay alive for the duration of a backup or (especially)
restore operation.  The existing Handler-based timing system was simply not
properly functional, so it's been retooled to use a repeating alarm delivering a
broastcast PendingIntent to our registered receiver.

We acquire a partial wake lock in the broadcast receiver [i.e. while the Alarm
Manager is holding one for the duration of broadcast delivery] and pass the
wakelock object to the backup thread, which eventually releases it when it's
finsihed operations.  A similar pattern is used for the threads handling restore
and clear.
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 7977d1f..e47d853 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -17,9 +17,11 @@
 package com.android.server;
 
 import android.app.ActivityManagerNative;
+import android.app.AlarmManager;
 import android.app.IActivityManager;
 import android.app.IApplicationThread;
 import android.app.IBackupAgent;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -41,6 +43,7 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
@@ -76,8 +79,9 @@
 
     // How often we perform a backup pass.  Privileged external callers can
     // trigger an immediate pass.
-    private static final long BACKUP_INTERVAL = 60 * 60 * 1000;
+    private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR;
 
+    private static final String RUN_BACKUP_ACTION = "_backup_run_";
     private static final int MSG_RUN_BACKUP = 1;
     private static final int MSG_RUN_FULL_BACKUP = 2;
     private static final int MSG_RUN_RESTORE = 3;
@@ -89,8 +93,15 @@
     private Context mContext;
     private PackageManager mPackageManager;
     private IActivityManager mActivityManager;
+    private PowerManager mPowerManager;
+    private AlarmManager mAlarmManager;
+
     private boolean mEnabled;   // access to this is synchronized on 'this'
+    private PowerManager.WakeLock mWakelock;
     private final BackupHandler mBackupHandler = new BackupHandler();
+    private PendingIntent mRunBackupIntent;
+    private BroadcastReceiver mRunBackupReceiver;
+    private IntentFilter mRunBackupFilter;
     // map UIDs to the set of backup client services within that UID's app set
     private final SparseArray<HashSet<ApplicationInfo>> mBackupParticipants
         = new SparseArray<HashSet<ApplicationInfo>>();
@@ -171,12 +182,26 @@
         mPackageManager = context.getPackageManager();
         mActivityManager = ActivityManagerNative.getDefault();
 
+        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+
         // Set up our bookkeeping
-        mEnabled = Settings.Secure.getInt(context.getContentResolver(),
+        boolean areEnabled = Settings.Secure.getInt(context.getContentResolver(),
                 Settings.Secure.BACKUP_ENABLED, 0) != 0;
         mBaseStateDir = new File(Environment.getDataDirectory(), "backup");
         mDataDir = Environment.getDownloadCacheDirectory();
 
+        mRunBackupReceiver = new RunBackupReceiver();
+        mRunBackupFilter = new IntentFilter();
+        mRunBackupFilter.addAction(RUN_BACKUP_ACTION);
+        context.registerReceiver(mRunBackupReceiver, mRunBackupFilter);
+
+        Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
+        // !!! TODO: restrict delivery to our receiver; the naive setClass() doesn't seem to work
+        //backupIntent.setClass(context, mRunBackupReceiver.getClass());
+        backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mRunBackupIntent = PendingIntent.getBroadcast(context, MSG_RUN_BACKUP, backupIntent, 0);
+
         // Set up the backup-request journaling
         mJournalDir = new File(mBaseStateDir, "pending");
         mJournalDir.mkdirs();   // creates mBaseStateDir along the way
@@ -224,12 +249,29 @@
         filter.addDataScheme("package");
         mContext.registerReceiver(mBroadcastReceiver, filter);
 
-        // Schedule the first backup pass -- okay because no other threads are
-        // running yet
-        if (mEnabled) {
-            scheduleBackupPassLocked(BACKUP_INTERVAL);
+        // Power management
+        mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "backup");
+
+        // Start the backup passes going
+        setBackupEnabled(areEnabled);
+    }
+
+    private class RunBackupReceiver extends BroadcastReceiver {
+        public void onReceive(Context context, Intent intent) {
+            if (RUN_BACKUP_ACTION.equals(intent.getAction())) {
+                if (DEBUG) Log.v(TAG, "Running a backup pass");
+
+                synchronized (mQueueLock) {
+                    // acquire a wakelock and pass it to the backup thread.  it will
+                    // be released once backup concludes.
+                    mWakelock.acquire();
+
+                    Message msg = mBackupHandler.obtainMessage(MSG_RUN_BACKUP);
+                    mBackupHandler.sendMessage(msg);
+                }
+            }
         }
-}
+    }
 
     private void makeJournalLocked() {
         try {
@@ -344,6 +386,7 @@
                 IBackupTransport transport = getTransport(mCurrentTransport);
                 if (transport == null) {
                     Log.v(TAG, "Backup requested but no transport available");
+                    mWakelock.release();
                     break;
                 }
 
@@ -377,13 +420,9 @@
                         (new PerformBackupThread(transport, queue, oldJournal)).start();
                     } else {
                         Log.v(TAG, "Backup requested but nothing pending");
+                        mWakelock.release();
                     }
                 }
-
-                // Schedule the next pass.
-                synchronized (mQueueLock) {
-                    scheduleBackupPassLocked(BACKUP_INTERVAL);
-                }
                 break;
             }
 
@@ -394,7 +433,8 @@
             {
                 RestoreParams params = (RestoreParams)msg.obj;
                 Log.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
-                (new PerformRestoreThread(params.transport, params.observer, params.token)).start();
+                (new PerformRestoreThread(params.transport, params.observer,
+                        params.token)).start();
                 break;
             }
 
@@ -521,19 +561,6 @@
         addPackageParticipantsLockedInner(packageName, allApps);
     }
 
-    // The queue lock should be held when scheduling a backup pass
-    private void scheduleBackupPassLocked(long timeFromNowMillis) {
-        // We only schedule backups when we're actually enabled
-        synchronized (this) {
-            if (mEnabled) {
-                mBackupHandler.removeMessages(MSG_RUN_BACKUP);
-                mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, timeFromNowMillis);
-            } else if (DEBUG) {
-                Log.v(TAG, "Disabled, so not scheduling backup pass");
-            }
-        }
-    }
-
     // Return the given transport
     private IBackupTransport getTransport(String transportName) {
         synchronized (mTransports) {
@@ -685,6 +712,9 @@
             if (!mJournal.delete()) {
                 Log.e(TAG, "Unable to remove backup journal file " + mJournal.getAbsolutePath());
             }
+
+            // Only once we're entirely finished do we release the wakelock
+            mWakelock.release();
         }
 
         private void doQueuedBackups(IBackupTransport transport) {
@@ -1041,6 +1071,9 @@
                         Log.d(TAG, "Restore observer died at restoreFinished");
                     }
                 }
+
+                // done; we can finally release the wakelock
+                mWakelock.release();
             }
         }
 
@@ -1125,6 +1158,9 @@
                 } catch (RemoteException e) {
                     // can't happen; the transport is local
                 }
+
+                // Last but not least, release the cpu
+                mWakelock.release();
             }
         }
     }
@@ -1237,6 +1273,7 @@
                 if (DEBUG) Log.v(TAG, "Found the app - running clear process");
                 // found it; fire off the clear request
                 synchronized (mQueueLock) {
+                    mWakelock.acquire();
                     Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR,
                             new ClearParams(getTransport(mCurrentTransport), info));
                     mBackupHandler.sendMessage(msg);
@@ -1253,13 +1290,20 @@
 
         if (DEBUG) Log.v(TAG, "Scheduling immediate backup pass");
         synchronized (mQueueLock) {
-            scheduleBackupPassLocked(0);
+            try {
+                if (DEBUG) Log.v(TAG, "sending immediate backup broadcast");
+                mRunBackupIntent.send();
+            } catch (PendingIntent.CanceledException e) {
+                // should never happen
+                Log.e(TAG, "run-backup intent cancelled!");
+            }
         }
     }
 
     // Enable/disable the backup transport
     public void setBackupEnabled(boolean enable) {
-        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "setBackupEnabled");
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+                "setBackupEnabled");
 
         boolean wasEnabled = mEnabled;
         synchronized (this) {
@@ -1271,10 +1315,12 @@
         synchronized (mQueueLock) {
             if (enable && !wasEnabled) {
                 // if we've just been enabled, start scheduling backup passes
-                scheduleBackupPassLocked(BACKUP_INTERVAL);
+                long when = System.currentTimeMillis() + BACKUP_INTERVAL;
+                mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, when,
+                        BACKUP_INTERVAL, mRunBackupIntent);
             } else if (!enable) {
-                // No longer enabled, so stop running backups.
-                mBackupHandler.removeMessages(MSG_RUN_BACKUP);
+                // No longer enabled, so stop running backups
+                mAlarmManager.cancel(mRunBackupIntent);
             }
         }
     }
@@ -1422,6 +1468,7 @@
             if (mRestoreSets != null) {
                 for (int i = 0; i < mRestoreSets.length; i++) {
                     if (token == mRestoreSets[i].token) {
+                        mWakelock.acquire();
                         Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
                         msg.obj = new RestoreParams(mRestoreTransport, observer, token);
                         mBackupHandler.sendMessage(msg);
@@ -1457,14 +1504,6 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mQueueLock) {
             pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled"));
-            boolean scheduled = mBackupHandler.hasMessages(MSG_RUN_BACKUP);
-            if (scheduled != mEnabled) {
-                if (mEnabled) {
-                    pw.println("ERROR: backups enabled but none scheduled!");
-                } else {
-                    pw.println("ERROR: backups are scheduled but not enabled!");
-                }
-            }
             pw.println("Available transports:");
             for (String t : listAllTransports()) {
                 String pad = (t.equals(mCurrentTransport)) ? "  * " : "    ";