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