Maintain ordering when alarm removal alters batch bounds

Also: turn off verbose logging, but enable a new debugging
consistency check.

Bug 9965704

Change-Id: Ib20fb8b6275b77ff98291cf12a43b2a1128ab4b9
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index 3840ba1..a2b4e12 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -42,6 +42,7 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 
+import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
@@ -81,7 +82,8 @@
     private static final String TAG = "AlarmManager";
     private static final String ClockReceiver_TAG = "ClockReceiver";
     private static final boolean localLOGV = false;
-    private static final boolean DEBUG_BATCH = localLOGV || true;
+    private static final boolean DEBUG_BATCH = localLOGV || false;
+    private static final boolean DEBUG_VALIDATE = localLOGV || true;
     private static final int ALARM_EVENT = 1;
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
     
@@ -367,33 +369,27 @@
 
     // The RTC clock has moved arbitrarily, so we need to recalculate all the batching
     void rebatchAllAlarms() {
-        if (DEBUG_BATCH) {
-            Slog.v(TAG, "RTC changed; rebatching");
-        }
         synchronized (mLock) {
-            ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone();
-            mAlarmBatches.clear();
-            final long nowElapsed = SystemClock.elapsedRealtime();
-            for (Batch batch : oldSet) {
-                final int N = batch.size();
-                for (int i = 0; i < N; i++) {
-                    Alarm a = batch.get(i);
-                    long whenElapsed = convertToElapsed(a.when, a.type);
-                    long maxElapsed = (a.whenElapsed == a.maxWhen)
-                            ? whenElapsed
-                            : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
-                    if (batch.standalone) {
-                        // this will also be the only alarm in the batch
-                        a = new Alarm(a.type, a.when, whenElapsed, maxElapsed,
-                                a.repeatInterval, a.operation);
-                        Batch newBatch = new Batch(a);
-                        newBatch.standalone = true;
-                        addBatchLocked(mAlarmBatches, newBatch);
-                    } else {
-                        setImplLocked(a.type, a.when, whenElapsed, maxElapsed,
-                                a.repeatInterval, a.operation, false);
-                    }
-                }
+            rebatchAllAlarmsLocked(true);
+        }
+    }
+
+    void rebatchAllAlarmsLocked(boolean doValidate) {
+        ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone();
+        mAlarmBatches.clear();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final int oldBatches = oldSet.size();
+        for (int batchNum = 0; batchNum < oldBatches; batchNum++) {
+            Batch batch = oldSet.get(batchNum);
+            final int N = batch.size();
+            for (int i = 0; i < N; i++) {
+                Alarm a = batch.get(i);
+                long whenElapsed = convertToElapsed(a.when, a.type);
+                long maxElapsed = (a.whenElapsed == a.maxWhen)
+                        ? whenElapsed
+                                : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
+                setImplLocked(a.type, a.when, whenElapsed, maxElapsed,
+                        a.repeatInterval, a.operation, batch.standalone, doValidate);
             }
         }
     }
@@ -544,23 +540,20 @@
                         + " interval=" + interval + " standalone=" + isStandalone);
             }
             setImplLocked(type, triggerAtTime, triggerElapsed, maxElapsed,
-                    interval, operation, isStandalone);
+                    interval, operation, isStandalone, true);
         }
     }
 
     private void setImplLocked(int type, long when, long whenElapsed, long maxWhen, long interval,
-            PendingIntent operation, boolean isStandalone) {
+            PendingIntent operation, boolean isStandalone, boolean doValidate) {
         Alarm a = new Alarm(type, when, whenElapsed, maxWhen, interval, operation);
         removeLocked(operation);
 
-        final boolean reschedule;
+        boolean reschedule;
         int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen);
         if (whichBatch < 0) {
             Batch batch = new Batch(a);
             batch.standalone = isStandalone;
-            if (DEBUG_BATCH) {
-                Slog.v(TAG, "Starting new alarm batch " + batch);
-            }
             reschedule = addBatchLocked(mAlarmBatches, batch);
         } else {
             Batch batch = mAlarmBatches.get(whichBatch);
@@ -573,11 +566,52 @@
             }
         }
 
