SipService.java revision c2bd6162eddad0cdfdafc037142e043680ffa705
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
17package com.android.server.sip;
18
19import android.app.AlarmManager;
20import android.app.PendingIntent;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.net.ConnectivityManager;
26import android.net.NetworkInfo;
27import android.net.sip.ISipService;
28import android.net.sip.ISipSession;
29import android.net.sip.ISipSessionListener;
30import android.net.sip.SipErrorCode;
31import android.net.sip.SipManager;
32import android.net.sip.SipProfile;
33import android.net.sip.SipSession;
34import android.net.sip.SipSessionAdapter;
35import android.net.wifi.WifiManager;
36import android.os.Binder;
37import android.os.Bundle;
38import android.os.Handler;
39import android.os.HandlerThread;
40import android.os.Looper;
41import android.os.Message;
42import android.os.PowerManager;
43import android.os.Process;
44import android.os.RemoteException;
45import android.os.ServiceManager;
46import android.os.SystemClock;
47import android.text.TextUtils;
48import android.util.Log;
49
50import java.io.IOException;
51import java.net.DatagramSocket;
52import java.net.InetAddress;
53import java.net.UnknownHostException;
54import java.util.ArrayList;
55import java.util.Collection;
56import java.util.Comparator;
57import java.util.HashMap;
58import java.util.Iterator;
59import java.util.Map;
60import java.util.Timer;
61import java.util.TimerTask;
62import java.util.TreeSet;
63import java.util.concurrent.Executor;
64import javax.sip.SipException;
65
66/**
67 * @hide
68 */
69public 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            String password = localProfile.getPassword();
457            SipProfile p = duplicate(localProfile);
458            mSipGroup = createSipSessionGroup(mLocalIp, p, password);
459            mIncomingCallPendingIntent = incomingCallPendingIntent;
460            mAutoRegistration.setListener(listener);
461        }
462
463        public SipProfile getLocalProfile() {
464            return mSipGroup.getLocalProfile();
465        }
466
467        public boolean containsSession(String callId) {
468            return mSipGroup.containsSession(callId);
469        }
470
471        public void onKeepAliveIntervalChanged() {
472            mAutoRegistration.onKeepAliveIntervalChanged();
473        }
474
475        // TODO: remove this method once SipWakeupTimer can better handle variety
476        // of timeout values
477        void setWakeupTimer(SipWakeupTimer timer) {
478            mSipGroup.setWakeupTimer(timer);
479        }
480
481        // network connectivity is tricky because network can be disconnected
482        // at any instant so need to deal with exceptions carefully even when
483        // you think you are connected
484        private SipSessionGroup createSipSessionGroup(String localIp,
485                SipProfile localProfile, String password) throws SipException {
486            try {
487                return new SipSessionGroup(localIp, localProfile, password,
488                        mTimer, mMyWakeLock);
489            } catch (IOException e) {
490                // network disconnected
491                Log.w(TAG, "createSipSessionGroup(): network disconnected?");
492                if (localIp != null) {
493                    return createSipSessionGroup(null, localProfile, password);
494                } else {
495                    // recursive
496                    Log.wtf(TAG, "impossible! recursive!");
497                    throw new RuntimeException("createSipSessionGroup");
498                }
499            }
500        }
501
502        private SipProfile duplicate(SipProfile p) {
503            try {
504                return new SipProfile.Builder(p).setPassword("*").build();
505            } catch (Exception e) {
506                Log.wtf(TAG, "duplicate()", e);
507                throw new RuntimeException("duplicate profile", e);
508            }
509        }
510
511        public void setListener(ISipSessionListener listener) {
512            mAutoRegistration.setListener(listener);
513        }
514
515        public void setIncomingCallPendingIntent(PendingIntent pIntent) {
516            mIncomingCallPendingIntent = pIntent;
517        }
518
519        public void openToReceiveCalls() throws SipException {
520            mOpenedToReceiveCalls = true;
521            if (mNetworkType != -1) {
522                mSipGroup.openToReceiveCalls(this);
523                mAutoRegistration.start(mSipGroup);
524            }
525            if (DEBUG) Log.d(TAG, "  openToReceiveCalls: " + getUri() + ": "
526                    + mIncomingCallPendingIntent);
527        }
528
529        public void onConnectivityChanged(boolean connected)
530                throws SipException {
531            mSipGroup.onConnectivityChanged();
532            if (connected) {
533                resetGroup(mLocalIp);
534                if (mOpenedToReceiveCalls) openToReceiveCalls();
535            } else {
536                // close mSipGroup but remember mOpenedToReceiveCalls
537                if (DEBUG) Log.d(TAG, "  close auto reg temporarily: "
538                        + getUri() + ": " + mIncomingCallPendingIntent);
539                mSipGroup.close();
540                mAutoRegistration.stop();
541            }
542        }
543
544        private void resetGroup(String localIp) throws SipException {
545            try {
546                mSipGroup.reset(localIp);
547            } catch (IOException e) {
548                // network disconnected
549                Log.w(TAG, "resetGroup(): network disconnected?");
550                if (localIp != null) {
551                    resetGroup(null); // reset w/o local IP
552                } else {
553                    // recursive
554                    Log.wtf(TAG, "impossible!");
555                    throw new RuntimeException("resetGroup");
556                }
557            }
558        }
559
560        public void close() {
561            mOpenedToReceiveCalls = false;
562            mSipGroup.close();
563            mAutoRegistration.stop();
564            if (DEBUG) Log.d(TAG, "   close: " + getUri() + ": "
565                    + mIncomingCallPendingIntent);
566        }
567
568        public ISipSession createSession(ISipSessionListener listener) {
569            return mSipGroup.createSession(listener);
570        }
571
572        @Override
573        public void onRinging(ISipSession s, SipProfile caller,
574                String sessionDescription) {
575            if (DEBUG) Log.d(TAG, "<<<<< onRinging()");
576            SipSessionGroup.SipSessionImpl session =
577                    (SipSessionGroup.SipSessionImpl) s;
578            synchronized (SipService.this) {
579                try {
580                    if (!isRegistered() || callingSelf(this, session)) {
581                        session.endCall();
582                        return;
583                    }
584
585                    // send out incoming call broadcast
586                    addPendingSession(session);
587                    Intent intent = SipManager.createIncomingCallBroadcast(
588                            session.getCallId(), sessionDescription);
589                    if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": "
590                            + caller.getUri() + ": " + session.getCallId()
591                            + " " + mIncomingCallPendingIntent);
592                    mIncomingCallPendingIntent.send(mContext,
593                            SipManager.INCOMING_CALL_RESULT_CODE, intent);
594                } catch (PendingIntent.CanceledException e) {
595                    Log.w(TAG, "pendingIntent is canceled, drop incoming call");
596                    session.endCall();
597                }
598            }
599        }
600
601        @Override
602        public void onError(ISipSession session, int errorCode,
603                String message) {
604            if (DEBUG) Log.d(TAG, "sip session error: "
605                    + SipErrorCode.toString(errorCode) + ": " + message);
606        }
607
608        public boolean isOpenedToReceiveCalls() {
609            return mOpenedToReceiveCalls;
610        }
611
612        public boolean isRegistered() {
613            return mAutoRegistration.isRegistered();
614        }
615
616        private String getUri() {
617            return mSipGroup.getLocalProfileUri();
618        }
619    }
620
621    private class IntervalMeasurementProcess implements Runnable,
622            SipSessionGroup.KeepAliveProcessCallback {
623        private static final String TAG = "SipKeepAliveInterval";
624        private static final int MIN_INTERVAL = 5; // in seconds
625        private static final int PASS_THRESHOLD = 10;
626        private static final int MAX_RETRY_COUNT = 5;
627        private static final int NAT_MEASUREMENT_RETRY_INTERVAL = 120; // in seconds
628        private SipProfile mLocalProfile;
629        private SipSessionGroupExt mGroup;
630        private SipSessionGroup.SipSessionImpl mSession;
631        private int mMinInterval;
632        private int mMaxInterval;
633        private int mInterval;
634        private int mPassCount;
635
636        public IntervalMeasurementProcess(SipProfile localProfile,
637                int minInterval, int maxInterval) {
638            mMaxInterval = maxInterval;
639            mMinInterval = minInterval;
640            mLocalProfile = localProfile;
641        }
642
643        public void start() {
644            synchronized (SipService.this) {
645                if (mSession != null) {
646                    return;
647                }
648
649                mInterval = (mMaxInterval + mMinInterval) / 2;
650                mPassCount = 0;
651
652                // Don't start measurement if the interval is too small
653                if (mInterval < DEFAULT_KEEPALIVE_INTERVAL || checkTermination()) {
654                    Log.w(TAG, "measurement aborted; interval=[" +
655                            mMinInterval + "," + mMaxInterval + "]");
656                    return;
657                }
658
659                try {
660                    Log.d(TAG, "start measurement w interval=" + mInterval);
661
662                    mGroup = new SipSessionGroupExt(mLocalProfile, null, null);
663                    // TODO: remove this line once SipWakeupTimer can better handle
664                    // variety of timeout values
665                    mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor));
666
667                    mSession = (SipSessionGroup.SipSessionImpl)
668                            mGroup.createSession(null);
669                    mSession.startKeepAliveProcess(mInterval, this);
670                } catch (Throwable t) {
671                    onError(SipErrorCode.CLIENT_ERROR, t.toString());
672                }
673            }
674        }
675
676        public void stop() {
677            synchronized (SipService.this) {
678                if (mSession != null) {
679                    mSession.stopKeepAliveProcess();
680                    mSession = null;
681                }
682                if (mGroup != null) {
683                    mGroup.close();
684                    mGroup = null;
685                }
686                mTimer.cancel(this);
687            }
688        }
689
690        private void restart() {
691            synchronized (SipService.this) {
692                // Return immediately if the measurement process is stopped
693                if (mSession == null) return;
694
695                Log.d(TAG, "restart measurement w interval=" + mInterval);
696                try {
697                    mSession.stopKeepAliveProcess();
698                    mPassCount = 0;
699                    mSession.startKeepAliveProcess(mInterval, this);
700                } catch (SipException e) {
701                    Log.e(TAG, "restart()", e);
702                }
703            }
704        }
705
706        private boolean checkTermination() {
707            return ((mMaxInterval - mMinInterval) < MIN_INTERVAL);
708        }
709
710        // SipSessionGroup.KeepAliveProcessCallback
711        @Override
712        public void onResponse(boolean portChanged) {
713            synchronized (SipService.this) {
714                if (!portChanged) {
715                    if (++mPassCount != PASS_THRESHOLD) return;
716                    // update the interval, since the current interval is good to
717                    // keep the port mapping.
718                    if (mKeepAliveInterval > 0) {
719                        mLastGoodKeepAliveInterval = mKeepAliveInterval;
720                    }
721                    mKeepAliveInterval = mMinInterval = mInterval;
722                    if (DEBUG) {
723                        Log.d(TAG, "measured good keepalive interval: "
724                                + mKeepAliveInterval);
725                    }
726                    onKeepAliveIntervalChanged();
727                } else {
728                    // Since the rport is changed, shorten the interval.
729                    mMaxInterval = mInterval;
730                }
731                if (checkTermination()) {
732                    // update mKeepAliveInterval and stop measurement.
733                    stop();
734                    // If all the measurements failed, we still set it to
735                    // mMinInterval; If mMinInterval still doesn't work, a new
736                    // measurement with min interval=DEFAULT_KEEPALIVE_INTERVAL
737                    // will be conducted.
738                    mKeepAliveInterval = mMinInterval;
739                    if (DEBUG) {
740                        Log.d(TAG, "measured keepalive interval: "
741                                + mKeepAliveInterval);
742                    }
743                } else {
744                    // calculate the new interval and continue.
745                    mInterval = (mMaxInterval + mMinInterval) / 2;
746                    if (DEBUG) {
747                        Log.d(TAG, "current interval: " + mKeepAliveInterval
748                                + ", test new interval: " + mInterval);
749                    }
750                    restart();
751                }
752            }
753        }
754
755        // SipSessionGroup.KeepAliveProcessCallback
756        @Override
757        public void onError(int errorCode, String description) {
758            Log.w(TAG, "interval measurement error: " + description);
759            restartLater();
760        }
761
762        // timeout handler
763        @Override
764        public void run() {
765            mTimer.cancel(this);
766            restart();
767        }
768
769        private void restartLater() {
770            synchronized (SipService.this) {
771                int interval = NAT_MEASUREMENT_RETRY_INTERVAL;
772                mTimer.cancel(this);
773                mTimer.set(interval * 1000, this);
774            }
775        }
776    }
777
778    private class AutoRegistrationProcess extends SipSessionAdapter
779            implements Runnable, SipSessionGroup.KeepAliveProcessCallback {
780        private static final int MIN_KEEPALIVE_SUCCESS_COUNT = 10;
781        private String TAG = "SipAutoReg";
782
783        private SipSessionGroup.SipSessionImpl mSession;
784        private SipSessionGroup.SipSessionImpl mKeepAliveSession;
785        private SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
786        private int mBackoff = 1;
787        private boolean mRegistered;
788        private long mExpiryTime;
789        private int mErrorCode;
790        private String mErrorMessage;
791        private boolean mRunning = false;
792
793        private int mKeepAliveSuccessCount = 0;
794
795        private String getAction() {
796            return toString();
797        }
798
799        public void start(SipSessionGroup group) {
800            if (!mRunning) {
801                mRunning = true;
802                mBackoff = 1;
803                mSession = (SipSessionGroup.SipSessionImpl)
804                        group.createSession(this);
805                // return right away if no active network connection.
806                if (mSession == null) return;
807
808                // start unregistration to clear up old registration at server
809                // TODO: when rfc5626 is deployed, use reg-id and sip.instance
810                // in registration to avoid adding duplicate entries to server
811                mMyWakeLock.acquire(mSession);
812                mSession.unregister();
813                TAG = "SipAutoReg:" + mSession.getLocalProfile().getUriString();
814            }
815        }
816
817        private void startKeepAliveProcess(int interval) {
818            if (DEBUG) Log.d(TAG, "start keepalive w interval=" + interval);
819            if (mKeepAliveSession == null) {
820                mKeepAliveSession = mSession.duplicate();
821            } else {
822                mKeepAliveSession.stopKeepAliveProcess();
823            }
824            try {
825                mKeepAliveSession.startKeepAliveProcess(interval, this);
826            } catch (SipException e) {
827                Log.e(TAG, "failed to start keepalive w interval=" + interval,
828                        e);
829            }
830        }
831
832        private void stopKeepAliveProcess() {
833            if (mKeepAliveSession != null) {
834                mKeepAliveSession.stopKeepAliveProcess();
835                mKeepAliveSession = null;
836            }
837            mKeepAliveSuccessCount = 0;
838        }
839
840        // SipSessionGroup.KeepAliveProcessCallback
841        @Override
842        public void onResponse(boolean portChanged) {
843            synchronized (SipService.this) {
844                if (portChanged) {
845                    int interval = getKeepAliveInterval();
846                    if (mKeepAliveSuccessCount < MIN_KEEPALIVE_SUCCESS_COUNT) {
847                        Log.i(TAG, "keepalive doesn't work with interval "
848                                + interval + ", past success count="
849                                + mKeepAliveSuccessCount);
850                        if (interval > DEFAULT_KEEPALIVE_INTERVAL) {
851                            restartPortMappingLifetimeMeasurement(
852                                    mSession.getLocalProfile(), interval);
853                            mKeepAliveSuccessCount = 0;
854                        }
855                    } else {
856                        if (DEBUG) {
857                            Log.i(TAG, "keep keepalive going with interval "
858                                    + interval + ", past success count="
859                                    + mKeepAliveSuccessCount);
860                        }
861                        mKeepAliveSuccessCount /= 2;
862                    }
863                } else {
864                    // Start keep-alive interval measurement on the first
865                    // successfully kept-alive SipSessionGroup
866                    startPortMappingLifetimeMeasurement(
867                            mSession.getLocalProfile());
868                    mKeepAliveSuccessCount++;
869                }
870
871                if (!mRunning || !portChanged) return;
872
873                // The keep alive process is stopped when port is changed;
874                // Nullify the session so that the process can be restarted
875                // again when the re-registration is done
876                mKeepAliveSession = null;
877
878                // Acquire wake lock for the registration process. The
879                // lock will be released when registration is complete.
880                mMyWakeLock.acquire(mSession);
881                mSession.register(EXPIRY_TIME);
882            }
883        }
884
885        // SipSessionGroup.KeepAliveProcessCallback
886        @Override
887        public void onError(int errorCode, String description) {
888            if (DEBUG) {
889                Log.e(TAG, "keepalive error: " + description);
890            }
891            onResponse(true); // re-register immediately
892        }
893
894        public void stop() {
895            if (!mRunning) return;
896            mRunning = false;
897            mMyWakeLock.release(mSession);
898            if (mSession != null) {
899                mSession.setListener(null);
900                if (mNetworkType != -1 && mRegistered) mSession.unregister();
901            }
902
903            mTimer.cancel(this);
904            stopKeepAliveProcess();
905
906            mRegistered = false;
907            setListener(mProxy.getListener());
908        }
909
910        public void onKeepAliveIntervalChanged() {
911            if (mKeepAliveSession != null) {
912                int newInterval = getKeepAliveInterval();
913                if (DEBUG) {
914                    Log.v(TAG, "restart keepalive w interval=" + newInterval);
915                }
916                mKeepAliveSuccessCount = 0;
917                startKeepAliveProcess(newInterval);
918            }
919        }
920
921        public void setListener(ISipSessionListener listener) {
922            synchronized (SipService.this) {
923                mProxy.setListener(listener);
924
925                try {
926                    int state = (mSession == null)
927                            ? SipSession.State.READY_TO_CALL
928                            : mSession.getState();
929                    if ((state == SipSession.State.REGISTERING)
930                            || (state == SipSession.State.DEREGISTERING)) {
931                        mProxy.onRegistering(mSession);
932                    } else if (mRegistered) {
933                        int duration = (int)
934                                (mExpiryTime - SystemClock.elapsedRealtime());
935                        mProxy.onRegistrationDone(mSession, duration);
936                    } else if (mErrorCode != SipErrorCode.NO_ERROR) {
937                        if (mErrorCode == SipErrorCode.TIME_OUT) {
938                            mProxy.onRegistrationTimeout(mSession);
939                        } else {
940                            mProxy.onRegistrationFailed(mSession, mErrorCode,
941                                    mErrorMessage);
942                        }
943                    } else if (mNetworkType == -1) {
944                        mProxy.onRegistrationFailed(mSession,
945                                SipErrorCode.DATA_CONNECTION_LOST,
946                                "no data connection");
947                    } else if (!mRunning) {
948                        mProxy.onRegistrationFailed(mSession,
949                                SipErrorCode.CLIENT_ERROR,
950                                "registration not running");
951                    } else {
952                        mProxy.onRegistrationFailed(mSession,
953                                SipErrorCode.IN_PROGRESS,
954                                String.valueOf(state));
955                    }
956                } catch (Throwable t) {
957                    Log.w(TAG, "setListener(): " + t);
958                }
959            }
960        }
961
962        public boolean isRegistered() {
963            return mRegistered;
964        }
965
966        // timeout handler: re-register
967        @Override
968        public void run() {
969            synchronized (SipService.this) {
970                if (!mRunning) return;
971
972                mErrorCode = SipErrorCode.NO_ERROR;
973                mErrorMessage = null;
974                if (DEBUG) Log.d(TAG, "registering");
975                if (mNetworkType != -1) {
976                    mMyWakeLock.acquire(mSession);
977                    mSession.register(EXPIRY_TIME);
978                }
979            }
980        }
981
982        private void restart(int duration) {
983            Log.d(TAG, "Refresh registration " + duration + "s later.");
984            mTimer.cancel(this);
985            mTimer.set(duration * 1000, this);
986        }
987
988        private int backoffDuration() {
989            int duration = SHORT_EXPIRY_TIME * mBackoff;
990            if (duration > 3600) {
991                duration = 3600;
992            } else {
993                mBackoff *= 2;
994            }
995            return duration;
996        }
997
998        @Override
999        public void onRegistering(ISipSession session) {
1000            if (DEBUG) Log.d(TAG, "onRegistering(): " + session);
1001            synchronized (SipService.this) {
1002                if (notCurrentSession(session)) return;
1003
1004                mRegistered = false;
1005                mProxy.onRegistering(session);
1006            }
1007        }
1008
1009        private boolean notCurrentSession(ISipSession session) {
1010            if (session != mSession) {
1011                ((SipSessionGroup.SipSessionImpl) session).setListener(null);
1012                mMyWakeLock.release(session);
1013                return true;
1014            }
1015            return !mRunning;
1016        }
1017
1018        @Override
1019        public void onRegistrationDone(ISipSession session, int duration) {
1020            if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
1021            synchronized (SipService.this) {
1022                if (notCurrentSession(session)) return;
1023
1024                mProxy.onRegistrationDone(session, duration);
1025
1026                if (duration > 0) {
1027                    mExpiryTime = SystemClock.elapsedRealtime()
1028                            + (duration * 1000);
1029
1030                    if (!mRegistered) {
1031                        mRegistered = true;
1032                        // allow some overlap to avoid call drop during renew
1033                        duration -= MIN_EXPIRY_TIME;
1034                        if (duration < MIN_EXPIRY_TIME) {
1035                            duration = MIN_EXPIRY_TIME;
1036                        }
1037                        restart(duration);
1038
1039                        SipProfile localProfile = mSession.getLocalProfile();
1040                        if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp)
1041                                || localProfile.getSendKeepAlive())) {
1042                            startKeepAliveProcess(getKeepAliveInterval());
1043                        }
1044                    }
1045                    mMyWakeLock.release(session);
1046                } else {
1047                    mRegistered = false;
1048                    mExpiryTime = -1L;
1049                    if (DEBUG) Log.d(TAG, "Refresh registration immediately");
1050                    run();
1051                }
1052            }
1053        }
1054
1055        @Override
1056        public void onRegistrationFailed(ISipSession session, int errorCode,
1057                String message) {
1058            if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": "
1059                    + SipErrorCode.toString(errorCode) + ": " + message);
1060            synchronized (SipService.this) {
1061                if (notCurrentSession(session)) return;
1062
1063                switch (errorCode) {
1064                    case SipErrorCode.INVALID_CREDENTIALS:
1065                    case SipErrorCode.SERVER_UNREACHABLE:
1066                        if (DEBUG) Log.d(TAG, "   pause auto-registration");
1067                        stop();
1068                        break;
1069                    default:
1070                        restartLater();
1071                }
1072
1073                mErrorCode = errorCode;
1074                mErrorMessage = message;
1075                mProxy.onRegistrationFailed(session, errorCode, message);
1076                mMyWakeLock.release(session);
1077            }
1078        }
1079
1080        @Override
1081        public void onRegistrationTimeout(ISipSession session) {
1082            if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session);
1083            synchronized (SipService.this) {
1084                if (notCurrentSession(session)) return;
1085
1086                mErrorCode = SipErrorCode.TIME_OUT;
1087                mProxy.onRegistrationTimeout(session);
1088                restartLater();
1089                mMyWakeLock.release(session);
1090            }
1091        }
1092
1093        private void restartLater() {
1094            mRegistered = false;
1095            restart(backoffDuration());
1096        }
1097    }
1098
1099    private class ConnectivityReceiver extends BroadcastReceiver {
1100        @Override
1101        public void onReceive(Context context, Intent intent) {
1102            Bundle bundle = intent.getExtras();
1103            if (bundle != null) {
1104                final NetworkInfo info = (NetworkInfo)
1105                        bundle.get(ConnectivityManager.EXTRA_NETWORK_INFO);
1106
1107                // Run the handler in MyExecutor to be protected by wake lock
1108                mExecutor.execute(new Runnable() {
1109                    public void run() {
1110                        onConnectivityChanged(info);
1111                    }
1112                });
1113            }
1114        }
1115    }
1116
1117    private void registerReceivers() {
1118        mContext.registerReceiver(mConnectivityReceiver,
1119                new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
1120        if (DEBUG) Log.d(TAG, " +++ register receivers");
1121    }
1122
1123    private void unregisterReceivers() {
1124        mContext.unregisterReceiver(mConnectivityReceiver);
1125        if (DEBUG) Log.d(TAG, " --- unregister receivers");
1126
1127        // Reset variables maintained by ConnectivityReceiver.
1128        mWifiLock.release();
1129        mNetworkType = -1;
1130    }
1131
1132    private void updateWakeLocks() {
1133        for (SipSessionGroupExt group : mSipGroups.values()) {
1134            if (group.isOpenedToReceiveCalls()) {
1135                // Also grab the WifiLock when we are disconnected, so the
1136                // system will keep trying to reconnect. It will be released
1137                // when the system eventually connects to something else.
1138                if (mNetworkType == ConnectivityManager.TYPE_WIFI || mNetworkType == -1) {
1139                    mWifiLock.acquire();
1140                } else {
1141                    mWifiLock.release();
1142                }
1143                return;
1144            }
1145        }
1146        mWifiLock.release();
1147        mMyWakeLock.reset(); // in case there's a leak
1148    }
1149
1150    private synchronized void onConnectivityChanged(NetworkInfo info) {
1151        // We only care about the default network, and getActiveNetworkInfo()
1152        // is the only way to distinguish them. However, as broadcasts are
1153        // delivered asynchronously, we might miss DISCONNECTED events from
1154        // getActiveNetworkInfo(), which is critical to our SIP stack. To
1155        // solve this, if it is a DISCONNECTED event to our current network,
1156        // respect it. Otherwise get a new one from getActiveNetworkInfo().
1157        if (info == null || info.isConnected() || info.getType() != mNetworkType) {
1158            ConnectivityManager cm = (ConnectivityManager)
1159                    mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
1160            info = cm.getActiveNetworkInfo();
1161        }
1162
1163        // Some devices limit SIP on Wi-Fi. In this case, if we are not on
1164        // Wi-Fi, treat it as a DISCONNECTED event.
1165        int networkType = (info != null && info.isConnected()) ? info.getType() : -1;
1166        if (mSipOnWifiOnly && networkType != ConnectivityManager.TYPE_WIFI) {
1167            networkType = -1;
1168        }
1169
1170        // Ignore the event if the current active network is not changed.
1171        if (mNetworkType == networkType) {
1172            return;
1173        }
1174        if (DEBUG) {
1175            Log.d(TAG, "onConnectivityChanged(): " + mNetworkType +
1176                    " -> " + networkType);
1177        }
1178
1179        try {
1180            if (mNetworkType != -1) {
1181                mLocalIp = null;
1182                stopPortMappingMeasurement();
1183                for (SipSessionGroupExt group : mSipGroups.values()) {
1184                    group.onConnectivityChanged(false);
1185                }
1186            }
1187            mNetworkType = networkType;
1188
1189            if (mNetworkType != -1) {
1190                mLocalIp = determineLocalIp();
1191                mKeepAliveInterval = -1;
1192                mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
1193                for (SipSessionGroupExt group : mSipGroups.values()) {
1194                    group.onConnectivityChanged(true);
1195                }
1196            }
1197            updateWakeLocks();
1198        } catch (SipException e) {
1199            Log.e(TAG, "onConnectivityChanged()", e);
1200        }
1201    }
1202
1203    private static Looper createLooper() {
1204        HandlerThread thread = new HandlerThread("SipService.Executor");
1205        thread.start();
1206        return thread.getLooper();
1207    }
1208
1209    // Executes immediate tasks in a single thread.
1210    // Hold/release wake lock for running tasks
1211    private class MyExecutor extends Handler implements Executor {
1212        MyExecutor() {
1213            super(createLooper());
1214        }
1215
1216        @Override
1217        public void execute(Runnable task) {
1218            mMyWakeLock.acquire(task);
1219            Message.obtain(this, 0/* don't care */, task).sendToTarget();
1220        }
1221
1222        @Override
1223        public void handleMessage(Message msg) {
1224            if (msg.obj instanceof Runnable) {
1225                executeInternal((Runnable) msg.obj);
1226            } else {
1227                Log.w(TAG, "can't handle msg: " + msg);
1228            }
1229        }
1230
1231        private void executeInternal(Runnable task) {
1232            try {
1233                task.run();
1234            } catch (Throwable t) {
1235                Log.e(TAG, "run task: " + task, t);
1236            } finally {
1237                mMyWakeLock.release(task);
1238            }
1239        }
1240    }
1241}
1242