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 && true; // 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 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 */ 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 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 84 private void cancelAlarm() { 85 mAlarmManager.cancel(mPendingIntent); 86 mPendingIntent = null; 87 } 88 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. 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 */ 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 */ 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 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 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 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 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 275 private String getAction() { 276 return toString(); 277 } 278 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 294 MyEvent(int period, Runnable callback, long now) { 295 mPeriod = mMaxPeriod = period; 296 mCallback = callback; 297 mLastTriggerTime = now; 298 } 299 300 @Override 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 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 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 328 public boolean equals(Object that) { 329 return (this == that); 330 } 331 } 332 333 private void log(String s) { 334 Rlog.d(TAG, s); 335 } 336 } 337