Home | History | Annotate | Download | only in sip
      1 /*
      2  * Copyright (C) 2010, 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.net.ConnectivityManager;
     26 import android.net.NetworkInfo;
     27 import android.net.sip.ISipService;
     28 import android.net.sip.ISipSession;
     29 import android.net.sip.ISipSessionListener;
     30 import android.net.sip.SipErrorCode;
     31 import android.net.sip.SipManager;
     32 import android.net.sip.SipProfile;
     33 import android.net.sip.SipSession;
     34 import android.net.sip.SipSessionAdapter;
     35 import android.net.wifi.WifiManager;
     36 import android.os.Binder;
     37 import android.os.Bundle;
     38 import android.os.Handler;
     39 import android.os.HandlerThread;
     40 import android.os.Looper;
     41 import android.os.Message;
     42 import android.os.PowerManager;
     43 import android.os.Process;
     44 import android.os.RemoteException;
     45 import android.os.ServiceManager;
     46 import android.os.SystemClock;
     47 import android.text.TextUtils;
     48 import android.util.Log;
     49 
     50 import java.io.IOException;
     51 import java.net.DatagramSocket;
     52 import java.net.InetAddress;
     53 import java.net.UnknownHostException;
     54 import java.util.ArrayList;
     55 import java.util.Collection;
     56 import java.util.Comparator;
     57 import java.util.HashMap;
     58 import java.util.Iterator;
     59 import java.util.Map;
     60 import java.util.Timer;
     61 import java.util.TimerTask;
     62 import java.util.TreeSet;
     63 import javax.sip.SipException;
     64 
     65 /**
     66  * @hide
     67  */
     68 public final class SipService extends ISipService.Stub {
     69     static final String TAG = "SipService";
     70     static final boolean DEBUGV = false;
     71     private static final boolean DEBUG = false;
     72     private static final boolean DEBUG_TIMER = DEBUG && false;
     73     private static final int EXPIRY_TIME = 3600;
     74     private static final int SHORT_EXPIRY_TIME = 10;
     75     private static final int MIN_EXPIRY_TIME = 60;
     76 
     77     private Context mContext;
     78     private String mLocalIp;
     79     private String mNetworkType;
     80     private boolean mConnected;
     81     private WakeupTimer mTimer;
     82     private WifiScanProcess mWifiScanProcess;
     83     private WifiManager.WifiLock mWifiLock;
     84     private boolean mWifiOnly;
     85 
     86     private MyExecutor mExecutor;
     87 
     88     // SipProfile URI --> group
     89     private Map<String, SipSessionGroupExt> mSipGroups =
     90             new HashMap<String, SipSessionGroupExt>();
     91 
     92     // session ID --> session
     93     private Map<String, ISipSession> mPendingSessions =
     94             new HashMap<String, ISipSession>();
     95 
     96     private ConnectivityReceiver mConnectivityReceiver;
     97     private boolean mWifiEnabled;
     98     private SipWakeLock mMyWakeLock;
     99 
    100     /**
    101      * Starts the SIP service. Do nothing if the SIP API is not supported on the
    102      * device.
    103      */
    104     public static void start(Context context) {
    105         if (SipManager.isApiSupported(context)) {
    106             ServiceManager.addService("sip", new SipService(context));
    107             context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP));
    108             if (DEBUG) Log.d(TAG, "SIP service started");
    109         }
    110     }
    111 
    112     private SipService(Context context) {
    113         if (DEBUG) Log.d(TAG, " service started!");
    114         mContext = context;
    115         mConnectivityReceiver = new ConnectivityReceiver();
    116         mMyWakeLock = new SipWakeLock((PowerManager)
    117                 context.getSystemService(Context.POWER_SERVICE));
    118 
    119         mTimer = new WakeupTimer(context);
    120         mWifiOnly = SipManager.isSipWifiOnly(context);
    121     }
    122 
    123     private BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() {
    124         @Override
    125         public void onReceive(Context context, Intent intent) {
    126             String action = intent.getAction();
    127             if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
    128                 int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
    129                         WifiManager.WIFI_STATE_UNKNOWN);
    130                 synchronized (SipService.this) {
    131                     switch (state) {
    132                         case WifiManager.WIFI_STATE_ENABLED:
    133                             mWifiEnabled = true;
    134                             if (anyOpenedToReceiveCalls()) grabWifiLock();
    135                             break;
    136                         case WifiManager.WIFI_STATE_DISABLED:
    137                             mWifiEnabled = false;
    138                             releaseWifiLock();
    139                             break;
    140                     }
    141                 }
    142             }
    143         }
    144     };
    145 
    146     private void registerReceivers() {
    147         mContext.registerReceiver(mConnectivityReceiver,
    148                 new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
    149         mContext.registerReceiver(mWifiStateReceiver,
    150                 new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));
    151         if (DEBUG) Log.d(TAG, " +++ register receivers");
    152     }
    153 
    154     private void unregisterReceivers() {
    155         mContext.unregisterReceiver(mConnectivityReceiver);
    156         mContext.unregisterReceiver(mWifiStateReceiver);
    157         if (DEBUG) Log.d(TAG, " --- unregister receivers");
    158     }
    159 
    160     private MyExecutor getExecutor() {
    161         // create mExecutor lazily
    162         if (mExecutor == null) mExecutor = new MyExecutor();
    163         return mExecutor;
    164     }
    165 
    166     public synchronized SipProfile[] getListOfProfiles() {
    167         mContext.enforceCallingOrSelfPermission(
    168                 android.Manifest.permission.USE_SIP, null);
    169         boolean isCallerRadio = isCallerRadio();
    170         ArrayList<SipProfile> profiles = new ArrayList<SipProfile>();
    171         for (SipSessionGroupExt group : mSipGroups.values()) {
    172             if (isCallerRadio || isCallerCreator(group)) {
    173                 profiles.add(group.getLocalProfile());
    174             }
    175         }
    176         return profiles.toArray(new SipProfile[profiles.size()]);
    177     }
    178 
    179     public synchronized void open(SipProfile localProfile) {
    180         mContext.enforceCallingOrSelfPermission(
    181                 android.Manifest.permission.USE_SIP, null);
    182         localProfile.setCallingUid(Binder.getCallingUid());
    183         try {
    184             boolean addingFirstProfile = mSipGroups.isEmpty();
    185             createGroup(localProfile);
    186             if (addingFirstProfile && !mSipGroups.isEmpty()) registerReceivers();
    187         } catch (SipException e) {
    188             Log.e(TAG, "openToMakeCalls()", e);
    189             // TODO: how to send the exception back
    190         }
    191     }
    192 
    193     public synchronized void open3(SipProfile localProfile,
    194             PendingIntent incomingCallPendingIntent,
    195             ISipSessionListener listener) {
    196         mContext.enforceCallingOrSelfPermission(
    197                 android.Manifest.permission.USE_SIP, null);
    198         localProfile.setCallingUid(Binder.getCallingUid());
    199         if (incomingCallPendingIntent == null) {
    200             Log.w(TAG, "incomingCallPendingIntent cannot be null; "
    201                     + "the profile is not opened");
    202             return;
    203         }
    204         if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": "
    205                 + incomingCallPendingIntent + ": " + listener);
    206         try {
    207             boolean addingFirstProfile = mSipGroups.isEmpty();
    208             SipSessionGroupExt group = createGroup(localProfile,
    209                     incomingCallPendingIntent, listener);
    210             if (addingFirstProfile && !mSipGroups.isEmpty()) registerReceivers();
    211             if (localProfile.getAutoRegistration()) {
    212                 group.openToReceiveCalls();
    213                 if (mWifiEnabled) grabWifiLock();
    214             }
    215         } catch (SipException e) {
    216             Log.e(TAG, "openToReceiveCalls()", e);
    217             // TODO: how to send the exception back
    218         }
    219     }
    220 
    221     private boolean isCallerCreator(SipSessionGroupExt group) {
    222         SipProfile profile = group.getLocalProfile();
    223         return (profile.getCallingUid() == Binder.getCallingUid());
    224     }
    225 
    226     private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) {
    227         return (isCallerRadio() || isCallerCreator(group));
    228     }
    229 
    230     private boolean isCallerRadio() {
    231         return (Binder.getCallingUid() == Process.PHONE_UID);
    232     }
    233 
    234     public synchronized void close(String localProfileUri) {
    235         mContext.enforceCallingOrSelfPermission(
    236                 android.Manifest.permission.USE_SIP, null);
    237         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
    238         if (group == null) return;
    239         if (!isCallerCreatorOrRadio(group)) {
    240             Log.w(TAG, "only creator or radio can close this profile");
    241             return;
    242         }
    243 
    244         group = mSipGroups.remove(localProfileUri);
    245         notifyProfileRemoved(group.getLocalProfile());
    246         group.close();
    247 
    248         if (!anyOpenedToReceiveCalls()) {
    249             releaseWifiLock();
    250             mMyWakeLock.reset(); // in case there's leak
    251         }
    252         if (mSipGroups.isEmpty()) unregisterReceivers();
    253     }
    254 
    255     public synchronized boolean isOpened(String localProfileUri) {
    256         mContext.enforceCallingOrSelfPermission(
    257                 android.Manifest.permission.USE_SIP, null);
    258         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
    259         if (group == null) return false;
    260         if (isCallerCreatorOrRadio(group)) {
    261             return true;
    262         } else {
    263             Log.w(TAG, "only creator or radio can query on the profile");
    264             return false;
    265         }
    266     }
    267 
    268     public synchronized boolean isRegistered(String localProfileUri) {
    269         mContext.enforceCallingOrSelfPermission(
    270                 android.Manifest.permission.USE_SIP, null);
    271         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
    272         if (group == null) return false;
    273         if (isCallerCreatorOrRadio(group)) {
    274             return group.isRegistered();
    275         } else {
    276             Log.w(TAG, "only creator or radio can query on the profile");
    277             return false;
    278         }
    279     }
    280 
    281     public synchronized void setRegistrationListener(String localProfileUri,
    282             ISipSessionListener listener) {
    283         mContext.enforceCallingOrSelfPermission(
    284                 android.Manifest.permission.USE_SIP, null);
    285         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
    286         if (group == null) return;
    287         if (isCallerCreator(group)) {
    288             group.setListener(listener);
    289         } else {
    290             Log.w(TAG, "only creator can set listener on the profile");
    291         }
    292     }
    293 
    294     public synchronized ISipSession createSession(SipProfile localProfile,
    295             ISipSessionListener listener) {
    296         mContext.enforceCallingOrSelfPermission(
    297                 android.Manifest.permission.USE_SIP, null);
    298         localProfile.setCallingUid(Binder.getCallingUid());
    299         if (!mConnected) return null;
    300         try {
    301             SipSessionGroupExt group = createGroup(localProfile);
    302             return group.createSession(listener);
    303         } catch (SipException e) {
    304             if (DEBUG) Log.d(TAG, "createSession()", e);
    305             return null;
    306         }
    307     }
    308 
    309     public synchronized ISipSession getPendingSession(String callId) {
    310         mContext.enforceCallingOrSelfPermission(
    311                 android.Manifest.permission.USE_SIP, null);
    312         if (callId == null) return null;
    313         return mPendingSessions.get(callId);
    314     }
    315 
    316     private String determineLocalIp() {
    317         try {
    318             DatagramSocket s = new DatagramSocket();
    319             s.connect(InetAddress.getByName("192.168.1.1"), 80);
    320             return s.getLocalAddress().getHostAddress();
    321         } catch (IOException e) {
    322             if (DEBUG) Log.d(TAG, "determineLocalIp()", e);
    323             // dont do anything; there should be a connectivity change going
    324             return null;
    325         }
    326     }
    327 
    328     private SipSessionGroupExt createGroup(SipProfile localProfile)
    329             throws SipException {
    330         String key = localProfile.getUriString();
    331         SipSessionGroupExt group = mSipGroups.get(key);
    332         if (group == null) {
    333             group = new SipSessionGroupExt(localProfile, null, null);
    334             mSipGroups.put(key, group);
    335             notifyProfileAdded(localProfile);
    336         } else if (!isCallerCreator(group)) {
    337             throw new SipException("only creator can access the profile");
    338         }
    339         return group;
    340     }
    341 
    342     private SipSessionGroupExt createGroup(SipProfile localProfile,
    343             PendingIntent incomingCallPendingIntent,
    344             ISipSessionListener listener) throws SipException {
    345         String key = localProfile.getUriString();
    346         SipSessionGroupExt group = mSipGroups.get(key);
    347         if (group != null) {
    348             if (!isCallerCreator(group)) {
    349                 throw new SipException("only creator can access the profile");
    350             }
    351             group.setIncomingCallPendingIntent(incomingCallPendingIntent);
    352             group.setListener(listener);
    353         } else {
    354             group = new SipSessionGroupExt(localProfile,
    355                     incomingCallPendingIntent, listener);
    356             mSipGroups.put(key, group);
    357             notifyProfileAdded(localProfile);
    358         }
    359         return group;
    360     }
    361 
    362     private void notifyProfileAdded(SipProfile localProfile) {
    363         if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile);
    364         Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE);
    365         intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
    366         mContext.sendBroadcast(intent);
    367     }
    368 
    369     private void notifyProfileRemoved(SipProfile localProfile) {
    370         if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile);
    371         Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE);
    372         intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
    373         mContext.sendBroadcast(intent);
    374     }
    375 
    376     private boolean anyOpenedToReceiveCalls() {
    377         for (SipSessionGroupExt group : mSipGroups.values()) {
    378             if (group.isOpenedToReceiveCalls()) return true;
    379         }
    380         return false;
    381     }
    382 
    383     private void grabWifiLock() {
    384         if (mWifiLock == null) {
    385             if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ acquire wifi lock");
    386             mWifiLock = ((WifiManager)
    387                     mContext.getSystemService(Context.WIFI_SERVICE))
    388                     .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
    389             mWifiLock.acquire();
    390             if (!mConnected) startWifiScanner();
    391         }
    392     }
    393 
    394     private void releaseWifiLock() {
    395         if (mWifiLock != null) {
    396             if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ release wifi lock");
    397             mWifiLock.release();
    398             mWifiLock = null;
    399             stopWifiScanner();
    400         }
    401     }
    402 
    403     private synchronized void startWifiScanner() {
    404         if (mWifiScanProcess == null) {
    405             mWifiScanProcess = new WifiScanProcess();
    406         }
    407         mWifiScanProcess.start();
    408     }
    409 
    410     private synchronized void stopWifiScanner() {
    411         if (mWifiScanProcess != null) {
    412             mWifiScanProcess.stop();
    413         }
    414     }
    415 
    416     private synchronized void onConnectivityChanged(
    417             String type, boolean connected) {
    418         if (DEBUG) Log.d(TAG, "onConnectivityChanged(): "
    419                 + mNetworkType + (mConnected? " CONNECTED" : " DISCONNECTED")
    420                 + " --> " + type + (connected? " CONNECTED" : " DISCONNECTED"));
    421 
    422         boolean sameType = type.equals(mNetworkType);
    423         if (!sameType && !connected) return;
    424 
    425         boolean wasWifi = "WIFI".equalsIgnoreCase(mNetworkType);
    426         boolean isWifi = "WIFI".equalsIgnoreCase(type);
    427         boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType);
    428         boolean wifiOn = isWifi && connected;
    429 
    430         try {
    431             boolean wasConnected = mConnected;
    432             mNetworkType = type;
    433             mConnected = connected;
    434 
    435             if (wasConnected) {
    436                 mLocalIp = null;
    437                 for (SipSessionGroupExt group : mSipGroups.values()) {
    438                     group.onConnectivityChanged(false);
    439                 }
    440             }
    441 
    442             if (connected) {
    443                 mLocalIp = determineLocalIp();
    444                 for (SipSessionGroupExt group : mSipGroups.values()) {
    445                     group.onConnectivityChanged(true);
    446                 }
    447                 if (isWifi && (mWifiLock != null)) stopWifiScanner();
    448             } else {
    449                 mMyWakeLock.reset(); // in case there's a leak
    450                 if (isWifi && (mWifiLock != null)) startWifiScanner();
    451             }
    452         } catch (SipException e) {
    453             Log.e(TAG, "onConnectivityChanged()", e);
    454         }
    455     }
    456 
    457     private synchronized void addPendingSession(ISipSession session) {
    458         try {
    459             cleanUpPendingSessions();
    460             mPendingSessions.put(session.getCallId(), session);
    461             if (DEBUG) Log.d(TAG, "#pending sess=" + mPendingSessions.size());
    462         } catch (RemoteException e) {
    463             // should not happen with a local call
    464             Log.e(TAG, "addPendingSession()", e);
    465         }
    466     }
    467 
    468     private void cleanUpPendingSessions() throws RemoteException {
    469         Map.Entry<String, ISipSession>[] entries =
    470                 mPendingSessions.entrySet().toArray(
    471                 new Map.Entry[mPendingSessions.size()]);
    472         for (Map.Entry<String, ISipSession> entry : entries) {
    473             if (entry.getValue().getState() != SipSession.State.INCOMING_CALL) {
    474                 mPendingSessions.remove(entry.getKey());
    475             }
    476         }
    477     }
    478 
    479     private synchronized boolean callingSelf(SipSessionGroupExt ringingGroup,
    480             SipSessionGroup.SipSessionImpl ringingSession) {
    481         String callId = ringingSession.getCallId();
    482         for (SipSessionGroupExt group : mSipGroups.values()) {
    483             if ((group != ringingGroup) && group.containsSession(callId)) {
    484                 if (DEBUG) Log.d(TAG, "call self: "
    485                         + ringingSession.getLocalProfile().getUriString()
    486                         + " -> " + group.getLocalProfile().getUriString());
    487                 return true;
    488             }
    489         }
    490         return false;
    491     }
    492 
    493 
    494     private class SipSessionGroupExt extends SipSessionAdapter {
    495         private SipSessionGroup mSipGroup;
    496         private PendingIntent mIncomingCallPendingIntent;
    497         private boolean mOpenedToReceiveCalls;
    498 
    499         private AutoRegistrationProcess mAutoRegistration =
    500                 new AutoRegistrationProcess();
    501 
    502         public SipSessionGroupExt(SipProfile localProfile,
    503                 PendingIntent incomingCallPendingIntent,
    504                 ISipSessionListener listener) throws SipException {
    505             String password = localProfile.getPassword();
    506             SipProfile p = duplicate(localProfile);
    507             mSipGroup = createSipSessionGroup(mLocalIp, p, password);
    508             mIncomingCallPendingIntent = incomingCallPendingIntent;
    509             mAutoRegistration.setListener(listener);
    510         }
    511 
    512         public SipProfile getLocalProfile() {
    513             return mSipGroup.getLocalProfile();
    514         }
    515 
    516         public boolean containsSession(String callId) {
    517             return mSipGroup.containsSession(callId);
    518         }
    519 
    520         // network connectivity is tricky because network can be disconnected
    521         // at any instant so need to deal with exceptions carefully even when
    522         // you think you are connected
    523         private SipSessionGroup createSipSessionGroup(String localIp,
    524                 SipProfile localProfile, String password) throws SipException {
    525             try {
    526                 return new SipSessionGroup(localIp, localProfile, password,
    527                         mMyWakeLock);
    528             } catch (IOException e) {
    529                 // network disconnected
    530                 Log.w(TAG, "createSipSessionGroup(): network disconnected?");
    531                 if (localIp != null) {
    532                     return createSipSessionGroup(null, localProfile, password);
    533                 } else {
    534                     // recursive
    535                     Log.wtf(TAG, "impossible! recursive!");
    536                     throw new RuntimeException("createSipSessionGroup");
    537                 }
    538             }
    539         }
    540 
    541         private SipProfile duplicate(SipProfile p) {
    542             try {
    543                 return new SipProfile.Builder(p).setPassword("*").build();
    544             } catch (Exception e) {
    545                 Log.wtf(TAG, "duplicate()", e);
    546                 throw new RuntimeException("duplicate profile", e);
    547             }
    548         }
    549 
    550         public void setListener(ISipSessionListener listener) {
    551             mAutoRegistration.setListener(listener);
    552         }
    553 
    554         public void setIncomingCallPendingIntent(PendingIntent pIntent) {
    555             mIncomingCallPendingIntent = pIntent;
    556         }
    557 
    558         public void openToReceiveCalls() throws SipException {
    559             mOpenedToReceiveCalls = true;
    560             if (mConnected) {
    561                 mSipGroup.openToReceiveCalls(this);
    562                 mAutoRegistration.start(mSipGroup);
    563             }
    564             if (DEBUG) Log.d(TAG, "  openToReceiveCalls: " + getUri() + ": "
    565                     + mIncomingCallPendingIntent);
    566         }
    567 
    568         public void onConnectivityChanged(boolean connected)
    569                 throws SipException {
    570             mSipGroup.onConnectivityChanged();
    571             if (connected) {
    572                 resetGroup(mLocalIp);
    573                 if (mOpenedToReceiveCalls) openToReceiveCalls();
    574             } else {
    575                 // close mSipGroup but remember mOpenedToReceiveCalls
    576                 if (DEBUG) Log.d(TAG, "  close auto reg temporarily: "
    577                         + getUri() + ": " + mIncomingCallPendingIntent);
    578                 mSipGroup.close();
    579                 mAutoRegistration.stop();
    580             }
    581         }
    582 
    583         private void resetGroup(String localIp) throws SipException {
    584             try {
    585                 mSipGroup.reset(localIp);
    586             } catch (IOException e) {
    587                 // network disconnected
    588                 Log.w(TAG, "resetGroup(): network disconnected?");
    589                 if (localIp != null) {
    590                     resetGroup(null); // reset w/o local IP
    591                 } else {
    592                     // recursive
    593                     Log.wtf(TAG, "impossible!");
    594                     throw new RuntimeException("resetGroup");
    595                 }
    596             }
    597         }
    598 
    599         public void close() {
    600             mOpenedToReceiveCalls = false;
    601             mSipGroup.close();
    602             mAutoRegistration.stop();
    603             if (DEBUG) Log.d(TAG, "   close: " + getUri() + ": "
    604                     + mIncomingCallPendingIntent);
    605         }
    606 
    607         public ISipSession createSession(ISipSessionListener listener) {
    608             return mSipGroup.createSession(listener);
    609         }
    610 
    611         @Override
    612         public void onRinging(ISipSession s, SipProfile caller,
    613                 String sessionDescription) {
    614             if (DEBUGV) Log.d(TAG, "<<<<< onRinging()");
    615             SipSessionGroup.SipSessionImpl session =
    616                     (SipSessionGroup.SipSessionImpl) s;
    617             synchronized (SipService.this) {
    618                 try {
    619                     if (!isRegistered() || callingSelf(this, session)) {
    620                         session.endCall();
    621                         return;
    622                     }
    623 
    624                     // send out incoming call broadcast
    625                     addPendingSession(session);
    626                     Intent intent = SipManager.createIncomingCallBroadcast(
    627                             session.getCallId(), sessionDescription);
    628                     if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": "
    629                             + caller.getUri() + ": " + session.getCallId()
    630                             + " " + mIncomingCallPendingIntent);
    631                     mIncomingCallPendingIntent.send(mContext,
    632                             SipManager.INCOMING_CALL_RESULT_CODE, intent);
    633                 } catch (PendingIntent.CanceledException e) {
    634                     Log.w(TAG, "pendingIntent is canceled, drop incoming call");
    635                     session.endCall();
    636                 }
    637             }
    638         }
    639 
    640         @Override
    641         public void onError(ISipSession session, int errorCode,
    642                 String message) {
    643             if (DEBUG) Log.d(TAG, "sip session error: "
    644                     + SipErrorCode.toString(errorCode) + ": " + message);
    645         }
    646 
    647         public boolean isOpenedToReceiveCalls() {
    648             return mOpenedToReceiveCalls;
    649         }
    650 
    651         public boolean isRegistered() {
    652             return mAutoRegistration.isRegistered();
    653         }
    654 
    655         private String getUri() {
    656             return mSipGroup.getLocalProfileUri();
    657         }
    658     }
    659 
    660     private class WifiScanProcess implements Runnable {
    661         private static final String TAG = "\\WIFI_SCAN/";
    662         private static final int INTERVAL = 60;
    663         private boolean mRunning = false;
    664 
    665         private WifiManager mWifiManager;
    666 
    667         public void start() {
    668             if (mRunning) return;
    669             mRunning = true;
    670             mTimer.set(INTERVAL * 1000, this);
    671         }
    672 
    673         WifiScanProcess() {
    674             mWifiManager = (WifiManager)
    675                     mContext.getSystemService(Context.WIFI_SERVICE);
    676         }
    677 
    678         public void run() {
    679             // scan and associate now
    680             if (DEBUGV) Log.v(TAG, "just wake up here for wifi scanning...");
    681             mWifiManager.startScanActive();
    682         }
    683 
    684         public void stop() {
    685             mRunning = false;
    686             mTimer.cancel(this);
    687         }
    688     }
    689 
    690     // KeepAliveProcess is controlled by AutoRegistrationProcess.
    691     // All methods will be invoked in sync with SipService.this.
    692     private class KeepAliveProcess implements Runnable {
    693         private static final String TAG = "\\KEEPALIVE/";
    694         private static final int INTERVAL = 10;
    695         private SipSessionGroup.SipSessionImpl mSession;
    696         private boolean mRunning = false;
    697 
    698         public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
    699             mSession = session;
    700         }
    701 
    702         public void start() {
    703             if (mRunning) return;
    704             mRunning = true;
    705             mTimer.set(INTERVAL * 1000, this);
    706         }
    707 
    708         // timeout handler
    709         public void run() {
    710             synchronized (SipService.this) {
    711                 if (!mRunning) return;
    712 
    713                 if (DEBUGV) Log.v(TAG, "~~~ keepalive: "
    714                         + mSession.getLocalProfile().getUriString());
    715                 SipSessionGroup.SipSessionImpl session = mSession.duplicate();
    716                 try {
    717                     session.sendKeepAlive();
    718                     if (session.isReRegisterRequired()) {
    719                         // Acquire wake lock for the registration process. The
    720                         // lock will be released when registration is complete.
    721                         mMyWakeLock.acquire(mSession);
    722                         mSession.register(EXPIRY_TIME);
    723                     }
    724                 } catch (Throwable t) {
    725                     Log.w(TAG, "keepalive error: " + t);
    726                 }
    727             }
    728         }
    729 
    730         public void stop() {
    731             if (DEBUGV && (mSession != null)) Log.v(TAG, "stop keepalive:"
    732                     + mSession.getLocalProfile().getUriString());
    733             mRunning = false;
    734             mSession = null;
    735             mTimer.cancel(this);
    736         }
    737     }
    738 
    739     private class AutoRegistrationProcess extends SipSessionAdapter
    740             implements Runnable {
    741         private SipSessionGroup.SipSessionImpl mSession;
    742         private SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
    743         private KeepAliveProcess mKeepAliveProcess;
    744         private int mBackoff = 1;
    745         private boolean mRegistered;
    746         private long mExpiryTime;
    747         private int mErrorCode;
    748         private String mErrorMessage;
    749         private boolean mRunning = false;
    750 
    751         private String getAction() {
    752             return toString();
    753         }
    754 
    755         public void start(SipSessionGroup group) {
    756             if (!mRunning) {
    757                 mRunning = true;
    758                 mBackoff = 1;
    759                 mSession = (SipSessionGroup.SipSessionImpl)
    760                         group.createSession(this);
    761                 // return right away if no active network connection.
    762                 if (mSession == null) return;
    763 
    764                 // start unregistration to clear up old registration at server
    765                 // TODO: when rfc5626 is deployed, use reg-id and sip.instance
    766                 // in registration to avoid adding duplicate entries to server
    767                 mMyWakeLock.acquire(mSession);
    768                 mSession.unregister();
    769                 if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for "
    770                         + mSession.getLocalProfile().getUriString());
    771             }
    772         }
    773 
    774         public void stop() {
    775             if (!mRunning) return;
    776             mRunning = false;
    777             mMyWakeLock.release(mSession);
    778             if (mSession != null) {
    779                 mSession.setListener(null);
    780                 if (mConnected && mRegistered) mSession.unregister();
    781             }
    782 
    783             mTimer.cancel(this);
    784             if (mKeepAliveProcess != null) {
    785                 mKeepAliveProcess.stop();
    786                 mKeepAliveProcess = null;
    787             }
    788 
    789             mRegistered = false;
    790             setListener(mProxy.getListener());
    791         }
    792 
    793         public void setListener(ISipSessionListener listener) {
    794             synchronized (SipService.this) {
    795                 mProxy.setListener(listener);
    796 
    797                 try {
    798                     int state = (mSession == null)
    799                             ? SipSession.State.READY_TO_CALL
    800                             : mSession.getState();
    801                     if ((state == SipSession.State.REGISTERING)
    802                             || (state == SipSession.State.DEREGISTERING)) {
    803                         mProxy.onRegistering(mSession);
    804                     } else if (mRegistered) {
    805                         int duration = (int)
    806                                 (mExpiryTime - SystemClock.elapsedRealtime());
    807                         mProxy.onRegistrationDone(mSession, duration);
    808                     } else if (mErrorCode != SipErrorCode.NO_ERROR) {
    809                         if (mErrorCode == SipErrorCode.TIME_OUT) {
    810                             mProxy.onRegistrationTimeout(mSession);
    811                         } else {
    812                             mProxy.onRegistrationFailed(mSession, mErrorCode,
    813                                     mErrorMessage);
    814                         }
    815                     } else if (!mConnected) {
    816                         mProxy.onRegistrationFailed(mSession,
    817                                 SipErrorCode.DATA_CONNECTION_LOST,
    818                                 "no data connection");
    819                     } else if (!mRunning) {
    820                         mProxy.onRegistrationFailed(mSession,
    821                                 SipErrorCode.CLIENT_ERROR,
    822                                 "registration not running");
    823                     } else {
    824                         mProxy.onRegistrationFailed(mSession,
    825                                 SipErrorCode.IN_PROGRESS,
    826                                 String.valueOf(state));
    827                     }
    828                 } catch (Throwable t) {
    829                     Log.w(TAG, "setListener(): " + t);
    830                 }
    831             }
    832         }
    833 
    834         public boolean isRegistered() {
    835             return mRegistered;
    836         }
    837 
    838         // timeout handler: re-register
    839         public void run() {
    840             synchronized (SipService.this) {
    841                 if (!mRunning) return;
    842 
    843                 mErrorCode = SipErrorCode.NO_ERROR;
    844                 mErrorMessage = null;
    845                 if (DEBUG) Log.d(TAG, "~~~ registering");
    846                 if (mConnected) {
    847                     mMyWakeLock.acquire(mSession);
    848                     mSession.register(EXPIRY_TIME);
    849                 }
    850             }
    851         }
    852 
    853         private boolean isBehindNAT(String address) {
    854             try {
    855                 byte[] d = InetAddress.getByName(address).getAddress();
    856                 if ((d[0] == 10) ||
    857                         (((0x000000FF & ((int)d[0])) == 172) &&
    858                         ((0x000000F0 & ((int)d[1])) == 16)) ||
    859                         (((0x000000FF & ((int)d[0])) == 192) &&
    860                         ((0x000000FF & ((int)d[1])) == 168))) {
    861                     return true;
    862                 }
    863             } catch (UnknownHostException e) {
    864                 Log.e(TAG, "isBehindAT()" + address, e);
    865             }
    866             return false;
    867         }
    868 
    869         private void restart(int duration) {
    870             if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later.");
    871             mTimer.cancel(this);
    872             mTimer.set(duration * 1000, this);
    873         }
    874 
    875         private int backoffDuration() {
    876             int duration = SHORT_EXPIRY_TIME * mBackoff;
    877             if (duration > 3600) {
    878                 duration = 3600;
    879             } else {
    880                 mBackoff *= 2;
    881             }
    882             return duration;
    883         }
    884 
    885         @Override
    886         public void onRegistering(ISipSession session) {
    887             if (DEBUG) Log.d(TAG, "onRegistering(): " + session);
    888             synchronized (SipService.this) {
    889                 if (notCurrentSession(session)) return;
    890 
    891                 mRegistered = false;
    892                 mProxy.onRegistering(session);
    893             }
    894         }
    895 
    896         private boolean notCurrentSession(ISipSession session) {
    897             if (session != mSession) {
    898                 ((SipSessionGroup.SipSessionImpl) session).setListener(null);
    899                 mMyWakeLock.release(session);
    900                 return true;
    901             }
    902             return !mRunning;
    903         }
    904 
    905         @Override
    906         public void onRegistrationDone(ISipSession session, int duration) {
    907             if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
    908             synchronized (SipService.this) {
    909                 if (notCurrentSession(session)) return;
    910 
    911                 mProxy.onRegistrationDone(session, duration);
    912 
    913                 if (duration > 0) {
    914                     mSession.clearReRegisterRequired();
    915                     mExpiryTime = SystemClock.elapsedRealtime()
    916                             + (duration * 1000);
    917 
    918                     if (!mRegistered) {
    919                         mRegistered = true;
    920                         // allow some overlap to avoid call drop during renew
    921                         duration -= MIN_EXPIRY_TIME;
    922                         if (duration < MIN_EXPIRY_TIME) {
    923                             duration = MIN_EXPIRY_TIME;
    924                         }
    925                         restart(duration);
    926 
    927                         if (isBehindNAT(mLocalIp) ||
    928                                 mSession.getLocalProfile().getSendKeepAlive()) {
    929                             if (mKeepAliveProcess == null) {
    930                                 mKeepAliveProcess =
    931                                         new KeepAliveProcess(mSession);
    932                             }
    933                             mKeepAliveProcess.start();
    934                         }
    935                     }
    936                     mMyWakeLock.release(session);
    937                 } else {
    938                     mRegistered = false;
    939                     mExpiryTime = -1L;
    940                     if (DEBUG) Log.d(TAG, "Refresh registration immediately");
    941                     run();
    942                 }
    943             }
    944         }
    945 
    946         @Override
    947         public void onRegistrationFailed(ISipSession session, int errorCode,
    948                 String message) {
    949             if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": "
    950                     + SipErrorCode.toString(errorCode) + ": " + message);
    951             synchronized (SipService.this) {
    952                 if (notCurrentSession(session)) return;
    953 
    954                 switch (errorCode) {
    955                     case SipErrorCode.INVALID_CREDENTIALS:
    956                     case SipErrorCode.SERVER_UNREACHABLE:
    957                         if (DEBUG) Log.d(TAG, "   pause auto-registration");
    958                         stop();
    959                         break;
    960                     default:
    961                         restartLater();
    962                 }
    963 
    964                 mErrorCode = errorCode;
    965                 mErrorMessage = message;
    966                 mProxy.onRegistrationFailed(session, errorCode, message);
    967                 mMyWakeLock.release(session);
    968             }
    969         }
    970 
    971         @Override
    972         public void onRegistrationTimeout(ISipSession session) {
    973             if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session);
    974             synchronized (SipService.this) {
    975                 if (notCurrentSession(session)) return;
    976 
    977                 mErrorCode = SipErrorCode.TIME_OUT;
    978                 mProxy.onRegistrationTimeout(session);
    979                 restartLater();
    980                 mMyWakeLock.release(session);
    981             }
    982         }
    983 
    984         private void restartLater() {
    985             mRegistered = false;
    986             restart(backoffDuration());
    987             if (mKeepAliveProcess != null) {
    988                 mKeepAliveProcess.stop();
    989                 mKeepAliveProcess = null;
    990             }
    991         }
    992     }
    993 
    994     private class ConnectivityReceiver extends BroadcastReceiver {
    995         private Timer mTimer = new Timer();
    996         private MyTimerTask mTask;
    997 
    998         @Override
    999         public void onReceive(final Context context, final Intent intent) {
   1000             // Run the handler in MyExecutor to be protected by wake lock
   1001             getExecutor().execute(new Runnable() {
   1002                 public void run() {
   1003                     onReceiveInternal(context, intent);
   1004                 }
   1005             });
   1006         }
   1007 
   1008         private void onReceiveInternal(Context context, Intent intent) {
   1009             String action = intent.getAction();
   1010             if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
   1011                 Bundle b = intent.getExtras();
   1012                 if (b != null) {
   1013                     NetworkInfo netInfo = (NetworkInfo)
   1014                             b.get(ConnectivityManager.EXTRA_NETWORK_INFO);
   1015                     String type = netInfo.getTypeName();
   1016                     NetworkInfo.State state = netInfo.getState();
   1017 
   1018                     if (mWifiOnly && (netInfo.getType() !=
   1019                             ConnectivityManager.TYPE_WIFI)) {
   1020                         if (DEBUG) {
   1021                             Log.d(TAG, "Wifi only, other connectivity ignored: "
   1022                                     + type);
   1023                         }
   1024                         return;
   1025                     }
   1026 
   1027                     NetworkInfo activeNetInfo = getActiveNetworkInfo();
   1028                     if (DEBUG) {
   1029                         if (activeNetInfo != null) {
   1030                             Log.d(TAG, "active network: "
   1031                                     + activeNetInfo.getTypeName()
   1032                                     + ((activeNetInfo.getState() == NetworkInfo.State.CONNECTED)
   1033                                             ? " CONNECTED" : " DISCONNECTED"));
   1034                         } else {
   1035                             Log.d(TAG, "active network: null");
   1036                         }
   1037                     }
   1038                     if ((state == NetworkInfo.State.CONNECTED)
   1039                             && (activeNetInfo != null)
   1040                             && (activeNetInfo.getType() != netInfo.getType())) {
   1041                         if (DEBUG) Log.d(TAG, "ignore connect event: " + type
   1042                                 + ", active: " + activeNetInfo.getTypeName());
   1043                         return;
   1044                     }
   1045 
   1046                     if (state == NetworkInfo.State.CONNECTED) {
   1047                         if (DEBUG) Log.d(TAG, "Connectivity alert: CONNECTED " + type);
   1048                         onChanged(type, true);
   1049                     } else if (state == NetworkInfo.State.DISCONNECTED) {
   1050                         if (DEBUG) Log.d(TAG, "Connectivity alert: DISCONNECTED " + type);
   1051                         onChanged(type, false);
   1052                     } else {
   1053                         if (DEBUG) Log.d(TAG, "Connectivity alert not processed: "
   1054                                 + state + " " + type);
   1055                     }
   1056                 }
   1057             }
   1058         }
   1059 
   1060         private NetworkInfo getActiveNetworkInfo() {
   1061             ConnectivityManager cm = (ConnectivityManager)
   1062                     mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
   1063             return cm.getActiveNetworkInfo();
   1064         }
   1065 
   1066         private void onChanged(String type, boolean connected) {
   1067             synchronized (SipService.this) {
   1068                 // When turning on WIFI, it needs some time for network
   1069                 // connectivity to get stabile so we defer good news (because
   1070                 // we want to skip the interim ones) but deliver bad news
   1071                 // immediately
   1072                 if (connected) {
   1073                     if (mTask != null) {
   1074                         mTask.cancel();
   1075                         mMyWakeLock.release(mTask);
   1076                     }
   1077                     mTask = new MyTimerTask(type, connected);
   1078                     mTimer.schedule(mTask, 2 * 1000L);
   1079                     // hold wakup lock so that we can finish changes before the
   1080                     // device goes to sleep
   1081                     mMyWakeLock.acquire(mTask);
   1082                 } else {
   1083                     if ((mTask != null) && mTask.mNetworkType.equals(type)) {
   1084                         mTask.cancel();
   1085                         mMyWakeLock.release(mTask);
   1086                     }
   1087                     onConnectivityChanged(type, false);
   1088                 }
   1089             }
   1090         }
   1091 
   1092         private class MyTimerTask extends TimerTask {
   1093             private boolean mConnected;
   1094             private String mNetworkType;
   1095 
   1096             public MyTimerTask(String type, boolean connected) {
   1097                 mNetworkType = type;
   1098                 mConnected = connected;
   1099             }
   1100 
   1101             // timeout handler
   1102             @Override
   1103             public void run() {
   1104                 // delegate to mExecutor
   1105                 getExecutor().execute(new Runnable() {
   1106                     public void run() {
   1107                         realRun();
   1108                     }
   1109                 });
   1110             }
   1111 
   1112             private void realRun() {
   1113                 synchronized (SipService.this) {
   1114                     if (mTask != this) {
   1115                         Log.w(TAG, "  unexpected task: " + mNetworkType
   1116                                 + (mConnected ? " CONNECTED" : "DISCONNECTED"));
   1117                         mMyWakeLock.release(this);
   1118                         return;
   1119                     }
   1120                     mTask = null;
   1121                     if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType
   1122                             + (mConnected ? " CONNECTED" : "DISCONNECTED"));
   1123                     onConnectivityChanged(mNetworkType, mConnected);
   1124                     mMyWakeLock.release(this);
   1125                 }
   1126             }
   1127         }
   1128     }
   1129 
   1130     /**
   1131      * Timer that can schedule events to occur even when the device is in sleep.
   1132      * Only used internally in this package.
   1133      */
   1134     class WakeupTimer extends BroadcastReceiver {
   1135         private static final String TAG = "_SIP.WkTimer_";
   1136         private static final String TRIGGER_TIME = "TriggerTime";
   1137 
   1138         private Context mContext;
   1139         private AlarmManager mAlarmManager;
   1140 
   1141         // runnable --> time to execute in SystemClock
   1142         private TreeSet<MyEvent> mEventQueue =
   1143                 new TreeSet<MyEvent>(new MyEventComparator());
   1144 
   1145         private PendingIntent mPendingIntent;
   1146 
   1147         public WakeupTimer(Context context) {
   1148             mContext = context;
   1149             mAlarmManager = (AlarmManager)
   1150                     context.getSystemService(Context.ALARM_SERVICE);
   1151 
   1152             IntentFilter filter = new IntentFilter(getAction());
   1153             context.registerReceiver(this, filter);
   1154         }
   1155 
   1156         /**
   1157          * Stops the timer. No event can be scheduled after this method is called.
   1158          */
   1159         public synchronized void stop() {
   1160             mContext.unregisterReceiver(this);
   1161             if (mPendingIntent != null) {
   1162                 mAlarmManager.cancel(mPendingIntent);
   1163                 mPendingIntent = null;
   1164             }
   1165             mEventQueue.clear();
   1166             mEventQueue = null;
   1167         }
   1168 
   1169         private synchronized boolean stopped() {
   1170             if (mEventQueue == null) {
   1171                 Log.w(TAG, "Timer stopped");
   1172                 return true;
   1173             } else {
   1174                 return false;
   1175             }
   1176         }
   1177 
   1178         private void cancelAlarm() {
   1179             mAlarmManager.cancel(mPendingIntent);
   1180             mPendingIntent = null;
   1181         }
   1182 
   1183         private void recalculatePeriods() {
   1184             if (mEventQueue.isEmpty()) return;
   1185 
   1186             MyEvent firstEvent = mEventQueue.first();
   1187             int minPeriod = firstEvent.mMaxPeriod;
   1188             long minTriggerTime = firstEvent.mTriggerTime;
   1189             for (MyEvent e : mEventQueue) {
   1190                 e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod;
   1191                 int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod
   1192                         - minTriggerTime);
   1193                 interval = interval / minPeriod * minPeriod;
   1194                 e.mTriggerTime = minTriggerTime + interval;
   1195             }
   1196             TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(
   1197                     mEventQueue.comparator());
   1198             newQueue.addAll((Collection<MyEvent>) mEventQueue);
   1199             mEventQueue.clear();
   1200             mEventQueue = newQueue;
   1201             if (DEBUG_TIMER) {
   1202                 Log.d(TAG, "queue re-calculated");
   1203                 printQueue();
   1204             }
   1205         }
   1206 
   1207         // Determines the period and the trigger time of the new event and insert it
   1208         // to the queue.
   1209         private void insertEvent(MyEvent event) {
   1210             long now = SystemClock.elapsedRealtime();
   1211             if (mEventQueue.isEmpty()) {
   1212                 event.mTriggerTime = now + event.mPeriod;
   1213                 mEventQueue.add(event);
   1214                 return;
   1215             }
   1216             MyEvent firstEvent = mEventQueue.first();
   1217             int minPeriod = firstEvent.mPeriod;
   1218             if (minPeriod <= event.mMaxPeriod) {
   1219                 event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod;
   1220                 int interval = event.mMaxPeriod;
   1221                 interval -= (int) (firstEvent.mTriggerTime - now);
   1222                 interval = interval / minPeriod * minPeriod;
   1223                 event.mTriggerTime = firstEvent.mTriggerTime + interval;
   1224                 mEventQueue.add(event);
   1225             } else {
   1226                 long triggerTime = now + event.mPeriod;
   1227                 if (firstEvent.mTriggerTime < triggerTime) {
   1228                     event.mTriggerTime = firstEvent.mTriggerTime;
   1229                     event.mLastTriggerTime -= event.mPeriod;
   1230                 } else {
   1231                     event.mTriggerTime = triggerTime;
   1232                 }
   1233                 mEventQueue.add(event);
   1234                 recalculatePeriods();
   1235             }
   1236         }
   1237 
   1238         /**
   1239          * Sets a periodic timer.
   1240          *
   1241          * @param period the timer period; in milli-second
   1242          * @param callback is called back when the timer goes off; the same callback
   1243          *      can be specified in multiple timer events
   1244          */
   1245         public synchronized void set(int period, Runnable callback) {
   1246             if (stopped()) return;
   1247 
   1248             long now = SystemClock.elapsedRealtime();
   1249             MyEvent event = new MyEvent(period, callback, now);
   1250             insertEvent(event);
   1251 
   1252             if (mEventQueue.first() == event) {
   1253                 if (mEventQueue.size() > 1) cancelAlarm();
   1254                 scheduleNext();
   1255             }
   1256 
   1257             long triggerTime = event.mTriggerTime;
   1258             if (DEBUG_TIMER) {
   1259                 Log.d(TAG, " add event " + event + " scheduled at "
   1260                         + showTime(triggerTime) + " at " + showTime(now)
   1261                         + ", #events=" + mEventQueue.size());
   1262                 printQueue();
   1263             }
   1264         }
   1265 
   1266         /**
   1267          * Cancels all the timer events with the specified callback.
   1268          *
   1269          * @param callback the callback
   1270          */
   1271         public synchronized void cancel(Runnable callback) {
   1272             if (stopped() || mEventQueue.isEmpty()) return;
   1273             if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback);
   1274 
   1275             MyEvent firstEvent = mEventQueue.first();
   1276             for (Iterator<MyEvent> iter = mEventQueue.iterator();
   1277                     iter.hasNext();) {
   1278                 MyEvent event = iter.next();
   1279                 if (event.mCallback == callback) {
   1280                     iter.remove();
   1281                     if (DEBUG_TIMER) Log.d(TAG, "    cancel found:" + event);
   1282                 }
   1283             }
   1284             if (mEventQueue.isEmpty()) {
   1285                 cancelAlarm();
   1286             } else if (mEventQueue.first() != firstEvent) {
   1287                 cancelAlarm();
   1288                 firstEvent = mEventQueue.first();
   1289                 firstEvent.mPeriod = firstEvent.mMaxPeriod;
   1290                 firstEvent.mTriggerTime = firstEvent.mLastTriggerTime
   1291                         + firstEvent.mPeriod;
   1292                 recalculatePeriods();
   1293                 scheduleNext();
   1294             }
   1295             if (DEBUG_TIMER) {
   1296                 Log.d(TAG, "after cancel:");
   1297                 printQueue();
   1298             }
   1299         }
   1300 
   1301         private void scheduleNext() {
   1302             if (stopped() || mEventQueue.isEmpty()) return;
   1303 
   1304             if (mPendingIntent != null) {
   1305                 throw new RuntimeException("pendingIntent is not null!");
   1306             }
   1307 
   1308             MyEvent event = mEventQueue.first();
   1309             Intent intent = new Intent(getAction());
   1310             intent.putExtra(TRIGGER_TIME, event.mTriggerTime);
   1311             PendingIntent pendingIntent = mPendingIntent =
   1312                     PendingIntent.getBroadcast(mContext, 0, intent,
   1313                             PendingIntent.FLAG_UPDATE_CURRENT);
   1314             mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
   1315                     event.mTriggerTime, pendingIntent);
   1316         }
   1317 
   1318         @Override
   1319         public void onReceive(Context context, Intent intent) {
   1320             // This callback is already protected by AlarmManager's wake lock.
   1321             String action = intent.getAction();
   1322             if (getAction().equals(action)
   1323                     && intent.getExtras().containsKey(TRIGGER_TIME)) {
   1324                 mPendingIntent = null;
   1325                 long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L);
   1326                 execute(triggerTime);
   1327             } else {
   1328                 Log.d(TAG, "unrecognized intent: " + intent);
   1329             }
   1330         }
   1331 
   1332         private void printQueue() {
   1333             int count = 0;
   1334             for (MyEvent event : mEventQueue) {
   1335                 Log.d(TAG, "     " + event + ": scheduled at "
   1336                         + showTime(event.mTriggerTime) + ": last at "
   1337                         + showTime(event.mLastTriggerTime));
   1338                 if (++count >= 5) break;
   1339             }
   1340             if (mEventQueue.size() > count) {
   1341                 Log.d(TAG, "     .....");
   1342             } else if (count == 0) {
   1343                 Log.d(TAG, "     <empty>");
   1344             }
   1345         }
   1346 
   1347         private synchronized void execute(long triggerTime) {
   1348             if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = "
   1349                     + showTime(triggerTime) + ": " + mEventQueue.size());
   1350             if (stopped() || mEventQueue.isEmpty()) return;
   1351 
   1352             for (MyEvent event : mEventQueue) {
   1353                 if (event.mTriggerTime != triggerTime) break;
   1354                 if (DEBUG_TIMER) Log.d(TAG, "execute " + event);
   1355 
   1356                 event.mLastTriggerTime = event.mTriggerTime;
   1357                 event.mTriggerTime += event.mPeriod;
   1358 
   1359                 // run the callback in the handler thread to prevent deadlock
   1360                 getExecutor().execute(event.mCallback);
   1361             }
   1362             if (DEBUG_TIMER) {
   1363                 Log.d(TAG, "after timeout execution");
   1364                 printQueue();
   1365             }
   1366             scheduleNext();
   1367         }
   1368 
   1369         private String getAction() {
   1370             return toString();
   1371         }
   1372 
   1373         private String showTime(long time) {
   1374             int ms = (int) (time % 1000);
   1375             int s = (int) (time / 1000);
   1376             int m = s / 60;
   1377             s %= 60;
   1378             return String.format("%d.%d.%d", m, s, ms);
   1379         }
   1380     }
   1381 
   1382     private static class MyEvent {
   1383         int mPeriod;
   1384         int mMaxPeriod;
   1385         long mTriggerTime;
   1386         long mLastTriggerTime;
   1387         Runnable mCallback;
   1388 
   1389         MyEvent(int period, Runnable callback, long now) {
   1390             mPeriod = mMaxPeriod = period;
   1391             mCallback = callback;
   1392             mLastTriggerTime = now;
   1393         }
   1394 
   1395         @Override
   1396         public String toString() {
   1397             String s = super.toString();
   1398             s = s.substring(s.indexOf("@"));
   1399             return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":"
   1400                     + toString(mCallback);
   1401         }
   1402 
   1403         private String toString(Object o) {
   1404             String s = o.toString();
   1405             int index = s.indexOf("$");
   1406             if (index > 0) s = s.substring(index + 1);
   1407             return s;
   1408         }
   1409     }
   1410 
   1411     private static class MyEventComparator implements Comparator<MyEvent> {
   1412         public int compare(MyEvent e1, MyEvent e2) {
   1413             if (e1 == e2) return 0;
   1414             int diff = e1.mMaxPeriod - e2.mMaxPeriod;
   1415             if (diff == 0) diff = -1;
   1416             return diff;
   1417         }
   1418 
   1419         public boolean equals(Object that) {
   1420             return (this == that);
   1421         }
   1422     }
   1423 
   1424     private static Looper createLooper() {
   1425         HandlerThread thread = new HandlerThread("SipService.Executor");
   1426         thread.start();
   1427         return thread.getLooper();
   1428     }
   1429 
   1430     // Executes immediate tasks in a single thread.
   1431     // Hold/release wake lock for running tasks
   1432     private class MyExecutor extends Handler {
   1433         MyExecutor() {
   1434             super(createLooper());
   1435         }
   1436 
   1437         void execute(Runnable task) {
   1438             mMyWakeLock.acquire(task);
   1439             Message.obtain(this, 0/* don't care */, task).sendToTarget();
   1440         }
   1441 
   1442         @Override
   1443         public void handleMessage(Message msg) {
   1444             if (msg.obj instanceof Runnable) {
   1445                 executeInternal((Runnable) msg.obj);
   1446             } else {
   1447                 Log.w(TAG, "can't handle msg: " + msg);
   1448             }
   1449         }
   1450 
   1451         private void executeInternal(Runnable task) {
   1452             try {
   1453                 task.run();
   1454             } catch (Throwable t) {
   1455                 Log.e(TAG, "run task: " + task, t);
   1456             } finally {
   1457                 mMyWakeLock.release(task);
   1458             }
   1459         }
   1460     }
   1461 }
   1462