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.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