+        if (DEBUG_VALIDATE) {
+            if (doValidate && validateConsistencyLocked()) {
+                rebatchAllAlarmsLocked(false);
+                reschedule = true;
+            }
+        }
+
         if (reschedule) {
             rescheduleKernelAlarmsLocked();
         }
     }
 
+    private void logBatchesLocked() {
+        ByteArrayOutputStream bs = new ByteArrayOutputStream(2048);
+        PrintWriter pw = new PrintWriter(bs);
+        final long nowRTC = System.currentTimeMillis();
+        final long nowELAPSED = SystemClock.elapsedRealtime();
+        final int NZ = mAlarmBatches.size();
+        for (int iz = 0; iz < NZ; iz++) {
+            Batch bz = mAlarmBatches.get(iz);
+            Slog.v(TAG, "Batch " + iz + ": " + bz);
+            dumpAlarmList(pw, bz.alarms, "  ", nowELAPSED, nowRTC);
+            Slog.v(TAG, bs.toString());
+            bs.reset();
+        }
+    }
+
+    private boolean validateConsistencyLocked() {
+        if (DEBUG_VALIDATE) {
+            long lastTime = Long.MIN_VALUE;
+            final int N = mAlarmBatches.size();
+            for (int i = 0; i < N; i++) {
+                Batch b = mAlarmBatches.get(i);
+                if (b.start >= lastTime) {
+                    // duplicate start times are okay because of standalone batches
+                    lastTime = b.start;
+                } else {
+                    Slog.e(TAG, "CONSISTENCY FAILURE: Batch " + i + " is out of order");
+                    logBatchesLocked();
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
     private Batch findFirstWakeupBatchLocked() {
         final int N = mAlarmBatches.size();
         for (int i = 0; i < N; i++) {
@@ -663,33 +697,60 @@
     }
     
     public void removeLocked(PendingIntent operation) {
+        boolean didRemove = false;
         for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
             Batch b = mAlarmBatches.get(i);
-            b.remove(operation);
+            didRemove |= b.remove(operation);
             if (b.size() == 0) {
                 mAlarmBatches.remove(i);
             }
         }
+
+        if (didRemove) {
+            if (DEBUG_BATCH) {
+                Slog.v(TAG, "remove(operation) changed bounds; rebatching");
+            }
+            rebatchAllAlarmsLocked(true);
+            rescheduleKernelAlarmsLocked();
+        }
     }
 
     public void removeLocked(String packageName) {
+        boolean didRemove = false;
         for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
             Batch b = mAlarmBatches.get(i);
-            b.remove(packageName);
+            didRemove |= b.remove(packageName);
             if (b.size() == 0) {
                 mAlarmBatches.remove(i);
             }
         }
+
+        if (didRemove) {
+            if (DEBUG_BATCH) {
+                Slog.v(TAG, "remove(package) changed bounds; rebatching");
+            }
+            rebatchAllAlarmsLocked(true);
+            rescheduleKernelAlarmsLocked();
+        }
     }
 
     public void removeUserLocked(int userHandle) {
+        boolean didRemove = false;
         for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
             Batch b = mAlarmBatches.get(i);
-            b.remove(userHandle);
+            didRemove |= b.remove(userHandle);
             if (b.size() == 0) {
                 mAlarmBatches.remove(i);
             }
         }
+
+        if (didRemove) {
+            if (DEBUG_BATCH) {
+                Slog.v(TAG, "remove(user) changed bounds; rebatching");
+            }
+            rebatchAllAlarmsLocked(true);
+            rescheduleKernelAlarmsLocked();
+        }
     }
 
     public boolean lookForPackageLocked(String packageName) {
@@ -962,7 +1023,7 @@
                     final long nextElapsed = alarm.whenElapsed + delta;
                     setImplLocked(alarm.type, alarm.when + delta, nextElapsed,
                             maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
-                            alarm.repeatInterval, alarm.operation, batch.standalone);
+                            alarm.repeatInterval, alarm.operation, batch.standalone, true);
                 }
 
             }
@@ -1067,7 +1128,7 @@
 
                 if ((result & TIME_CHANGED_MASK) != 0) {
                     if (DEBUG_BATCH) {
-                        Slog.v(TAG, "Time changed notification from kernel");
+                        Slog.v(TAG, "Time changed notification from kernel; rebatching");
                     }
                     remove(mTimeTickSender);
                     rebatchAllAlarms();