summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/AlarmManagerService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/AlarmManagerService.java')
-rw-r--r--services/core/java/com/android/server/AlarmManagerService.java1515
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);
+ }
+ }
+ }
+ }
+ }
+}