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