Add AlarmManager.setWindow(...) for supplying an explicit delivery window

Bug 9532215

Change-Id: I0efe32cbaaae8ce6ab223041eed116c3470a7326
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index e78b822..d7cdb9d 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.app.ActivityManagerNative;
+import android.app.AlarmManager;
 import android.app.IAlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -314,7 +315,7 @@
     }
     
     // minimum recurrence period or alarm futurity for us to be able to fuzz it
-    private static final long MAX_FUZZABLE_INTERVAL = 10000;
+    private static final long MIN_FUZZABLE_INTERVAL = 10000;
     private static final BatchTimeOrder sBatchOrder = new BatchTimeOrder();
     private final ArrayList<Batch> mAlarmBatches = new ArrayList<Batch>();
 
@@ -336,7 +337,7 @@
         long futurity = (interval == 0)
                 ? (triggerAtTime - now)
                 : interval;
-        if (futurity < MAX_FUZZABLE_INTERVAL) {
+        if (futurity < MIN_FUZZABLE_INTERVAL) {
             futurity = 0;
         }
         return triggerAtTime + (long)(.75 * futurity);
@@ -499,32 +500,45 @@
     }
 
     @Override
-    public void set(int type, long triggerAtTime, long interval,
-            PendingIntent operation, boolean isExact) {
-        set(type, triggerAtTime, interval, operation, isExact, false);
+    public void set(int type, long triggerAtTime, long windowLength, long interval,
+            PendingIntent operation) {
+        set(type, triggerAtTime, windowLength, interval, operation, false);
     }
 
-    public void set(int type, long triggerAtTime, long interval,
-            PendingIntent operation, boolean isExact, boolean isStandalone) {
+    public void set(int type, long triggerAtTime, long windowLength, long interval,
+            PendingIntent operation, boolean isStandalone) {
         if (operation == null) {
             Slog.w(TAG, "set/setRepeating ignored because there is no intent");
             return;
         }
 
+        // Sanity check the window length.  This will catch people mistakenly
+        // trying to pass an end-of-window timestamp rather than a duration.
+        if (windowLength > AlarmManager.INTERVAL_HALF_DAY) {
+            Slog.w(TAG, "Window length " + windowLength
+                    + "ms suspiciously long; limiting to 1 hour");
+            windowLength = AlarmManager.INTERVAL_HOUR;
+        }
+
         if (type < RTC_WAKEUP || type > ELAPSED_REALTIME) {
             throw new IllegalArgumentException("Invalid alarm type " + type);
         }
 
-        long nowElapsed = SystemClock.elapsedRealtime();
-        long triggerElapsed = convertToElapsed(triggerAtTime, type);
-        long maxElapsed = (isExact)
-                ? triggerElapsed
-                : maxTriggerTime(nowElapsed, triggerElapsed, interval);
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long triggerElapsed = convertToElapsed(triggerAtTime, type);
+        final long maxElapsed;
+        if (windowLength == AlarmManager.WINDOW_EXACT) {
+            maxElapsed = triggerElapsed;
+        } else if (windowLength < 0) {
+            maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval);
+        } else {
+            maxElapsed = triggerElapsed + windowLength;
+        }
 
         synchronized (mLock) {
             if (DEBUG_BATCH) {
                 Slog.v(TAG, "set(" + operation + ") : type=" + type
-                        + " triggerAtTime=" + triggerAtTime
+                        + " triggerAtTime=" + triggerAtTime + " win=" + windowLength
                         + " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed
                         + " interval=" + interval + " standalone=" + isStandalone);
             }
@@ -1218,8 +1232,8 @@
             // the top of the next minute.
             final long tickEventDelay = nextTime - currentTime;
 
-            set(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay,
-                    0, mTimeTickSender, true, true);
+            set(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
+                    0, mTimeTickSender, true);
         }
 	
         public void scheduleDateChangedEvent() {
@@ -1231,7 +1245,7 @@
             calendar.set(Calendar.MILLISECOND, 0);
             calendar.add(Calendar.DAY_OF_MONTH, 1);
       
-            set(RTC, calendar.getTimeInMillis(), 0, mDateChangeSender, true, true);
+            set(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true);
         }
     }