1 /*
2  * Copyright (C) 2011, 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 
17 package com.android.server.sip;
18 
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;
27 
28 import java.util.Comparator;
29 import java.util.Iterator;
30 import java.util.TreeSet;
31 import java.util.concurrent.Executor;
32 
33 /**
34  * Timer that can schedule events to occur even when the device is in sleep.
35  */
36 class SipWakeupTimer extends BroadcastReceiver {
37     private static final String TAG = "SipWakeupTimer";
38     private static final boolean DBG = SipService.DBG && false; // STOPSHIP if true
39     private static final String TRIGGER_TIME = "TriggerTime";
40 
41     private Context mContext;
42     private AlarmManager mAlarmManager;
43 
44     // runnable --> time to execute in SystemClock
45     private TreeSet<MyEvent> mEventQueue =
46             new TreeSet<MyEvent>(new MyEventComparator());
47 
48     private PendingIntent mPendingIntent;
49 
50     private Executor mExecutor;
51 
SipWakeupTimer(Context context, Executor executor)52     public SipWakeupTimer(Context context, Executor executor) {
53         mContext = context;
54         mAlarmManager = (AlarmManager)
55                 context.getSystemService(Context.ALARM_SERVICE);
56 
57         IntentFilter filter = new IntentFilter(getAction());
58         context.registerReceiver(this, filter);
59         mExecutor = executor;
60     }
61 
62     /**
63      * Stops the timer. No event can be scheduled after this method is called.
64      */
stop()65     public synchronized void stop() {
66         mContext.unregisterReceiver(this);
67         if (mPendingIntent != null) {
68             mAlarmManager.cancel(mPendingIntent);
69             mPendingIntent = null;
70         }
71         mEventQueue.clear();
72         mEventQueue = null;
73     }
74 
stopped()75     private boolean stopped() {
76         if (mEventQueue == null) {
77             if (DBG) log("Timer stopped");
78             return true;
79         } else {
80             return false;
81         }
82     }
83 
cancelAlarm()84     private void cancelAlarm() {
85         mAlarmManager.cancel(mPendingIntent);
86         mPendingIntent = null;
87     }
88 
recalculatePeriods()89     private void recalculatePeriods() {
90         if (mEventQueue.isEmpty()) return;
91 
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
98                     - minTriggerTime);
99             interval = interval / minPeriod * minPeriod;
100             e.mTriggerTime = minTriggerTime + interval;
101         }
102         TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(
103                 mEventQueue.comparator());
104         newQueue.addAll(mEventQueue);
105         mEventQueue.clear();
106         mEventQueue = newQueue;
107         if (DBG) {
108             log("queue re-calculated");
109             printQueue();
110         }
111     }
112 
113     // Determines the period and the trigger time of the new event and insert it
114     // to the queue.
insertEvent(MyEvent event)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);
120             return;
121         }
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);
131         } else {
132             long triggerTime = now + event.mPeriod;
133             if (firstEvent.mTriggerTime < triggerTime) {
134                 event.mTriggerTime = firstEvent.mTriggerTime;
135                 event.mLastTriggerTime -= event.mPeriod;
136             } else {
137                 event.mTriggerTime = triggerTime;
138             }
139             mEventQueue.add(event);
140             recalculatePeriods();
141         }
142     }
143 
144     /**
145      * Sets a periodic timer.
146      *
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
150      */
set(int period, Runnable callback)151     public synchronized void set(int period, Runnable callback) {
152         if (stopped()) return;
153 
154         long now = SystemClock.elapsedRealtime();
155         MyEvent event = new MyEvent(period, callback, now);
156         insertEvent(event);
157 
158         if (mEventQueue.first() == event) {
159             if (mEventQueue.size() > 1) cancelAlarm();
160             scheduleNext();
161         }
162 
163         long triggerTime = event.mTriggerTime;
164         if (DBG) {
165             log("set: add event " + event + " scheduled on "
166                     + showTime(triggerTime) + " at " + showTime(now)
167                     + ", #events=" + mEventQueue.size());
168             printQueue();
169         }
170     }
171 
172     /**
173      * Cancels all the timer events with the specified callback.
174      *
175      * @param callback the callback
176      */
cancel(Runnable callback)177     public synchronized void cancel(Runnable callback) {
178         if (stopped() || mEventQueue.isEmpty()) return;
179         if (DBG) log("cancel:" + callback);
180 
181         MyEvent firstEvent = mEventQueue.first();
182         for (Iterator<MyEvent> iter = mEventQueue.iterator();
183                 iter.hasNext();) {
184             MyEvent event = iter.next();
185             if (event.mCallback == callback) {
186                 iter.remove();
187                 if (DBG) log("    cancel found:" + event);
188             }
189         }
190         if (mEventQueue.isEmpty()) {
191             cancelAlarm();
192         } else if (mEventQueue.first() != firstEvent) {
193             cancelAlarm();
194             firstEvent = mEventQueue.first();
195             firstEvent.mPeriod = firstEvent.mMaxPeriod;
196             firstEvent.mTriggerTime = firstEvent.mLastTriggerTime
197                     + firstEvent.mPeriod;
198             recalculatePeriods();
199             scheduleNext();
200         }
201         if (DBG) {
202             log("cancel: X");
203             printQueue();
204         }
205     }
206 
scheduleNext()207     private void scheduleNext() {
208         if (stopped() || mEventQueue.isEmpty()) return;
209 
210         if (mPendingIntent != null) {
211             throw new RuntimeException("pendingIntent is not null!");
212         }
213 
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);
222     }
223 
224     @Override
onReceive(Context context, Intent intent)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);
233         } else {
234             log("onReceive: unrecognized intent: " + intent);
235         }
236     }
237 
printQueue()238     private void printQueue() {
239         int count = 0;
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;
245         }
246         if (mEventQueue.size() > count) {
247             log("     .....");
248         } else if (count == 0) {
249             log("     <empty>");
250         }
251     }
252 
execute(long triggerTime)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;
257 
258         for (MyEvent event : mEventQueue) {
259             if (event.mTriggerTime != triggerTime) continue;
260             if (DBG) log("execute " + event);
261 
262             event.mLastTriggerTime = triggerTime;
263             event.mTriggerTime += event.mPeriod;
264 
265             // run the callback in the handler thread to prevent deadlock
266             mExecutor.execute(event.mCallback);
267         }
268         if (DBG) {
269             log("after timeout execution");
270             printQueue();
271         }
272         scheduleNext();
273     }
274 
getAction()275     private String getAction() {
276         return toString();
277     }
278 
showTime(long time)279     private String showTime(long time) {
280         int ms = (int) (time % 1000);
281         int s = (int) (time / 1000);
282         int m = s / 60;
283         s %= 60;
284         return String.format("%d.%d.%d", m, s, ms);
285     }
286 
287     private static class MyEvent {
288         int mPeriod;
289         int mMaxPeriod;
290         long mTriggerTime;
291         long mLastTriggerTime;
292         Runnable mCallback;
293 
MyEvent(int period, Runnable callback, long now)294         MyEvent(int period, Runnable callback, long now) {
295             mPeriod = mMaxPeriod = period;
296             mCallback = callback;
297             mLastTriggerTime = now;
298         }
299 
300         @Override
toString()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);
306         }
307 
toString(Object o)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);
312             return s;
313         }
314     }
315 
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> {
319         @Override
compare(MyEvent e1, MyEvent e2)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;
324             return diff;
325         }
326 
327         @Override
equals(Object that)328         public boolean equals(Object that) {
329             return (this == that);
330         }
331     }
332 
log(String s)333     private void log(String s) {
334         Rlog.d(TAG, s);
335     }
336 }
337