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

Bug 9532215

Change-Id: I0efe32cbaaae8ce6ab223041eed116c3470a7326
diff --git a/api/current.txt b/api/current.txt
index e7a518f..ee0c395 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3057,6 +3057,7 @@
     method public void setRepeating(int, long, long, android.app.PendingIntent);
     method public void setTime(long);
     method public void setTimeZone(java.lang.String);
+    method public void setWindow(int, long, long, android.app.PendingIntent);
     field public static final int ELAPSED_REALTIME = 3; // 0x3
     field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2
     field public static final deprecated long INTERVAL_DAY = 86400000L; // 0x5265c00L
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index dbccbeb..d9c3775 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -84,9 +84,15 @@
      */
     public static final int ELAPSED_REALTIME = 3;
 
+    /** @hide */
+    public static final long WINDOW_EXACT = 0;
+    /** @hide */
+    public static final long WINDOW_HEURISTIC = -1;
+
     private final IAlarmManager mService;
     private final boolean mAlwaysExact;
 
+
     /**
      * package private on purpose
      */
@@ -96,9 +102,15 @@
         final int sdkVersion = ctx.getApplicationInfo().targetSdkVersion;
         mAlwaysExact = (sdkVersion < Build.VERSION_CODES.KEY_LIME_PIE);
     }
-    
+
+    private long legacyExactLength() {
+        return (mAlwaysExact ? WINDOW_EXACT : WINDOW_HEURISTIC);
+    }
+
     /**
-     * Schedule an alarm.  <b>Note: for timing operations (ticks, timeouts,
+     * TBW: discussion of fuzzy nature of alarms in KLP+.
+     *
+     * <p>Schedule an alarm.  <b>Note: for timing operations (ticks, timeouts,
      * etc) it is easier and much more efficient to use
      * {@link android.os.Handler}.</b>  If there is already an alarm scheduled
      * for the same IntentSender, it will first be canceled.
@@ -130,7 +142,9 @@
      * IntentSender.getBroadcast()}.
      *
      * @see android.os.Handler
+     * @see #setExact
      * @see #setRepeating
+     * @see #setWindow
      * @see #cancel
      * @see android.content.Context#sendBroadcast
      * @see android.content.Context#registerReceiver
@@ -141,7 +155,7 @@
      * @see #RTC_WAKEUP
      */
     public void set(int type, long triggerAtMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, 0, operation, mAlwaysExact);
+        setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation);
     }
 
     /**
@@ -182,6 +196,8 @@
      *
      * @see android.os.Handler
      * @see #set
+     * @see #setExact
+     * @see #setWindow
      * @see #cancel
      * @see android.content.Context#sendBroadcast
      * @see android.content.Context#registerReceiver
@@ -193,7 +209,42 @@
      */
     public void setRepeating(int type, long triggerAtMillis,
             long intervalMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, intervalMillis, operation, mAlwaysExact);
+        setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, operation);
+    }
+
+    /**
+     * Schedule an alarm to be delivered within a given window of time.
+     *
+     * TBW: clean up these docs
+     *
+     * @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC or
+     *        RTC_WAKEUP.
+     * @param windowStartMillis The earliest time, in milliseconds, that the alarm should
+     *        be delivered, expressed in the appropriate clock's units (depending on the alarm
+     *        type).
+     * @param windowLengthMillis The length of the requested delivery window,
+     *        in milliseconds.  The alarm will be delivered no later than this many
+     *        milliseconds after the windowStartMillis time.  Note that this parameter
+     *        is a <i>duration,</i> not the timestamp of the end of the window.
+     * @param operation Action to perform when the alarm goes off;
+     *        typically comes from {@link PendingIntent#getBroadcast
+     *        IntentSender.getBroadcast()}.
+     *
+     * @see #set
+     * @see #setExact
+     * @see #setRepeating
+     * @see #cancel
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     * @see #ELAPSED_REALTIME
+     * @see #ELAPSED_REALTIME_WAKEUP
+     * @see #RTC
+     * @see #RTC_WAKEUP
+     */
+    public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
+            PendingIntent operation) {
+        setImpl(type, windowStartMillis, windowLengthMillis, 0, operation);
     }
 
     /**
@@ -201,13 +252,13 @@
      * to the precise time specified.
      */
     public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, 0, operation, true);
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, operation);
     }
 
-    private void setImpl(int type, long triggerAtMillis, long intervalMillis,
-            PendingIntent operation, boolean isExact) {
+    private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
+            PendingIntent operation) {
         try {
-            mService.set(type, triggerAtMillis, intervalMillis, operation, isExact);
+            mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation);
         } catch (RemoteException ex) {
         }
     }
@@ -300,7 +351,7 @@
     @Deprecated
     public void setInexactRepeating(int type, long triggerAtMillis,
             long intervalMillis, PendingIntent operation) {
-        setRepeating(type, triggerAtMillis, intervalMillis, operation);
+        setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, operation);
     }
     
     /**
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index 3a2b215..0a49ddf 100644
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -24,7 +24,9 @@
  * {@hide}
  */
 interface IAlarmManager {
-    void set(int type, long triggerAtTime, long interval, in PendingIntent operation, boolean isExact);
+	/** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */
+    void set(int type, long triggerAtTime, long windowLength,
+            long interval, in PendingIntent operation);
     void setTime(long millis);
     void setTimeZone(String zone);
     void remove(in PendingIntent operation);
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);
         }
     }