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;