Tune delivery and batching of alarms

cherry-pick from lmp-mr1-dev

* Inexact alarms no longer coalesce with exact alarms.  The motivation here
is that exact alarms are far more likely to be wall-clock aligned, and in
general pulling all alarms toward wall-clock alignment is a bad idea.

* Wakeup times are now fuzzed within the target batch's allowed window
rather than being hard pinned at the start of the window.

Bug 18631821

Change-Id: Iefaf34eee3f2a6546abefc27e177ee2fdcff935f
(cherry picked from commit 81f9882b5aadd6a2289c9f521a06a7af5f35ebf0)
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 9d6ccfb..ed8bc51 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -61,6 +61,7 @@
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.Locale;
+import java.util.Random;
 import java.util.TimeZone;
 
 import static android.app.AlarmManager.RTC_WAKEUP;
@@ -108,8 +109,12 @@
     final Object mLock = new Object();
 
     long mNativeData;
-    private long mNextWakeup;
+
+    private final Random mFuzzer = new Random();
+    private long mNextWakeupBatchStart;     // nominal start of next wakeup's delivery window
+    private long mNextWakeup;               // actual scheduled next wakeup within that window
     private long mNextNonWakeup;
+
     int mBroadcastRefCount = 0;
     PowerManager.WakeLock mWakeLock;
     boolean mLastWakeLockUnimportantForLogging;
@@ -361,14 +366,27 @@
 
     static class BatchTimeOrder implements Comparator<Batch> {
         public int compare(Batch b1, Batch b2) {
-            long when1 = b1.start;
-            long when2 = b2.start;
-            if (when1 - when2 > 0) {
+            final long start1 = b1.start;
+            final long start2 = b2.start;
+            if (start1 > start2) {
                 return 1;
             }
-            if (when1 - when2 < 0) {
+            if (start1 < start2) {
                 return -1;
             }
+
+            // Identical trigger times.  As a secondary ordering, require that
+            // the batch with the shorter allowable delivery window sorts first.
+            final long interval1 = b1.end - b1.start;
+            final long interval2 = b2.end - b2.start;
+            if (interval1 > interval2) {
+                return 1;
+            }
+            if (interval2 < interval1) {
+                return -1;
+            }
+
+            // equal start + delivery window => they're identical
             return 0;
         }
     }
@@ -591,7 +609,7 @@
     @Override
     public void onStart() {
         mNativeData = init();
-        mNextWakeup = mNextNonWakeup = 0;
+        mNextWakeup = mNextWakeupBatchStart = mNextNonWakeup = 0;
 
         // We have to set current TimeZone info to kernel
         // because kernel doesn't keep this after reboot
@@ -786,8 +804,9 @@
                         "AlarmManager.set");
             }
 
+            // Exact alarms are standalone; inexact get batched together
             setImpl(type, triggerAtTime, windowLength, interval, operation,
-                    false, workSource, alarmClock);
+                    windowLength == AlarmManager.WINDOW_EXACT, workSource, alarmClock);
         }
 
         @Override
@@ -858,7 +877,7 @@
 
             pw.print("nowRTC="); pw.print(nowRTC);
             pw.print("="); pw.print(sdf.format(new Date(nowRTC)));
-            pw.print(" nowELAPSED="); TimeUtils.formatDuration(nowELAPSED, pw);
+            pw.print(" nowELAPSED="); pw.print(nowELAPSED);
             pw.println();
             if (!mInteractive) {
                 pw.print("Time since non-interactive: ");
@@ -1064,17 +1083,6 @@
         return true;
     }
 
-    private Batch findFirstWakeupBatchLocked() {
-        final int N = mAlarmBatches.size();
-        for (int i = 0; i < N; i++) {
-            Batch b = mAlarmBatches.get(i);
-            if (b.hasWakeups()) {
-                return b;
-            }
-        }
-        return null;
-    }
-
     private AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
         synchronized (mLock) {
             return mNextAlarmClockForUser.get(userId);
@@ -1208,16 +1216,48 @@
         // prior to that which contains no wakeups, we schedule that as well.
         long nextNonWakeup = 0;
         if (mAlarmBatches.size() > 0) {
-            final Batch firstWakeup = findFirstWakeupBatchLocked();
+            // Find the first wakeup alarm and note the following batch as well.  We'll be
+            // choosing a fuzzed delivery time within the first's allowable interval but
+            // ensuring that it does not encroach on the second's start time, to minimize
+            // alarm reordering.
+            Batch firstWakeup = null, nextAfterWakeup = null;
+            final int N = mAlarmBatches.size();
+            for (int i = 0; i < N; i++) {
+                Batch b = mAlarmBatches.get(i);
+                if (b.hasWakeups()) {
+                    firstWakeup = b;
+                    if (i < N-1) {
+                        nextAfterWakeup = mAlarmBatches.get(i+1);
+                    }
+                    break;
+                }
+            }
+
+            // There's a subtlety here: we depend on the invariant that if two batches
+            // exist with the same start time, the one with the shorter delivery window
+            // is sorted before the other.  This guarantees us that we need only look
+            // at the first [relevant] batch in the queue in order to schedule an alarm
+            // appropriately.
             final Batch firstBatch = mAlarmBatches.get(0);
-            if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
-                mNextWakeup = firstWakeup.start;
-                setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
+            if (firstWakeup != null && mNextWakeupBatchStart != firstWakeup.start) {
+                mNextWakeupBatchStart = mNextWakeup = firstWakeup.start;
+                final long windowEnd = (nextAfterWakeup == null)
+                        ? firstWakeup.end
+                        : Math.min(firstWakeup.end, nextAfterWakeup.start);
+                final long interval = windowEnd - firstWakeup.start;
+                // if the interval is over maxint we're into crazy land anyway, but
+                // just in case we check and don't fuzz if the conversion to int for
+                // random-number purposes would blow up
+                if (interval > 0 && interval < Integer.MAX_VALUE) {
+                    mNextWakeup += mFuzzer.nextInt((int) interval);
+                }
+                setLocked(ELAPSED_REALTIME_WAKEUP, mNextWakeup);
             }
             if (firstBatch != firstWakeup) {
                 nextNonWakeup = firstBatch.start;
             }
         }
+
         if (mPendingNonWakeupAlarms.size() > 0) {
             if (nextNonWakeup == 0 || mNextNonWakeupDeliveryTime < nextNonWakeup) {
                 nextNonWakeup = mNextNonWakeupDeliveryTime;