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