Home | History | Annotate | Download | only in sip
      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 
     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