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 java.util.concurrent.Executor;
     64 import javax.sip.SipException;
     65 
     66 /**
     67  * @hide
     68  */
     69 public final class SipService extends ISipService.Stub {
     70     static final String TAG = "SipService";
     71     static final boolean DEBUGV = false;
     72     static final boolean DEBUG = true;
     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     private static final int DEFAULT_KEEPALIVE_INTERVAL = 10; // in seconds
     77     private static final int DEFAULT_MAX_KEEPALIVE_INTERVAL = 120; // in seconds
     78 
     79     private Context mContext;
     80     private String mLocalIp;
     81     private String mNetworkType;
     82     private boolean mConnected;
     83     private SipWakeupTimer mTimer;
     84     private WifiManager.WifiLock mWifiLock;
     85     private boolean mSipOnWifiOnly;
     86 
     87     private IntervalMeasurementProcess mIntervalMeasurementProcess;
     88 
     89     private MyExecutor mExecutor = new MyExecutor();
     90 
     91     // SipProfile URI --> group
     92     private Map<String, SipSessionGroupExt> mSipGroups =
     93             new HashMap<String, SipSessionGroupExt>();
     94 
     95     // session ID --> session
     96     private Map<String, ISipSession> mPendingSessions =
     97             new HashMap<String, ISipSession>();
     98 
     99     private ConnectivityReceiver mConnectivityReceiver;
    100     private SipWakeLock mMyWakeLock;
    101     private int mKeepAliveInterval;
    102     private int mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
    103 
    104     /**
    105      * Starts the SIP service. Do nothing if the SIP API is not supported on the
    106      * device.
    107      */
    108     public static void start(Context context) {
    109         if (SipManager.isApiSupported(context)) {
    110             ServiceManager.addService("sip", new SipService(context));
    111             context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP));
    112             if (DEBUG) Log.d(TAG, "SIP service started");
    113         }
    114     }
    115 
    116     private SipService(Context context) {
    117         if (DEBUG) Log.d(TAG, " service started!");
    118         mContext = context;
    119         mConnectivityReceiver = new ConnectivityReceiver();
    120 
    121         mWifiLock = ((WifiManager)
    122                 context.getSystemService(Context.WIFI_SERVICE))
    123                 .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
    124         mWifiLock.setReferenceCounted(false);
    125         mSipOnWifiOnly = SipManager.isSipWifiOnly(context);
    126 
    127         mMyWakeLock = new SipWakeLock((PowerManager)
    128                 context.getSystemService(Context.POWER_SERVICE));
    129 
    130         mTimer = new SipWakeupTimer(context, mExecutor);
    131     }
    132 
    133     public synchronized SipProfile[] getListOfProfiles() {
    134         mContext.enforceCallingOrSelfPermission(
    135                 android.Manifest.permission.USE_SIP, null);
    136         boolean isCallerRadio = isCallerRadio();
    137         ArrayList<SipProfile> profiles = new ArrayList<SipProfile>();
    138         for (SipSessionGroupExt group : mSipGroups.values()) {
    139             if (isCallerRadio || isCallerCreator(group)) {
    140                 profiles.add(group.getLocalProfile());
    141             }
    142         }
    143         return profiles.toArray(new SipProfile[profiles.size()]);
    144     }
    145 
    146     public synchronized void open(SipProfile localProfile) {
    147         mContext.enforceCallingOrSelfPermission(
    148                 android.Manifest.permission.USE_SIP, null);
    149         localProfile.setCallingUid(Binder.getCallingUid());
    150         try {
    151             boolean addingFirstProfile = mSipGroups.isEmpty();
    152             createGroup(localProfile);
    153             if (addingFirstProfile && !mSipGroups.isEmpty()) registerReceivers();
    154         } catch (SipException e) {
    155             Log.e(TAG, "openToMakeCalls()", e);
    156             // TODO: how to send the exception back
    157         }
    158     }
    159 
    160     public synchronized void open3(SipProfile localProfile,
    161             PendingIntent incomingCallPendingIntent,
    162             ISipSessionListener listener) {
    163         mContext.enforceCallingOrSelfPermission(
    164                 android.Manifest.permission.USE_SIP, null);
    165         localProfile.setCallingUid(Binder.getCallingUid());
    166         if (incomingCallPendingIntent == null) {
    167             Log.w(TAG, "incomingCallPendingIntent cannot be null; "
    168                     + "the profile is not opened");
    169             return;
    170         }
    171         if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": "
    172                 + incomingCallPendingIntent + ": " + listener);
    173         try {
    174             boolean addingFirstProfile = mSipGroups.isEmpty();
    175             SipSessionGroupExt group = createGroup(localProfile,
    176                     incomingCallPendingIntent, listener);
    177             if (addingFirstProfile && !mSipGroups.isEmpty()) registerReceivers();
    178             if (localProfile.getAutoRegistration()) {
    179                 group.openToReceiveCalls();
    180             }
    181         } catch (SipException e) {
    182             Log.e(TAG, "openToReceiveCalls()", e);
    183             // TODO: how to send the exception back
    184         }
    185     }
    186 
    187     private boolean isCallerCreator(SipSessionGroupExt group) {
    188         SipProfile profile = group.getLocalProfile();
    189         return (profile.getCallingUid() == Binder.getCallingUid());
    190     }
    191 
    192     private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) {
    193         return (isCallerRadio() || isCallerCreator(group));
    194     }
    195 
    196     private boolean isCallerRadio() {
    197         return (Binder.getCallingUid() == Process.PHONE_UID);
    198     }
    199 
    200     public synchronized void close(String localProfileUri) {
    201         mContext.enforceCallingOrSelfPermission(
    202                 android.Manifest.permission.USE_SIP, null);
    203         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
    204         if (group == null) return;
    205         if (!isCallerCreatorOrRadio(group)) {
    206             Log.w(TAG, "only creator or radio can close this profile");
    207             return;
    208         }
    209 
    210         group = mSipGroups.remove(localProfileUri);
    211         notifyProfileRemoved(group.getLocalProfile());
    212         group.close();
    213 
    214         if (!anyOpenedToReceiveCalls()) {
    215             unregisterReceivers();
    216             mMyWakeLock.reset(); // in case there's leak
    217         }
    218     }
    219 
    220     public synchronized boolean isOpened(String localProfileUri) {
    221         mContext.enforceCallingOrSelfPermission(
    222                 android.Manifest.permission.USE_SIP, null);
    223         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
    224         if (group == null) return false;
    225         if (isCallerCreatorOrRadio(group)) {
    226             return true;
    227         } else {
    228             Log.w(TAG, "only creator or radio can query on the profile");
    229             return false;
    230         }
    231     }
    232 
    233     public synchronized boolean isRegistered(String localProfileUri) {
    234         mContext.enforceCallingOrSelfPermission(
    235                 android.Manifest.permission.USE_SIP, null);
    236         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
    237         if (group == null) return false;
    238         if (isCallerCreatorOrRadio(group)) {
    239             return group.isRegistered();
    240         } else {
    241             Log.w(TAG, "only creator or radio can query on the profile");
    242             return false;
    243         }
    244     }
    245 
    246     public synchronized void setRegistrationListener(String localProfileUri,
    247             ISipSessionListener listener) {
    248         mContext.enforceCallingOrSelfPermission(
    249                 android.Manifest.permission.USE_SIP, null);
    250         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
    251         if (group == null) return;
    252         if (isCallerCreator(group)) {
    253             group.setListener(listener);
    254         } else {
    255             Log.w(TAG, "only creator can set listener on the profile");
    256         }
    257     }
    258 
    259     public synchronized ISipSession createSession(SipProfile localProfile,
    260             ISipSessionListener listener) {
    261         mContext.enforceCallingOrSelfPermission(
    262                 android.Manifest.permission.USE_SIP, null);
    263         localProfile.setCallingUid(Binder.getCallingUid());
    264         if (!mConnected) return null;
    265         try {
    266             SipSessionGroupExt group = createGroup(localProfile);
    267             return group.createSession(listener);
    268         } catch (SipException e) {
    269             if (DEBUG) Log.d(TAG, "createSession()", e);
    270             return null;
    271         }
    272     }
    273 
    274     public synchronized ISipSession getPendingSession(String callId) {
    275         mContext.enforceCallingOrSelfPermission(
    276                 android.Manifest.permission.USE_SIP, null);
    277         if (callId == null) return null;
    278         return mPendingSessions.get(callId);
    279     }
    280 
    281     private String determineLocalIp() {
    282         try {
    283             DatagramSocket s = new DatagramSocket();
    284             s.connect(InetAddress.getByName("192.168.1.1"), 80);
    285             return s.getLocalAddress().getHostAddress();
    286         } catch (IOException e) {
    287             if (DEBUG) Log.d(TAG, "determineLocalIp()", e);
    288             // dont do anything; there should be a connectivity change going
    289             return null;
    290         }
    291     }
    292 
    293     private SipSessionGroupExt createGroup(SipProfile localProfile)
    294             throws SipException {
    295         String key = localProfile.getUriString();
    296         SipSessionGroupExt group = mSipGroups.get(key);
    297         if (group == null) {
    298             group = new SipSessionGroupExt(localProfile, null, null);
    299             mSipGroups.put(key, group);
    300             notifyProfileAdded(localProfile);
    301         } else if (!isCallerCreator(group)) {
    302             throw new SipException("only creator can access the profile");
    303         }
    304         return group;
    305     }
    306 
    307     private SipSessionGroupExt createGroup(SipProfile localProfile,
    308             PendingIntent incomingCallPendingIntent,
    309             ISipSessionListener listener) throws SipException {
    310         String key = localProfile.getUriString();
    311         SipSessionGroupExt group = mSipGroups.get(key);
    312         if (group != null) {
    313             if (!isCallerCreator(group)) {
    314                 throw new SipException("only creator can access the profile");
    315             }
    316             group.setIncomingCallPendingIntent(incomingCallPendingIntent);
    317             group.setListener(listener);
    318         } else {
    319             group = new SipSessionGroupExt(localProfile,
    320                     incomingCallPendingIntent, listener);
    321             mSipGroups.put(key, group);
    322             notifyProfileAdded(localProfile);
    323         }
    324         return group;
    325     }
    326 
    327     private void notifyProfileAdded(SipProfile localProfile) {
    328         if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile);
    329         Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE);
    330         intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
    331         mContext.sendBroadcast(intent);
    332     }
    333 
    334     private void notifyProfileRemoved(SipProfile localProfile) {
    335         if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile);
    336         Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE);
    337         intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
    338         mContext.sendBroadcast(intent);
    339     }
    340 
    341     private boolean anyOpenedToReceiveCalls() {
    342         for (SipSessionGroupExt group : mSipGroups.values()) {
    343             if (group.isOpenedToReceiveCalls()) return true;
    344         }
    345         return false;
    346     }
    347 
    348     private void stopPortMappingMeasurement() {
    349         if (mIntervalMeasurementProcess != null) {
    350             mIntervalMeasurementProcess.stop();
    351             mIntervalMeasurementProcess = null;
    352         }
    353     }
    354 
    355     private void startPortMappingLifetimeMeasurement(
    356             SipProfile localProfile) {
    357         startPortMappingLifetimeMeasurement(localProfile,
    358                 DEFAULT_MAX_KEEPALIVE_INTERVAL);
    359     }
    360 
    361     private void startPortMappingLifetimeMeasurement(
    362             SipProfile localProfile, int maxInterval) {
    363         if ((mIntervalMeasurementProcess == null)
    364                 && (mKeepAliveInterval == -1)
    365                 && isBehindNAT(mLocalIp)) {
    366             Log.d(TAG, "start NAT port mapping timeout measurement on "
    367                     + localProfile.getUriString());
    368 
    369             int minInterval = mLastGoodKeepAliveInterval;
    370             if (minInterval >= maxInterval) {
    371                 // If mLastGoodKeepAliveInterval also does not work, reset it
    372                 // to the default min
    373                 minInterval = mLastGoodKeepAliveInterval
    374                         = DEFAULT_KEEPALIVE_INTERVAL;
    375                 Log.d(TAG, "  reset min interval to " + minInterval);
    376             }
    377             mIntervalMeasurementProcess = new IntervalMeasurementProcess(
    378                     localProfile, minInterval, maxInterval);
    379             mIntervalMeasurementProcess.start();
    380         }
    381     }
    382 
    383     private void restartPortMappingLifetimeMeasurement(
    384             SipProfile localProfile, int maxInterval) {
    385         stopPortMappingMeasurement();
    386         mKeepAliveInterval = -1;
    387         startPortMappingLifetimeMeasurement(localProfile, maxInterval);
    388     }
    389 
    390     private synchronized void addPendingSession(ISipSession session) {
    391         try {
    392             cleanUpPendingSessions();
    393             mPendingSessions.put(session.getCallId(), session);
    394             if (DEBUG) Log.d(TAG, "#pending sess=" + mPendingSessions.size());
    395         } catch (RemoteException e) {
    396             // should not happen with a local call
    397             Log.e(TAG, "addPendingSession()", e);
    398         }
    399     }
    400 
    401     private void cleanUpPendingSessions() throws RemoteException {
    402         Map.Entry<String, ISipSession>[] entries =
    403                 mPendingSessions.entrySet().toArray(
    404                 new Map.Entry[mPendingSessions.size()]);
    405         for (Map.Entry<String, ISipSession> entry : entries) {
    406             if (entry.getValue().getState() != SipSession.State.INCOMING_CALL) {
    407                 mPendingSessions.remove(entry.getKey());
    408             }
    409         }
    410     }
    411 
    412     private synchronized boolean callingSelf(SipSessionGroupExt ringingGroup,
    413             SipSessionGroup.SipSessionImpl ringingSession) {
    414         String callId = ringingSession.getCallId();
    415         for (SipSessionGroupExt group : mSipGroups.values()) {
    416             if ((group != ringingGroup) && group.containsSession(callId)) {
    417                 if (DEBUG) Log.d(TAG, "call self: "
    418                         + ringingSession.getLocalProfile().getUriString()
    419                         + " -> " + group.getLocalProfile().getUriString());
    420                 return true;
    421             }
    422         }
    423         return false;
    424     }
    425 
    426     private synchronized void onKeepAliveIntervalChanged() {
    427         for (SipSessionGroupExt group : mSipGroups.values()) {
    428             group.onKeepAliveIntervalChanged();
    429         }
    430     }
    431 
    432     private int getKeepAliveInterval() {
    433         return (mKeepAliveInterval < 0)
    434                 ? mLastGoodKeepAliveInterval
    435                 : mKeepAliveInterval;
    436     }
    437 
    438     private boolean isBehindNAT(String address) {
    439         try {
    440             byte[] d = InetAddress.getByName(address).getAddress();
    441             if ((d[0] == 10) ||
    442                     (((0x000000FF & ((int)d[0])) == 172) &&
    443                     ((0x000000F0 & ((int)d[1])) == 16)) ||
    444                     (((0x000000FF & ((int)d[0])) == 192) &&
    445                     ((0x000000FF & ((int)d[1])) == 168))) {
    446                 return true;
    447             }
    448         } catch (UnknownHostException e) {
    449             Log.e(TAG, "isBehindAT()" + address, e);
    450         }
    451         return false;
    452     }
    453 
    454     private class SipSessionGroupExt extends SipSessionAdapter {
    455         private SipSessionGroup mSipGroup;
    456         private PendingIntent mIncomingCallPendingIntent;
    457         private boolean mOpenedToReceiveCalls;
    458 
    459         private AutoRegistrationProcess mAutoRegistration =
    460                 new AutoRegistrationProcess();
    461 
    462         public SipSessionGroupExt(SipProfile localProfile,
    463                 PendingIntent incomingCallPendingIntent,
    464                 ISipSessionListener listener) throws SipException {
    465             String password = localProfile.getPassword();
    466             SipProfile p = duplicate(localProfile);
    467             mSipGroup = createSipSessionGroup(mLocalIp, p, password);
    468             mIncomingCallPendingIntent = incomingCallPendingIntent;
    469             mAutoRegistration.setListener(listener);
    470         }
    471 
    472         public SipProfile getLocalProfile() {
    473             return mSipGroup.getLocalProfile();
    474         }
    475 
    476         public boolean containsSession(String callId) {
    477             return mSipGroup.containsSession(callId);
    478         }
    479 
    480         public void onKeepAliveIntervalChanged() {
    481             mAutoRegistration.onKeepAliveIntervalChanged();
    482         }
    483 
    484         // TODO: remove this method once SipWakeupTimer can better handle variety
    485         // of timeout values
    486         void setWakeupTimer(SipWakeupTimer timer) {
    487             mSipGroup.setWakeupTimer(timer);
    488         }
    489 
    490         // network connectivity is tricky because network can be disconnected
    491         // at any instant so need to deal with exceptions carefully even when
    492         // you think you are connected
    493         private SipSessionGroup createSipSessionGroup(String localIp,
    494                 SipProfile localProfile, String password) throws SipException {
    495             try {
    496                 return new SipSessionGroup(localIp, localProfile, password,
    497                         mTimer, mMyWakeLock);
    498             } catch (IOException e) {
    499                 // network disconnected
    500                 Log.w(TAG, "createSipSessionGroup(): network disconnected?");
    501                 if (localIp != null) {
    502                     return createSipSessionGroup(null, localProfile, password);
    503                 } else {
    504                     // recursive
    505                     Log.wtf(TAG, "impossible! recursive!");
    506                     throw new RuntimeException("createSipSessionGroup");
    507                 }
    508             }
    509         }
    510 
    511         private SipProfile duplicate(SipProfile p) {
    512             try {
    513                 return new SipProfile.Builder(p).setPassword("*").build();
    514             } catch (Exception e) {
    515                 Log.wtf(TAG, "duplicate()", e);
    516                 throw new RuntimeException("duplicate profile", e);
    517             }
    518         }
    519 
    520         public void setListener(ISipSessionListener listener) {
    521             mAutoRegistration.setListener(listener);
    522         }
    523 
    524         public void setIncomingCallPendingIntent(PendingIntent pIntent) {
    525             mIncomingCallPendingIntent = pIntent;
    526         }
    527 
    528         public void openToReceiveCalls() throws SipException {
    529             mOpenedToReceiveCalls = true;
    530             if (mConnected) {
    531                 mSipGroup.openToReceiveCalls(this);
    532                 mAutoRegistration.start(mSipGroup);
    533             }
    534             if (DEBUG) Log.d(TAG, "  openToReceiveCalls: " + getUri() + ": "
    535                     + mIncomingCallPendingIntent);
    536         }
    537 
    538         public void onConnectivityChanged(boolean connected)
    539                 throws SipException {
    540             mSipGroup.onConnectivityChanged();
    541             if (connected) {
    542                 resetGroup(mLocalIp);
    543                 if (mOpenedToReceiveCalls) openToReceiveCalls();
    544             } else {
    545                 // close mSipGroup but remember mOpenedToReceiveCalls
    546                 if (DEBUG) Log.d(TAG, "  close auto reg temporarily: "
    547                         + getUri() + ": " + mIncomingCallPendingIntent);
    548                 mSipGroup.close();
    549                 mAutoRegistration.stop();
    550             }
    551         }
    552 
    553         private void resetGroup(String localIp) throws SipException {
    554             try {
    555                 mSipGroup.reset(localIp);
    556             } catch (IOException e) {
    557                 // network disconnected
    558                 Log.w(TAG, "resetGroup(): network disconnected?");
    559                 if (localIp != null) {
    560                     resetGroup(null); // reset w/o local IP
    561                 } else {
    562                     // recursive
    563                     Log.wtf(TAG, "impossible!");
    564                     throw new RuntimeException("resetGroup");
    565                 }
    566             }
    567         }
    568 
    569         public void close() {
    570             mOpenedToReceiveCalls = false;
    571             mSipGroup.close();
    572             mAutoRegistration.stop();
    573             if (DEBUG) Log.d(TAG, "   close: " + getUri() + ": "
    574                     + mIncomingCallPendingIntent);
    575         }
    576 
    577         public ISipSession createSession(ISipSessionListener listener) {
    578             return mSipGroup.createSession(listener);
    579         }
    580 
    581         @Override
    582         public void onRinging(ISipSession s, SipProfile caller,
    583                 String sessionDescription) {
    584             if (DEBUGV) Log.d(TAG, "<<<<< onRinging()");
    585             SipSessionGroup.SipSessionImpl session =
    586                     (SipSessionGroup.SipSessionImpl) s;
    587             synchronized (SipService.this) {
    588                 try {
    589                     if (!isRegistered() || callingSelf(this, session)) {
    590                         session.endCall();
    591                         return;
    592                     }
    593 
    594                     // send out incoming call broadcast
    595                     addPendingSession(session);
    596                     Intent intent = SipManager.createIncomingCallBroadcast(
    597                             session.getCallId(), sessionDescription);
    598                     if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": "
    599                             + caller.getUri() + ": " + session.getCallId()
    600                             + " " + mIncomingCallPendingIntent);
    601                     mIncomingCallPendingIntent.send(mContext,
    602                             SipManager.INCOMING_CALL_RESULT_CODE, intent);
    603                 } catch (PendingIntent.CanceledException e) {
    604                     Log.w(TAG, "pendingIntent is canceled, drop incoming call");
    605                     session.endCall();
    606                 }
    607             }
    608         }
    609 
    610         @Override
    611         public void onError(ISipSession session, int errorCode,
    612                 String message) {
    613             if (DEBUG) Log.d(TAG, "sip session error: "
    614                     + SipErrorCode.toString(errorCode) + ": " + message);
    615         }
    616 
    617         public boolean isOpenedToReceiveCalls() {
    618             return mOpenedToReceiveCalls;
    619         }
    620 
    621         public boolean isRegistered() {
    622             return mAutoRegistration.isRegistered();
    623         }
    624 
    625         private String getUri() {
    626             return mSipGroup.getLocalProfileUri();
    627         }
    628     }
    629 
    630     private class IntervalMeasurementProcess implements Runnable,
    631             SipSessionGroup.KeepAliveProcessCallback {
    632         private static final String TAG = "SipKeepAliveInterval";
    633         private static final int MIN_INTERVAL = 5; // in seconds
    634         private static final int PASS_THRESHOLD = 10;
    635         private static final int MAX_RETRY_COUNT = 5;
    636         private static final int NAT_MEASUREMENT_RETRY_INTERVAL = 120; // in seconds
    637         private SipProfile mLocalProfile;
    638         private SipSessionGroupExt mGroup;
    639         private SipSessionGroup.SipSessionImpl mSession;
    640         private int mMinInterval;
    641         private int mMaxInterval;
    642         private int mInterval;
    643         private int mPassCount;
    644 
    645         public IntervalMeasurementProcess(SipProfile localProfile,
    646                 int minInterval, int maxInterval) {
    647             mMaxInterval = maxInterval;
    648             mMinInterval = minInterval;
    649             mLocalProfile = localProfile;
    650         }
    651 
    652         public void start() {
    653             synchronized (SipService.this) {
    654                 if (mSession != null) {
    655                     return;
    656                 }
    657 
    658                 mInterval = (mMaxInterval + mMinInterval) / 2;
    659                 mPassCount = 0;
    660 
    661                 // Don't start measurement if the interval is too small
    662                 if (mInterval < DEFAULT_KEEPALIVE_INTERVAL || checkTermination()) {
    663                     Log.w(TAG, "measurement aborted; interval=[" +
    664                             mMinInterval + "," + mMaxInterval + "]");
    665                     return;
    666                 }
    667 
    668                 try {
    669                     Log.d(TAG, "start measurement w interval=" + mInterval);
    670 
    671                     mGroup = new SipSessionGroupExt(mLocalProfile, null, null);
    672                     // TODO: remove this line once SipWakeupTimer can better handle
    673                     // variety of timeout values
    674                     mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor));
    675 
    676                     mSession = (SipSessionGroup.SipSessionImpl)
    677                             mGroup.createSession(null);
    678                     mSession.startKeepAliveProcess(mInterval, this);
    679                 } catch (Throwable t) {
    680                     onError(SipErrorCode.CLIENT_ERROR, t.toString());
    681                 }
    682             }
    683         }
    684 
    685         public void stop() {
    686             synchronized (SipService.this) {
    687                 if (mSession != null) {
    688                     mSession.stopKeepAliveProcess();
    689                     mSession = null;
    690                 }
    691                 if (mGroup != null) {
    692                     mGroup.close();
    693                     mGroup = null;
    694                 }
    695                 mTimer.cancel(this);
    696             }
    697         }
    698 
    699         private void restart() {
    700             synchronized (SipService.this) {
    701                 // Return immediately if the measurement process is stopped
    702                 if (mSession == null) return;
    703 
    704                 Log.d(TAG, "restart measurement w interval=" + mInterval);
    705                 try {
    706                     mSession.stopKeepAliveProcess();
    707                     mPassCount = 0;
    708                     mSession.startKeepAliveProcess(mInterval, this);
    709                 } catch (SipException e) {
    710                     Log.e(TAG, "restart()", e);
    711                 }
    712             }
    713         }
    714 
    715         private boolean checkTermination() {
    716             return ((mMaxInterval - mMinInterval) < MIN_INTERVAL);
    717         }
    718 
    719         // SipSessionGroup.KeepAliveProcessCallback
    720         @Override
    721         public void onResponse(boolean portChanged) {
    722             synchronized (SipService.this) {
    723                 if (!portChanged) {
    724                     if (++mPassCount != PASS_THRESHOLD) return;
    725                     // update the interval, since the current interval is good to
    726                     // keep the port mapping.
    727                     if (mKeepAliveInterval > 0) {
    728                         mLastGoodKeepAliveInterval = mKeepAliveInterval;
    729                     }
    730                     mKeepAliveInterval = mMinInterval = mInterval;
    731                     if (DEBUG) {
    732                         Log.d(TAG, "measured good keepalive interval: "
    733                                 + mKeepAliveInterval);
    734                     }
    735                     onKeepAliveIntervalChanged();
    736                 } else {
    737                     // Since the rport is changed, shorten the interval.
    738                     mMaxInterval = mInterval;
    739                 }
    740                 if (checkTermination()) {
    741                     // update mKeepAliveInterval and stop measurement.
    742                     stop();
    743                     // If all the measurements failed, we still set it to
    744                     // mMinInterval; If mMinInterval still doesn't work, a new
    745                     // measurement with min interval=DEFAULT_KEEPALIVE_INTERVAL
    746                     // will be conducted.
    747                     mKeepAliveInterval = mMinInterval;
    748                     if (DEBUG) {
    749                         Log.d(TAG, "measured keepalive interval: "
    750                                 + mKeepAliveInterval);
    751                     }
    752                 } else {
    753                     // calculate the new interval and continue.
    754                     mInterval = (mMaxInterval + mMinInterval) / 2;
    755                     if (DEBUG) {
    756                         Log.d(TAG, "current interval: " + mKeepAliveInterval
    757                                 + ", test new interval: " + mInterval);
    758                     }
    759                     restart();
    760                 }
    761             }
    762         }
    763 
    764         // SipSessionGroup.KeepAliveProcessCallback
    765         @Override
    766         public void onError(int errorCode, String description) {
    767             Log.w(TAG, "interval measurement error: " + description);
    768             restartLater();
    769         }
    770 
    771         // timeout handler
    772         @Override
    773         public void run() {
    774             mTimer.cancel(this);
    775             restart();
    776         }
    777 
    778         private void restartLater() {
    779             synchronized (SipService.this) {
    780                 int interval = NAT_MEASUREMENT_RETRY_INTERVAL;
    781                 Log.d(TAG, "Retry measurement " + interval + "s later.");
    782                 mTimer.cancel(this);
    783                 mTimer.set(interval * 1000, this);
    784             }
    785         }
    786     }
    787 
    788     private class AutoRegistrationProcess extends SipSessionAdapter
    789             implements Runnable, SipSessionGroup.KeepAliveProcessCallback {
    790         private static final int MIN_KEEPALIVE_SUCCESS_COUNT = 10;
    791         private String TAG = "SipAudoReg";
    792 
    793         private SipSessionGroup.SipSessionImpl mSession;
    794         private SipSessionGroup.SipSessionImpl mKeepAliveSession;
    795         private SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
    796         private int mBackoff = 1;
    797         private boolean mRegistered;
    798         private long mExpiryTime;
    799         private int mErrorCode;
    800         private String mErrorMessage;
    801         private boolean mRunning = false;
    802 
    803         private int mKeepAliveSuccessCount = 0;
    804 
    805         private String getAction() {
    806             return toString();
    807         }
    808 
    809         public void start(SipSessionGroup group) {
    810             if (!mRunning) {
    811                 mRunning = true;
    812                 mBackoff = 1;
    813                 mSession = (SipSessionGroup.SipSessionImpl)
    814                         group.createSession(this);
    815                 // return right away if no active network connection.
    816                 if (mSession == null) return;
    817 
    818                 // start unregistration to clear up old registration at server
    819                 // TODO: when rfc5626 is deployed, use reg-id and sip.instance
    820                 // in registration to avoid adding duplicate entries to server
    821                 mMyWakeLock.acquire(mSession);
    822                 mSession.unregister();
    823                 if (DEBUG) TAG = mSession.getLocalProfile().getUriString();
    824                 if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess");
    825             }
    826         }
    827 
    828         private void startKeepAliveProcess(int interval) {
    829             Log.d(TAG, "start keepalive w interval=" + interval);
    830             if (mKeepAliveSession == null) {
    831                 mKeepAliveSession = mSession.duplicate();
    832             } else {
    833                 mKeepAliveSession.stopKeepAliveProcess();
    834             }
    835             try {
    836                 mKeepAliveSession.startKeepAliveProcess(interval, this);
    837             } catch (SipException e) {
    838                 Log.e(TAG, "failed to start keepalive w interval=" + interval,
    839                         e);
    840             }
    841         }
    842 
    843         private void stopKeepAliveProcess() {
    844             if (mKeepAliveSession != null) {
    845                 mKeepAliveSession.stopKeepAliveProcess();
    846                 mKeepAliveSession = null;
    847             }
    848             mKeepAliveSuccessCount = 0;
    849         }
    850 
    851         // SipSessionGroup.KeepAliveProcessCallback
    852         @Override
    853         public void onResponse(boolean portChanged) {
    854             synchronized (SipService.this) {
    855                 if (portChanged) {
    856                     int interval = getKeepAliveInterval();
    857                     if (mKeepAliveSuccessCount < MIN_KEEPALIVE_SUCCESS_COUNT) {
    858                         Log.i(TAG, "keepalive doesn't work with interval "
    859                                 + interval + ", past success count="
    860                                 + mKeepAliveSuccessCount);
    861                         if (interval > DEFAULT_KEEPALIVE_INTERVAL) {
    862                             restartPortMappingLifetimeMeasurement(
    863                                     mSession.getLocalProfile(), interval);
    864                             mKeepAliveSuccessCount = 0;
    865                         }
    866                     } else {
    867                         Log.i(TAG, "keep keepalive going with interval "
    868                                 + interval + ", past success count="
    869                                 + mKeepAliveSuccessCount);
    870                         mKeepAliveSuccessCount /= 2;
    871                     }
    872                 } else {
    873                     // Start keep-alive interval measurement on the first
    874                     // successfully kept-alive SipSessionGroup
    875                     startPortMappingLifetimeMeasurement(
    876                             mSession.getLocalProfile());
    877                     mKeepAliveSuccessCount++;
    878                 }
    879 
    880                 if (!mRunning || !portChanged) return;
    881 
    882                 // The keep alive process is stopped when port is changed;
    883                 // Nullify the session so that the process can be restarted
    884                 // again when the re-registration is done
    885                 mKeepAliveSession = null;
    886 
    887                 // Acquire wake lock for the registration process. The
    888                 // lock will be released when registration is complete.
    889                 mMyWakeLock.acquire(mSession);
    890                 mSession.register(EXPIRY_TIME);
    891             }
    892         }
    893 
    894         // SipSessionGroup.KeepAliveProcessCallback
    895         @Override
    896         public void onError(int errorCode, String description) {
    897             Log.e(TAG, "keepalive error: " + description);
    898             onResponse(true); // re-register immediately
    899         }
    900 
    901         public void stop() {
    902             if (!mRunning) return;
    903             mRunning = false;
    904             mMyWakeLock.release(mSession);
    905             if (mSession != null) {
    906                 mSession.setListener(null);
    907                 if (mConnected && mRegistered) mSession.unregister();
    908             }
    909 
    910             mTimer.cancel(this);
    911             stopKeepAliveProcess();
    912 
    913             mRegistered = false;
    914             setListener(mProxy.getListener());
    915         }
    916 
    917         public void onKeepAliveIntervalChanged() {
    918             if (mKeepAliveSession != null) {
    919                 int newInterval = getKeepAliveInterval();
    920                 if (DEBUGV) {
    921                     Log.v(TAG, "restart keepalive w interval=" + newInterval);
    922                 }
    923                 mKeepAliveSuccessCount = 0;
    924                 startKeepAliveProcess(newInterval);
    925             }
    926         }
    927 
    928         public void setListener(ISipSessionListener listener) {
    929             synchronized (SipService.this) {
    930                 mProxy.setListener(listener);
    931 
    932                 try {
    933                     int state = (mSession == null)
    934                             ? SipSession.State.READY_TO_CALL
    935                             : mSession.getState();
    936                     if ((state == SipSession.State.REGISTERING)
    937                             || (state == SipSession.State.DEREGISTERING)) {
    938                         mProxy.onRegistering(mSession);
    939                     } else if (mRegistered) {
    940                         int duration = (int)
    941                                 (mExpiryTime - SystemClock.elapsedRealtime());
    942                         mProxy.onRegistrationDone(mSession, duration);
    943                     } else if (mErrorCode != SipErrorCode.NO_ERROR) {
    944                         if (mErrorCode == SipErrorCode.TIME_OUT) {
    945                             mProxy.onRegistrationTimeout(mSession);
    946                         } else {
    947                             mProxy.onRegistrationFailed(mSession, mErrorCode,
    948                                     mErrorMessage);
    949                         }
    950                     } else if (!mConnected) {
    951                         mProxy.onRegistrationFailed(mSession,
    952                                 SipErrorCode.DATA_CONNECTION_LOST,
    953                                 "no data connection");
    954                     } else if (!mRunning) {
    955                         mProxy.onRegistrationFailed(mSession,
    956                                 SipErrorCode.CLIENT_ERROR,
    957                                 "registration not running");
    958                     } else {
    959                         mProxy.onRegistrationFailed(mSession,
    960                                 SipErrorCode.IN_PROGRESS,
    961                                 String.valueOf(state));
    962                     }
    963                 } catch (Throwable t) {
    964                     Log.w(TAG, "setListener(): " + t);
    965                 }
    966             }
    967         }
    968 
    969         public boolean isRegistered() {
    970             return mRegistered;
    971         }
    972 
    973         // timeout handler: re-register
    974         @Override
    975         public void run() {
    976             synchronized (SipService.this) {
    977                 if (!mRunning) return;
    978 
    979                 mErrorCode = SipErrorCode.NO_ERROR;
    980                 mErrorMessage = null;
    981                 if (DEBUG) Log.d(TAG, "registering");
    982                 if (mConnected) {
    983                     mMyWakeLock.acquire(mSession);
    984                     mSession.register(EXPIRY_TIME);
    985                 }
    986             }
    987         }
    988 
    989         private void restart(int duration) {
    990             if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later.");
    991             mTimer.cancel(this);
    992             mTimer.set(duration * 1000, this);
    993         }
    994 
    995         private int backoffDuration() {
    996             int duration = SHORT_EXPIRY_TIME * mBackoff;
    997             if (duration > 3600) {
    998                 duration = 3600;
    999             } else {
   1000                 mBackoff *= 2;
   1001             }
   1002             return duration;
   1003         }
   1004 
   1005         @Override
   1006         public void onRegistering(ISipSession session) {
   1007             if (DEBUG) Log.d(TAG, "onRegistering(): " + session);
   1008             synchronized (SipService.this) {
   1009                 if (notCurrentSession(session)) return;
   1010 
   1011                 mRegistered = false;
   1012                 mProxy.onRegistering(session);
   1013             }
   1014         }
   1015 
   1016         private boolean notCurrentSession(ISipSession session) {
   1017             if (session != mSession) {
   1018                 ((SipSessionGroup.SipSessionImpl) session).setListener(null);
   1019                 mMyWakeLock.release(session);
   1020                 return true;
   1021             }
   1022             return !mRunning;
   1023         }
   1024 
   1025         @Override
   1026         public void onRegistrationDone(ISipSession session, int duration) {
   1027             if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
   1028             synchronized (SipService.this) {
   1029                 if (notCurrentSession(session)) return;
   1030 
   1031                 mProxy.onRegistrationDone(session, duration);
   1032 
   1033                 if (duration > 0) {
   1034                     mExpiryTime = SystemClock.elapsedRealtime()
   1035                             + (duration * 1000);
   1036 
   1037                     if (!mRegistered) {
   1038                         mRegistered = true;
   1039                         // allow some overlap to avoid call drop during renew
   1040                         duration -= MIN_EXPIRY_TIME;
   1041                         if (duration < MIN_EXPIRY_TIME) {
   1042                             duration = MIN_EXPIRY_TIME;
   1043                         }
   1044                         restart(duration);
   1045 
   1046                         SipProfile localProfile = mSession.getLocalProfile();
   1047                         if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp)
   1048                                 || localProfile.getSendKeepAlive())) {
   1049                             startKeepAliveProcess(getKeepAliveInterval());
   1050                         }
   1051                     }
   1052                     mMyWakeLock.release(session);
   1053                 } else {
   1054                     mRegistered = false;
   1055                     mExpiryTime = -1L;
   1056                     if (DEBUG) Log.d(TAG, "Refresh registration immediately");
   1057                     run();
   1058                 }
   1059             }
   1060         }
   1061 
   1062         @Override
   1063         public void onRegistrationFailed(ISipSession session, int errorCode,
   1064                 String message) {
   1065             if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": "
   1066                     + SipErrorCode.toString(errorCode) + ": " + message);
   1067             synchronized (SipService.this) {
   1068                 if (notCurrentSession(session)) return;
   1069 
   1070                 switch (errorCode) {
   1071                     case SipErrorCode.INVALID_CREDENTIALS:
   1072                     case SipErrorCode.SERVER_UNREACHABLE:
   1073                         if (DEBUG) Log.d(TAG, "   pause auto-registration");
   1074                         stop();
   1075                         break;
   1076                     default:
   1077                         restartLater();
   1078                 }
   1079 
   1080                 mErrorCode = errorCode;
   1081                 mErrorMessage = message;
   1082                 mProxy.onRegistrationFailed(session, errorCode, message);
   1083                 mMyWakeLock.release(session);
   1084             }
   1085         }
   1086 
   1087         @Override
   1088         public void onRegistrationTimeout(ISipSession session) {
   1089             if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session);
   1090             synchronized (SipService.this) {
   1091                 if (notCurrentSession(session)) return;
   1092 
   1093                 mErrorCode = SipErrorCode.TIME_OUT;
   1094                 mProxy.onRegistrationTimeout(session);
   1095                 restartLater();
   1096                 mMyWakeLock.release(session);
   1097             }
   1098         }
   1099 
   1100         private void restartLater() {
   1101             mRegistered = false;
   1102             restart(backoffDuration());
   1103         }
   1104     }
   1105 
   1106     private class ConnectivityReceiver extends BroadcastReceiver {
   1107         @Override
   1108         public void onReceive(Context context, Intent intent) {
   1109             Bundle bundle = intent.getExtras();
   1110             if (bundle != null) {
   1111                 final NetworkInfo info = (NetworkInfo)
   1112                         bundle.get(ConnectivityManager.EXTRA_NETWORK_INFO);
   1113 
   1114                 // Run the handler in MyExecutor to be protected by wake lock
   1115                 mExecutor.execute(new Runnable() {
   1116                     public void run() {
   1117                         onConnectivityChanged(info);
   1118                     }
   1119                 });
   1120             }
   1121         }
   1122     }
   1123 
   1124     private void registerReceivers() {
   1125         mContext.registerReceiver(mConnectivityReceiver,
   1126                 new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
   1127         if (DEBUG) Log.d(TAG, " +++ register receivers");
   1128     }
   1129 
   1130     private void unregisterReceivers() {
   1131         mContext.unregisterReceiver(mConnectivityReceiver);
   1132         if (DEBUG) Log.d(TAG, " --- unregister receivers");
   1133 
   1134         // Reset variables maintained by ConnectivityReceiver.
   1135         mWifiLock.release();
   1136         mConnected = false;
   1137     }
   1138 
   1139     private synchronized void onConnectivityChanged(NetworkInfo info) {
   1140         // We only care about the default network, and getActiveNetworkInfo()
   1141         // is the only way to distinguish them. However, as broadcasts are
   1142         // delivered asynchronously, we might miss DISCONNECTED events from
   1143         // getActiveNetworkInfo(), which is critical to our SIP stack. To
   1144         // solve this, if it is a DISCONNECTED event to our current network,
   1145         // respect it. Otherwise get a new one from getActiveNetworkInfo().
   1146         if (info == null || info.isConnected() ||
   1147                 !info.getTypeName().equals(mNetworkType)) {
   1148             ConnectivityManager cm = (ConnectivityManager)
   1149                     mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
   1150             info = cm.getActiveNetworkInfo();
   1151         }
   1152 
   1153         // Some devices limit SIP on Wi-Fi. In this case, if we are not on
   1154         // Wi-Fi, treat it as a DISCONNECTED event.
   1155         boolean connected = (info != null && info.isConnected() &&
   1156                 (!mSipOnWifiOnly || info.getType() == ConnectivityManager.TYPE_WIFI));
   1157         String networkType = connected ? info.getTypeName() : "null";
   1158 
   1159         // Ignore the event if the current active network is not changed.
   1160         if (connected == mConnected && networkType.equals(mNetworkType)) {
   1161             return;
   1162         }
   1163         if (DEBUG) {
   1164             Log.d(TAG, "onConnectivityChanged(): " + mNetworkType +
   1165                     " -> " + networkType);
   1166         }
   1167 
   1168         try {
   1169             if (mConnected) {
   1170                 mLocalIp = null;
   1171                 stopPortMappingMeasurement();
   1172                 for (SipSessionGroupExt group : mSipGroups.values()) {
   1173                     group.onConnectivityChanged(false);
   1174                 }
   1175             }
   1176 
   1177             mConnected = connected;
   1178             mNetworkType = networkType;
   1179 
   1180             if (connected) {
   1181                 mLocalIp = determineLocalIp();
   1182                 mKeepAliveInterval = -1;
   1183                 mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
   1184                 for (SipSessionGroupExt group : mSipGroups.values()) {
   1185                     group.onConnectivityChanged(true);
   1186                 }
   1187 
   1188                 // If we are on Wi-Fi, grab the WifiLock. Otherwise release it.
   1189                 if (info.getType() == ConnectivityManager.TYPE_WIFI) {
   1190                     mWifiLock.acquire();
   1191                 } else {
   1192                     mWifiLock.release();
   1193                 }
   1194             } else {
   1195                 // Always grab the WifiLock when we are disconnected, so the
   1196                 // system will keep trying to reconnect. We will release it
   1197                 // if we eventually connect via something else.
   1198                 mWifiLock.acquire();
   1199 
   1200                 mMyWakeLock.reset(); // in case there's a leak
   1201             }
   1202         } catch (SipException e) {
   1203             Log.e(TAG, "onConnectivityChanged()", e);
   1204         }
   1205     }
   1206 
   1207     private static Looper createLooper() {
   1208         HandlerThread thread = new HandlerThread("SipService.Executor");
   1209         thread.start();
   1210         return thread.getLooper();
   1211     }
   1212 
   1213     // Executes immediate tasks in a single thread.
   1214     // Hold/release wake lock for running tasks
   1215     private class MyExecutor extends Handler implements Executor {
   1216         MyExecutor() {
   1217             super(createLooper());
   1218         }
   1219 
   1220         @Override
   1221         public void execute(Runnable task) {
   1222             mMyWakeLock.acquire(task);
   1223             Message.obtain(this, 0/* don't care */, task).sendToTarget();
   1224         }
   1225 
   1226         @Override
   1227         public void handleMessage(Message msg) {
   1228             if (msg.obj instanceof Runnable) {
   1229                 executeInternal((Runnable) msg.obj);
   1230             } else {
   1231                 Log.w(TAG, "can't handle msg: " + msg);
   1232             }
   1233         }
   1234 
   1235         private void executeInternal(Runnable task) {
   1236             try {
   1237                 task.run();
   1238             } catch (Throwable t) {
   1239                 Log.e(TAG, "run task: " + task, t);
   1240             } finally {
   1241                 mMyWakeLock.release(task);
   1242             }
   1243         }
   1244     }
   1245 }
   1246