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