blob: 968467bf147bd2cd91a5c3c16a808c51ed4eed0c [file] [log] [blame]
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
19import android.app.ActivityManagerNative;
20import android.app.AlarmManager;
21import android.app.IAlarmManager;
22import android.app.PendingIntent;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.pm.PackageManager;
28import android.net.Uri;
29import android.os.Binder;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.Message;
33import android.os.PowerManager;
34import android.os.SystemClock;
35import android.os.SystemProperties;
36import android.text.TextUtils;
37import android.util.Config;
38import android.util.Log;
39
40import java.io.FileDescriptor;
41import java.io.IOException;
42import java.io.PrintWriter;
43import java.util.ArrayList;
44import java.util.Calendar;
45import java.util.Collections;
46import java.util.HashMap;
47import java.util.Iterator;
48import java.util.Map;
49import java.util.TimeZone;
50
51class AlarmManagerService extends IAlarmManager.Stub {
52 private static final int RTC_WAKEUP_MASK = 1 << AlarmManager.RTC_WAKEUP;
53 private static final int RTC_MASK = 1 << AlarmManager.RTC;
54 private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << AlarmManager.ELAPSED_REALTIME_WAKEUP;
55 private static final int ELAPSED_REALTIME_MASK = 1 << AlarmManager.ELAPSED_REALTIME;
56 private static final int TIME_CHANGED_MASK = 1 << 16;
57
58 private static final String TAG = "AlarmManager";
59 private static final String ClockReceiver_TAG = "ClockReceiver";
60 private static final boolean localLOGV = false;
61 private static final int ALARM_EVENT = 1;
62 private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
63
64 private static final Intent mBackgroundIntent
65 = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
66
67 private final Context mContext;
68
69 private Object mLock = new Object();
70
71 private final ArrayList<Alarm> mRtcWakeupAlarms = new ArrayList<Alarm>();
72 private final ArrayList<Alarm> mRtcAlarms = new ArrayList<Alarm>();
73 private final ArrayList<Alarm> mElapsedRealtimeWakeupAlarms = new ArrayList<Alarm>();
74 private final ArrayList<Alarm> mElapsedRealtimeAlarms = new ArrayList<Alarm>();
75
76 private int mDescriptor;
77 private int mBroadcastRefCount = 0;
78 private PowerManager.WakeLock mWakeLock;
79 private final AlarmThread mWaitThread = new AlarmThread();
80 private final AlarmHandler mHandler = new AlarmHandler();
81 private ClockReceiver mClockReceiver;
82 private UninstallReceiver mUninstallReceiver;
83 private final ResultReceiver mResultReceiver = new ResultReceiver();
84 private final PendingIntent mTimeTickSender;
85 private final PendingIntent mDateChangeSender;
86
87 private static final class FilterStats {
88 int count;
89 }
90
91 private static final class BroadcastStats {
92 long aggregateTime;
93 int numWakeup;
94 long startTime;
95 int nesting;
96 HashMap<Intent.FilterComparison, FilterStats> filterStats
97 = new HashMap<Intent.FilterComparison, FilterStats>();
98 }
99
100 private final HashMap<String, BroadcastStats> mBroadcastStats
101 = new HashMap<String, BroadcastStats>();
102
103 public AlarmManagerService(Context context) {
104 mContext = context;
105 mDescriptor = init();
106 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
107 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
108
109 mTimeTickSender = PendingIntent.getBroadcast(context, 0,
110 new Intent(Intent.ACTION_TIME_TICK).addFlags(
111 Intent.FLAG_RECEIVER_REGISTERED_ONLY), 0);
112 mDateChangeSender = PendingIntent.getBroadcast(context, 0,
113 new Intent(Intent.ACTION_DATE_CHANGED), 0);
114
115 // now that we have initied the driver schedule the alarm
116 mClockReceiver= new ClockReceiver();
117 mClockReceiver.scheduleTimeTickEvent();
118 mClockReceiver.scheduleDateChangedEvent();
119 mUninstallReceiver = new UninstallReceiver();
120
121 if (mDescriptor != -1) {
122 mWaitThread.start();
123 } else {
124 Log.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
125 }
126 }
127
128 protected void finalize() throws Throwable {
129 try {
130 close(mDescriptor);
131 } finally {
132 super.finalize();
133 }
134 }
135
136 public void set(int type, long triggerAtTime, PendingIntent operation) {
137 setRepeating(type, triggerAtTime, 0, operation);
138 }
139
140 public void setRepeating(int type, long triggerAtTime, long interval,
141 PendingIntent operation) {
142 if (operation == null) {
143 Log.w(TAG, "set/setRepeating ignored because there is no intent");
144 return;
145 }
146 synchronized (mLock) {
147 Alarm alarm = new Alarm();
148 alarm.type = type;
149 alarm.when = triggerAtTime;
150 alarm.repeatInterval = interval;
151 alarm.operation = operation;
152
153 // Remove this alarm if already scheduled.
154 removeLocked(operation);
155
156 if (localLOGV) Log.v(TAG, "set: " + alarm);
157
158 int index = addAlarmLocked(alarm);
159 if (index == 0) {
160 setLocked(alarm);
161 }
162 }
163 }
164
165 public void setTimeZone(String tz) {
166 mContext.enforceCallingOrSelfPermission(
167 "android.permission.SET_TIME_ZONE",
168 "setTimeZone");
169
170 if (TextUtils.isEmpty(tz)) return;
171 TimeZone zone = TimeZone.getTimeZone(tz);
172 // Prevent reentrant calls from stepping on each other when writing
173 // the time zone property
174 synchronized (this) {
175 SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());
176 }
177
178 TimeZone.setDefault(null);
179
180 Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
181 intent.putExtra("time-zone", zone.getID());
182 mContext.sendBroadcast(intent);
183 }
184
185 public void remove(PendingIntent operation) {
186 if (operation == null) {
187 return;
188 }
189 synchronized (mLock) {
190 removeLocked(operation);
191 }
192 }
193
194 public void removeLocked(PendingIntent operation) {
195 removeLocked(mRtcWakeupAlarms, operation);
196 removeLocked(mRtcAlarms, operation);
197 removeLocked(mElapsedRealtimeWakeupAlarms, operation);
198 removeLocked(mElapsedRealtimeAlarms, operation);
199 }
200
201 private void removeLocked(ArrayList<Alarm> alarmList,
202 PendingIntent operation) {
203 if (alarmList.size() <= 0) {
204 return;
205 }
206
207 // iterator over the list removing any it where the intent match
208 Iterator<Alarm> it = alarmList.iterator();
209
210 while (it.hasNext()) {
211 Alarm alarm = it.next();
212 if (alarm.operation.equals(operation)) {
213 it.remove();
214 }
215 }
216 }
217
218 public void removeLocked(String packageName) {
219 removeLocked(mRtcWakeupAlarms, packageName);
220 removeLocked(mRtcAlarms, packageName);
221 removeLocked(mElapsedRealtimeWakeupAlarms, packageName);
222 removeLocked(mElapsedRealtimeAlarms, packageName);
223 }
224
225 private void removeLocked(ArrayList<Alarm> alarmList,
226 String packageName) {
227 if (alarmList.size() <= 0) {
228 return;
229 }
230
231 // iterator over the list removing any it where the intent match
232 Iterator<Alarm> it = alarmList.iterator();
233
234 while (it.hasNext()) {
235 Alarm alarm = it.next();
236 if (alarm.operation.getTargetPackage().equals(packageName)) {
237 it.remove();
238 }
239 }
240 }
241
242 private ArrayList<Alarm> getAlarmList(int type) {
243 switch (type) {
244 case AlarmManager.RTC_WAKEUP: return mRtcWakeupAlarms;
245 case AlarmManager.RTC: return mRtcAlarms;
246 case AlarmManager.ELAPSED_REALTIME_WAKEUP: return mElapsedRealtimeWakeupAlarms;
247 case AlarmManager.ELAPSED_REALTIME: return mElapsedRealtimeAlarms;
248 }
249
250 return null;
251 }
252
253 private int addAlarmLocked(Alarm alarm) {
254 ArrayList<Alarm> alarmList = getAlarmList(alarm.type);
255
256 int index = Collections.binarySearch(alarmList, alarm);
257 index = (index < 0) ? ((index + 1) * -1) : index;
258 if (localLOGV) Log.v(
259 TAG, "Adding alarm " + alarm + " at " + index);
260 alarmList.add(index, alarm);
261
262 return index;
263 }
264
265 public long timeToNextAlarm() {
266 long nextAlarm = 0xfffffffffffffffl;
267 synchronized (mLock) {
268 for (int i=AlarmManager.RTC_WAKEUP;
269 i<=AlarmManager.ELAPSED_REALTIME; i++) {
270 ArrayList<Alarm> alarmList = getAlarmList(i);
271 if (alarmList.size() > 0) {
272 Alarm a = alarmList.get(0);
273 if (a.when < nextAlarm) {
274 nextAlarm = a.when;
275 }
276 }
277 }
278 }
279 return nextAlarm;
280 }
281
282 private void setLocked(Alarm alarm)
283 {
284 if (mDescriptor != -1)
285 {
286 set(mDescriptor, alarm.type, (alarm.when * 1000 * 1000));
287 }
288 else
289 {
290 Message msg = Message.obtain();
291 msg.what = ALARM_EVENT;
292
293 mHandler.removeMessages(ALARM_EVENT);
294 mHandler.sendMessageAtTime(msg, alarm.when);
295 }
296 }
297
298 @Override
299 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
300 if (mContext.checkCallingPermission("android.permission.DUMP")
301 != PackageManager.PERMISSION_GRANTED) {
302 pw.println("Permission Denial: can't dump AlarmManager from from pid="
303 + Binder.getCallingPid()
304 + ", uid=" + Binder.getCallingUid());
305 return;
306 }
307
308 synchronized (mLock) {
309 pw.println("Current Alarm Manager state:");
310 if (mRtcWakeupAlarms.size() > 0) {
311 pw.println(" ");
312 pw.println(" Realtime wakeup alarms that are scheduled:");
313 dumpAlarmList(pw, mRtcWakeupAlarms, " ", "RTC_WAKEUP");
314 }
315 if (mRtcAlarms.size() > 0) {
316 pw.println(" ");
317 pw.println(" Realtime alarms that are scheduled:");
318 dumpAlarmList(pw, mRtcAlarms, " ", "RTC");
319 }
320 if (mElapsedRealtimeWakeupAlarms.size() > 0) {
321 pw.println(" ");
322 pw.println(" Elapsed realtime wakeup alarms that are scheduled:");
323 dumpAlarmList(pw, mElapsedRealtimeWakeupAlarms, " ", "ELAPSED_REALTIME_WAKEUP");
324 }
325 if (mElapsedRealtimeAlarms.size() > 0) {
326 pw.println(" ");
327 pw.println(" Elapsed realtime alarms that are scheduled:");
328 dumpAlarmList(pw, mElapsedRealtimeAlarms, " ", "ELAPSED_REALTIME");
329 }
330
331 pw.println(" ");
332 pw.println(" Broadcast ref count: " + mBroadcastRefCount);
333
334 pw.println(" ");
335 pw.println(" Alarm Stats:");
336 for (Map.Entry<String, BroadcastStats> be : mBroadcastStats.entrySet()) {
337 BroadcastStats bs = be.getValue();
338 pw.println(" " + be.getKey());
339 pw.println(" " + bs.aggregateTime + "ms running, "
340 + bs.numWakeup + " wakeups");
341 for (Map.Entry<Intent.FilterComparison, FilterStats> fe
342 : bs.filterStats.entrySet()) {
343 pw.println(" " + fe.getValue().count + " alarms: "
344 + fe.getKey().getIntent());
345 }
346 }
347 }
348 }
349
350 private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list, String prefix, String label) {
351 for (int i=list.size()-1; i>=0; i--) {
352 Alarm a = list.get(i);
353 pw.println(prefix + label + " #" + i + ":");
354 a.dump(pw, prefix + " ");
355 }
356 }
357
358 private native int init();
359 private native void close(int fd);
360 private native void set(int fd, int type, long nanoseconds);
361 private native int waitForAlarm(int fd);
362
363 private void triggerAlarmsLocked(ArrayList<Alarm> alarmList,
364 ArrayList<Alarm> triggerList,
365 long now)
366 {
367 Iterator<Alarm> it = alarmList.iterator();
368 ArrayList<Alarm> repeats = new ArrayList<Alarm>();
369
370 while (it.hasNext())
371 {
372 Alarm alarm = it.next();
373
374 if (localLOGV) Log.v(TAG, "Checking active alarm when=" + alarm.when + " " + alarm);
375
376 if (alarm.when > now)
377 {
378 // don't fire alarms in the future
379 break;
380 }
381
382 // add it to the trigger list so we can trigger it without the lock held.
383 // recurring alarms may have passed several alarm intervals while the
384 // phone was asleep or off, so pass a trigger count when sending them.
385 if (localLOGV) Log.v(TAG, "Alarm triggering: " + alarm);
386 alarm.count = 1;
387 if (alarm.repeatInterval > 0) {
388 // this adjustment will be zero if we're late by
389 // less than one full repeat interval
390 alarm.count += (now - alarm.when) / alarm.repeatInterval;
391 }
392 triggerList.add(alarm);
393
394 // remove the alarm from the list
395 it.remove();
396
397 // if it repeats queue it up to be read-added to the list
398 if (alarm.repeatInterval > 0)
399 {
400 repeats.add(alarm);
401 }
402 }
403
404 // reset any repeating alarms.
405 it = repeats.iterator();
406 while (it.hasNext())
407 {
408 Alarm alarm = it.next();
409 alarm.when += alarm.count * alarm.repeatInterval;
410 addAlarmLocked(alarm);
411 }
412
413 if (alarmList.size() > 0)
414 {
415 setLocked(alarmList.get(0));
416 }
417 }
418
419 private class Alarm implements Comparable<Alarm> {
420 public int type;
421 public int count;
422 public long when;
423 public long repeatInterval;
424 public PendingIntent operation;
425
426 public Alarm() {
427 when = 0;
428 repeatInterval = 0;
429 operation = null;
430 }
431
432 public int compareTo(Alarm obj)
433 {
434 if (obj.when > this.when) return -1;
435 if (obj.when < this.when) return 1;
436 if (obj.operation.equals(this.operation)
437 && obj.repeatInterval == this.repeatInterval) return 0;
438 return -1;
439 }
440
441 public String toString()
442 {
443 return "Alarm{"
444 + Integer.toHexString(System.identityHashCode(this))
445 + " type " + type + " " + operation.getTargetPackage() + "}";
446 }
447
448 public void dump(PrintWriter pw, String prefix)
449 {
450 pw.println(prefix + this);
451 pw.println(prefix + "type=" + type + " when=" + when
452 + " repeatInterval=" + repeatInterval
453 + " count=" + count);
454 pw.println(prefix + "operation=" + operation);
455 }
456 }
457
458 private class AlarmThread extends Thread
459 {
460 public AlarmThread()
461 {
462 super("AlarmManager");
463 }
464
465 public void run()
466 {
467 while (true)
468 {
469 int result = waitForAlarm(mDescriptor);
470
471 ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
472
473 if ((result & TIME_CHANGED_MASK) != 0) {
474 remove(mTimeTickSender);
475 mClockReceiver.scheduleTimeTickEvent();
476 mContext.sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
477 }
478
479 synchronized (mLock) {
480 final long nowRTC = System.currentTimeMillis();
481 final long nowELAPSED = SystemClock.elapsedRealtime();
482 if (localLOGV) Log.v(
483 TAG, "Checking for alarms... rtc=" + nowRTC
484 + ", elapsed=" + nowELAPSED);
485
486 if ((result & RTC_WAKEUP_MASK) != 0)
487 triggerAlarmsLocked(mRtcWakeupAlarms, triggerList, nowRTC);
488
489 if ((result & RTC_MASK) != 0)
490 triggerAlarmsLocked(mRtcAlarms, triggerList, nowRTC);
491
492 if ((result & ELAPSED_REALTIME_WAKEUP_MASK) != 0)
493 triggerAlarmsLocked(mElapsedRealtimeWakeupAlarms, triggerList, nowELAPSED);
494
495 if ((result & ELAPSED_REALTIME_MASK) != 0)
496 triggerAlarmsLocked(mElapsedRealtimeAlarms, triggerList, nowELAPSED);
497
498 // now trigger the alarms
499 Iterator<Alarm> it = triggerList.iterator();
500 while (it.hasNext()) {
501 Alarm alarm = it.next();
502 try {
503 if (localLOGV) Log.v(TAG, "sending alarm " + alarm);
504 alarm.operation.send(mContext, 0,
505 mBackgroundIntent.putExtra(
506 Intent.EXTRA_ALARM_COUNT, alarm.count),
507 mResultReceiver, mHandler);
508
509 // we have an active broadcast so stay awake.
510 if (mBroadcastRefCount == 0) {
511 mWakeLock.acquire();
512 }
513 mBroadcastRefCount++;
514
515 BroadcastStats bs = getStatsLocked(alarm.operation);
516 if (bs.nesting == 0) {
517 bs.startTime = nowELAPSED;
518 } else {
519 bs.nesting++;
520 }
521 if (alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP
522 || alarm.type == AlarmManager.RTC_WAKEUP) {
523 bs.numWakeup++;
524 ActivityManagerNative.noteWakeupAlarm(
525 alarm.operation);
526 }
527 } catch (PendingIntent.CanceledException e) {
528 if (alarm.repeatInterval > 0) {
529 // This IntentSender is no longer valid, but this
530 // is a repeating alarm, so toss the hoser.
531 remove(alarm.operation);
532 }
533 } catch (RuntimeException e) {
534 Log.w(TAG, "Failure sending alarm.", e);
535 }
536 }
537 }
538 }
539 }
540 }
541
542 private class AlarmHandler extends Handler {
543 public static final int ALARM_EVENT = 1;
544 public static final int MINUTE_CHANGE_EVENT = 2;
545 public static final int DATE_CHANGE_EVENT = 3;
546
547 public AlarmHandler() {
548 }
549
550 public void handleMessage(Message msg) {
551 if (msg.what == ALARM_EVENT) {
552 ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
553 synchronized (mLock) {
554 final long nowRTC = System.currentTimeMillis();
555 triggerAlarmsLocked(mRtcWakeupAlarms, triggerList, nowRTC);
556 triggerAlarmsLocked(mRtcAlarms, triggerList, nowRTC);
557 triggerAlarmsLocked(mElapsedRealtimeWakeupAlarms, triggerList, nowRTC);
558 triggerAlarmsLocked(mElapsedRealtimeAlarms, triggerList, nowRTC);
559 }
560
561 // now trigger the alarms without the lock held
562 Iterator<Alarm> it = triggerList.iterator();
563 while (it.hasNext())
564 {
565 Alarm alarm = it.next();
566 try {
567 alarm.operation.send();
568 } catch (PendingIntent.CanceledException e) {
569 if (alarm.repeatInterval > 0) {
570 // This IntentSender is no longer valid, but this
571 // is a repeating alarm, so toss the hoser.
572 remove(alarm.operation);
573 }
574 }
575 }
576 }
577 }
578 }
579
580 class ClockReceiver extends BroadcastReceiver {
581 public ClockReceiver() {
582 IntentFilter filter = new IntentFilter();
583 filter.addAction(Intent.ACTION_TIME_TICK);
584 mContext.registerReceiver(this, filter);
585 }
586
587 @Override
588 public void onReceive(Context context, Intent intent) {
589 if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) {
590 scheduleTimeTickEvent();
591 } else if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) {
592 scheduleDateChangedEvent();
593 }
594 }
595
596 public void scheduleTimeTickEvent() {
597 Calendar calendar = Calendar.getInstance();
598 calendar.setTimeInMillis(System.currentTimeMillis());
599 calendar.add(Calendar.MINUTE, 1);
600 calendar.set(Calendar.SECOND, 0);
601 calendar.set(Calendar.MILLISECOND, 0);
602
603 set(AlarmManager.RTC, calendar.getTimeInMillis(), mTimeTickSender);
604 }
605
606 public void scheduleDateChangedEvent() {
607 Calendar calendar = Calendar.getInstance();
608 calendar.setTimeInMillis(System.currentTimeMillis());
609 calendar.add(Calendar.DAY_OF_MONTH, 1);
610 calendar.set(Calendar.HOUR, 0);
611 calendar.set(Calendar.MINUTE, 0);
612 calendar.set(Calendar.SECOND, 0);
613 calendar.set(Calendar.MILLISECOND, 0);
614
615 set(AlarmManager.RTC, calendar.getTimeInMillis(), mDateChangeSender);
616 }
617 }
618
619 class UninstallReceiver extends BroadcastReceiver {
620 public UninstallReceiver() {
621 IntentFilter filter = new IntentFilter();
622 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
623 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
624 filter.addDataScheme("package");
625 mContext.registerReceiver(this, filter);
626 }
627
628 @Override
629 public void onReceive(Context context, Intent intent) {
630 synchronized (mLock) {
631 Uri data = intent.getData();
632 if (data != null) {
633 String pkg = data.getSchemeSpecificPart();
634 removeLocked(pkg);
635 mBroadcastStats.remove(pkg);
636 }
637 }
638 }
639 }
640
641 private final BroadcastStats getStatsLocked(PendingIntent pi) {
642 String pkg = pi.getTargetPackage();
643 BroadcastStats bs = mBroadcastStats.get(pkg);
644 if (bs == null) {
645 bs = new BroadcastStats();
646 mBroadcastStats.put(pkg, bs);
647 }
648 return bs;
649 }
650
651 class ResultReceiver implements PendingIntent.OnFinished {
652 public void onSendFinished(PendingIntent pi, Intent intent, int resultCode,
653 String resultData, Bundle resultExtras) {
654 synchronized (mLock) {
655 BroadcastStats bs = getStatsLocked(pi);
656 if (bs != null) {
657 bs.nesting--;
658 if (bs.nesting <= 0) {
659 bs.nesting = 0;
660 bs.aggregateTime += SystemClock.elapsedRealtime()
661 - bs.startTime;
662 Intent.FilterComparison fc = new Intent.FilterComparison(intent);
663 FilterStats fs = bs.filterStats.get(fc);
664 if (fs == null) {
665 fs = new FilterStats();
666 bs.filterStats.put(fc, fs);
667 }
668 fs.count++;
669 }
670 }
671 mBroadcastRefCount--;
672 if (mBroadcastRefCount == 0) {
673 mWakeLock.release();
674 }
675 }
676 }
677 }
678}