2 * Copyright (C) 2011, The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.sip;
19 import android.app.AlarmManager;
20 import android.app.PendingIntent;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.os.SystemClock;
26 import android.telephony.Rlog;
28 import java.util.Comparator;
29 import java.util.Iterator;
30 import java.util.TreeSet;
31 import java.util.concurrent.Executor;
34 * Timer that can schedule events to occur even when the device is in sleep.
36 class SipWakeupTimer extends BroadcastReceiver {
37 private static final String TAG = "SipWakeupTimer";
38 private static final boolean DBG = SipService.DBG && true; // STOPSHIP if true
39 private static final String TRIGGER_TIME = "TriggerTime";
41 private Context mContext;
42 private AlarmManager mAlarmManager;
44 // runnable --> time to execute in SystemClock
45 private TreeSet<MyEvent> mEventQueue =
46 new TreeSet<MyEvent>(new MyEventComparator());
48 private PendingIntent mPendingIntent;
50 private Executor mExecutor;
52 public SipWakeupTimer(Context context, Executor executor) {
54 mAlarmManager = (AlarmManager)
55 context.getSystemService(Context.ALARM_SERVICE);
57 IntentFilter filter = new IntentFilter(getAction());
58 context.registerReceiver(this, filter);
63 * Stops the timer. No event can be scheduled after this method is called.
65 public synchronized void stop() {
66 mContext.unregisterReceiver(this);
67 if (mPendingIntent != null) {
68 mAlarmManager.cancel(mPendingIntent);
69 mPendingIntent = null;
75 private boolean stopped() {
76 if (mEventQueue == null) {
77 if (DBG) log("Timer stopped");
84 private void cancelAlarm() {
85 mAlarmManager.cancel(mPendingIntent);
86 mPendingIntent = null;
89 private void recalculatePeriods() {
90 if (mEventQueue.isEmpty()) return;
92 MyEvent firstEvent = mEventQueue.first();
93 int minPeriod = firstEvent.mMaxPeriod;
94 long minTriggerTime = firstEvent.mTriggerTime;
95 for (MyEvent e : mEventQueue) {
96 e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod;
97 int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod
99 interval = interval / minPeriod * minPeriod;
100 e.mTriggerTime = minTriggerTime + interval;
102 TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(
103 mEventQueue.comparator());
104 newQueue.addAll(mEventQueue);
106 mEventQueue = newQueue;
108 log("queue re-calculated");
113 // Determines the period and the trigger time of the new event and insert it
115 private void insertEvent(MyEvent event) {
116 long now = SystemClock.elapsedRealtime();
117 if (mEventQueue.isEmpty()) {
118 event.mTriggerTime = now + event.mPeriod;
119 mEventQueue.add(event);
122 MyEvent firstEvent = mEventQueue.first();
123 int minPeriod = firstEvent.mPeriod;
124 if (minPeriod <= event.mMaxPeriod) {
125 event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod;
126 int interval = event.mMaxPeriod;
127 interval -= (int) (firstEvent.mTriggerTime - now);
128 interval = interval / minPeriod * minPeriod;
129 event.mTriggerTime = firstEvent.mTriggerTime + interval;
130 mEventQueue.add(event);
132 long triggerTime = now + event.mPeriod;
133 if (firstEvent.mTriggerTime < triggerTime) {
134 event.mTriggerTime = firstEvent.mTriggerTime;
135 event.mLastTriggerTime -= event.mPeriod;
137 event.mTriggerTime = triggerTime;
139 mEventQueue.add(event);
140 recalculatePeriods();
145 * Sets a periodic timer.
147 * @param period the timer period; in milli-second
148 * @param callback is called back when the timer goes off; the same callback
149 * can be specified in multiple timer events
151 public synchronized void set(int period, Runnable callback) {
152 if (stopped()) return;
154 long now = SystemClock.elapsedRealtime();
155 MyEvent event = new MyEvent(period, callback, now);
158 if (mEventQueue.first() == event) {
159 if (mEventQueue.size() > 1) cancelAlarm();
163 long triggerTime = event.mTriggerTime;
165 log("set: add event " + event + " scheduled on "
166 + showTime(triggerTime) + " at " + showTime(now)
167 + ", #events=" + mEventQueue.size());
173 * Cancels all the timer events with the specified callback.
175 * @param callback the callback
177 public synchronized void cancel(Runnable callback) {
178 if (stopped() || mEventQueue.isEmpty()) return;
179 if (DBG) log("cancel:" + callback);
181 MyEvent firstEvent = mEventQueue.first();
182 for (Iterator<MyEvent> iter = mEventQueue.iterator();
184 MyEvent event = iter.next();
185 if (event.mCallback == callback) {
187 if (DBG) log(" cancel found:" + event);
190 if (mEventQueue.isEmpty()) {
192 } else if (mEventQueue.first() != firstEvent) {
194 firstEvent = mEventQueue.first();
195 firstEvent.mPeriod = firstEvent.mMaxPeriod;
196 firstEvent.mTriggerTime = firstEvent.mLastTriggerTime
197 + firstEvent.mPeriod;
198 recalculatePeriods();
207 private void scheduleNext() {
208 if (stopped() || mEventQueue.isEmpty()) return;
210 if (mPendingIntent != null) {
211 throw new RuntimeException("pendingIntent is not null!");
214 MyEvent event = mEventQueue.first();
215 Intent intent = new Intent(getAction());
216 intent.putExtra(TRIGGER_TIME, event.mTriggerTime);
217 PendingIntent pendingIntent = mPendingIntent =
218 PendingIntent.getBroadcast(mContext, 0, intent,
219 PendingIntent.FLAG_UPDATE_CURRENT);
220 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
221 event.mTriggerTime, pendingIntent);
225 public synchronized void onReceive(Context context, Intent intent) {
226 // This callback is already protected by AlarmManager's wake lock.
227 String action = intent.getAction();
228 if (getAction().equals(action)
229 && intent.getExtras().containsKey(TRIGGER_TIME)) {
230 mPendingIntent = null;
231 long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L);
232 execute(triggerTime);
234 log("onReceive: unrecognized intent: " + intent);
238 private void printQueue() {
240 for (MyEvent event : mEventQueue) {
241 log(" " + event + ": scheduled at "
242 + showTime(event.mTriggerTime) + ": last at "
243 + showTime(event.mLastTriggerTime));
244 if (++count >= 5) break;
246 if (mEventQueue.size() > count) {
248 } else if (count == 0) {
253 private void execute(long triggerTime) {
254 if (DBG) log("time's up, triggerTime = "
255 + showTime(triggerTime) + ": " + mEventQueue.size());
256 if (stopped() || mEventQueue.isEmpty()) return;
258 for (MyEvent event : mEventQueue) {
259 if (event.mTriggerTime != triggerTime) continue;
260 if (DBG) log("execute " + event);
262 event.mLastTriggerTime = triggerTime;
263 event.mTriggerTime += event.mPeriod;
265 // run the callback in the handler thread to prevent deadlock
266 mExecutor.execute(event.mCallback);
269 log("after timeout execution");
275 private String getAction() {
279 private String showTime(long time) {
280 int ms = (int) (time % 1000);
281 int s = (int) (time / 1000);
284 return String.format("%d.%d.%d", m, s, ms);
287 private static class MyEvent {
291 long mLastTriggerTime;
294 MyEvent(int period, Runnable callback, long now) {
295 mPeriod = mMaxPeriod = period;
296 mCallback = callback;
297 mLastTriggerTime = now;
301 public String toString() {
302 String s = super.toString();
303 s = s.substring(s.indexOf("@"));
304 return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":"
305 + toString(mCallback);
308 private String toString(Object o) {
309 String s = o.toString();
310 int index = s.indexOf("$");
311 if (index > 0) s = s.substring(index + 1);
316 // Sort the events by mMaxPeriod so that the first event can be used to
317 // align events with larger periods
318 private static class MyEventComparator implements Comparator<MyEvent> {
320 public int compare(MyEvent e1, MyEvent e2) {
321 if (e1 == e2) return 0;
322 int diff = e1.mMaxPeriod - e2.mMaxPeriod;
323 if (diff == 0) diff = -1;
328 public boolean equals(Object that) {
329 return (this == that);
333 private void log(String s) {