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