diff options
Diffstat (limited to 'services/java/com/android/server/sip/SipService.java')
-rw-r--r-- | services/java/com/android/server/sip/SipService.java | 1231 |
1 files changed, 0 insertions, 1231 deletions
diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java deleted file mode 100644 index 3f43e1c..0000000 --- a/services/java/com/android/server/sip/SipService.java +++ /dev/null @@ -1,1231 +0,0 @@ -/* - * Copyright (C) 2010, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.sip; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.sip.ISipService; -import android.net.sip.ISipSession; -import android.net.sip.ISipSessionListener; -import android.net.sip.SipErrorCode; -import android.net.sip.SipManager; -import android.net.sip.SipProfile; -import android.net.sip.SipSession; -import android.net.sip.SipSessionAdapter; -import android.net.wifi.WifiManager; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.Log; - -import java.io.IOException; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.TreeSet; -import javax.sip.SipException; - -/** - * @hide - */ -public final class SipService extends ISipService.Stub { - private static final String TAG = "SipService"; - private static final boolean DEBUG = true; - private static final boolean DEBUG_TIMER = DEBUG && false; - private static final int EXPIRY_TIME = 3600; - private static final int SHORT_EXPIRY_TIME = 10; - private static final int MIN_EXPIRY_TIME = 60; - - private Context mContext; - private String mLocalIp; - private String mNetworkType; - private boolean mConnected; - private WakeupTimer mTimer; - private WifiManager.WifiLock mWifiLock; - private boolean mWifiOnly; - - private MyExecutor mExecutor; - - // SipProfile URI --> group - private Map<String, SipSessionGroupExt> mSipGroups = - new HashMap<String, SipSessionGroupExt>(); - - // session ID --> session - private Map<String, ISipSession> mPendingSessions = - new HashMap<String, ISipSession>(); - - private ConnectivityReceiver mConnectivityReceiver; - - /** - * Creates a {@code SipService} instance. Returns null if SIP API is not - * supported. - */ - public static SipService create(Context context) { - return (SipManager.isApiSupported(context) ? new SipService(context) - : null); - } - - private SipService(Context context) { - if (DEBUG) Log.d(TAG, " service started!"); - mContext = context; - mConnectivityReceiver = new ConnectivityReceiver(); - context.registerReceiver(mConnectivityReceiver, - new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - - mTimer = new WakeupTimer(context); - mWifiOnly = SipManager.isSipWifiOnly(context); - } - - private MyExecutor getExecutor() { - // create mExecutor lazily - if (mExecutor == null) mExecutor = new MyExecutor(); - return mExecutor; - } - - public synchronized SipProfile[] getListOfProfiles() { - SipProfile[] profiles = new SipProfile[mSipGroups.size()]; - int i = 0; - for (SipSessionGroupExt group : mSipGroups.values()) { - profiles[i++] = group.getLocalProfile(); - } - return profiles; - } - - public void open(SipProfile localProfile) { - localProfile.setCallingUid(Binder.getCallingUid()); - if (localProfile.getAutoRegistration()) { - openToReceiveCalls(localProfile); - } else { - openToMakeCalls(localProfile); - } - } - - private void openToMakeCalls(SipProfile localProfile) { - try { - createGroup(localProfile); - } catch (SipException e) { - Log.e(TAG, "openToMakeCalls()", e); - // TODO: how to send the exception back - } - } - - private void openToReceiveCalls(SipProfile localProfile) { - open3(localProfile, SipManager.ACTION_SIP_INCOMING_CALL, null); - } - - public synchronized void open3(SipProfile localProfile, - String incomingCallBroadcastAction, ISipSessionListener listener) { - localProfile.setCallingUid(Binder.getCallingUid()); - if (TextUtils.isEmpty(incomingCallBroadcastAction)) { - throw new RuntimeException( - "empty broadcast action for incoming call"); - } - if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " - + incomingCallBroadcastAction + ": " + listener); - try { - SipSessionGroupExt group = createGroup(localProfile, - incomingCallBroadcastAction, listener); - if (localProfile.getAutoRegistration()) { - group.openToReceiveCalls(); - if (isWifiOn()) grabWifiLock(); - } - } catch (SipException e) { - Log.e(TAG, "openToReceiveCalls()", e); - // TODO: how to send the exception back - } - } - - public synchronized void close(String localProfileUri) { - SipSessionGroupExt group = mSipGroups.remove(localProfileUri); - if (group != null) { - notifyProfileRemoved(group.getLocalProfile()); - group.close(); - if (isWifiOn() && !anyOpened()) releaseWifiLock(); - } - } - - public synchronized boolean isOpened(String localProfileUri) { - SipSessionGroupExt group = mSipGroups.get(localProfileUri); - return ((group != null) ? group.isOpened() : false); - } - - public synchronized boolean isRegistered(String localProfileUri) { - SipSessionGroupExt group = mSipGroups.get(localProfileUri); - return ((group != null) ? group.isRegistered() : false); - } - - public synchronized void setRegistrationListener(String localProfileUri, - ISipSessionListener listener) { - SipSessionGroupExt group = mSipGroups.get(localProfileUri); - if (group != null) group.setListener(listener); - } - - public synchronized ISipSession createSession(SipProfile localProfile, - ISipSessionListener listener) { - localProfile.setCallingUid(Binder.getCallingUid()); - if (!mConnected) return null; - try { - SipSessionGroupExt group = createGroup(localProfile); - return group.createSession(listener); - } catch (SipException e) { - Log.w(TAG, "createSession()", e); - return null; - } - } - - public synchronized ISipSession getPendingSession(String callId) { - if (callId == null) return null; - return mPendingSessions.get(callId); - } - - private String determineLocalIp() { - try { - DatagramSocket s = new DatagramSocket(); - s.connect(InetAddress.getByName("192.168.1.1"), 80); - return s.getLocalAddress().getHostAddress(); - } catch (IOException e) { - Log.w(TAG, "determineLocalIp()", e); - // dont do anything; there should be a connectivity change going - return null; - } - } - - private SipSessionGroupExt createGroup(SipProfile localProfile) - throws SipException { - String key = localProfile.getUriString(); - SipSessionGroupExt group = mSipGroups.get(key); - if (group == null) { - group = new SipSessionGroupExt(localProfile, null, null); - mSipGroups.put(key, group); - notifyProfileAdded(localProfile); - } - return group; - } - - private SipSessionGroupExt createGroup(SipProfile localProfile, - String incomingCallBroadcastAction, ISipSessionListener listener) - throws SipException { - String key = localProfile.getUriString(); - SipSessionGroupExt group = mSipGroups.get(key); - if (group != null) { - group.setIncomingCallBroadcastAction( - incomingCallBroadcastAction); - group.setListener(listener); - } else { - group = new SipSessionGroupExt(localProfile, - incomingCallBroadcastAction, listener); - mSipGroups.put(key, group); - notifyProfileAdded(localProfile); - } - return group; - } - - private void notifyProfileAdded(SipProfile localProfile) { - if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile); - Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE); - intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); - mContext.sendBroadcast(intent); - } - - private void notifyProfileRemoved(SipProfile localProfile) { - if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile); - Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE); - intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); - mContext.sendBroadcast(intent); - } - - private boolean anyOpened() { - for (SipSessionGroupExt group : mSipGroups.values()) { - if (group.isOpened()) return true; - } - return false; - } - - private void grabWifiLock() { - if (mWifiLock == null) { - if (DEBUG) Log.d(TAG, "acquire wifi lock"); - mWifiLock = ((WifiManager) - mContext.getSystemService(Context.WIFI_SERVICE)) - .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); - mWifiLock.acquire(); - } - } - - private void releaseWifiLock() { - if (mWifiLock != null) { - if (DEBUG) Log.d(TAG, "release wifi lock"); - mWifiLock.release(); - mWifiLock = null; - } - } - - private boolean isWifiOn() { - return "WIFI".equalsIgnoreCase(mNetworkType); - //return (mConnected && "WIFI".equalsIgnoreCase(mNetworkType)); - } - - private synchronized void onConnectivityChanged( - String type, boolean connected) { - if (DEBUG) Log.d(TAG, "onConnectivityChanged(): " - + mNetworkType + (mConnected? " CONNECTED" : " DISCONNECTED") - + " --> " + type + (connected? " CONNECTED" : " DISCONNECTED")); - - boolean sameType = type.equals(mNetworkType); - if (!sameType && !connected) return; - - boolean wasWifi = "WIFI".equalsIgnoreCase(mNetworkType); - boolean isWifi = "WIFI".equalsIgnoreCase(type); - boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType); - boolean wifiOn = isWifi && connected; - if (wifiOff) { - releaseWifiLock(); - } else if (wifiOn) { - if (anyOpened()) grabWifiLock(); - } - - try { - boolean wasConnected = mConnected; - mNetworkType = type; - mConnected = connected; - - if (wasConnected) { - mLocalIp = null; - for (SipSessionGroupExt group : mSipGroups.values()) { - group.onConnectivityChanged(false); - } - } - - if (connected) { - mLocalIp = determineLocalIp(); - for (SipSessionGroupExt group : mSipGroups.values()) { - group.onConnectivityChanged(true); - } - } - - } catch (SipException e) { - Log.e(TAG, "onConnectivityChanged()", e); - } - } - - private synchronized void addPendingSession(ISipSession session) { - try { - mPendingSessions.put(session.getCallId(), session); - } catch (RemoteException e) { - // should not happen with a local call - Log.e(TAG, "addPendingSession()", e); - } - } - - private class SipSessionGroupExt extends SipSessionAdapter { - private SipSessionGroup mSipGroup; - private String mIncomingCallBroadcastAction; - private boolean mOpened; - - private AutoRegistrationProcess mAutoRegistration = - new AutoRegistrationProcess(); - - public SipSessionGroupExt(SipProfile localProfile, - String incomingCallBroadcastAction, - ISipSessionListener listener) throws SipException { - String password = localProfile.getPassword(); - SipProfile p = duplicate(localProfile); - mSipGroup = createSipSessionGroup(mLocalIp, p, password); - mIncomingCallBroadcastAction = incomingCallBroadcastAction; - mAutoRegistration.setListener(listener); - } - - public SipProfile getLocalProfile() { - return mSipGroup.getLocalProfile(); - } - - // network connectivity is tricky because network can be disconnected - // at any instant so need to deal with exceptions carefully even when - // you think you are connected - private SipSessionGroup createSipSessionGroup(String localIp, - SipProfile localProfile, String password) throws SipException { - try { - return new SipSessionGroup(localIp, localProfile, password); - } catch (IOException e) { - // network disconnected - Log.w(TAG, "createSipSessionGroup(): network disconnected?"); - if (localIp != null) { - return createSipSessionGroup(null, localProfile, password); - } else { - // recursive - Log.wtf(TAG, "impossible!"); - throw new RuntimeException("createSipSessionGroup"); - } - } - } - - private SipProfile duplicate(SipProfile p) { - try { - return new SipProfile.Builder(p).setPassword("*").build(); - } catch (Exception e) { - Log.wtf(TAG, "duplicate()", e); - throw new RuntimeException("duplicate profile", e); - } - } - - public void setListener(ISipSessionListener listener) { - mAutoRegistration.setListener(listener); - } - - public void setIncomingCallBroadcastAction(String action) { - mIncomingCallBroadcastAction = action; - } - - public void openToReceiveCalls() throws SipException { - mOpened = true; - if (mConnected) { - mSipGroup.openToReceiveCalls(this); - mAutoRegistration.start(mSipGroup); - } - if (DEBUG) Log.d(TAG, " openToReceiveCalls: " + getUri() + ": " - + mIncomingCallBroadcastAction); - } - - public void onConnectivityChanged(boolean connected) - throws SipException { - mSipGroup.onConnectivityChanged(); - if (connected) { - resetGroup(mLocalIp); - if (mOpened) openToReceiveCalls(); - } else { - // close mSipGroup but remember mOpened - if (DEBUG) Log.d(TAG, " close auto reg temporarily: " - + getUri() + ": " + mIncomingCallBroadcastAction); - mSipGroup.close(); - mAutoRegistration.stop(); - } - } - - private void resetGroup(String localIp) throws SipException { - try { - mSipGroup.reset(localIp); - } catch (IOException e) { - // network disconnected - Log.w(TAG, "resetGroup(): network disconnected?"); - if (localIp != null) { - resetGroup(null); // reset w/o local IP - } else { - // recursive - Log.wtf(TAG, "impossible!"); - throw new RuntimeException("resetGroup"); - } - } - } - - public void close() { - mOpened = false; - mSipGroup.close(); - mAutoRegistration.stop(); - if (DEBUG) Log.d(TAG, " close: " + getUri() + ": " - + mIncomingCallBroadcastAction); - } - - public ISipSession createSession(ISipSessionListener listener) { - return mSipGroup.createSession(listener); - } - - @Override - public void onRinging(ISipSession session, SipProfile caller, - String sessionDescription) { - synchronized (SipService.this) { - try { - if (!isRegistered()) { - session.endCall(); - return; - } - - // send out incoming call broadcast - addPendingSession(session); - Intent intent = SipManager.createIncomingCallBroadcast( - session.getCallId(), sessionDescription) - .setAction(mIncomingCallBroadcastAction); - if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": " - + caller.getUri() + ": " + session.getCallId() - + " " + mIncomingCallBroadcastAction); - mContext.sendBroadcast(intent); - } catch (RemoteException e) { - // should never happen with a local call - Log.e(TAG, "processCall()", e); - } - } - } - - @Override - public void onError(ISipSession session, int errorCode, - String message) { - if (DEBUG) Log.d(TAG, "sip session error: " - + SipErrorCode.toString(errorCode) + ": " + message); - } - - public boolean isOpened() { - return mOpened; - } - - public boolean isRegistered() { - return mAutoRegistration.isRegistered(); - } - - private String getUri() { - return mSipGroup.getLocalProfileUri(); - } - } - - private class KeepAliveProcess implements Runnable { - private static final String TAG = "\\KEEPALIVE/"; - private static final int INTERVAL = 10; - private SipSessionGroup.SipSessionImpl mSession; - - public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { - mSession = session; - } - - public void start() { - mTimer.set(INTERVAL * 1000, this); - } - - public void run() { - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(); - } - }); - } - - private void realRun() { - synchronized (SipService.this) { - SipSessionGroup.SipSessionImpl session = mSession.duplicate(); - if (DEBUG) Log.d(TAG, "~~~ keepalive"); - mTimer.cancel(this); - session.sendKeepAlive(); - if (session.isReRegisterRequired()) { - mSession.register(EXPIRY_TIME); - } else { - mTimer.set(INTERVAL * 1000, this); - } - } - } - - public void stop() { - mTimer.cancel(this); - } - } - - private class AutoRegistrationProcess extends SipSessionAdapter - implements Runnable { - private SipSessionGroup.SipSessionImpl mSession; - private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); - private KeepAliveProcess mKeepAliveProcess; - private int mBackoff = 1; - private boolean mRegistered; - private long mExpiryTime; - private int mErrorCode; - private String mErrorMessage; - - private String getAction() { - return toString(); - } - - public void start(SipSessionGroup group) { - if (mSession == null) { - mBackoff = 1; - mSession = (SipSessionGroup.SipSessionImpl) - group.createSession(this); - // return right away if no active network connection. - if (mSession == null) return; - - // start unregistration to clear up old registration at server - // TODO: when rfc5626 is deployed, use reg-id and sip.instance - // in registration to avoid adding duplicate entries to server - mSession.unregister(); - if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for " - + mSession.getLocalProfile().getUriString()); - } - } - - public void stop() { - stop(false); - } - - private void stopButKeepStates() { - stop(true); - } - - private void stop(boolean keepStates) { - if (mSession == null) return; - if (mConnected && mRegistered) mSession.unregister(); - mTimer.cancel(this); - if (mKeepAliveProcess != null) { - mKeepAliveProcess.stop(); - mKeepAliveProcess = null; - } - if (!keepStates) { - mSession = null; - mRegistered = false; - } - } - - private boolean isStopped() { - return (mSession == null); - } - - public void setListener(ISipSessionListener listener) { - synchronized (SipService.this) { - mProxy.setListener(listener); - if (mSession == null) return; - - try { - int state = (mSession == null) - ? SipSession.State.READY_TO_CALL - : mSession.getState(); - if ((state == SipSession.State.REGISTERING) - || (state == SipSession.State.DEREGISTERING)) { - mProxy.onRegistering(mSession); - } else if (mRegistered) { - int duration = (int) - (mExpiryTime - SystemClock.elapsedRealtime()); - mProxy.onRegistrationDone(mSession, duration); - } else if (mErrorCode != SipErrorCode.NO_ERROR) { - if (mErrorCode == SipErrorCode.TIME_OUT) { - mProxy.onRegistrationTimeout(mSession); - } else { - mProxy.onRegistrationFailed(mSession, mErrorCode, - mErrorMessage); - } - } - } catch (Throwable t) { - Log.w(TAG, "setListener(): " + t); - } - } - } - - public boolean isRegistered() { - return mRegistered; - } - - public void run() { - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(); - } - }); - } - - private void realRun() { - mErrorCode = SipErrorCode.NO_ERROR; - mErrorMessage = null; - if (DEBUG) Log.d(TAG, "~~~ registering"); - synchronized (SipService.this) { - if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME); - } - } - - private boolean isBehindNAT(String address) { - try { - byte[] d = InetAddress.getByName(address).getAddress(); - if ((d[0] == 10) || - (((0x000000FF & ((int)d[0])) == 172) && - ((0x000000F0 & ((int)d[1])) == 16)) || - (((0x000000FF & ((int)d[0])) == 192) && - ((0x000000FF & ((int)d[1])) == 168))) { - return true; - } - } catch (UnknownHostException e) { - Log.e(TAG, "isBehindAT()" + address, e); - } - return false; - } - - private void restart(int duration) { - if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later."); - mTimer.cancel(this); - mTimer.set(duration * 1000, this); - } - - private int backoffDuration() { - int duration = SHORT_EXPIRY_TIME * mBackoff; - if (duration > 3600) { - duration = 3600; - } else { - mBackoff *= 2; - } - return duration; - } - - @Override - public void onRegistering(ISipSession session) { - if (DEBUG) Log.d(TAG, "onRegistering(): " + session); - synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; - mRegistered = false; - mProxy.onRegistering(session); - } - } - - @Override - public void onRegistrationDone(ISipSession session, int duration) { - if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session); - synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; - - mProxy.onRegistrationDone(session, duration); - - if (isStopped()) return; - - if (duration > 0) { - mSession.clearReRegisterRequired(); - mExpiryTime = SystemClock.elapsedRealtime() - + (duration * 1000); - - if (!mRegistered) { - mRegistered = true; - // allow some overlap to avoid call drop during renew - duration -= MIN_EXPIRY_TIME; - if (duration < MIN_EXPIRY_TIME) { - duration = MIN_EXPIRY_TIME; - } - restart(duration); - - if (isBehindNAT(mLocalIp) || - mSession.getLocalProfile().getSendKeepAlive()) { - if (mKeepAliveProcess == null) { - mKeepAliveProcess = - new KeepAliveProcess(mSession); - } - mKeepAliveProcess.start(); - } - } - } else { - mRegistered = false; - mExpiryTime = -1L; - if (DEBUG) Log.d(TAG, "Refresh registration immediately"); - run(); - } - } - } - - @Override - public void onRegistrationFailed(ISipSession session, int errorCode, - String message) { - if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": " - + SipErrorCode.toString(errorCode) + ": " + message); - synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; - mErrorCode = errorCode; - mErrorMessage = message; - mProxy.onRegistrationFailed(session, errorCode, message); - - if (errorCode == SipErrorCode.INVALID_CREDENTIALS) { - if (DEBUG) Log.d(TAG, " pause auto-registration"); - stopButKeepStates(); - } else if (!isStopped()) { - onError(); - } - } - } - - @Override - public void onRegistrationTimeout(ISipSession session) { - if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session); - synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; - mErrorCode = SipErrorCode.TIME_OUT; - mProxy.onRegistrationTimeout(session); - - if (!isStopped()) { - mRegistered = false; - onError(); - } - } - } - - private void onError() { - mRegistered = false; - restart(backoffDuration()); - if (mKeepAliveProcess != null) { - mKeepAliveProcess.stop(); - mKeepAliveProcess = null; - } - } - } - - private class ConnectivityReceiver extends BroadcastReceiver { - private Timer mTimer = new Timer(); - private MyTimerTask mTask; - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - Bundle b = intent.getExtras(); - if (b != null) { - NetworkInfo netInfo = (NetworkInfo) - b.get(ConnectivityManager.EXTRA_NETWORK_INFO); - String type = netInfo.getTypeName(); - NetworkInfo.State state = netInfo.getState(); - - if (mWifiOnly && (netInfo.getType() != - ConnectivityManager.TYPE_WIFI)) { - if (DEBUG) { - Log.d(TAG, "Wifi only, other connectivity ignored: " - + type); - } - return; - } - - NetworkInfo activeNetInfo = getActiveNetworkInfo(); - if (DEBUG) { - if (activeNetInfo != null) { - Log.d(TAG, "active network: " - + activeNetInfo.getTypeName() - + ((activeNetInfo.getState() == NetworkInfo.State.CONNECTED) - ? " CONNECTED" : " DISCONNECTED")); - } else { - Log.d(TAG, "active network: null"); - } - } - if ((state == NetworkInfo.State.CONNECTED) - && (activeNetInfo != null) - && (activeNetInfo.getType() != netInfo.getType())) { - if (DEBUG) Log.d(TAG, "ignore connect event: " + type - + ", active: " + activeNetInfo.getTypeName()); - return; - } - - if (state == NetworkInfo.State.CONNECTED) { - if (DEBUG) Log.d(TAG, "Connectivity alert: CONNECTED " + type); - onChanged(type, true); - } else if (state == NetworkInfo.State.DISCONNECTED) { - if (DEBUG) Log.d(TAG, "Connectivity alert: DISCONNECTED " + type); - onChanged(type, false); - } else { - if (DEBUG) Log.d(TAG, "Connectivity alert not processed: " - + state + " " + type); - } - } - } - } - - private NetworkInfo getActiveNetworkInfo() { - ConnectivityManager cm = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - return cm.getActiveNetworkInfo(); - } - - private void onChanged(String type, boolean connected) { - synchronized (SipService.this) { - // When turning on WIFI, it needs some time for network - // connectivity to get stabile so we defer good news (because - // we want to skip the interim ones) but deliver bad news - // immediately - if (connected) { - if (mTask != null) mTask.cancel(); - mTask = new MyTimerTask(type, connected); - mTimer.schedule(mTask, 2 * 1000L); - // TODO: hold wakup lock so that we can finish change before - // the device goes to sleep - } else { - if ((mTask != null) && mTask.mNetworkType.equals(type)) { - mTask.cancel(); - } - onConnectivityChanged(type, false); - } - } - } - - private class MyTimerTask extends TimerTask { - private boolean mConnected; - private String mNetworkType; - - public MyTimerTask(String type, boolean connected) { - mNetworkType = type; - mConnected = connected; - } - - @Override - public void run() { - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(); - } - }); - } - - private void realRun() { - synchronized (SipService.this) { - if (mTask != this) { - Log.w(TAG, " unexpected task: " + mNetworkType - + (mConnected ? " CONNECTED" : "DISCONNECTED")); - return; - } - mTask = null; - if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType - + (mConnected ? " CONNECTED" : "DISCONNECTED")); - onConnectivityChanged(mNetworkType, mConnected); - } - } - } - } - - // TODO: clean up pending SipSession(s) periodically - - - /** - * Timer that can schedule events to occur even when the device is in sleep. - * Only used internally in this package. - */ - class WakeupTimer extends BroadcastReceiver { - private static final String TAG = "_SIP.WkTimer_"; - private static final String TRIGGER_TIME = "TriggerTime"; - - private Context mContext; - private AlarmManager mAlarmManager; - - // runnable --> time to execute in SystemClock - private TreeSet<MyEvent> mEventQueue = - new TreeSet<MyEvent>(new MyEventComparator()); - - private PendingIntent mPendingIntent; - - public WakeupTimer(Context context) { - mContext = context; - mAlarmManager = (AlarmManager) - context.getSystemService(Context.ALARM_SERVICE); - - IntentFilter filter = new IntentFilter(getAction()); - context.registerReceiver(this, filter); - } - - /** - * Stops the timer. No event can be scheduled after this method is called. - */ - public synchronized void stop() { - mContext.unregisterReceiver(this); - if (mPendingIntent != null) { - mAlarmManager.cancel(mPendingIntent); - mPendingIntent = null; - } - mEventQueue.clear(); - mEventQueue = null; - } - - private synchronized boolean stopped() { - if (mEventQueue == null) { - Log.w(TAG, "Timer stopped"); - return true; - } else { - return false; - } - } - - private void cancelAlarm() { - mAlarmManager.cancel(mPendingIntent); - mPendingIntent = null; - } - - private void recalculatePeriods() { - if (mEventQueue.isEmpty()) return; - - MyEvent firstEvent = mEventQueue.first(); - int minPeriod = firstEvent.mMaxPeriod; - long minTriggerTime = firstEvent.mTriggerTime; - for (MyEvent e : mEventQueue) { - e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod; - int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod - - minTriggerTime); - interval = interval / minPeriod * minPeriod; - e.mTriggerTime = minTriggerTime + interval; - } - TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>( - mEventQueue.comparator()); - newQueue.addAll((Collection<MyEvent>) mEventQueue); - mEventQueue.clear(); - mEventQueue = newQueue; - if (DEBUG_TIMER) { - Log.d(TAG, "queue re-calculated"); - printQueue(); - } - } - - // Determines the period and the trigger time of the new event and insert it - // to the queue. - private void insertEvent(MyEvent event) { - long now = SystemClock.elapsedRealtime(); - if (mEventQueue.isEmpty()) { - event.mTriggerTime = now + event.mPeriod; - mEventQueue.add(event); - return; - } - MyEvent firstEvent = mEventQueue.first(); - int minPeriod = firstEvent.mPeriod; - if (minPeriod <= event.mMaxPeriod) { - event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod; - int interval = event.mMaxPeriod; - interval -= (int) (firstEvent.mTriggerTime - now); - interval = interval / minPeriod * minPeriod; - event.mTriggerTime = firstEvent.mTriggerTime + interval; - mEventQueue.add(event); - } else { - long triggerTime = now + event.mPeriod; - if (firstEvent.mTriggerTime < triggerTime) { - event.mTriggerTime = firstEvent.mTriggerTime; - event.mLastTriggerTime -= event.mPeriod; - } else { - event.mTriggerTime = triggerTime; - } - mEventQueue.add(event); - recalculatePeriods(); - } - } - - /** - * Sets a periodic timer. - * - * @param period the timer period; in milli-second - * @param callback is called back when the timer goes off; the same callback - * can be specified in multiple timer events - */ - public synchronized void set(int period, Runnable callback) { - if (stopped()) return; - - long now = SystemClock.elapsedRealtime(); - MyEvent event = new MyEvent(period, callback, now); - insertEvent(event); - - if (mEventQueue.first() == event) { - if (mEventQueue.size() > 1) cancelAlarm(); - scheduleNext(); - } - - long triggerTime = event.mTriggerTime; - if (DEBUG_TIMER) { - Log.d(TAG, " add event " + event + " scheduled at " - + showTime(triggerTime) + " at " + showTime(now) - + ", #events=" + mEventQueue.size()); - printQueue(); - } - } - - /** - * Cancels all the timer events with the specified callback. - * - * @param callback the callback - */ - public synchronized void cancel(Runnable callback) { - if (stopped() || mEventQueue.isEmpty()) return; - if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback); - - MyEvent firstEvent = mEventQueue.first(); - for (Iterator<MyEvent> iter = mEventQueue.iterator(); - iter.hasNext();) { - MyEvent event = iter.next(); - if (event.mCallback == callback) { - iter.remove(); - if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event); - } - } - if (mEventQueue.isEmpty()) { - cancelAlarm(); - } else if (mEventQueue.first() != firstEvent) { - cancelAlarm(); - firstEvent = mEventQueue.first(); - firstEvent.mPeriod = firstEvent.mMaxPeriod; - firstEvent.mTriggerTime = firstEvent.mLastTriggerTime - + firstEvent.mPeriod; - recalculatePeriods(); - scheduleNext(); - } - if (DEBUG_TIMER) { - Log.d(TAG, "after cancel:"); - printQueue(); - } - } - - private void scheduleNext() { - if (stopped() || mEventQueue.isEmpty()) return; - - if (mPendingIntent != null) { - throw new RuntimeException("pendingIntent is not null!"); - } - - MyEvent event = mEventQueue.first(); - Intent intent = new Intent(getAction()); - intent.putExtra(TRIGGER_TIME, event.mTriggerTime); - PendingIntent pendingIntent = mPendingIntent = - PendingIntent.getBroadcast(mContext, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - event.mTriggerTime, pendingIntent); - } - - @Override - public synchronized void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (getAction().equals(action) - && intent.getExtras().containsKey(TRIGGER_TIME)) { - mPendingIntent = null; - long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L); - execute(triggerTime); - } else { - Log.d(TAG, "unrecognized intent: " + intent); - } - } - - private void printQueue() { - int count = 0; - for (MyEvent event : mEventQueue) { - Log.d(TAG, " " + event + ": scheduled at " - + showTime(event.mTriggerTime) + ": last at " - + showTime(event.mLastTriggerTime)); - if (++count >= 5) break; - } - if (mEventQueue.size() > count) { - Log.d(TAG, " ....."); - } else if (count == 0) { - Log.d(TAG, " <empty>"); - } - } - - private void execute(long triggerTime) { - if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " - + showTime(triggerTime) + ": " + mEventQueue.size()); - if (stopped() || mEventQueue.isEmpty()) return; - - for (MyEvent event : mEventQueue) { - if (event.mTriggerTime != triggerTime) break; - if (DEBUG_TIMER) Log.d(TAG, "execute " + event); - - event.mLastTriggerTime = event.mTriggerTime; - event.mTriggerTime += event.mPeriod; - - // run the callback in a new thread to prevent deadlock - new Thread(event.mCallback, "SipServiceTimerCallbackThread") - .start(); - } - if (DEBUG_TIMER) { - Log.d(TAG, "after timeout execution"); - printQueue(); - } - scheduleNext(); - } - - private String getAction() { - return toString(); - } - - private String showTime(long time) { - int ms = (int) (time % 1000); - int s = (int) (time / 1000); - int m = s / 60; - s %= 60; - return String.format("%d.%d.%d", m, s, ms); - } - } - - private static class MyEvent { - int mPeriod; - int mMaxPeriod; - long mTriggerTime; - long mLastTriggerTime; - Runnable mCallback; - - MyEvent(int period, Runnable callback, long now) { - mPeriod = mMaxPeriod = period; - mCallback = callback; - mLastTriggerTime = now; - } - - @Override - public String toString() { - String s = super.toString(); - s = s.substring(s.indexOf("@")); - return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":" - + toString(mCallback); - } - - private String toString(Object o) { - String s = o.toString(); - int index = s.indexOf("$"); - if (index > 0) s = s.substring(index + 1); - return s; - } - } - - private static class MyEventComparator implements Comparator<MyEvent> { - public int compare(MyEvent e1, MyEvent e2) { - if (e1 == e2) return 0; - int diff = e1.mMaxPeriod - e2.mMaxPeriod; - if (diff == 0) diff = -1; - return diff; - } - - public boolean equals(Object that) { - return (this == that); - } - } - - // Single-threaded executor - private static class MyExecutor extends Handler { - MyExecutor() { - super(createLooper()); - } - - private static Looper createLooper() { - HandlerThread thread = new HandlerThread("SipService"); - thread.start(); - return thread.getLooper(); - } - - void addTask(Runnable task) { - Message.obtain(this, 0/* don't care */, task).sendToTarget(); - } - - @Override - public void handleMessage(Message msg) { - if (msg.obj instanceof Runnable) { - ((Runnable) msg.obj).run(); - } else { - Log.w(TAG, "can't handle msg: " + msg); - } - } - } -} |