diff options
Diffstat (limited to 'services/core/java/com/android/server/AlarmManagerService.java')
-rw-r--r-- | services/core/java/com/android/server/AlarmManagerService.java | 1515 |
1 files changed, 1515 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java new file mode 100644 index 0000000..3cdf170 --- /dev/null +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -0,0 +1,1515 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.app.Activity; +import android.app.ActivityManagerNative; +import android.app.AlarmManager; +import android.app.IAlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.WorkSource; +import android.text.TextUtils; +import android.util.Pair; +import android.util.Slog; +import android.util.TimeUtils; + +import java.io.ByteArrayOutputStream; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +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 SystemService { + // 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 << 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; + static final int TIME_CHANGED_MASK = 1 << 16; + static final int IS_WAKEUP_MASK = RTC_WAKEUP_MASK|ELAPSED_REALTIME_WAKEUP_MASK; + + // Mask for testing whether a given alarm type is wakeup vs non-wakeup + static final int TYPE_NONWAKEUP_MASK = 0x1; // low bit => non-wakeup + + static final String TAG = "AlarmManager"; + static final String ClockReceiver_TAG = "ClockReceiver"; + static final boolean localLOGV = false; + static final boolean DEBUG_BATCH = localLOGV || false; + static final boolean DEBUG_VALIDATE = localLOGV || false; + static final int ALARM_EVENT = 1; + static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; + + static final Intent mBackgroundIntent + = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND); + static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder(); + + static final boolean WAKEUP_STATS = false; + + final LocalLog mLog = new LocalLog(TAG); + + final Object mLock = new Object(); + + long mNativeData; + private long mNextWakeup; + private long mNextNonWakeup; + int mBroadcastRefCount = 0; + PowerManager.WakeLock mWakeLock; + ArrayList<InFlight> mInFlight = new ArrayList<InFlight>(); + final AlarmHandler mHandler = new AlarmHandler(); + ClockReceiver mClockReceiver; + private UninstallReceiver mUninstallReceiver; + final ResultReceiver mResultReceiver = new ResultReceiver(); + PendingIntent mTimeTickSender; + PendingIntent mDateChangeSender; + + class WakeupEvent { + public long when; + public int uid; + public String action; + + public WakeupEvent(long theTime, int theUid, String theAction) { + when = theTime; + uid = theUid; + action = theAction; + } + } + + final LinkedList<WakeupEvent> mRecentWakeups = new LinkedList<WakeupEvent>(); + 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(); ) { + 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(); ) { + 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(); ) { + 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 + static final long MIN_FUZZABLE_INTERVAL = 10000; + static final BatchTimeOrder sBatchOrder = new BatchTimeOrder(); + 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 < MIN_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); + } + + // Return the index of the matching batch, or -1 if none found. + int 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 i; + } + } + return -1; + } + + // The RTC clock has moved arbitrarily, so we need to recalculate all the batching + void rebatchAllAlarms() { + synchronized (mLock) { + rebatchAllAlarmsLocked(true); + } + } + + void rebatchAllAlarmsLocked(boolean doValidate) { + ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone(); + mAlarmBatches.clear(); + final long nowElapsed = SystemClock.elapsedRealtime(); + final int oldBatches = oldSet.size(); + for (int batchNum = 0; batchNum < oldBatches; batchNum++) { + Batch batch = oldSet.get(batchNum); + final int N = batch.size(); + for (int i = 0; i < N; i++) { + Alarm a = batch.get(i); + long whenElapsed = convertToElapsed(a.when, a.type); + final long maxElapsed; + if (a.whenElapsed == a.maxWhen) { + // Exact + maxElapsed = whenElapsed; + } else { + // Not exact. Preserve any explicit window, otherwise recalculate + // the window based on the alarm's new futurity. Note that this + // reflects a policy of preferring timely to deferred delivery. + maxElapsed = (a.windowLength > 0) + ? (whenElapsed + a.windowLength) + : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval); + } + setImplLocked(a.type, a.when, whenElapsed, a.windowLength, maxElapsed, + a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource); + } + } + } + + static final class InFlight extends Intent { + final PendingIntent mPendingIntent; + final WorkSource mWorkSource; + final Pair<String, ComponentName> mTarget; + final BroadcastStats mBroadcastStats; + final FilterStats mFilterStats; + + InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource) { + mPendingIntent = pendingIntent; + mWorkSource = workSource; + Intent intent = pendingIntent.getIntent(); + mTarget = intent != null + ? new Pair<String, ComponentName>(intent.getAction(), intent.getComponent()) + : null; + mBroadcastStats = service.getStatsLocked(pendingIntent); + FilterStats fs = mBroadcastStats.filterStats.get(mTarget); + if (fs == null) { + fs = new FilterStats(mBroadcastStats, mTarget); + mBroadcastStats.filterStats.put(mTarget, fs); + } + mFilterStats = fs; + } + } + + static final class FilterStats { + final BroadcastStats mBroadcastStats; + final Pair<String, ComponentName> mTarget; + + long aggregateTime; + int count; + int numWakeup; + long startTime; + int nesting; + + FilterStats(BroadcastStats broadcastStats, Pair<String, ComponentName> target) { + mBroadcastStats = broadcastStats; + mTarget = target; + } + } + + static final class BroadcastStats { + final String mPackageName; + + long aggregateTime; + int count; + int numWakeup; + long startTime; + int nesting; + final HashMap<Pair<String, ComponentName>, FilterStats> filterStats + = new HashMap<Pair<String, ComponentName>, FilterStats>(); + + BroadcastStats(String packageName) { + mPackageName = packageName; + } + } + + final HashMap<String, BroadcastStats> mBroadcastStats + = new HashMap<String, BroadcastStats>(); + + @Override + public void onStart() { + mNativeData = init(); + mNextWakeup = mNextNonWakeup = 0; + + // We have to set current TimeZone info to kernel + // because kernel doesn't keep this after reboot + setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY)); + + PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + + mTimeTickSender = PendingIntent.getBroadcastAsUser(getContext(), 0, + new Intent(Intent.ACTION_TIME_TICK).addFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_FOREGROUND), 0, + UserHandle.ALL); + Intent intent = new Intent(Intent.ACTION_DATE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent, + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL); + + // now that we have initied the driver schedule the alarm + mClockReceiver = new ClockReceiver(); + mClockReceiver.scheduleTimeTickEvent(); + mClockReceiver.scheduleDateChangedEvent(); + mUninstallReceiver = new UninstallReceiver(); + + if (mNativeData != 0) { + AlarmThread waitThread = new AlarmThread(); + waitThread.start(); + } else { + Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler."); + } + + publishBinderService(Context.ALARM_SERVICE, mService); + } + + @Override + protected void finalize() throws Throwable { + try { + close(mNativeData); + } finally { + super.finalize(); + } + } + + void setTimeZoneImpl(String tz) { + if (TextUtils.isEmpty(tz)) { + return; + } + + TimeZone zone = TimeZone.getTimeZone(tz); + // Prevent reentrant calls from stepping on each other when writing + // the time zone property + boolean timeZoneWasChanged = false; + synchronized (this) { + String current = SystemProperties.get(TIMEZONE_PROPERTY); + if (current == null || !current.equals(zone.getID())) { + if (localLOGV) { + Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID()); + } + timeZoneWasChanged = true; + SystemProperties.set(TIMEZONE_PROPERTY, zone.getID()); + } + + // Update the kernel timezone information + // Kernel tracks time offsets as 'minutes west of GMT' + int gmtOffset = zone.getOffset(System.currentTimeMillis()); + setKernelTimezone(mNativeData, -(gmtOffset / 60000)); + } + + TimeZone.setDefault(null); + + if (timeZoneWasChanged) { + Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra("time-zone", zone.getID()); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL); + } + } + + void removeImpl(PendingIntent operation) { + if (operation == null) { + return; + } + synchronized (mLock) { + removeLocked(operation); + } + } + + void setImpl(int type, long triggerAtTime, long windowLength, long interval, + PendingIntent operation, boolean isStandalone, WorkSource workSource) { + 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); + } + + if (triggerAtTime < 0) { + final long who = Binder.getCallingUid(); + final long what = Binder.getCallingPid(); + Slog.w(TAG, "Invalid alarm trigger time! " + triggerAtTime + " from uid=" + who + + " pid=" + what); + triggerAtTime = 0; + } + + 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 + " win=" + windowLength + + " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed + + " interval=" + interval + " standalone=" + isStandalone); + } + setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed, + interval, operation, isStandalone, true, workSource); + } + } + + private void setImplLocked(int type, long when, long whenElapsed, long windowLength, + long maxWhen, long interval, PendingIntent operation, boolean isStandalone, + boolean doValidate, WorkSource workSource) { + Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval, + operation, workSource); + removeLocked(operation); + + int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen); + if (whichBatch < 0) { + Batch batch = new Batch(a); + batch.standalone = isStandalone; + addBatchLocked(mAlarmBatches, batch); + } else { + Batch batch = mAlarmBatches.get(whichBatch); + if (batch.add(a)) { + // The start time of this batch advanced, so batch ordering may + // have just been broken. Move it to where it now belongs. + mAlarmBatches.remove(whichBatch); + addBatchLocked(mAlarmBatches, batch); + } + } + + if (DEBUG_VALIDATE) { + if (doValidate && !validateConsistencyLocked()) { + Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when + + " when(hex)=" + Long.toHexString(when) + + " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen + + " interval=" + interval + " op=" + operation + + " standalone=" + isStandalone); + rebatchAllAlarmsLocked(false); + } + } + + rescheduleKernelAlarmsLocked(); + } + + private final IBinder mService = new IAlarmManager.Stub() { + @Override + public void set(int type, long triggerAtTime, long windowLength, long interval, + PendingIntent operation, WorkSource workSource) { + if (workSource != null) { + getContext().enforceCallingPermission( + android.Manifest.permission.UPDATE_DEVICE_STATS, + "AlarmManager.set"); + } + + setImpl(type, triggerAtTime, windowLength, interval, operation, + false, workSource); + } + + @Override + public void setTime(long millis) { + getContext().enforceCallingOrSelfPermission( + "android.permission.SET_TIME", + "setTime"); + + SystemClock.setCurrentTimeMillis(millis); + } + + @Override + public void setTimeZone(String tz) { + getContext().enforceCallingOrSelfPermission( + "android.permission.SET_TIME_ZONE", + "setTimeZone"); + + final long oldId = Binder.clearCallingIdentity(); + try { + setTimeZoneImpl(tz); + } finally { + Binder.restoreCallingIdentity(oldId); + } + } + + @Override + public void remove(PendingIntent operation) { + removeImpl(operation); + + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump AlarmManager from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + dumpImpl(pw); + } + }; + + void dumpImpl(PrintWriter pw) { + synchronized (mLock) { + pw.println("Current Alarm Manager state:"); + 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); + } + } + + pw.println(); + pw.print(" Broadcast ref count: "); pw.println(mBroadcastRefCount); + pw.println(); + + if (mLog.dump(pw, " Recent problems", " ")) { + pw.println(); + } + + final FilterStats[] topFilters = new FilterStats[10]; + final Comparator<FilterStats> comparator = new Comparator<FilterStats>() { + @Override + public int compare(FilterStats lhs, FilterStats rhs) { + if (lhs.aggregateTime < rhs.aggregateTime) { + return 1; + } else if (lhs.aggregateTime > rhs.aggregateTime) { + return -1; + } + return 0; + } + }; + int len = 0; + for (Map.Entry<String, BroadcastStats> be : mBroadcastStats.entrySet()) { + BroadcastStats bs = be.getValue(); + for (Map.Entry<Pair<String, ComponentName>, FilterStats> fe + : bs.filterStats.entrySet()) { + FilterStats fs = fe.getValue(); + int pos = len > 0 + ? Arrays.binarySearch(topFilters, 0, len, fs, comparator) : 0; + if (pos < 0) { + pos = -pos - 1; + } + if (pos < topFilters.length) { + int copylen = topFilters.length - pos - 1; + if (copylen > 0) { + System.arraycopy(topFilters, pos, topFilters, pos+1, copylen); + } + topFilters[pos] = fs; + if (len < topFilters.length) { + len++; + } + } + } + } + if (len > 0) { + pw.println(" Top Alarms:"); + for (int i=0; i<len; i++) { + FilterStats fs = topFilters[i]; + pw.print(" "); + if (fs.nesting > 0) pw.print("*ACTIVE* "); + TimeUtils.formatDuration(fs.aggregateTime, pw); + pw.print(" running, "); pw.print(fs.numWakeup); + pw.print(" wakeups, "); pw.print(fs.count); + pw.print(" alarms: "); pw.print(fs.mBroadcastStats.mPackageName); + pw.println(); + pw.print(" "); + if (fs.mTarget.first != null) { + pw.print(" act="); pw.print(fs.mTarget.first); + } + if (fs.mTarget.second != null) { + pw.print(" cmp="); pw.print(fs.mTarget.second.toShortString()); + } + pw.println(); + } + } + + pw.println(" "); + pw.println(" Alarm Stats:"); + final ArrayList<FilterStats> tmpFilters = new ArrayList<FilterStats>(); + for (Map.Entry<String, BroadcastStats> be : mBroadcastStats.entrySet()) { + BroadcastStats bs = be.getValue(); + pw.print(" "); + if (bs.nesting > 0) pw.print("*ACTIVE* "); + pw.print(be.getKey()); + pw.print(" "); TimeUtils.formatDuration(bs.aggregateTime, pw); + pw.print(" running, "); pw.print(bs.numWakeup); + pw.println(" wakeups:"); + tmpFilters.clear(); + for (Map.Entry<Pair<String, ComponentName>, FilterStats> fe + : bs.filterStats.entrySet()) { + tmpFilters.add(fe.getValue()); + } + Collections.sort(tmpFilters, comparator); + for (int i=0; i<tmpFilters.size(); i++) { + FilterStats fs = tmpFilters.get(i); + pw.print(" "); + if (fs.nesting > 0) pw.print("*ACTIVE* "); + TimeUtils.formatDuration(fs.aggregateTime, pw); + pw.print(" "); pw.print(fs.numWakeup); + pw.print(" wakes " ); pw.print(fs.count); + pw.print(" alarms:"); + if (fs.mTarget.first != null) { + pw.print(" act="); pw.print(fs.mTarget.first); + } + if (fs.mTarget.second != null) { + pw.print(" cmp="); pw.print(fs.mTarget.second.toShortString()); + } + pw.println(); + } + } + + if (WAKEUP_STATS) { + pw.println(); + pw.println(" Recent Wakeup History:"); + long last = -1; + for (WakeupEvent event : mRecentWakeups) { + pw.print(" "); pw.print(sdf.format(new Date(event.when))); + pw.print('|'); + if (last < 0) { + pw.print('0'); + } else { + pw.print(event.when - last); + } + last = event.when; + pw.print('|'); pw.print(event.uid); + pw.print('|'); pw.print(event.action); + pw.println(); + } + pw.println(); + } + } + } + + private void logBatchesLocked() { + ByteArrayOutputStream bs = new ByteArrayOutputStream(2048); + PrintWriter pw = new PrintWriter(bs); + final long nowRTC = System.currentTimeMillis(); + final long nowELAPSED = SystemClock.elapsedRealtime(); + final int NZ = mAlarmBatches.size(); + for (int iz = 0; iz < NZ; iz++) { + Batch bz = mAlarmBatches.get(iz); + pw.append("Batch "); pw.print(iz); pw.append(": "); pw.println(bz); + dumpAlarmList(pw, bz.alarms, " ", nowELAPSED, nowRTC); + pw.flush(); + Slog.v(TAG, bs.toString()); + bs.reset(); + } + } + + private boolean validateConsistencyLocked() { + if (DEBUG_VALIDATE) { + long lastTime = Long.MIN_VALUE; + final int N = mAlarmBatches.size(); + for (int i = 0; i < N; i++) { + Batch b = mAlarmBatches.get(i); + if (b.start >= lastTime) { + // duplicate start times are okay because of standalone batches + lastTime = b.start; + } else { + Slog.e(TAG, "CONSISTENCY FAILURE: Batch " + i + " is out of order"); + logBatchesLocked(); + return false; + } + } + } + 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; + } + + 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. + if (mAlarmBatches.size() > 0) { + 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); + } + } + } + + private void removeLocked(PendingIntent operation) { + boolean didRemove = false; + for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { + Batch b = mAlarmBatches.get(i); + didRemove |= b.remove(operation); + if (b.size() == 0) { + mAlarmBatches.remove(i); + } + } + + if (didRemove) { + if (DEBUG_BATCH) { + Slog.v(TAG, "remove(operation) changed bounds; rebatching"); + } + rebatchAllAlarmsLocked(true); + rescheduleKernelAlarmsLocked(); + } + } + + void removeLocked(String packageName) { + boolean didRemove = false; + for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { + Batch b = mAlarmBatches.get(i); + didRemove |= b.remove(packageName); + if (b.size() == 0) { + mAlarmBatches.remove(i); + } + } + + if (didRemove) { + if (DEBUG_BATCH) { + Slog.v(TAG, "remove(package) changed bounds; rebatching"); + } + rebatchAllAlarmsLocked(true); + rescheduleKernelAlarmsLocked(); + } + } + + void removeUserLocked(int userHandle) { + boolean didRemove = false; + for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { + Batch b = mAlarmBatches.get(i); + didRemove |= b.remove(userHandle); + if (b.size() == 0) { + mAlarmBatches.remove(i); + } + } + + if (didRemove) { + if (DEBUG_BATCH) { + Slog.v(TAG, "remove(user) changed bounds; rebatching"); + } + rebatchAllAlarmsLocked(true); + rescheduleKernelAlarmsLocked(); + } + } + + 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 void setLocked(int type, long when) { + if (mNativeData != 0) { + // The kernel never triggers alarms with negative wakeup times + // so we ensure they are positive. + long alarmSeconds, alarmNanoseconds; + if (when < 0) { + alarmSeconds = 0; + alarmNanoseconds = 0; + } else { + alarmSeconds = when / 1000; + alarmNanoseconds = (when % 1000) * 1000 * 1000; + } + + set(mNativeData, type, alarmSeconds, alarmNanoseconds); + } else { + Message msg = Message.obtain(); + msg.what = ALARM_EVENT; + + mHandler.removeMessages(ALARM_EVENT); + mHandler.sendMessageAtTime(msg, when); + } + } + + private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list, + String prefix, String label, long now) { + for (int i=list.size()-1; i>=0; i--) { + Alarm a = list.get(i); + pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i); + pw.print(": "); pw.println(a); + 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 long init(); + private native void close(long nativeData); + private native void set(long nativeData, int type, long seconds, long nanoseconds); + private native int waitForAlarm(long nativeData); + private native int setKernelTimezone(long nativeData, int minuteswest); + + void triggerAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED, long nowRTC) { + // 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 (mAlarmBatches.size() > 0) { + Batch batch = mAlarmBatches.get(0); + if (batch.start > nowELAPSED) { + // Everything else is scheduled for the future + break; + } + + // We will (re)schedule some alarms now; don't let that interfere + // with delivery of this current batch + mAlarmBatches.remove(0); + + 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, alarm.windowLength, + maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval), + alarm.repeatInterval, alarm.operation, batch.standalone, true, + alarm.workSource); + } + + } + } + } + + /** + * This Comparator sorts Alarms into increasing time order. + */ + public static class IncreasingTimeOrder implements Comparator<Alarm> { + public int compare(Alarm a1, Alarm a2) { + long when1 = a1.when; + long when2 = a2.when; + if (when1 - when2 > 0) { + return 1; + } + if (when1 - when2 < 0) { + return -1; + } + return 0; + } + } + + private static class Alarm { + public int type; + public int count; + public long when; + public long windowLength; + 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 WorkSource workSource; + + public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen, + long _interval, PendingIntent _op, WorkSource _ws) { + type = _type; + when = _when; + whenElapsed = _whenElapsed; + windowLength = _windowLength; + maxWhen = _maxWhen; + repeatInterval = _interval; + operation = _op; + workSource = _ws; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(128); + sb.append("Alarm{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" type "); + sb.append(type); + sb.append(" "); + sb.append(operation.getTargetPackage()); + sb.append('}'); + return sb.toString(); + } + + 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(" window="); pw.print(windowLength); + pw.print(" repeatInterval="); pw.print(repeatInterval); + pw.print(" count="); pw.println(count); + pw.print(prefix); pw.print("operation="); pw.println(operation); + } + } + + void recordWakeupAlarms(ArrayList<Batch> batches, long nowELAPSED, long nowRTC) { + final int numBatches = batches.size(); + for (int nextBatch = 0; nextBatch < numBatches; nextBatch++) { + Batch b = batches.get(nextBatch); + if (b.start > nowELAPSED) { + break; + } + + final int numAlarms = b.alarms.size(); + for (int nextAlarm = 0; nextAlarm < numAlarms; nextAlarm++) { + Alarm a = b.alarms.get(nextAlarm); + WakeupEvent e = new WakeupEvent(nowRTC, + a.operation.getCreatorUid(), + a.operation.getIntent().getAction()); + mRecentWakeups.add(e); + } + } + } + + private class AlarmThread extends Thread + { + public AlarmThread() + { + super("AlarmManager"); + } + + public void run() + { + ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); + + while (true) + { + int result = waitForAlarm(mNativeData); + + triggerList.clear(); + + if ((result & TIME_CHANGED_MASK) != 0) { + if (DEBUG_BATCH) { + Slog.v(TAG, "Time changed notification from kernel; rebatching"); + } + removeImpl(mTimeTickSender); + rebatchAllAlarms(); + mClockReceiver.scheduleTimeTickEvent(); + Intent intent = new Intent(Intent.ACTION_TIME_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING + | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL); + } + + synchronized (mLock) { + final long nowRTC = System.currentTimeMillis(); + final long nowELAPSED = SystemClock.elapsedRealtime(); + if (localLOGV) Slog.v( + TAG, "Checking for alarms... rtc=" + nowRTC + + ", elapsed=" + nowELAPSED); + + if (WAKEUP_STATS) { + if ((result & IS_WAKEUP_MASK) != 0) { + long newEarliest = nowRTC - RECENT_WAKEUP_PERIOD; + int n = 0; + for (WakeupEvent event : mRecentWakeups) { + if (event.when > newEarliest) break; + n++; // number of now-stale entries at the list head + } + for (int i = 0; i < n; i++) { + mRecentWakeups.remove(); + } + + recordWakeupAlarms(mAlarmBatches, nowELAPSED, nowRTC); + } + } + + triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC); + rescheduleKernelAlarmsLocked(); + + // now deliver the alarm intents + for (int i=0; i<triggerList.size(); i++) { + Alarm alarm = triggerList.get(i); + try { + if (localLOGV) Slog.v(TAG, "sending alarm " + alarm); + alarm.operation.send(getContext(), 0, + mBackgroundIntent.putExtra( + Intent.EXTRA_ALARM_COUNT, alarm.count), + mResultReceiver, mHandler); + + // we have an active broadcast so stay awake. + if (mBroadcastRefCount == 0) { + setWakelockWorkSource(alarm.operation, alarm.workSource); + mWakeLock.acquire(); + } + final InFlight inflight = new InFlight(AlarmManagerService.this, + alarm.operation, alarm.workSource); + mInFlight.add(inflight); + mBroadcastRefCount++; + + final BroadcastStats bs = inflight.mBroadcastStats; + bs.count++; + if (bs.nesting == 0) { + bs.nesting = 1; + bs.startTime = nowELAPSED; + } else { + bs.nesting++; + } + final FilterStats fs = inflight.mFilterStats; + fs.count++; + if (fs.nesting == 0) { + fs.nesting = 1; + fs.startTime = nowELAPSED; + } else { + fs.nesting++; + } + if (alarm.type == ELAPSED_REALTIME_WAKEUP + || alarm.type == RTC_WAKEUP) { + bs.numWakeup++; + fs.numWakeup++; + ActivityManagerNative.noteWakeupAlarm( + alarm.operation); + } + } catch (PendingIntent.CanceledException e) { + if (alarm.repeatInterval > 0) { + // This IntentSender is no longer valid, but this + // is a repeating alarm, so toss the hoser. + removeImpl(alarm.operation); + } + } catch (RuntimeException e) { + Slog.w(TAG, "Failure sending alarm.", e); + } + } + } + } + } + } + + /** + * Attribute blame for a WakeLock. + * @param pi PendingIntent to attribute blame to if ws is null. + * @param ws WorkSource to attribute blame. + */ + void setWakelockWorkSource(PendingIntent pi, WorkSource ws) { + try { + if (ws != null) { + mWakeLock.setWorkSource(ws); + return; + } + + final int uid = ActivityManagerNative.getDefault() + .getUidForIntentSender(pi.getTarget()); + if (uid >= 0) { + mWakeLock.setWorkSource(new WorkSource(uid)); + return; + } + } catch (Exception e) { + } + + // Something went wrong; fall back to attributing the lock to the OS + mWakeLock.setWorkSource(null); + } + + private class AlarmHandler extends Handler { + public static final int ALARM_EVENT = 1; + public static final int MINUTE_CHANGE_EVENT = 2; + public static final int DATE_CHANGE_EVENT = 3; + + public AlarmHandler() { + } + + public void handleMessage(Message msg) { + if (msg.what == ALARM_EVENT) { + ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); + synchronized (mLock) { + final long nowRTC = System.currentTimeMillis(); + final long nowELAPSED = SystemClock.elapsedRealtime(); + triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC); + } + + // now trigger the alarms without the lock held + for (int i=0; i<triggerList.size(); i++) { + Alarm alarm = triggerList.get(i); + try { + alarm.operation.send(); + } catch (PendingIntent.CanceledException e) { + if (alarm.repeatInterval > 0) { + // This IntentSender is no longer valid, but this + // is a repeating alarm, so toss the hoser. + removeImpl(alarm.operation); + } + } + } + } + } + } + + class ClockReceiver extends BroadcastReceiver { + public ClockReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_DATE_CHANGED); + getContext().registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) { + if (DEBUG_BATCH) { + Slog.v(TAG, "Received TIME_TICK alarm; rescheduling"); + } + scheduleTimeTickEvent(); + } else if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) { + // Since the kernel does not keep track of DST, we need to + // reset the TZ information at the beginning of each day + // based off of the current Zone gmt offset + userspace tracked + // daylight savings information. + TimeZone zone = TimeZone.getTimeZone(SystemProperties.get(TIMEZONE_PROPERTY)); + int gmtOffset = zone.getOffset(System.currentTimeMillis()); + setKernelTimezone(mNativeData, -(gmtOffset / 60000)); + scheduleDateChangedEvent(); + } + } + + public void scheduleTimeTickEvent() { + final long currentTime = System.currentTimeMillis(); + final long nextTime = 60000 * ((currentTime / 60000) + 1); + + // Schedule this event for the amount of time that it would take to get to + // the top of the next minute. + final long tickEventDelay = nextTime - currentTime; + + final WorkSource workSource = null; // Let system take blame for time tick events. + setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0, + 0, mTimeTickSender, true, workSource); + } + + public void scheduleDateChangedEvent() { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(System.currentTimeMillis()); + calendar.set(Calendar.HOUR, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + calendar.add(Calendar.DAY_OF_MONTH, 1); + + final WorkSource workSource = null; // Let system take blame for date change events. + setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource); + } + } + + class UninstallReceiver extends BroadcastReceiver { + public UninstallReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); + filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); + filter.addDataScheme("package"); + getContext().registerReceiver(this, filter); + // Register for events related to sdcard installation. + IntentFilter sdFilter = new IntentFilter(); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + sdFilter.addAction(Intent.ACTION_USER_STOPPED); + getContext().registerReceiver(this, sdFilter); + } + + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + String action = intent.getAction(); + String pkgList[] = null; + if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); + for (String packageName : pkgList) { + if (lookForPackageLocked(packageName)) { + setResultCode(Activity.RESULT_OK); + return; + } + } + return; + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userHandle >= 0) { + removeUserLocked(userHandle); + } + } else { + if (Intent.ACTION_PACKAGE_REMOVED.equals(action) + && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + // This package is being updated; don't kill its alarms. + return; + } + Uri data = intent.getData(); + if (data != null) { + String pkg = data.getSchemeSpecificPart(); + if (pkg != null) { + pkgList = new String[]{pkg}; + } + } + } + if (pkgList != null && (pkgList.length > 0)) { + for (String pkg : pkgList) { + removeLocked(pkg); + mBroadcastStats.remove(pkg); + } + } + } + } + } + + private final BroadcastStats getStatsLocked(PendingIntent pi) { + String pkg = pi.getTargetPackage(); + BroadcastStats bs = mBroadcastStats.get(pkg); + if (bs == null) { + bs = new BroadcastStats(pkg); + mBroadcastStats.put(pkg, bs); + } + return bs; + } + + class ResultReceiver implements PendingIntent.OnFinished { + public void onSendFinished(PendingIntent pi, Intent intent, int resultCode, + String resultData, Bundle resultExtras) { + synchronized (mLock) { + InFlight inflight = null; + for (int i=0; i<mInFlight.size(); i++) { + if (mInFlight.get(i).mPendingIntent == pi) { + inflight = mInFlight.remove(i); + break; + } + } + if (inflight != null) { + final long nowELAPSED = SystemClock.elapsedRealtime(); + BroadcastStats bs = inflight.mBroadcastStats; + bs.nesting--; + if (bs.nesting <= 0) { + bs.nesting = 0; + bs.aggregateTime += nowELAPSED - bs.startTime; + } + FilterStats fs = inflight.mFilterStats; + fs.nesting--; + if (fs.nesting <= 0) { + fs.nesting = 0; + fs.aggregateTime += nowELAPSED - fs.startTime; + } + } else { + mLog.w("No in-flight alarm for " + pi + " " + intent); + } + mBroadcastRefCount--; + if (mBroadcastRefCount == 0) { + mWakeLock.release(); + if (mInFlight.size() > 0) { + mLog.w("Finished all broadcasts with " + mInFlight.size() + + " remaining inflights"); + for (int i=0; i<mInFlight.size(); i++) { + mLog.w(" Remaining #" + i + ": " + mInFlight.get(i)); + } + mInFlight.clear(); + } + } else { + // the next of our alarms is now in flight. reattribute the wakelock. + if (mInFlight.size() > 0) { + InFlight inFlight = mInFlight.get(0); + setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource); + } else { + // should never happen + mLog.w("Alarm wakelock still held but sent queue empty"); + mWakeLock.setWorkSource(null); + } + } + } + } + } +} |