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            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