Batch alarms to reduce device wakeups
The default Alarm Manager behavior for KLP+ apps will be to aggressively
coalesce alarms, trading exact timeliness of delivery for minimizing the
number of alarm-delivery points, especially wakeup points.
There is new API in AlarmManager, setExact() and setExactRepeating(),
for use by apps that absolutely *must* get their alarms at a specific
point in time.
Bug 9532215
Change-Id: I40b4eea90220211cc958172d2629664b921ff051
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index 0be9ca5..b1489ad 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -18,7 +18,6 @@
import android.app.Activity;
import android.app.ActivityManagerNative;
-import android.app.AlarmManager;
import android.app.IAlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -38,7 +37,6 @@
import android.os.UserHandle;
import android.os.WorkSource;
import android.text.TextUtils;
-import android.text.format.Time;
import android.util.Pair;
import android.util.Slog;
import android.util.TimeUtils;
@@ -57,31 +55,38 @@
import java.util.Map;
import java.util.TimeZone;
+import static android.app.AlarmManager.RTC_WAKEUP;
+import static android.app.AlarmManager.RTC;
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.ELAPSED_REALTIME;
+
import com.android.internal.util.LocalLog;
class AlarmManagerService extends IAlarmManager.Stub {
// The threshold for how long an alarm can be late before we print a
// warning message. The time duration is in milliseconds.
private static final long LATE_ALARM_THRESHOLD = 10 * 1000;
-
- private static final int RTC_WAKEUP_MASK = 1 << AlarmManager.RTC_WAKEUP;
- private static final int RTC_MASK = 1 << AlarmManager.RTC;
- private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << AlarmManager.ELAPSED_REALTIME_WAKEUP;
- private static final int ELAPSED_REALTIME_MASK = 1 << AlarmManager.ELAPSED_REALTIME;
+
+ private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
+ private static final int RTC_MASK = 1 << RTC;
+ private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << ELAPSED_REALTIME_WAKEUP;
+ private static final int ELAPSED_REALTIME_MASK = 1 << ELAPSED_REALTIME;
private static final int TIME_CHANGED_MASK = 1 << 16;
private static final int IS_WAKEUP_MASK = RTC_WAKEUP_MASK|ELAPSED_REALTIME_WAKEUP_MASK;
- // Alignment quantum for inexact repeating alarms
- private static final long QUANTUM = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
+ // Mask for testing whether a given alarm type is wakeup vs non-wakeup
+ private static final int TYPE_NONWAKEUP_MASK = 0x1; // low bit => non-wakeup
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 || false;
private static final int ALARM_EVENT = 1;
private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
private static final Intent mBackgroundIntent
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
+ private static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder();
private static final boolean WAKEUP_STATS = true;
@@ -90,14 +95,10 @@
private final LocalLog mLog = new LocalLog(TAG);
private Object mLock = new Object();
-
- private final ArrayList<Alarm> mRtcWakeupAlarms = new ArrayList<Alarm>();
- private final ArrayList<Alarm> mRtcAlarms = new ArrayList<Alarm>();
- private final ArrayList<Alarm> mElapsedRealtimeWakeupAlarms = new ArrayList<Alarm>();
- private final ArrayList<Alarm> mElapsedRealtimeAlarms = new ArrayList<Alarm>();
- private final IncreasingTimeOrder mIncreasingTimeOrder = new IncreasingTimeOrder();
-
+
private int mDescriptor;
+ private long mNextWakeup;
+ private long mNextNonWakeup;
private int mBroadcastRefCount = 0;
private PowerManager.WakeLock mWakeLock;
private ArrayList<InFlight> mInFlight = new ArrayList<InFlight>();
@@ -124,6 +125,277 @@
private final LinkedList<WakeupEvent> mRecentWakeups = new LinkedList<WakeupEvent>();
private final long RECENT_WAKEUP_PERIOD = 1000L * 60 * 60 * 24; // one day
+ static final class Batch {
+ long start; // These endpoints are always in ELAPSED
+ long end;
+ boolean standalone; // certain "batches" don't participate in coalescing
+
+ final ArrayList<Alarm> alarms = new ArrayList<Alarm>();
+
+ Batch() {
+ start = 0;
+ end = Long.MAX_VALUE;
+ }
+
+ Batch(Alarm seed) {
+ start = seed.whenElapsed;
+ end = seed.maxWhen;
+ alarms.add(seed);
+ }
+
+ int size() {
+ return alarms.size();
+ }
+
+ Alarm get(int index) {
+ return alarms.get(index);
+ }
+
+ boolean canHold(long whenElapsed, long maxWhen) {
+ return (end >= whenElapsed) && (start <= maxWhen);
+ }
+
+ boolean add(Alarm alarm) {
+ boolean newStart = false;
+ // narrows the batch if necessary; presumes that canHold(alarm) is true
+ int index = Collections.binarySearch(alarms, alarm, sIncreasingTimeOrder);
+ if (index < 0) {
+ index = 0 - index - 1;
+ }
+ alarms.add(index, alarm);
+ if (DEBUG_BATCH) {
+ Slog.v(TAG, "Adding " + alarm + " to " + this);
+ }
+ if (alarm.whenElapsed > start) {
+ start = alarm.whenElapsed;
+ newStart = true;
+ }
+ if (alarm.maxWhen < end) {
+ end = alarm.maxWhen;
+ }
+
+ if (DEBUG_BATCH) {
+ Slog.v(TAG, " => now " + this);
+ }
+ return newStart;
+ }
+
+ boolean remove(final PendingIntent operation) {
+ boolean didRemove = false;
+ long newStart = 0; // recalculate endpoints as we go
+ long newEnd = Long.MAX_VALUE;
+ for (int i = 0; i < alarms.size(); i++) {
+ Alarm alarm = alarms.get(i);
+ if (alarm.operation.equals(operation)) {
+ alarms.remove(i);
+ didRemove = true;
+ } else {
+ if (alarm.whenElapsed > newStart) {
+ newStart = alarm.whenElapsed;
+ }
+ if (alarm.maxWhen < newEnd) {
+ newEnd = alarm.maxWhen;
+ }
+ i++;
+ }
+ }
+ if (didRemove) {
+ // commit the new batch bounds
+ start = newStart;
+ end = newEnd;
+ }
+ return didRemove;
+ }
+
+ boolean remove(final String packageName) {
+ boolean didRemove = false;
+ long newStart = 0; // recalculate endpoints as we go
+ long newEnd = Long.MAX_VALUE;
+ for (int i = 0; i < alarms.size(); i++) {
+ Alarm alarm = alarms.get(i);
+ if (alarm.operation.getTargetPackage().equals(packageName)) {
+ alarms.remove(i);
+ didRemove = true;
+ } else {
+ if (alarm.whenElapsed > newStart) {
+ newStart = alarm.whenElapsed;
+ }
+ if (alarm.maxWhen < newEnd) {
+ newEnd = alarm.maxWhen;
+ }
+ i++;
+ }
+ }
+ if (didRemove) {
+ // commit the new batch bounds
+ start = newStart;
+ end = newEnd;
+ }
+ return didRemove;
+ }
+
+ boolean remove(final int userHandle) {
+ boolean didRemove = false;
+ long newStart = 0; // recalculate endpoints as we go
+ long newEnd = Long.MAX_VALUE;
+ for (int i = 0; i < alarms.size(); i++) {
+ Alarm alarm = alarms.get(i);
+ if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) {
+ alarms.remove(i);
+ didRemove = true;
+ } else {
+ if (alarm.whenElapsed > newStart) {
+ newStart = alarm.whenElapsed;
+ }
+ if (alarm.maxWhen < newEnd) {
+ newEnd = alarm.maxWhen;
+ }
+ i++;
+ }
+ }
+ if (didRemove) {
+ // commit the new batch bounds
+ start = newStart;
+ end = newEnd;
+ }
+ return didRemove;
+ }
+
+ boolean hasPackage(final String packageName) {
+ final int N = alarms.size();
+ for (int i = 0; i < N; i++) {
+ Alarm a = alarms.get(i);
+ if (a.operation.getTargetPackage().equals(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean hasWakeups() {
+ final int N = alarms.size();
+ for (int i = 0; i < N; i++) {
+ Alarm a = alarms.get(i);
+ // non-wakeup alarms are types 1 and 3, i.e. have the low bit set
+ if ((a.type & TYPE_NONWAKEUP_MASK) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(40);
+ b.append("Batch{"); b.append(Integer.toHexString(this.hashCode()));
+ b.append(" num="); b.append(size());
+ b.append(" start="); b.append(start);
+ b.append(" end="); b.append(end);
+ if (standalone) {
+ b.append(" STANDALONE");
+ }
+ b.append('}');
+ return b.toString();
+ }
+ }
+
+ 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) {
+ return 1;
+ }
+ if (when1 - when2 < 0) {
+ return -1;
+ }
+ return 0;
+ }
+ }
+
+ // 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 BatchTimeOrder sBatchOrder = new BatchTimeOrder();
+ private final ArrayList<Batch> mAlarmBatches = new ArrayList<Batch>();
+
+ static long convertToElapsed(long when, int type) {
+ final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
+ if (isRtc) {
+ when -= System.currentTimeMillis() - SystemClock.elapsedRealtime();
+ }
+ return when;
+ }
+
+ // Apply a heuristic to { recurrence interval, futurity of the trigger time } to
+ // calculate the end of our nominal delivery window for the alarm.
+ static long maxTriggerTime(long now, long triggerAtTime, long interval) {
+ // Current heuristic: batchable window is 75% of either the recurrence interval
+ // [for a periodic alarm] or of the time from now to the desired delivery time,
+ // with a minimum delay/interval of 10 seconds, under which we will simply not
+ // defer the alarm.
+ long futurity = (interval == 0)
+ ? (triggerAtTime - now)
+ : interval;
+ if (futurity < MAX_FUZZABLE_INTERVAL) {
+ futurity = 0;
+ }
+ return triggerAtTime + (long)(.75 * futurity);
+ }
+
+ // returns true if the batch was added at the head
+ static boolean addBatchLocked(ArrayList<Batch> list, Batch newBatch) {
+ int index = Collections.binarySearch(list, newBatch, sBatchOrder);
+ if (index < 0) {
+ index = 0 - index - 1;
+ }
+ list.add(index, newBatch);
+ return (index == 0);
+ }
+
+ Batch attemptCoalesceLocked(long whenElapsed, long maxWhen) {
+ final int N = mAlarmBatches.size();
+ for (int i = 0; i < N; i++) {
+ Batch b = mAlarmBatches.get(i);
+ if (!b.standalone && b.canHold(whenElapsed, maxWhen)) {
+ return b;
+ }
+ }
+ return null;
+ }
+
+ // 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);
+ }
+ }
+ }
+ }
+ }
+
private static final class InFlight extends Intent {
final PendingIntent mPendingIntent;
final Pair<String, ComponentName> mTarget;
@@ -184,6 +456,7 @@
public AlarmManagerService(Context context) {
mContext = context;
mDescriptor = init();
+ mNextWakeup = mNextNonWakeup = 0;
// We have to set current TimeZone info to kernel
// because kernel doesn't keep this after reboot
@@ -224,77 +497,89 @@
super.finalize();
}
}
-
- public void set(int type, long triggerAtTime, PendingIntent operation) {
- setRepeating(type, triggerAtTime, 0, operation);
+
+ @Override
+ public void set(int type, long triggerAtTime, long interval,
+ PendingIntent operation, boolean isExact) {
+ set(type, triggerAtTime, interval, operation, isExact, false);
}
-
- public void setRepeating(int type, long triggerAtTime, long interval,
- PendingIntent operation) {
+
+ public void set(int type, long triggerAtTime, long interval,
+ PendingIntent operation, boolean isExact, boolean isStandalone) {
if (operation == null) {
Slog.w(TAG, "set/setRepeating ignored because there is no intent");
return;
}
+
+ 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);
+
synchronized (mLock) {
- Alarm alarm = new Alarm();
- alarm.type = type;
- alarm.when = triggerAtTime;
- alarm.repeatInterval = interval;
- alarm.operation = operation;
-
- // Remove this alarm if already scheduled.
- removeLocked(operation);
-
- if (localLOGV) Slog.v(TAG, "set: " + alarm);
-
- int index = addAlarmLocked(alarm);
- if (index == 0) {
- setLocked(alarm);
+ if (DEBUG_BATCH) {
+ Slog.v(TAG, "set(" + operation + ") : type=" + type
+ + " triggerAtTime=" + triggerAtTime
+ + " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed
+ + " interval=" + interval + " standalone=" + isStandalone);
}
+ setImplLocked(type, triggerAtTime, triggerElapsed, maxElapsed,
+ interval, operation, isStandalone);
}
}
-
- public void setInexactRepeating(int type, long triggerAtTime, long interval,
- PendingIntent operation) {
- if (operation == null) {
- Slog.w(TAG, "setInexactRepeating ignored because there is no intent");
- return;
- }
- if (interval <= 0) {
- Slog.w(TAG, "setInexactRepeating ignored because interval " + interval
- + " is invalid");
- return;
- }
+ private void setImplLocked(int type, long when, long whenElapsed, long maxWhen, long interval,
+ PendingIntent operation, boolean isStandalone) {
+ Alarm a = new Alarm(type, when, whenElapsed, maxWhen, interval, operation);
+ removeLocked(operation);
- // If the requested interval isn't a multiple of 15 minutes, just treat it as exact
- if (interval % QUANTUM != 0) {
- if (localLOGV) Slog.v(TAG, "Interval " + interval + " not a quantum multiple");
- setRepeating(type, triggerAtTime, interval, operation);
- return;
- }
-
- // Translate times into the ELAPSED timebase for alignment purposes so that
- // alignment never tries to match against wall clock times.
- final boolean isRtc = (type == AlarmManager.RTC || type == AlarmManager.RTC_WAKEUP);
- final long skew = (isRtc)
- ? System.currentTimeMillis() - SystemClock.elapsedRealtime()
- : 0;
-
- // Slip forward to the next ELAPSED-timebase quantum after the stated time. If
- // we're *at* a quantum point, leave it alone.
- final long adjustedTriggerTime;
- long offset = (triggerAtTime - skew) % QUANTUM;
- if (offset != 0) {
- adjustedTriggerTime = triggerAtTime - offset + QUANTUM;
+ final boolean reschedule;
+ Batch batch = (isStandalone) ? null : attemptCoalesceLocked(whenElapsed, maxWhen);
+ if (batch == null) {
+ batch = new Batch(a);
+ batch.standalone = isStandalone;
+ if (DEBUG_BATCH) {
+ Slog.v(TAG, "Starting new alarm batch " + batch);
+ }
+ reschedule = addBatchLocked(mAlarmBatches, batch);
} else {
- adjustedTriggerTime = triggerAtTime;
+ reschedule = batch.add(a);
}
- // Set the alarm based on the quantum-aligned start time
- if (localLOGV) Slog.v(TAG, "setInexactRepeating: type=" + type + " interval=" + interval
- + " trigger=" + adjustedTriggerTime + " orig=" + triggerAtTime);
- setRepeating(type, adjustedTriggerTime, interval, operation);
+ if (reschedule) {
+ rescheduleKernelAlarmsLocked();
+ }
+ }
+
+ 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 void rescheduleKernelAlarmsLocked() {
+ // Schedule the next upcoming wakeup alarm. If there is a deliverable batch
+ // prior to that which contains no wakeups, we schedule that as well.
+ final Batch firstWakeup = findFirstWakeupBatchLocked();
+ final Batch firstBatch = mAlarmBatches.get(0);
+ if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
+ mNextWakeup = firstWakeup.start;
+ setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
+ }
+ if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) {
+ mNextNonWakeup = firstBatch.start;
+ setLocked(ELAPSED_REALTIME, firstBatch.start);
+ }
}
public void setTime(long millis) {
@@ -356,160 +641,61 @@
}
public void removeLocked(PendingIntent operation) {
- removeLocked(mRtcWakeupAlarms, operation);
- removeLocked(mRtcAlarms, operation);
- removeLocked(mElapsedRealtimeWakeupAlarms, operation);
- removeLocked(mElapsedRealtimeAlarms, operation);
- }
-
- private void removeLocked(ArrayList<Alarm> alarmList,
- PendingIntent operation) {
- if (alarmList.size() <= 0) {
- return;
- }
-
- // iterator over the list removing any it where the intent match
- for (int i=0; i<alarmList.size(); i++) {
- Alarm alarm = alarmList.get(i);
- if (alarm.operation.equals(operation)) {
- alarmList.remove(i);
- i--;
+ for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
+ Batch b = mAlarmBatches.get(i);
+ b.remove(operation);
+ if (b.size() == 0) {
+ mAlarmBatches.remove(i);
}
}
}
public void removeLocked(String packageName) {
- removeLocked(mRtcWakeupAlarms, packageName);
- removeLocked(mRtcAlarms, packageName);
- removeLocked(mElapsedRealtimeWakeupAlarms, packageName);
- removeLocked(mElapsedRealtimeAlarms, packageName);
- }
-
- private void removeLocked(ArrayList<Alarm> alarmList,
- String packageName) {
- if (alarmList.size() <= 0) {
- return;
- }
-
- // iterator over the list removing any it where the intent match
- for (int i=0; i<alarmList.size(); i++) {
- Alarm alarm = alarmList.get(i);
- if (alarm.operation.getTargetPackage().equals(packageName)) {
- alarmList.remove(i);
- i--;
+ for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
+ Batch b = mAlarmBatches.get(i);
+ b.remove(packageName);
+ if (b.size() == 0) {
+ mAlarmBatches.remove(i);
}
}
}
public void removeUserLocked(int userHandle) {
- removeUserLocked(mRtcWakeupAlarms, userHandle);
- removeUserLocked(mRtcAlarms, userHandle);
- removeUserLocked(mElapsedRealtimeWakeupAlarms, userHandle);
- removeUserLocked(mElapsedRealtimeAlarms, userHandle);
- }
-
- private void removeUserLocked(ArrayList<Alarm> alarmList, int userHandle) {
- if (alarmList.size() <= 0) {
- return;
- }
-
- // iterator over the list removing any it where the intent match
- for (int i=0; i<alarmList.size(); i++) {
- Alarm alarm = alarmList.get(i);
- if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) {
- alarmList.remove(i);
- i--;
+ for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
+ Batch b = mAlarmBatches.get(i);
+ b.remove(userHandle);
+ if (b.size() == 0) {
+ mAlarmBatches.remove(i);
}
}
}
-
- public boolean lookForPackageLocked(String packageName) {
- return lookForPackageLocked(mRtcWakeupAlarms, packageName)
- || lookForPackageLocked(mRtcAlarms, packageName)
- || lookForPackageLocked(mElapsedRealtimeWakeupAlarms, packageName)
- || lookForPackageLocked(mElapsedRealtimeAlarms, packageName);
- }
- private boolean lookForPackageLocked(ArrayList<Alarm> alarmList, String packageName) {
- for (int i=alarmList.size()-1; i>=0; i--) {
- if (alarmList.get(i).operation.getTargetPackage().equals(packageName)) {
+ public boolean lookForPackageLocked(String packageName) {
+ for (int i = 0; i < mAlarmBatches.size(); i++) {
+ Batch b = mAlarmBatches.get(i);
+ if (b.hasPackage(packageName)) {
return true;
}
}
return false;
}
-
- private ArrayList<Alarm> getAlarmList(int type) {
- switch (type) {
- case AlarmManager.RTC_WAKEUP: return mRtcWakeupAlarms;
- case AlarmManager.RTC: return mRtcAlarms;
- case AlarmManager.ELAPSED_REALTIME_WAKEUP: return mElapsedRealtimeWakeupAlarms;
- case AlarmManager.ELAPSED_REALTIME: return mElapsedRealtimeAlarms;
- }
-
- return null;
- }
-
- private int addAlarmLocked(Alarm alarm) {
- ArrayList<Alarm> alarmList = getAlarmList(alarm.type);
-
- int index = Collections.binarySearch(alarmList, alarm, mIncreasingTimeOrder);
- if (index < 0) {
- index = 0 - index - 1;
- }
- if (localLOGV) Slog.v(TAG, "Adding alarm " + alarm + " at " + index);
- alarmList.add(index, alarm);
- if (localLOGV) {
- // Display the list of alarms for this alarm type
- Slog.v(TAG, "alarms: " + alarmList.size() + " type: " + alarm.type);
- int position = 0;
- for (Alarm a : alarmList) {
- Time time = new Time();
- time.set(a.when);
- String timeStr = time.format("%b %d %I:%M:%S %p");
- Slog.v(TAG, position + ": " + timeStr
- + " " + a.operation.getTargetPackage());
- position += 1;
- }
- }
-
- return index;
- }
-
- public long timeToNextAlarm() {
- long nextAlarm = Long.MAX_VALUE;
- synchronized (mLock) {
- for (int i=AlarmManager.RTC_WAKEUP;
- i<=AlarmManager.ELAPSED_REALTIME; i++) {
- ArrayList<Alarm> alarmList = getAlarmList(i);
- if (alarmList.size() > 0) {
- Alarm a = alarmList.get(0);
- if (a.when < nextAlarm) {
- nextAlarm = a.when;
- }
- }
- }
- }
- return nextAlarm;
- }
-
- private void setLocked(Alarm alarm)
+ private void setLocked(int type, long when)
{
if (mDescriptor != -1)
{
// The kernel never triggers alarms with negative wakeup times
// so we ensure they are positive.
long alarmSeconds, alarmNanoseconds;
- if (alarm.when < 0) {
+ if (when < 0) {
alarmSeconds = 0;
alarmNanoseconds = 0;
} else {
- alarmSeconds = alarm.when / 1000;
- alarmNanoseconds = (alarm.when % 1000) * 1000 * 1000;
+ alarmSeconds = when / 1000;
+ alarmNanoseconds = (when % 1000) * 1000 * 1000;
}
- set(mDescriptor, alarm.type, alarmSeconds, alarmNanoseconds);
+ set(mDescriptor, type, alarmSeconds, alarmNanoseconds);
}
else
{
@@ -517,7 +703,7 @@
msg.what = ALARM_EVENT;
mHandler.removeMessages(ALARM_EVENT);
- mHandler.sendMessageAtTime(msg, alarm.when);
+ mHandler.sendMessageAtTime(msg, when);
}
}
@@ -533,29 +719,28 @@
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
- if (mRtcWakeupAlarms.size() > 0 || mRtcAlarms.size() > 0) {
- final long now = System.currentTimeMillis();
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- pw.println(" ");
- pw.print(" Realtime wakeup (now=");
- pw.print(sdf.format(new Date(now))); pw.println("):");
- if (mRtcWakeupAlarms.size() > 0) {
- dumpAlarmList(pw, mRtcWakeupAlarms, " ", "RTC_WAKEUP", now);
- }
- if (mRtcAlarms.size() > 0) {
- dumpAlarmList(pw, mRtcAlarms, " ", "RTC", now);
- }
- }
- if (mElapsedRealtimeWakeupAlarms.size() > 0 || mElapsedRealtimeAlarms.size() > 0) {
- final long now = SystemClock.elapsedRealtime();
- pw.println(" ");
- pw.print(" Elapsed realtime wakeup (now=");
- TimeUtils.formatDuration(now, pw); pw.println("):");
- if (mElapsedRealtimeWakeupAlarms.size() > 0) {
- dumpAlarmList(pw, mElapsedRealtimeWakeupAlarms, " ", "ELAPSED_WAKEUP", now);
- }
- if (mElapsedRealtimeAlarms.size() > 0) {
- dumpAlarmList(pw, mElapsedRealtimeAlarms, " ", "ELAPSED", now);
+ final long nowRTC = System.currentTimeMillis();
+ final long nowELAPSED = SystemClock.elapsedRealtime();
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ pw.print("nowRTC="); pw.print(nowRTC);
+ pw.print("="); pw.print(sdf.format(new Date(nowRTC)));
+ pw.print(" nowELAPSED="); pw.println(nowELAPSED);
+
+ long nextWakeupRTC = mNextWakeup + (nowRTC - nowELAPSED);
+ long nextNonWakeupRTC = mNextNonWakeup + (nowRTC - nowELAPSED);
+ pw.print("Next alarm: "); pw.print(mNextNonWakeup);
+ pw.print(" = "); pw.println(sdf.format(new Date(nextNonWakeupRTC)));
+ pw.print("Next wakeup: "); pw.print(mNextWakeup);
+ pw.print(" = "); pw.println(sdf.format(new Date(nextWakeupRTC)));
+
+ if (mAlarmBatches.size() > 0) {
+ pw.println();
+ pw.print("Pending alarm batches: ");
+ pw.println(mAlarmBatches.size());
+ for (Batch b : mAlarmBatches) {
+ pw.print(b); pw.println(':');
+ dumpAlarmList(pw, b.alarms, " ", nowELAPSED, nowRTC);
}
}
@@ -662,7 +847,6 @@
if (WAKEUP_STATS) {
pw.println();
pw.println(" Recent Wakeup History:");
- final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss.SSS");
long last = -1;
for (WakeupEvent event : mRecentWakeups) {
pw.print(" "); pw.print(sdf.format(new Date(event.when)));
@@ -691,77 +875,78 @@
a.dump(pw, prefix + " ", now);
}
}
-
+
+ private static final String labelForType(int type) {
+ switch (type) {
+ case RTC: return "RTC";
+ case RTC_WAKEUP : return "RTC_WAKEUP";
+ case ELAPSED_REALTIME : return "ELAPSED";
+ case ELAPSED_REALTIME_WAKEUP: return "ELAPSED_WAKEUP";
+ default:
+ break;
+ }
+ return "--unknown--";
+ }
+
+ private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list,
+ String prefix, long nowELAPSED, long nowRTC) {
+ for (int i=list.size()-1; i>=0; i--) {
+ Alarm a = list.get(i);
+ final String label = labelForType(a.type);
+ long now = (a.type <= RTC) ? nowRTC : nowELAPSED;
+ pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
+ pw.print(": "); pw.println(a);
+ a.dump(pw, prefix + " ", now);
+ }
+ }
+
private native int init();
private native void close(int fd);
private native void set(int fd, int type, long seconds, long nanoseconds);
private native int waitForAlarm(int fd);
private native int setKernelTimezone(int fd, int minuteswest);
- private void triggerAlarmsLocked(ArrayList<Alarm> alarmList,
- ArrayList<Alarm> triggerList,
- long now)
- {
- ArrayList<Alarm> repeats = null;
+ private void triggerAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED, long nowRTC) {
+ Batch batch;
- for (int i=0; i<alarmList.size(); i++) {
- Alarm alarm = alarmList.get(i);
-
- if (localLOGV) Slog.v(TAG, "Checking active alarm when=" + alarm.when + " " + alarm);
-
- if (alarm.when > now) {
- // don't fire alarms in the future
+ // batches are temporally sorted, so we need only pull from the
+ // start of the list until we either empty it or hit a batch
+ // that is not yet deliverable
+ while ((batch = mAlarmBatches.get(0)) != null) {
+ if (batch.start > nowELAPSED) {
+ // Everything else is scheduled for the future
break;
}
-
- // If the alarm is late, then print a warning message.
- // Note that this can happen if the user creates a new event on
- // the Calendar app with a reminder that is in the past. In that
- // case, the reminder alarm will fire immediately.
- if (localLOGV && now - alarm.when > LATE_ALARM_THRESHOLD) {
- Slog.v(TAG, "alarm is late! alarm time: " + alarm.when
- + " now: " + now + " delay (in seconds): "
- + (now - alarm.when) / 1000);
- }
- // Recurring alarms may have passed several alarm intervals while the
- // phone was asleep or off, so pass a trigger count when sending them.
- if (localLOGV) Slog.v(TAG, "Alarm triggering: " + alarm);
- alarm.count = 1;
- if (alarm.repeatInterval > 0) {
- // this adjustment will be zero if we're late by
- // less than one full repeat interval
- alarm.count += (now - alarm.when) / alarm.repeatInterval;
- }
- triggerList.add(alarm);
-
- // remove the alarm from the list
- alarmList.remove(i);
- i--;
+ // We will (re)schedule some alarms now; don't let that interfere
+ // with delivery of this current batch
+ mAlarmBatches.remove(0);
- // if it repeats queue it up to be read-added to the list
- if (alarm.repeatInterval > 0) {
- if (repeats == null) {
- repeats = new ArrayList<Alarm>();
+ final int N = batch.size();
+ for (int i = 0; i < N; i++) {
+ Alarm alarm = batch.get(i);
+ alarm.count = 1;
+ triggerList.add(alarm);
+
+ // Recurring alarms may have passed several alarm intervals while the
+ // phone was asleep or off, so pass a trigger count when sending them.
+ if (alarm.repeatInterval > 0) {
+ // this adjustment will be zero if we're late by
+ // less than one full repeat interval
+ alarm.count += (nowELAPSED - alarm.whenElapsed) / alarm.repeatInterval;
+
+ // Also schedule its next recurrence
+ final long delta = alarm.count * alarm.repeatInterval;
+ final long nextElapsed = alarm.whenElapsed + delta;
+ setImplLocked(alarm.type, alarm.when + delta, nextElapsed,
+ maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
+ alarm.repeatInterval, alarm.operation, batch.standalone);
}
- repeats.add(alarm);
- }
- }
- // reset any repeating alarms.
- if (repeats != null) {
- for (int i=0; i<repeats.size(); i++) {
- Alarm alarm = repeats.get(i);
- alarm.when += alarm.count * alarm.repeatInterval;
- addAlarmLocked(alarm);
}
}
-
- if (alarmList.size() > 0) {
- setLocked(alarmList.get(0));
- }
}
-
+
/**
* This Comparator sorts Alarms into increasing time order.
*/
@@ -783,15 +968,21 @@
public int type;
public int count;
public long when;
+ public long whenElapsed; // 'when' in the elapsed time base
+ public long maxWhen; // also in the elapsed time base
public long repeatInterval;
public PendingIntent operation;
- public Alarm() {
- when = 0;
- repeatInterval = 0;
- operation = null;
+ public Alarm(int _type, long _when, long _whenElapsed, long _maxWhen,
+ long _interval, PendingIntent _op) {
+ type = _type;
+ when = _when;
+ whenElapsed = _whenElapsed;
+ maxWhen = _maxWhen;
+ repeatInterval = _interval;
+ operation = _op;
}
-
+
@Override
public String toString()
{
@@ -808,6 +999,7 @@
public void dump(PrintWriter pw, String prefix, long now) {
pw.print(prefix); pw.print("type="); pw.print(type);
+ pw.print(" whenElapsed="); pw.print(whenElapsed);
pw.print(" when="); TimeUtils.formatDuration(when, now, pw);
pw.print(" repeatInterval="); pw.print(repeatInterval);
pw.print(" count="); pw.println(count);
@@ -815,16 +1007,22 @@
}
}
- void recordWakeupAlarms(ArrayList<Alarm> alarms, long now, long skewToRTC) {
- for (Alarm a : alarms) {
- if (a.when > now) {
+ void recordWakeupAlarms(ArrayList<Batch> batches, long nowELAPSED, long nowRTC) {
+ final int numBatches = batches.size();
+ for (int i = 0; i < numBatches; i++) {
+ Batch b = batches.get(i);
+ if (b.start > nowELAPSED) {
break;
}
- WakeupEvent e = new WakeupEvent(now + skewToRTC,
- a.operation.getCreatorUid(),
- a.operation.getIntent().getAction());
- mRecentWakeups.add(e);
+ final int numAlarms = b.alarms.size();
+ for (int j = 0; j < numAlarms; j++) {
+ Alarm a = b.alarms.get(i);
+ WakeupEvent e = new WakeupEvent(nowRTC,
+ a.operation.getCreatorUid(),
+ a.operation.getIntent().getAction());
+ mRecentWakeups.add(e);
+ }
}
}
@@ -847,6 +1045,7 @@
if ((result & TIME_CHANGED_MASK) != 0) {
remove(mTimeTickSender);
+ rebatchAllAlarms();
mClockReceiver.scheduleTimeTickEvent();
Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
@@ -873,28 +1072,14 @@
mRecentWakeups.remove();
}
- recordWakeupAlarms(mRtcWakeupAlarms,
- nowRTC,
- 0);
- recordWakeupAlarms(mElapsedRealtimeWakeupAlarms,
- nowELAPSED,
- nowRTC - nowELAPSED);
+ recordWakeupAlarms(mAlarmBatches, nowELAPSED, nowRTC);
}
}
- if ((result & RTC_WAKEUP_MASK) != 0)
- triggerAlarmsLocked(mRtcWakeupAlarms, triggerList, nowRTC);
-
- if ((result & RTC_MASK) != 0)
- triggerAlarmsLocked(mRtcAlarms, triggerList, nowRTC);
-
- if ((result & ELAPSED_REALTIME_WAKEUP_MASK) != 0)
- triggerAlarmsLocked(mElapsedRealtimeWakeupAlarms, triggerList, nowELAPSED);
-
- if ((result & ELAPSED_REALTIME_MASK) != 0)
- triggerAlarmsLocked(mElapsedRealtimeAlarms, triggerList, nowELAPSED);
-
- // now trigger the alarms
+ triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
+ rescheduleKernelAlarmsLocked();
+
+ // now deliver the alarm intents
for (int i=0; i<triggerList.size(); i++) {
Alarm alarm = triggerList.get(i);
try {
@@ -930,8 +1115,8 @@
} else {
fs.nesting++;
}
- if (alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP
- || alarm.type == AlarmManager.RTC_WAKEUP) {
+ if (alarm.type == ELAPSED_REALTIME_WAKEUP
+ || alarm.type == RTC_WAKEUP) {
bs.numWakeup++;
fs.numWakeup++;
ActivityManagerNative.noteWakeupAlarm(
@@ -980,10 +1165,8 @@
ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
synchronized (mLock) {
final long nowRTC = System.currentTimeMillis();
- triggerAlarmsLocked(mRtcWakeupAlarms, triggerList, nowRTC);
- triggerAlarmsLocked(mRtcAlarms, triggerList, nowRTC);
- triggerAlarmsLocked(mElapsedRealtimeWakeupAlarms, triggerList, nowRTC);
- triggerAlarmsLocked(mElapsedRealtimeAlarms, triggerList, nowRTC);
+ final long nowELAPSED = SystemClock.elapsedRealtime();
+ triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
}
// now trigger the alarms without the lock held
@@ -1035,8 +1218,8 @@
// the top of the next minute.
final long tickEventDelay = nextTime - currentTime;
- set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay,
- mTimeTickSender);
+ set(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay,
+ 0, mTimeTickSender, true, true);
}
public void scheduleDateChangedEvent() {
@@ -1048,7 +1231,7 @@
calendar.set(Calendar.MILLISECOND, 0);
calendar.add(Calendar.DAY_OF_MONTH, 1);
- set(AlarmManager.RTC, calendar.getTimeInMillis(), mDateChangeSender);
+ set(RTC, calendar.getTimeInMillis(), 0, mDateChangeSender, true, true);
}
}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 5074409..05eeb36 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -154,7 +154,7 @@
private final Context mContext;
private final INetworkManagementService mNetworkManager;
- private final IAlarmManager mAlarmManager;
+ private final AlarmManager mAlarmManager;
private final TrustedTime mTime;
private final TelephonyManager mTeleManager;
private final NetworkStatsSettings mSettings;
@@ -261,10 +261,10 @@
NetworkStatsSettings settings) {
mContext = checkNotNull(context, "missing Context");
mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
- mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager");
mTime = checkNotNull(time, "missing TrustedTime");
mTeleManager = checkNotNull(TelephonyManager.getDefault(), "missing TelephonyManager");
mSettings = checkNotNull(settings, "missing NetworkStatsSettings");
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
final PowerManager powerManager = (PowerManager) context.getSystemService(
Context.POWER_SERVICE);
@@ -420,20 +420,16 @@
* reschedule based on current {@link NetworkStatsSettings#getPollInterval()}.
*/
private void registerPollAlarmLocked() {
- try {
- if (mPollIntent != null) {
- mAlarmManager.remove(mPollIntent);
- }
-
- mPollIntent = PendingIntent.getBroadcast(
- mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
-
- final long currentRealtime = SystemClock.elapsedRealtime();
- mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
- mSettings.getPollInterval(), mPollIntent);
- } catch (RemoteException e) {
- // ignored; service lives in system_server
+ if (mPollIntent != null) {
+ mAlarmManager.cancel(mPollIntent);
}
+
+ mPollIntent = PendingIntent.getBroadcast(
+ mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
+
+ final long currentRealtime = SystemClock.elapsedRealtime();
+ mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
+ mSettings.getPollInterval(), mPollIntent);
}
/**