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