SipService.java revision aa562ffdb8f728569e6957b742f271eb7303f878
1/*
2 * Copyright (C) 2010, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.sip;
18
19import android.app.AlarmManager;
20import android.app.PendingIntent;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.net.ConnectivityManager;
26import android.net.NetworkInfo;
27import android.net.sip.ISipService;
28import android.net.sip.ISipSession;
29import android.net.sip.ISipSessionListener;
30import android.net.sip.SipErrorCode;
31import android.net.sip.SipManager;
32import android.net.sip.SipProfile;
33import android.net.sip.SipSession;
34import android.net.sip.SipSessionAdapter;
35import android.net.wifi.WifiManager;
36import android.os.Binder;
37import android.os.Bundle;
38import android.os.Handler;
39import android.os.HandlerThread;
40import android.os.Looper;
41import android.os.Message;
42import android.os.Process;
43import android.os.RemoteException;
44import android.os.ServiceManager;
45import android.os.SystemClock;
46import android.text.TextUtils;
47import android.util.Log;
48
49import java.io.IOException;
50import java.net.DatagramSocket;
51import java.net.InetAddress;
52import java.net.UnknownHostException;
53import java.util.ArrayList;
54import java.util.Collection;
55import java.util.Comparator;
56import java.util.HashMap;
57import java.util.Iterator;
58import java.util.Map;
59import java.util.Timer;
60import java.util.TimerTask;
61import java.util.TreeSet;
62import javax.sip.SipException;
63
64/**
65 * @hide
66 */
67public final class SipService extends ISipService.Stub {
68    private static final String TAG = "SipService";
69    private static final boolean DEBUGV = false;
70    private static final boolean DEBUG = true;
71    private static final boolean DEBUG_TIMER = DEBUG && false;
72    private static final int EXPIRY_TIME = 3600;
73    private static final int SHORT_EXPIRY_TIME = 10;
74    private static final int MIN_EXPIRY_TIME = 60;
75
76    private Context mContext;
77    private String mLocalIp;
78    private String mNetworkType;
79    private boolean mConnected;
80    private WakeupTimer mTimer;
81    private WifiManager.WifiLock mWifiLock;
82    private boolean mWifiOnly;
83
84    private MyExecutor mExecutor;
85
86    // SipProfile URI --> group
87    private Map<String, SipSessionGroupExt> mSipGroups =
88            new HashMap<String, SipSessionGroupExt>();
89
90    // session ID --> session
91    private Map<String, ISipSession> mPendingSessions =
92            new HashMap<String, ISipSession>();
93
94    private ConnectivityReceiver mConnectivityReceiver;
95
96    /**
97     * Starts the SIP service. Do nothing if the SIP API is not supported on the
98     * device.
99     */
100    public static void start(Context context) {
101        if (SipManager.isApiSupported(context)) {
102            ServiceManager.addService("sip", new SipService(context));
103            context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP));
104            Log.i(TAG, "SIP service started");
105        }
106    }
107
108    private SipService(Context context) {
109        if (DEBUG) Log.d(TAG, " service started!");
110        mContext = context;
111        mConnectivityReceiver = new ConnectivityReceiver();
112        context.registerReceiver(mConnectivityReceiver,
113                new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
114
115        mTimer = new WakeupTimer(context);
116        mWifiOnly = SipManager.isSipWifiOnly(context);
117    }
118
119    private MyExecutor getExecutor() {
120        // create mExecutor lazily
121        if (mExecutor == null) mExecutor = new MyExecutor();
122        return mExecutor;
123    }
124
125    public synchronized SipProfile[] getListOfProfiles() {
126        mContext.enforceCallingOrSelfPermission(
127                android.Manifest.permission.USE_SIP, null);
128        boolean isCallerRadio = isCallerRadio();
129        ArrayList<SipProfile> profiles = new ArrayList<SipProfile>();
130        for (SipSessionGroupExt group : mSipGroups.values()) {
131            if (isCallerRadio || isCallerCreator(group)) {
132                profiles.add(group.getLocalProfile());
133            }
134        }
135        return profiles.toArray(new SipProfile[profiles.size()]);
136    }
137
138    public 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            Log.e(TAG, "openToMakeCalls()", e);
146            // TODO: how to send the exception back
147        }
148    }
149
150    public synchronized void open3(SipProfile localProfile,
151            PendingIntent incomingCallPendingIntent,
152            ISipSessionListener listener) {
153        mContext.enforceCallingOrSelfPermission(
154                android.Manifest.permission.USE_SIP, null);
155        localProfile.setCallingUid(Binder.getCallingUid());
156        if (incomingCallPendingIntent == null) {
157            Log.w(TAG, "incomingCallPendingIntent cannot be null; "
158                    + "the profile is not opened");
159            return;
160        }
161        if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": "
162                + incomingCallPendingIntent + ": " + listener);
163        try {
164            SipSessionGroupExt group = createGroup(localProfile,
165                    incomingCallPendingIntent, listener);
166            if (localProfile.getAutoRegistration()) {
167                group.openToReceiveCalls();
168                if (isWifiActive()) grabWifiLock();
169            }
170        } catch (SipException e) {
171            Log.e(TAG, "openToReceiveCalls()", e);
172            // TODO: how to send the exception back
173        }
174    }
175
176    private boolean isCallerCreator(SipSessionGroupExt group) {
177        SipProfile profile = group.getLocalProfile();
178        return (profile.getCallingUid() == Binder.getCallingUid());
179    }
180
181    private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) {
182        return (isCallerRadio() || isCallerCreator(group));
183    }
184
185    private boolean isCallerRadio() {
186        return (Binder.getCallingUid() == Process.PHONE_UID);
187    }
188
189    public synchronized void close(String localProfileUri) {
190        mContext.enforceCallingOrSelfPermission(
191                android.Manifest.permission.USE_SIP, null);
192        SipSessionGroupExt group = mSipGroups.get(localProfileUri);
193        if (group == null) return;
194        if (!isCallerCreatorOrRadio(group)) {
195            Log.d(TAG, "only creator or radio can close this profile");
196            return;
197        }
198
199        group = mSipGroups.remove(localProfileUri);
200        notifyProfileRemoved(group.getLocalProfile());
201        group.close();
202        if (isWifiActive() && !anyOpened()) releaseWifiLock();
203    }
204
205    public synchronized boolean isOpened(String localProfileUri) {
206        mContext.enforceCallingOrSelfPermission(
207                android.Manifest.permission.USE_SIP, null);
208        SipSessionGroupExt group = mSipGroups.get(localProfileUri);
209        if (group == null) return false;
210        if (isCallerCreatorOrRadio(group)) {
211            return group.isOpened();
212        } else {
213            Log.i(TAG, "only creator or radio can query on the profile");
214            return false;
215        }
216    }
217
218    public synchronized boolean isRegistered(String localProfileUri) {
219        mContext.enforceCallingOrSelfPermission(
220                android.Manifest.permission.USE_SIP, null);
221        SipSessionGroupExt group = mSipGroups.get(localProfileUri);
222        if (group == null) return false;
223        if (isCallerCreatorOrRadio(group)) {
224            return group.isRegistered();
225        } else {
226            Log.i(TAG, "only creator or radio can query on the profile");
227            return false;
228        }
229    }
230
231    public synchronized void setRegistrationListener(String localProfileUri,
232            ISipSessionListener listener) {
233        mContext.enforceCallingOrSelfPermission(
234                android.Manifest.permission.USE_SIP, null);
235        SipSessionGroupExt group = mSipGroups.get(localProfileUri);
236        if (group == null) return;
237        if (isCallerCreator(group)) {
238            group.setListener(listener);
239        } else {
240            Log.i(TAG, "only creator can set listener on the profile");
241        }
242    }
243
244    public synchronized ISipSession createSession(SipProfile localProfile,
245            ISipSessionListener listener) {
246        mContext.enforceCallingOrSelfPermission(
247                android.Manifest.permission.USE_SIP, null);
248        localProfile.setCallingUid(Binder.getCallingUid());
249        if (!mConnected) return null;
250        try {
251            SipSessionGroupExt group = createGroup(localProfile);
252            return group.createSession(listener);
253        } catch (SipException e) {
254            Log.w(TAG, "createSession()", e);
255            return null;
256        }
257    }
258
259    public synchronized ISipSession getPendingSession(String callId) {
260        mContext.enforceCallingOrSelfPermission(
261                android.Manifest.permission.USE_SIP, null);
262        if (callId == null) return null;
263        return mPendingSessions.get(callId);
264    }
265
266    private String determineLocalIp() {
267        try {
268            DatagramSocket s = new DatagramSocket();
269            s.connect(InetAddress.getByName("192.168.1.1"), 80);
270            return s.getLocalAddress().getHostAddress();
271        } catch (IOException e) {
272            Log.w(TAG, "determineLocalIp()", e);
273            // dont do anything; there should be a connectivity change going
274            return null;
275        }
276    }
277
278    private SipSessionGroupExt createGroup(SipProfile localProfile)
279            throws SipException {
280        String key = localProfile.getUriString();
281        SipSessionGroupExt group = mSipGroups.get(key);
282        if (group == null) {
283            group = new SipSessionGroupExt(localProfile, null, null);
284            mSipGroups.put(key, group);
285            notifyProfileAdded(localProfile);
286        } else if (!isCallerCreator(group)) {
287            throw new SipException("only creator can access the profile");
288        }
289        return group;
290    }
291
292    private SipSessionGroupExt createGroup(SipProfile localProfile,
293            PendingIntent incomingCallPendingIntent,
294            ISipSessionListener listener) throws SipException {
295        String key = localProfile.getUriString();
296        SipSessionGroupExt group = mSipGroups.get(key);
297        if (group != null) {
298            if (!isCallerCreator(group)) {
299                throw new SipException("only creator can access the profile");
300            }
301            group.setIncomingCallPendingIntent(incomingCallPendingIntent);
302            group.setListener(listener);
303        } else {
304            group = new SipSessionGroupExt(localProfile,
305                    incomingCallPendingIntent, listener);
306            mSipGroups.put(key, group);
307            notifyProfileAdded(localProfile);
308        }
309        return group;
310    }
311
312    private void notifyProfileAdded(SipProfile localProfile) {
313        if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile);
314        Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE);
315        intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
316        mContext.sendBroadcast(intent);
317    }
318
319    private void notifyProfileRemoved(SipProfile localProfile) {
320        if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile);
321        Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE);
322        intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
323        mContext.sendBroadcast(intent);
324    }
325
326    private boolean anyOpened() {
327        for (SipSessionGroupExt group : mSipGroups.values()) {
328            if (group.isOpened()) return true;
329        }
330        return false;
331    }
332
333    private void grabWifiLock() {
334        if (mWifiLock == null) {
335            if (DEBUG) Log.d(TAG, "acquire wifi lock");
336            mWifiLock = ((WifiManager)
337                    mContext.getSystemService(Context.WIFI_SERVICE))
338                    .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
339            mWifiLock.acquire();
340        }
341    }
342
343    private void releaseWifiLock() {
344        if (mWifiLock != null) {
345            if (DEBUG) Log.d(TAG, "release wifi lock");
346            mWifiLock.release();
347            mWifiLock = null;
348        }
349    }
350
351    private boolean isWifiActive() {
352        return "WIFI".equalsIgnoreCase(mNetworkType);
353    }
354
355    private synchronized void onConnectivityChanged(
356            String type, boolean connected) {
357        if (DEBUG) Log.d(TAG, "onConnectivityChanged(): "
358                + mNetworkType + (mConnected? " CONNECTED" : " DISCONNECTED")
359                + " --> " + type + (connected? " CONNECTED" : " DISCONNECTED"));
360
361        boolean sameType = type.equals(mNetworkType);
362        if (!sameType && !connected) return;
363
364        boolean wasWifi = "WIFI".equalsIgnoreCase(mNetworkType);
365        boolean isWifi = "WIFI".equalsIgnoreCase(type);
366        boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType);
367        boolean wifiOn = isWifi && connected;
368        if (wifiOff) {
369            releaseWifiLock();
370        } else if (wifiOn) {
371            if (anyOpened()) grabWifiLock();
372        }
373
374        try {
375            boolean wasConnected = mConnected;
376            mNetworkType = type;
377            mConnected = connected;
378
379            if (wasConnected) {
380                mLocalIp = null;
381                for (SipSessionGroupExt group : mSipGroups.values()) {
382                    group.onConnectivityChanged(false);
383                }
384            }
385
386            if (connected) {
387                mLocalIp = determineLocalIp();
388                for (SipSessionGroupExt group : mSipGroups.values()) {
389                    group.onConnectivityChanged(true);
390                }
391            }
392
393        } catch (SipException e) {
394            Log.e(TAG, "onConnectivityChanged()", e);
395        }
396    }
397
398    private synchronized void addPendingSession(ISipSession session) {
399        try {
400            mPendingSessions.put(session.getCallId(), session);
401        } catch (RemoteException e) {
402            // should not happen with a local call
403            Log.e(TAG, "addPendingSession()", e);
404        }
405    }
406
407    private class SipSessionGroupExt extends SipSessionAdapter {
408        private SipSessionGroup mSipGroup;
409        private PendingIntent mIncomingCallPendingIntent;
410        private boolean mOpened;
411
412        private AutoRegistrationProcess mAutoRegistration =
413                new AutoRegistrationProcess();
414
415        public SipSessionGroupExt(SipProfile localProfile,
416                PendingIntent incomingCallPendingIntent,
417                ISipSessionListener listener) throws SipException {
418            String password = localProfile.getPassword();
419            SipProfile p = duplicate(localProfile);
420            mSipGroup = createSipSessionGroup(mLocalIp, p, password);
421            mIncomingCallPendingIntent = incomingCallPendingIntent;
422            mAutoRegistration.setListener(listener);
423        }
424
425        public SipProfile getLocalProfile() {
426            return mSipGroup.getLocalProfile();
427        }
428
429        // network connectivity is tricky because network can be disconnected
430        // at any instant so need to deal with exceptions carefully even when
431        // you think you are connected
432        private SipSessionGroup createSipSessionGroup(String localIp,
433                SipProfile localProfile, String password) throws SipException {
434            try {
435                return new SipSessionGroup(localIp, localProfile, password);
436            } catch (IOException e) {
437                // network disconnected
438                Log.w(TAG, "createSipSessionGroup(): network disconnected?");
439                if (localIp != null) {
440                    return createSipSessionGroup(null, localProfile, password);
441                } else {
442                    // recursive
443                    Log.wtf(TAG, "impossible!");
444                    throw new RuntimeException("createSipSessionGroup");
445                }
446            }
447        }
448
449        private SipProfile duplicate(SipProfile p) {
450            try {
451                return new SipProfile.Builder(p).setPassword("*").build();
452            } catch (Exception e) {
453                Log.wtf(TAG, "duplicate()", e);
454                throw new RuntimeException("duplicate profile", e);
455            }
456        }
457
458        public void setListener(ISipSessionListener listener) {
459            mAutoRegistration.setListener(listener);
460        }
461
462        public void setIncomingCallPendingIntent(PendingIntent pIntent) {
463            mIncomingCallPendingIntent = pIntent;
464        }
465
466        public void openToReceiveCalls() throws SipException {
467            mOpened = true;
468            if (mConnected) {
469                mSipGroup.openToReceiveCalls(this);
470                mAutoRegistration.start(mSipGroup);
471            }
472            if (DEBUG) Log.d(TAG, "  openToReceiveCalls: " + getUri() + ": "
473                    + mIncomingCallPendingIntent);
474        }
475
476        public void onConnectivityChanged(boolean connected)
477                throws SipException {
478            mSipGroup.onConnectivityChanged();
479            if (connected) {
480                resetGroup(mLocalIp);
481                if (mOpened) openToReceiveCalls();
482            } else {
483                // close mSipGroup but remember mOpened
484                if (DEBUG) Log.d(TAG, "  close auto reg temporarily: "
485                        + getUri() + ": " + mIncomingCallPendingIntent);
486                mSipGroup.close();
487                mAutoRegistration.stop();
488            }
489        }
490
491        private void resetGroup(String localIp) throws SipException {
492            try {
493                mSipGroup.reset(localIp);
494            } catch (IOException e) {
495                // network disconnected
496                Log.w(TAG, "resetGroup(): network disconnected?");
497                if (localIp != null) {
498                    resetGroup(null); // reset w/o local IP
499                } else {
500                    // recursive
501                    Log.wtf(TAG, "impossible!");
502                    throw new RuntimeException("resetGroup");
503                }
504            }
505        }
506
507        public void close() {
508            mOpened = false;
509            mSipGroup.close();
510            mAutoRegistration.stop();
511            if (DEBUG) Log.d(TAG, "   close: " + getUri() + ": "
512                    + mIncomingCallPendingIntent);
513        }
514
515        public ISipSession createSession(ISipSessionListener listener) {
516            return mSipGroup.createSession(listener);
517        }
518
519        @Override
520        public void onRinging(ISipSession s, SipProfile caller,
521                String sessionDescription) {
522            SipSessionGroup.SipSessionImpl session =
523                    (SipSessionGroup.SipSessionImpl) s;
524            synchronized (SipService.this) {
525                try {
526                    if (!isRegistered()) {
527                        session.endCall();
528                        return;
529                    }
530
531                    // send out incoming call broadcast
532                    addPendingSession(session);
533                    Intent intent = SipManager.createIncomingCallBroadcast(
534                            session.getCallId(), sessionDescription);
535                    if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": "
536                            + caller.getUri() + ": " + session.getCallId()
537                            + " " + mIncomingCallPendingIntent);
538                    mIncomingCallPendingIntent.send(mContext,
539                            SipManager.INCOMING_CALL_RESULT_CODE, intent);
540                } catch (PendingIntent.CanceledException e) {
541                    Log.w(TAG, "pendingIntent is canceled, drop incoming call");
542                    session.endCall();
543                }
544            }
545        }
546
547        @Override
548        public void onError(ISipSession session, int errorCode,
549                String message) {
550            if (DEBUG) Log.d(TAG, "sip session error: "
551                    + SipErrorCode.toString(errorCode) + ": " + message);
552        }
553
554        public boolean isOpened() {
555            return mOpened;
556        }
557
558        public boolean isRegistered() {
559            return mAutoRegistration.isRegistered();
560        }
561
562        private String getUri() {
563            return mSipGroup.getLocalProfileUri();
564        }
565    }
566
567    // KeepAliveProcess is controlled by AutoRegistrationProcess.
568    // All methods will be invoked in sync with SipService.this except realRun()
569    private class KeepAliveProcess implements Runnable {
570        private static final String TAG = "\\KEEPALIVE/";
571        private static final int INTERVAL = 10;
572        private SipSessionGroup.SipSessionImpl mSession;
573        private boolean mRunning = false;
574
575        public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
576            mSession = session;
577        }
578
579        public void start() {
580            if (mRunning) return;
581            mRunning = true;
582            mTimer.set(INTERVAL * 1000, this);
583        }
584
585        // timeout handler
586        public void run() {
587            if (!mRunning) return;
588            final SipSessionGroup.SipSessionImpl session = mSession;
589
590            // delegate to mExecutor
591            getExecutor().addTask(new Runnable() {
592                public void run() {
593                    realRun(session);
594                }
595            });
596        }
597
598        // real timeout handler
599        private void realRun(SipSessionGroup.SipSessionImpl session) {
600            synchronized (SipService.this) {
601                if (notCurrentSession(session)) return;
602
603                session = session.duplicate();
604                if (DEBUGV) Log.v(TAG, "~~~ keepalive");
605                mTimer.cancel(this);
606                session.sendKeepAlive();
607                if (session.isReRegisterRequired()) {
608                    mSession.register(EXPIRY_TIME);
609                } else {
610                    mTimer.set(INTERVAL * 1000, this);
611                }
612            }
613        }
614
615        public void stop() {
616            mRunning = false;
617            mSession = null;
618            mTimer.cancel(this);
619        }
620
621        private boolean notCurrentSession(ISipSession session) {
622            return (session != mSession) || !mRunning;
623        }
624    }
625
626    private class AutoRegistrationProcess extends SipSessionAdapter
627            implements Runnable {
628        private SipSessionGroup.SipSessionImpl mSession;
629        private SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
630        private KeepAliveProcess mKeepAliveProcess;
631        private int mBackoff = 1;
632        private boolean mRegistered;
633        private long mExpiryTime;
634        private int mErrorCode;
635        private String mErrorMessage;
636        private boolean mRunning = false;
637
638        private String getAction() {
639            return toString();
640        }
641
642        public void start(SipSessionGroup group) {
643            if (!mRunning) {
644                mRunning = true;
645                mBackoff = 1;
646                mSession = (SipSessionGroup.SipSessionImpl)
647                        group.createSession(this);
648                // return right away if no active network connection.
649                if (mSession == null) return;
650
651                // start unregistration to clear up old registration at server
652                // TODO: when rfc5626 is deployed, use reg-id and sip.instance
653                // in registration to avoid adding duplicate entries to server
654                mSession.unregister();
655                if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for "
656                        + mSession.getLocalProfile().getUriString());
657            }
658        }
659
660        public void stop() {
661            if (!mRunning) return;
662            mRunning = false;
663            mSession.setListener(null);
664            if (mConnected && mRegistered) mSession.unregister();
665
666            mTimer.cancel(this);
667            if (mKeepAliveProcess != null) {
668                mKeepAliveProcess.stop();
669                mKeepAliveProcess = null;
670            }
671
672            mRegistered = false;
673            setListener(mProxy.getListener());
674        }
675
676        public void setListener(ISipSessionListener listener) {
677            synchronized (SipService.this) {
678                mProxy.setListener(listener);
679
680                try {
681                    int state = (mSession == null)
682                            ? SipSession.State.READY_TO_CALL
683                            : mSession.getState();
684                    if ((state == SipSession.State.REGISTERING)
685                            || (state == SipSession.State.DEREGISTERING)) {
686                        mProxy.onRegistering(mSession);
687                    } else if (mRegistered) {
688                        int duration = (int)
689                                (mExpiryTime - SystemClock.elapsedRealtime());
690                        mProxy.onRegistrationDone(mSession, duration);
691                    } else if (mErrorCode != SipErrorCode.NO_ERROR) {
692                        if (mErrorCode == SipErrorCode.TIME_OUT) {
693                            mProxy.onRegistrationTimeout(mSession);
694                        } else {
695                            mProxy.onRegistrationFailed(mSession, mErrorCode,
696                                    mErrorMessage);
697                        }
698                    } else if (!mConnected) {
699                        mProxy.onRegistrationFailed(mSession,
700                                SipErrorCode.DATA_CONNECTION_LOST,
701                                "no data connection");
702                    } else if (!mRunning) {
703                        mProxy.onRegistrationFailed(mSession,
704                                SipErrorCode.CLIENT_ERROR,
705                                "registration not running");
706                    } else {
707                        mProxy.onRegistrationFailed(mSession,
708                                SipErrorCode.IN_PROGRESS,
709                                String.valueOf(state));
710                    }
711                } catch (Throwable t) {
712                    Log.w(TAG, "setListener(): " + t);
713                }
714            }
715        }
716
717        public boolean isRegistered() {
718            return mRegistered;
719        }
720
721        // timeout handler
722        public void run() {
723            synchronized (SipService.this) {
724                if (!mRunning) return;
725                final SipSessionGroup.SipSessionImpl session = mSession;
726
727                // delegate to mExecutor
728                getExecutor().addTask(new Runnable() {
729                    public void run() {
730                        realRun(session);
731                    }
732                });
733            }
734        }
735
736        // real timeout handler
737        private void realRun(SipSessionGroup.SipSessionImpl session) {
738            synchronized (SipService.this) {
739                if (notCurrentSession(session)) return;
740                mErrorCode = SipErrorCode.NO_ERROR;
741                mErrorMessage = null;
742                if (DEBUG) Log.d(TAG, "~~~ registering");
743                if (mConnected) session.register(EXPIRY_TIME);
744            }
745        }
746
747        private boolean isBehindNAT(String address) {
748            try {
749                byte[] d = InetAddress.getByName(address).getAddress();
750                if ((d[0] == 10) ||
751                        (((0x000000FF & ((int)d[0])) == 172) &&
752                        ((0x000000F0 & ((int)d[1])) == 16)) ||
753                        (((0x000000FF & ((int)d[0])) == 192) &&
754                        ((0x000000FF & ((int)d[1])) == 168))) {
755                    return true;
756                }
757            } catch (UnknownHostException e) {
758                Log.e(TAG, "isBehindAT()" + address, e);
759            }
760            return false;
761        }
762
763        private void restart(int duration) {
764            if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later.");
765            mTimer.cancel(this);
766            mTimer.set(duration * 1000, this);
767        }
768
769        private int backoffDuration() {
770            int duration = SHORT_EXPIRY_TIME * mBackoff;
771            if (duration > 3600) {
772                duration = 3600;
773            } else {
774                mBackoff *= 2;
775            }
776            return duration;
777        }
778
779        @Override
780        public void onRegistering(ISipSession session) {
781            if (DEBUG) Log.d(TAG, "onRegistering(): " + session);
782            synchronized (SipService.this) {
783                if (notCurrentSession(session)) return;
784
785                mRegistered = false;
786                mProxy.onRegistering(session);
787            }
788        }
789
790        private boolean notCurrentSession(ISipSession session) {
791            if (session != mSession) {
792                ((SipSessionGroup.SipSessionImpl) session).setListener(null);
793                return true;
794            }
795            return !mRunning;
796        }
797
798        @Override
799        public void onRegistrationDone(ISipSession session, int duration) {
800            if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
801            synchronized (SipService.this) {
802                if (notCurrentSession(session)) return;
803
804                mProxy.onRegistrationDone(session, duration);
805
806                if (duration > 0) {
807                    mSession.clearReRegisterRequired();
808                    mExpiryTime = SystemClock.elapsedRealtime()
809                            + (duration * 1000);
810
811                    if (!mRegistered) {
812                        mRegistered = true;
813                        // allow some overlap to avoid call drop during renew
814                        duration -= MIN_EXPIRY_TIME;
815                        if (duration < MIN_EXPIRY_TIME) {
816                            duration = MIN_EXPIRY_TIME;
817                        }
818                        restart(duration);
819
820                        if (isBehindNAT(mLocalIp) ||
821                                mSession.getLocalProfile().getSendKeepAlive()) {
822                            if (mKeepAliveProcess == null) {
823                                mKeepAliveProcess =
824                                        new KeepAliveProcess(mSession);
825                            }
826                            mKeepAliveProcess.start();
827                        }
828                    }
829                } else {
830                    mRegistered = false;
831                    mExpiryTime = -1L;
832                    if (DEBUG) Log.d(TAG, "Refresh registration immediately");
833                    run();
834                }
835            }
836        }
837
838        @Override
839        public void onRegistrationFailed(ISipSession session, int errorCode,
840                String message) {
841            if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": "
842                    + SipErrorCode.toString(errorCode) + ": " + message);
843            synchronized (SipService.this) {
844                if (notCurrentSession(session)) return;
845
846                switch (errorCode) {
847                    case SipErrorCode.INVALID_CREDENTIALS:
848                    case SipErrorCode.SERVER_UNREACHABLE:
849                        if (DEBUG) Log.d(TAG, "   pause auto-registration");
850                        stop();
851                    default:
852                        restartLater();
853                }
854
855                mErrorCode = errorCode;
856                mErrorMessage = message;
857                mProxy.onRegistrationFailed(session, errorCode, message);
858            }
859        }
860
861        @Override
862        public void onRegistrationTimeout(ISipSession session) {
863            if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session);
864            synchronized (SipService.this) {
865                if (notCurrentSession(session)) return;
866
867                mErrorCode = SipErrorCode.TIME_OUT;
868                mProxy.onRegistrationTimeout(session);
869                restartLater();
870            }
871        }
872
873        private void restartLater() {
874            mRegistered = false;
875            restart(backoffDuration());
876            if (mKeepAliveProcess != null) {
877                mKeepAliveProcess.stop();
878                mKeepAliveProcess = null;
879            }
880        }
881    }
882
883    private class ConnectivityReceiver extends BroadcastReceiver {
884        private Timer mTimer = new Timer();
885        private MyTimerTask mTask;
886
887        @Override
888        public void onReceive(Context context, Intent intent) {
889            String action = intent.getAction();
890            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
891                Bundle b = intent.getExtras();
892                if (b != null) {
893                    NetworkInfo netInfo = (NetworkInfo)
894                            b.get(ConnectivityManager.EXTRA_NETWORK_INFO);
895                    String type = netInfo.getTypeName();
896                    NetworkInfo.State state = netInfo.getState();
897
898                    if (mWifiOnly && (netInfo.getType() !=
899                            ConnectivityManager.TYPE_WIFI)) {
900                        if (DEBUG) {
901                            Log.d(TAG, "Wifi only, other connectivity ignored: "
902                                    + type);
903                        }
904                        return;
905                    }
906
907                    NetworkInfo activeNetInfo = getActiveNetworkInfo();
908                    if (DEBUG) {
909                        if (activeNetInfo != null) {
910                            Log.d(TAG, "active network: "
911                                    + activeNetInfo.getTypeName()
912                                    + ((activeNetInfo.getState() == NetworkInfo.State.CONNECTED)
913                                            ? " CONNECTED" : " DISCONNECTED"));
914                        } else {
915                            Log.d(TAG, "active network: null");
916                        }
917                    }
918                    if ((state == NetworkInfo.State.CONNECTED)
919                            && (activeNetInfo != null)
920                            && (activeNetInfo.getType() != netInfo.getType())) {
921                        if (DEBUG) Log.d(TAG, "ignore connect event: " + type
922                                + ", active: " + activeNetInfo.getTypeName());
923                        return;
924                    }
925
926                    if (state == NetworkInfo.State.CONNECTED) {
927                        if (DEBUG) Log.d(TAG, "Connectivity alert: CONNECTED " + type);
928                        onChanged(type, true);
929                    } else if (state == NetworkInfo.State.DISCONNECTED) {
930                        if (DEBUG) Log.d(TAG, "Connectivity alert: DISCONNECTED " + type);
931                        onChanged(type, false);
932                    } else {
933                        if (DEBUG) Log.d(TAG, "Connectivity alert not processed: "
934                                + state + " " + type);
935                    }
936                }
937            }
938        }
939
940        private NetworkInfo getActiveNetworkInfo() {
941            ConnectivityManager cm = (ConnectivityManager)
942                    mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
943            return cm.getActiveNetworkInfo();
944        }
945
946        private void onChanged(String type, boolean connected) {
947            synchronized (SipService.this) {
948                // When turning on WIFI, it needs some time for network
949                // connectivity to get stabile so we defer good news (because
950                // we want to skip the interim ones) but deliver bad news
951                // immediately
952                if (connected) {
953                    if (mTask != null) mTask.cancel();
954                    mTask = new MyTimerTask(type, connected);
955                    mTimer.schedule(mTask, 2 * 1000L);
956                    // TODO: hold wakup lock so that we can finish change before
957                    // the device goes to sleep
958                } else {
959                    if ((mTask != null) && mTask.mNetworkType.equals(type)) {
960                        mTask.cancel();
961                    }
962                    onConnectivityChanged(type, false);
963                }
964            }
965        }
966
967        private class MyTimerTask extends TimerTask {
968            private boolean mConnected;
969            private String mNetworkType;
970
971            public MyTimerTask(String type, boolean connected) {
972                mNetworkType = type;
973                mConnected = connected;
974            }
975
976            // timeout handler
977            @Override
978            public void run() {
979                // delegate to mExecutor
980                getExecutor().addTask(new Runnable() {
981                    public void run() {
982                        realRun();
983                    }
984                });
985            }
986
987            private void realRun() {
988                synchronized (SipService.this) {
989                    if (mTask != this) {
990                        Log.w(TAG, "  unexpected task: " + mNetworkType
991                                + (mConnected ? " CONNECTED" : "DISCONNECTED"));
992                        return;
993                    }
994                    mTask = null;
995                    if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType
996                            + (mConnected ? " CONNECTED" : "DISCONNECTED"));
997                    onConnectivityChanged(mNetworkType, mConnected);
998                }
999            }
1000        }
1001    }
1002
1003    // TODO: clean up pending SipSession(s) periodically
1004
1005
1006    /**
1007     * Timer that can schedule events to occur even when the device is in sleep.
1008     * Only used internally in this package.
1009     */
1010    class WakeupTimer extends BroadcastReceiver {
1011        private static final String TAG = "_SIP.WkTimer_";
1012        private static final String TRIGGER_TIME = "TriggerTime";
1013
1014        private Context mContext;
1015        private AlarmManager mAlarmManager;
1016
1017        // runnable --> time to execute in SystemClock
1018        private TreeSet<MyEvent> mEventQueue =
1019                new TreeSet<MyEvent>(new MyEventComparator());
1020
1021        private PendingIntent mPendingIntent;
1022
1023        public WakeupTimer(Context context) {
1024            mContext = context;
1025            mAlarmManager = (AlarmManager)
1026                    context.getSystemService(Context.ALARM_SERVICE);
1027
1028            IntentFilter filter = new IntentFilter(getAction());
1029            context.registerReceiver(this, filter);
1030        }
1031
1032        /**
1033         * Stops the timer. No event can be scheduled after this method is called.
1034         */
1035        public synchronized void stop() {
1036            mContext.unregisterReceiver(this);
1037            if (mPendingIntent != null) {
1038                mAlarmManager.cancel(mPendingIntent);
1039                mPendingIntent = null;
1040            }
1041            mEventQueue.clear();
1042            mEventQueue = null;
1043        }
1044
1045        private synchronized boolean stopped() {
1046            if (mEventQueue == null) {
1047                Log.w(TAG, "Timer stopped");
1048                return true;
1049            } else {
1050                return false;
1051            }
1052        }
1053
1054        private void cancelAlarm() {
1055            mAlarmManager.cancel(mPendingIntent);
1056            mPendingIntent = null;
1057        }
1058
1059        private void recalculatePeriods() {
1060            if (mEventQueue.isEmpty()) return;
1061
1062            MyEvent firstEvent = mEventQueue.first();
1063            int minPeriod = firstEvent.mMaxPeriod;
1064            long minTriggerTime = firstEvent.mTriggerTime;
1065            for (MyEvent e : mEventQueue) {
1066                e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod;
1067                int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod
1068                        - minTriggerTime);
1069                interval = interval / minPeriod * minPeriod;
1070                e.mTriggerTime = minTriggerTime + interval;
1071            }
1072            TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(
1073                    mEventQueue.comparator());
1074            newQueue.addAll((Collection<MyEvent>) mEventQueue);
1075            mEventQueue.clear();
1076            mEventQueue = newQueue;
1077            if (DEBUG_TIMER) {
1078                Log.d(TAG, "queue re-calculated");
1079                printQueue();
1080            }
1081        }
1082
1083        // Determines the period and the trigger time of the new event and insert it
1084        // to the queue.
1085        private void insertEvent(MyEvent event) {
1086            long now = SystemClock.elapsedRealtime();
1087            if (mEventQueue.isEmpty()) {
1088                event.mTriggerTime = now + event.mPeriod;
1089                mEventQueue.add(event);
1090                return;
1091            }
1092            MyEvent firstEvent = mEventQueue.first();
1093            int minPeriod = firstEvent.mPeriod;
1094            if (minPeriod <= event.mMaxPeriod) {
1095                event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod;
1096                int interval = event.mMaxPeriod;
1097                interval -= (int) (firstEvent.mTriggerTime - now);
1098                interval = interval / minPeriod * minPeriod;
1099                event.mTriggerTime = firstEvent.mTriggerTime + interval;
1100                mEventQueue.add(event);
1101            } else {
1102                long triggerTime = now + event.mPeriod;
1103                if (firstEvent.mTriggerTime < triggerTime) {
1104                    event.mTriggerTime = firstEvent.mTriggerTime;
1105                    event.mLastTriggerTime -= event.mPeriod;
1106                } else {
1107                    event.mTriggerTime = triggerTime;
1108                }
1109                mEventQueue.add(event);
1110                recalculatePeriods();
1111            }
1112        }
1113
1114        /**
1115         * Sets a periodic timer.
1116         *
1117         * @param period the timer period; in milli-second
1118         * @param callback is called back when the timer goes off; the same callback
1119         *      can be specified in multiple timer events
1120         */
1121        public synchronized void set(int period, Runnable callback) {
1122            if (stopped()) return;
1123
1124            long now = SystemClock.elapsedRealtime();
1125            MyEvent event = new MyEvent(period, callback, now);
1126            insertEvent(event);
1127
1128            if (mEventQueue.first() == event) {
1129                if (mEventQueue.size() > 1) cancelAlarm();
1130                scheduleNext();
1131            }
1132
1133            long triggerTime = event.mTriggerTime;
1134            if (DEBUG_TIMER) {
1135                Log.d(TAG, " add event " + event + " scheduled at "
1136                        + showTime(triggerTime) + " at " + showTime(now)
1137                        + ", #events=" + mEventQueue.size());
1138                printQueue();
1139            }
1140        }
1141
1142        /**
1143         * Cancels all the timer events with the specified callback.
1144         *
1145         * @param callback the callback
1146         */
1147        public synchronized void cancel(Runnable callback) {
1148            if (stopped() || mEventQueue.isEmpty()) return;
1149            if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback);
1150
1151            MyEvent firstEvent = mEventQueue.first();
1152            for (Iterator<MyEvent> iter = mEventQueue.iterator();
1153                    iter.hasNext();) {
1154                MyEvent event = iter.next();
1155                if (event.mCallback == callback) {
1156                    iter.remove();
1157                    if (DEBUG_TIMER) Log.d(TAG, "    cancel found:" + event);
1158                }
1159            }
1160            if (mEventQueue.isEmpty()) {
1161                cancelAlarm();
1162            } else if (mEventQueue.first() != firstEvent) {
1163                cancelAlarm();
1164                firstEvent = mEventQueue.first();
1165                firstEvent.mPeriod = firstEvent.mMaxPeriod;
1166                firstEvent.mTriggerTime = firstEvent.mLastTriggerTime
1167                        + firstEvent.mPeriod;
1168                recalculatePeriods();
1169                scheduleNext();
1170            }
1171            if (DEBUG_TIMER) {
1172                Log.d(TAG, "after cancel:");
1173                printQueue();
1174            }
1175        }
1176
1177        private void scheduleNext() {
1178            if (stopped() || mEventQueue.isEmpty()) return;
1179
1180            if (mPendingIntent != null) {
1181                throw new RuntimeException("pendingIntent is not null!");
1182            }
1183
1184            MyEvent event = mEventQueue.first();
1185            Intent intent = new Intent(getAction());
1186            intent.putExtra(TRIGGER_TIME, event.mTriggerTime);
1187            PendingIntent pendingIntent = mPendingIntent =
1188                    PendingIntent.getBroadcast(mContext, 0, intent,
1189                            PendingIntent.FLAG_UPDATE_CURRENT);
1190            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
1191                    event.mTriggerTime, pendingIntent);
1192        }
1193
1194        @Override
1195        public synchronized void onReceive(Context context, Intent intent) {
1196            String action = intent.getAction();
1197            if (getAction().equals(action)
1198                    && intent.getExtras().containsKey(TRIGGER_TIME)) {
1199                mPendingIntent = null;
1200                long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L);
1201                execute(triggerTime);
1202            } else {
1203                Log.d(TAG, "unrecognized intent: " + intent);
1204            }
1205        }
1206
1207        private void printQueue() {
1208            int count = 0;
1209            for (MyEvent event : mEventQueue) {
1210                Log.d(TAG, "     " + event + ": scheduled at "
1211                        + showTime(event.mTriggerTime) + ": last at "
1212                        + showTime(event.mLastTriggerTime));
1213                if (++count >= 5) break;
1214            }
1215            if (mEventQueue.size() > count) {
1216                Log.d(TAG, "     .....");
1217            } else if (count == 0) {
1218                Log.d(TAG, "     <empty>");
1219            }
1220        }
1221
1222        private void execute(long triggerTime) {
1223            if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = "
1224                    + showTime(triggerTime) + ": " + mEventQueue.size());
1225            if (stopped() || mEventQueue.isEmpty()) return;
1226
1227            for (MyEvent event : mEventQueue) {
1228                if (event.mTriggerTime != triggerTime) break;
1229                if (DEBUG_TIMER) Log.d(TAG, "execute " + event);
1230
1231                event.mLastTriggerTime = event.mTriggerTime;
1232                event.mTriggerTime += event.mPeriod;
1233
1234                // run the callback in a new thread to prevent deadlock
1235                new Thread(event.mCallback, "SipServiceTimerCallbackThread")
1236                        .start();
1237            }
1238            if (DEBUG_TIMER) {
1239                Log.d(TAG, "after timeout execution");
1240                printQueue();
1241            }
1242            scheduleNext();
1243        }
1244
1245        private String getAction() {
1246            return toString();
1247        }
1248
1249        private String showTime(long time) {
1250            int ms = (int) (time % 1000);
1251            int s = (int) (time / 1000);
1252            int m = s / 60;
1253            s %= 60;
1254            return String.format("%d.%d.%d", m, s, ms);
1255        }
1256    }
1257
1258    private static class MyEvent {
1259        int mPeriod;
1260        int mMaxPeriod;
1261        long mTriggerTime;
1262        long mLastTriggerTime;
1263        Runnable mCallback;
1264
1265        MyEvent(int period, Runnable callback, long now) {
1266            mPeriod = mMaxPeriod = period;
1267            mCallback = callback;
1268            mLastTriggerTime = now;
1269        }
1270
1271        @Override
1272        public String toString() {
1273            String s = super.toString();
1274            s = s.substring(s.indexOf("@"));
1275            return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":"
1276                    + toString(mCallback);
1277        }
1278
1279        private String toString(Object o) {
1280            String s = o.toString();
1281            int index = s.indexOf("$");
1282            if (index > 0) s = s.substring(index + 1);
1283            return s;
1284        }
1285    }
1286
1287    private static class MyEventComparator implements Comparator<MyEvent> {
1288        public int compare(MyEvent e1, MyEvent e2) {
1289            if (e1 == e2) return 0;
1290            int diff = e1.mMaxPeriod - e2.mMaxPeriod;
1291            if (diff == 0) diff = -1;
1292            return diff;
1293        }
1294
1295        public boolean equals(Object that) {
1296            return (this == that);
1297        }
1298    }
1299
1300    // Single-threaded executor
1301    private static class MyExecutor extends Handler {
1302        MyExecutor() {
1303            super(createLooper());
1304        }
1305
1306        private static Looper createLooper() {
1307            HandlerThread thread = new HandlerThread("SipService");
1308            thread.start();
1309            return thread.getLooper();
1310        }
1311
1312        void addTask(Runnable task) {
1313            Message.obtain(this, 0/* don't care */, task).sendToTarget();
1314        }
1315
1316        @Override
1317        public void handleMessage(Message msg) {
1318            if (msg.obj instanceof Runnable) {
1319                ((Runnable) msg.obj).run();
1320            } else {
1321                Log.w(TAG, "can't handle msg: " + msg);
1322            }
1323        }
1324    }
1325}
1326