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.util.Log; 27 28 import java.io.IOException; 29 import java.net.DatagramSocket; 30 import java.net.InetAddress; 31 import java.net.UnknownHostException; 32 import java.util.ArrayList; 33 import java.util.Collection; 34 import java.util.Comparator; 35 import java.util.HashMap; 36 import java.util.Iterator; 37 import java.util.Map; 38 import java.util.Timer; 39 import java.util.TimerTask; 40 import java.util.TreeSet; 41 import java.util.concurrent.Executor; 42 import javax.sip.SipException; 43 44 /** 45 * Timer that can schedule events to occur even when the device is in sleep. 46 */ 47 class SipWakeupTimer extends BroadcastReceiver { 48 private static final String TAG = "_SIP.WkTimer_"; 49 private static final String TRIGGER_TIME = "TriggerTime"; 50 private static final boolean DEBUG_TIMER = SipService.DEBUG && false; 51 52 private Context mContext; 53 private AlarmManager mAlarmManager; 54 55 // runnable --> time to execute in SystemClock 56 private TreeSet<MyEvent> mEventQueue = 57 new TreeSet<MyEvent>(new MyEventComparator()); 58 59 private PendingIntent mPendingIntent; 60 61 private Executor mExecutor; 62 63 public SipWakeupTimer(Context context, Executor executor) { 64 mContext = context; 65 mAlarmManager = (AlarmManager) 66 context.getSystemService(Context.ALARM_SERVICE); 67 68 IntentFilter filter = new IntentFilter(getAction()); 69 context.registerReceiver(this, filter); 70 mExecutor = executor; 71 } 72 73 /** 74 * Stops the timer. No event can be scheduled after this method is called. 75 */ 76 public synchronized void stop() { 77 mContext.unregisterReceiver(this); 78 if (mPendingIntent != null) { 79 mAlarmManager.cancel(mPendingIntent); 80 mPendingIntent = null; 81 } 82 mEventQueue.clear(); 83 mEventQueue = null; 84 } 85 86 private boolean stopped() { 87 if (mEventQueue == null) { 88 Log.w(TAG, "Timer stopped"); 89 return true; 90 } else { 91 return false; 92 } 93 } 94 95 private void cancelAlarm() { 96 mAlarmManager.cancel(mPendingIntent); 97 mPendingIntent = null; 98 } 99 100 private void recalculatePeriods() { 101 if (mEventQueue.isEmpty()) return; 102 103 MyEvent firstEvent = mEventQueue.first(); 104 int minPeriod = firstEvent.mMaxPeriod; 105 long minTriggerTime = firstEvent.mTriggerTime; 106 for (MyEvent e : mEventQueue) { 107 e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod; 108 int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod 109 - minTriggerTime); 110 interval = interval / minPeriod * minPeriod; 111 e.mTriggerTime = minTriggerTime + interval; 112 } 113 TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>( 114 mEventQueue.comparator()); 115 newQueue.addAll((Collection<MyEvent>) mEventQueue); 116 mEventQueue.clear(); 117 mEventQueue = newQueue; 118 if (DEBUG_TIMER) { 119 Log.d(TAG, "queue re-calculated"); 120 printQueue(); 121 } 122 } 123 124 // Determines the period and the trigger time of the new event and insert it 125 // to the queue. 126 private void insertEvent(MyEvent event) { 127 long now = SystemClock.elapsedRealtime(); 128 if (mEventQueue.isEmpty()) { 129 event.mTriggerTime = now + event.mPeriod; 130 mEventQueue.add(event); 131 return; 132 } 133 MyEvent firstEvent = mEventQueue.first(); 134 int minPeriod = firstEvent.mPeriod; 135 if (minPeriod <= event.mMaxPeriod) { 136 event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod; 137 int interval = event.mMaxPeriod; 138 interval -= (int) (firstEvent.mTriggerTime - now); 139 interval = interval / minPeriod * minPeriod; 140 event.mTriggerTime = firstEvent.mTriggerTime + interval; 141 mEventQueue.add(event); 142 } else { 143 long triggerTime = now + event.mPeriod; 144 if (firstEvent.mTriggerTime < triggerTime) { 145 event.mTriggerTime = firstEvent.mTriggerTime; 146 event.mLastTriggerTime -= event.mPeriod; 147 } else { 148 event.mTriggerTime = triggerTime; 149 } 150 mEventQueue.add(event); 151 recalculatePeriods(); 152 } 153 } 154 155 /** 156 * Sets a periodic timer. 157 * 158 * @param period the timer period; in milli-second 159 * @param callback is called back when the timer goes off; the same callback 160 * can be specified in multiple timer events 161 */ 162 public synchronized void set(int period, Runnable callback) { 163 if (stopped()) return; 164 165 long now = SystemClock.elapsedRealtime(); 166 MyEvent event = new MyEvent(period, callback, now); 167 insertEvent(event); 168 169 if (mEventQueue.first() == event) { 170 if (mEventQueue.size() > 1) cancelAlarm(); 171 scheduleNext(); 172 } 173 174 long triggerTime = event.mTriggerTime; 175 if (DEBUG_TIMER) { 176 Log.d(TAG, " add event " + event + " scheduled on " 177 + showTime(triggerTime) + " at " + showTime(now) 178 + ", #events=" + mEventQueue.size()); 179 printQueue(); 180 } 181 } 182 183 /** 184 * Cancels all the timer events with the specified callback. 185 * 186 * @param callback the callback 187 */ 188 public synchronized void cancel(Runnable callback) { 189 if (stopped() || mEventQueue.isEmpty()) return; 190 if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback); 191 192 MyEvent firstEvent = mEventQueue.first(); 193 for (Iterator<MyEvent> iter = mEventQueue.iterator(); 194 iter.hasNext();) { 195 MyEvent event = iter.next(); 196 if (event.mCallback == callback) { 197 iter.remove(); 198 if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event); 199 } 200 } 201 if (mEventQueue.isEmpty()) { 202 cancelAlarm(); 203 } else if (mEventQueue.first() != firstEvent) { 204 cancelAlarm(); 205 firstEvent = mEventQueue.first(); 206 firstEvent.mPeriod = firstEvent.mMaxPeriod; 207 firstEvent.mTriggerTime = firstEvent.mLastTriggerTime 208 + firstEvent.mPeriod; 209 recalculatePeriods(); 210 scheduleNext(); 211 } 212 if (DEBUG_TIMER) { 213 Log.d(TAG, "after cancel:"); 214 printQueue(); 215 } 216 } 217 218 private void scheduleNext() { 219 if (stopped() || mEventQueue.isEmpty()) return; 220 221 if (mPendingIntent != null) { 222 throw new RuntimeException("pendingIntent is not null!"); 223 } 224 225 MyEvent event = mEventQueue.first(); 226 Intent intent = new Intent(getAction()); 227 intent.putExtra(TRIGGER_TIME, event.mTriggerTime); 228 PendingIntent pendingIntent = mPendingIntent = 229 PendingIntent.getBroadcast(mContext, 0, intent, 230 PendingIntent.FLAG_UPDATE_CURRENT); 231 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 232 event.mTriggerTime, pendingIntent); 233 } 234 235 @Override 236 public synchronized void onReceive(Context context, Intent intent) { 237 // This callback is already protected by AlarmManager's wake lock. 238 String action = intent.getAction(); 239 if (getAction().equals(action) 240 && intent.getExtras().containsKey(TRIGGER_TIME)) { 241 mPendingIntent = null; 242 long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L); 243 execute(triggerTime); 244 } else { 245 Log.d(TAG, "unrecognized intent: " + intent); 246 } 247 } 248 249 private void printQueue() { 250 int count = 0; 251 for (MyEvent event : mEventQueue) { 252 Log.d(TAG, " " + event + ": scheduled at " 253 + showTime(event.mTriggerTime) + ": last at " 254 + showTime(event.mLastTriggerTime)); 255 if (++count >= 5) break; 256 } 257 if (mEventQueue.size() > count) { 258 Log.d(TAG, " ....."); 259 } else if (count == 0) { 260 Log.d(TAG, " <empty>"); 261 } 262 } 263 264 private void execute(long triggerTime) { 265 if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " 266 + showTime(triggerTime) + ": " + mEventQueue.size()); 267 if (stopped() || mEventQueue.isEmpty()) return; 268 269 for (MyEvent event : mEventQueue) { 270 if (event.mTriggerTime != triggerTime) continue; 271 if (DEBUG_TIMER) Log.d(TAG, "execute " + event); 272 273 event.mLastTriggerTime = triggerTime; 274 event.mTriggerTime += event.mPeriod; 275 276 // run the callback in the handler thread to prevent deadlock 277 mExecutor.execute(event.mCallback); 278 } 279 if (DEBUG_TIMER) { 280 Log.d(TAG, "after timeout execution"); 281 printQueue(); 282 } 283 scheduleNext(); 284 } 285 286 private String getAction() { 287 return toString(); 288 } 289 290 private String showTime(long time) { 291 int ms = (int) (time % 1000); 292 int s = (int) (time / 1000); 293 int m = s / 60; 294 s %= 60; 295 return String.format("%d.%d.%d", m, s, ms); 296 } 297 298 private static class MyEvent { 299 int mPeriod; 300 int mMaxPeriod; 301 long mTriggerTime; 302 long mLastTriggerTime; 303 Runnable mCallback; 304 305 MyEvent(int period, Runnable callback, long now) { 306 mPeriod = mMaxPeriod = period; 307 mCallback = callback; 308 mLastTriggerTime = now; 309 } 310 311 @Override 312 public String toString() { 313 String s = super.toString(); 314 s = s.substring(s.indexOf("@")); 315 return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":" 316 + toString(mCallback); 317 } 318 319 private String toString(Object o) { 320 String s = o.toString(); 321 int index = s.indexOf("$"); 322 if (index > 0) s = s.substring(index + 1); 323 return s; 324 } 325 } 326 327 // Sort the events by mMaxPeriod so that the first event can be used to 328 // align events with larger periods 329 private static class MyEventComparator implements Comparator<MyEvent> { 330 public int compare(MyEvent e1, MyEvent e2) { 331 if (e1 == e2) return 0; 332 int diff = e1.mMaxPeriod - e2.mMaxPeriod; 333 if (diff == 0) diff = -1; 334 return diff; 335 } 336 337 public boolean equals(Object that) { 338 return (this == that); 339 } 340 } 341 } 342