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);
}
}