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