diff options
| author | 2011-06-22 21:38:27 -0700 | |
|---|---|---|
| committer | 2011-06-22 21:38:27 -0700 | |
| commit | ea380219dfc5a343a4062cc1613101d845bf9047 (patch) | |
| tree | 21708613254f9b19535d34c17083286dd485e5a2 | |
| parent | d9b0ee029fa4d098c7597b4074b9c59e558e6c38 (diff) | |
| parent | 4a267a9158a62010cd76ab93681586ea8e3d6015 (diff) | |
Merge "Move the keepalive process to SipSessionImpl and make it reusable."
| -rw-r--r-- | voip/java/com/android/server/sip/SipHelper.java | 51 | ||||
| -rw-r--r-- | voip/java/com/android/server/sip/SipService.java | 334 | ||||
| -rw-r--r-- | voip/java/com/android/server/sip/SipSessionGroup.java | 338 |
3 files changed, 446 insertions, 277 deletions
diff --git a/voip/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java index 4ee86b6e58cb..018e6de5c31b 100644 --- a/voip/java/com/android/server/sip/SipHelper.java +++ b/voip/java/com/android/server/sip/SipHelper.java @@ -71,6 +71,7 @@ import javax.sip.message.Response; class SipHelper { private static final String TAG = SipHelper.class.getSimpleName(); private static final boolean DEBUG = true; + private static final boolean DEBUG_PING = false; private SipStack mSipStack; private SipProvider mSipProvider; @@ -177,17 +178,19 @@ class SipHelper { return uri; } - public ClientTransaction sendKeepAlive(SipProfile userProfile, String tag) - throws SipException { + public ClientTransaction sendOptions(SipProfile caller, SipProfile callee, + String tag) throws SipException { try { - Request request = createRequest(Request.OPTIONS, userProfile, tag); + Request request = (caller == callee) + ? createRequest(Request.OPTIONS, caller, tag) + : createRequest(Request.OPTIONS, caller, callee, tag); ClientTransaction clientTransaction = mSipProvider.getNewClientTransaction(request); clientTransaction.sendRequest(); return clientTransaction; } catch (Exception e) { - throw new SipException("sendKeepAlive()", e); + throw new SipException("sendOptions()", e); } } @@ -249,23 +252,29 @@ class SipHelper { return ct; } + private Request createRequest(String requestType, SipProfile caller, + SipProfile callee, String tag) throws ParseException, SipException { + FromHeader fromHeader = createFromHeader(caller, tag); + ToHeader toHeader = createToHeader(callee); + SipURI requestURI = callee.getUri(); + List<ViaHeader> viaHeaders = createViaHeaders(); + CallIdHeader callIdHeader = createCallIdHeader(); + CSeqHeader cSeqHeader = createCSeqHeader(requestType); + MaxForwardsHeader maxForwards = createMaxForwardsHeader(); + + Request request = mMessageFactory.createRequest(requestURI, + requestType, callIdHeader, cSeqHeader, fromHeader, + toHeader, viaHeaders, maxForwards); + + request.addHeader(createContactHeader(caller)); + return request; + } + public ClientTransaction sendInvite(SipProfile caller, SipProfile callee, String sessionDescription, String tag) throws SipException { try { - FromHeader fromHeader = createFromHeader(caller, tag); - ToHeader toHeader = createToHeader(callee); - SipURI requestURI = callee.getUri(); - List<ViaHeader> viaHeaders = createViaHeaders(); - CallIdHeader callIdHeader = createCallIdHeader(); - CSeqHeader cSeqHeader = createCSeqHeader(Request.INVITE); - MaxForwardsHeader maxForwards = createMaxForwardsHeader(); - - Request request = mMessageFactory.createRequest(requestURI, - Request.INVITE, callIdHeader, cSeqHeader, fromHeader, - toHeader, viaHeaders, maxForwards); - - request.addHeader(createContactHeader(caller)); + Request request = createRequest(Request.INVITE, caller, callee, tag); request.setContent(sessionDescription, mHeaderFactory.createContentTypeHeader( "application", "sdp")); @@ -419,9 +428,13 @@ class SipHelper { public void sendResponse(RequestEvent event, int responseCode) throws SipException { try { + Request request = event.getRequest(); Response response = mMessageFactory.createResponse( - responseCode, event.getRequest()); - if (DEBUG) Log.d(TAG, "send response: " + response); + responseCode, request); + if (DEBUG && (!Request.OPTIONS.equals(request.getMethod()) + || DEBUG_PING)) { + Log.d(TAG, "send response: " + response); + } getServerTransaction(event).sendResponse(response); } catch (ParseException e) { throw new SipException("sendResponse()", e); diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java index 5ad5d26aee92..802e56d7de2d 100644 --- a/voip/java/com/android/server/sip/SipService.java +++ b/voip/java/com/android/server/sip/SipService.java @@ -69,10 +69,11 @@ import javax.sip.SipException; public final class SipService extends ISipService.Stub { static final String TAG = "SipService"; static final boolean DEBUGV = false; - static final boolean DEBUG = false; + static final boolean DEBUG = true; private static final int EXPIRY_TIME = 3600; private static final int SHORT_EXPIRY_TIME = 10; private static final int MIN_EXPIRY_TIME = 60; + private static final int DEFAULT_KEEPALIVE_INTERVAL = 10; // in seconds private Context mContext; private String mLocalIp; @@ -378,7 +379,7 @@ public final class SipService extends ISipService.Stub { private void grabWifiLock() { if (mWifiLock == null) { - if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ acquire wifi lock"); + if (DEBUG) Log.d(TAG, "acquire wifi lock"); mWifiLock = ((WifiManager) mContext.getSystemService(Context.WIFI_SERVICE)) .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); @@ -389,7 +390,7 @@ public final class SipService extends ISipService.Stub { private void releaseWifiLock() { if (mWifiLock != null) { - if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ release wifi lock"); + if (DEBUG) Log.d(TAG, "release wifi lock"); mWifiLock.release(); mWifiLock = null; stopWifiScanner(); @@ -459,9 +460,17 @@ public final class SipService extends ISipService.Stub { } } - private void startPortMappingLifetimeMeasurement(SipSessionGroup group) { - mIntervalMeasurementProcess = new IntervalMeasurementProcess(group); - mIntervalMeasurementProcess.start(); + private void startPortMappingLifetimeMeasurement( + SipProfile localProfile) { + if ((mIntervalMeasurementProcess == null) + && (mKeepAliveInterval == -1) + && isBehindNAT(mLocalIp)) { + Log.d(TAG, "start NAT port mapping timeout measurement on " + + localProfile.getUriString()); + + mIntervalMeasurementProcess = new IntervalMeasurementProcess(localProfile); + mIntervalMeasurementProcess.start(); + } } private synchronized void addPendingSession(ISipSession session) { @@ -500,6 +509,33 @@ public final class SipService extends ISipService.Stub { return false; } + private synchronized void onKeepAliveIntervalChanged() { + for (SipSessionGroupExt group : mSipGroups.values()) { + group.onKeepAliveIntervalChanged(); + } + } + + private int getKeepAliveInterval() { + return (mKeepAliveInterval < 0) + ? DEFAULT_KEEPALIVE_INTERVAL + : mKeepAliveInterval; + } + + 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 class SipSessionGroupExt extends SipSessionAdapter { private SipSessionGroup mSipGroup; @@ -527,6 +563,16 @@ public final class SipService extends ISipService.Stub { return mSipGroup.containsSession(callId); } + public void onKeepAliveIntervalChanged() { + mAutoRegistration.onKeepAliveIntervalChanged(); + } + + // TODO: remove this method once SipWakeupTimer can better handle variety + // of timeout values + void setWakeupTimer(SipWakeupTimer timer) { + mSipGroup.setWakeupTimer(timer); + } + // 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 @@ -534,7 +580,7 @@ public final class SipService extends ISipService.Stub { SipProfile localProfile, String password) throws SipException { try { return new SipSessionGroup(localIp, localProfile, password, - mMyWakeLock); + mTimer, mMyWakeLock); } catch (IOException e) { // network disconnected Log.w(TAG, "createSipSessionGroup(): network disconnected?"); @@ -697,158 +743,114 @@ public final class SipService extends ISipService.Stub { } } - private class IntervalMeasurementProcess extends SipSessionAdapter - implements Runnable { - private static final String TAG = "\\INTERVAL/"; + private class IntervalMeasurementProcess implements + SipSessionGroup.KeepAliveProcessCallback { + private static final String TAG = "SipKeepAliveInterval"; private static final int MAX_INTERVAL = 120; // seconds private static final int MIN_INTERVAL = SHORT_EXPIRY_TIME; - private static final int PASS_THRESHOLD = 6; + private static final int PASS_THRESHOLD = 10; private SipSessionGroupExt mGroup; private SipSessionGroup.SipSessionImpl mSession; private boolean mRunning; - private int mMinInterval = 10; + private int mMinInterval = 10; // in seconds private int mMaxInterval = MAX_INTERVAL; private int mInterval = MAX_INTERVAL / 2; private int mPassCounter = 0; - private SipWakeupTimer mTimer = new SipWakeupTimer(mContext, mExecutor); - // TODO: fix SipWakeupTimer so that we only use one instance of the timer - public IntervalMeasurementProcess(SipSessionGroup group) { + public IntervalMeasurementProcess(SipProfile localProfile) { try { - mGroup = new SipSessionGroupExt( - group.getLocalProfile(), null, null); + mGroup = new SipSessionGroupExt(localProfile, null, null); + // TODO: remove this line once SipWakeupTimer can better handle + // variety of timeout values + mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor)); mSession = (SipSessionGroup.SipSessionImpl) - mGroup.createSession(this); + mGroup.createSession(null); } catch (Exception e) { Log.w(TAG, "start interval measurement error: " + e); } } public void start() { - if (mRunning) return; - mRunning = true; - mTimer.set(mInterval * 1000, this); - if (DEBUGV) Log.v(TAG, "start interval measurement"); - run(); + synchronized (SipService.this) { + try { + mSession.startKeepAliveProcess(mInterval, this); + } catch (SipException e) { + Log.e(TAG, "start()", e); + } + } } public void stop() { - mRunning = false; - mTimer.cancel(this); - } - - private void restart() { - mTimer.cancel(this); - mTimer.set(mInterval * 1000, this); - } - - private void calculateNewInterval() { - if (!mSession.isReRegisterRequired()) { - if (++mPassCounter != PASS_THRESHOLD) return; - // update the interval, since the current interval is good to - // keep the port mapping. - mKeepAliveInterval = mMinInterval = mInterval; - } else { - // Since the rport is changed, shorten the interval. - mSession.clearReRegisterRequired(); - mMaxInterval = mInterval; - } - if ((mMaxInterval - mMinInterval) < MIN_INTERVAL) { - // update mKeepAliveInterval and stop measurement. - stop(); - mKeepAliveInterval = mMinInterval; - if (DEBUGV) Log.v(TAG, "measured interval: " + mKeepAliveInterval); - } else { - // calculate the new interval and continue. - mInterval = (mMaxInterval + mMinInterval) / 2; - mPassCounter = 0; - if (DEBUGV) { - Log.v(TAG, " current interval: " + mKeepAliveInterval - + "test new interval: " + mInterval); - } - restart(); + synchronized (SipService.this) { + mSession.stopKeepAliveProcess(); } } - public void run() { + private void restart() { synchronized (SipService.this) { - if (!mRunning) return; try { - mSession.sendKeepAlive(); - calculateNewInterval(); - } catch (Throwable t) { - stop(); - Log.w(TAG, "interval measurement error: " + t); + mSession.stopKeepAliveProcess(); + mSession.startKeepAliveProcess(mInterval, this); + } catch (SipException e) { + Log.e(TAG, "restart()", e); } } } - } - - // KeepAliveProcess is controlled by AutoRegistrationProcess. - // All methods will be invoked in sync with SipService.this. - private class KeepAliveProcess implements Runnable { - private static final String TAG = "\\KEEPALIVE/"; - private static final int INTERVAL = 10; - private SipSessionGroup.SipSessionImpl mSession; - private boolean mRunning = false; - private int mInterval = INTERVAL; - - public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { - mSession = session; - } - - public void start() { - if (mRunning) return; - mRunning = true; - mTimer.set(INTERVAL * 1000, this); - } - private void restart(int duration) { - if (DEBUG) Log.d(TAG, "Refresh NAT port mapping " + duration + "s later."); - mTimer.cancel(this); - mTimer.set(duration * 1000, this); - } - - // timeout handler - public void run() { + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onResponse(boolean portChanged) { synchronized (SipService.this) { - if (!mRunning) return; - - if (DEBUGV) Log.v(TAG, "~~~ keepalive: " - + mSession.getLocalProfile().getUriString()); - SipSessionGroup.SipSessionImpl session = mSession.duplicate(); - try { - session.sendKeepAlive(); - if (session.isReRegisterRequired()) { - // Acquire wake lock for the registration process. The - // lock will be released when registration is complete. - mMyWakeLock.acquire(mSession); - mSession.register(EXPIRY_TIME); + if (!portChanged) { + if (++mPassCounter != PASS_THRESHOLD) return; + // update the interval, since the current interval is good to + // keep the port mapping. + mKeepAliveInterval = mMinInterval = mInterval; + if (DEBUG) { + Log.d(TAG, "measured good keepalive interval: " + + mKeepAliveInterval); } - if (mKeepAliveInterval > mInterval) { - mInterval = mKeepAliveInterval; - restart(mInterval); + onKeepAliveIntervalChanged(); + } else { + // Since the rport is changed, shorten the interval. + mMaxInterval = mInterval; + } + if ((mMaxInterval - mMinInterval) < MIN_INTERVAL) { + // update mKeepAliveInterval and stop measurement. + stop(); + mKeepAliveInterval = mMinInterval; + if (DEBUG) { + Log.d(TAG, "measured keepalive interval: " + + mKeepAliveInterval); } - } catch (Throwable t) { - Log.w(TAG, "keepalive error: " + t); + } else { + // calculate the new interval and continue. + mInterval = (mMaxInterval + mMinInterval) / 2; + mPassCounter = 0; + if (DEBUG) { + Log.d(TAG, "current interval: " + mKeepAliveInterval + + ", test new interval: " + mInterval); + } + restart(); } } } - public void stop() { - if (DEBUGV && (mSession != null)) Log.v(TAG, "stop keepalive:" - + mSession.getLocalProfile().getUriString()); - mRunning = false; - mSession = null; - mTimer.cancel(this); + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onError(int errorCode, String description) { + synchronized (SipService.this) { + Log.w(TAG, "interval measurement error: " + description); + } } } private class AutoRegistrationProcess extends SipSessionAdapter - implements Runnable { + implements Runnable, SipSessionGroup.KeepAliveProcessCallback { + private String TAG = "SipAudoReg"; private SipSessionGroup.SipSessionImpl mSession; + private SipSessionGroup.SipSessionImpl mKeepAliveSession; private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); - private KeepAliveProcess mKeepAliveProcess; private int mBackoff = 1; private boolean mRegistered; private long mExpiryTime; @@ -869,27 +871,38 @@ public final class SipService extends ISipService.Stub { // return right away if no active network connection. if (mSession == null) return; - synchronized (SipService.this) { - if (isBehindNAT(mLocalIp) - && (mIntervalMeasurementProcess == null) - && (mKeepAliveInterval == -1)) { - // Start keep-alive interval measurement, here we allow - // the first profile only as the target service provider - // to measure the life time of NAT port mapping. - startPortMappingLifetimeMeasurement(group); - } - } - // 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 mMyWakeLock.acquire(mSession); mSession.unregister(); - if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for " - + mSession.getLocalProfile().getUriString()); + if (DEBUG) TAG = mSession.getLocalProfile().getUriString(); + if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess"); + } + } + + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onResponse(boolean portChanged) { + synchronized (SipService.this) { + // Start keep-alive interval measurement on the first successfully + // kept-alive SipSessionGroup + startPortMappingLifetimeMeasurement(mSession.getLocalProfile()); + + if (!mRunning || !portChanged) return; + // Acquire wake lock for the registration process. The + // lock will be released when registration is complete. + mMyWakeLock.acquire(mSession); + mSession.register(EXPIRY_TIME); } } + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onError(int errorCode, String description) { + Log.e(TAG, "keepalive error: " + description); + } + public void stop() { if (!mRunning) return; mRunning = false; @@ -900,15 +913,30 @@ public final class SipService extends ISipService.Stub { } mTimer.cancel(this); - if (mKeepAliveProcess != null) { - mKeepAliveProcess.stop(); - mKeepAliveProcess = null; + if (mKeepAliveSession != null) { + mKeepAliveSession.stopKeepAliveProcess(); + mKeepAliveSession = null; } mRegistered = false; setListener(mProxy.getListener()); } + public void onKeepAliveIntervalChanged() { + if (mKeepAliveSession != null) { + int newInterval = getKeepAliveInterval(); + if (DEBUGV) { + Log.v(TAG, "restart keepalive w interval=" + newInterval); + } + mKeepAliveSession.stopKeepAliveProcess(); + try { + mKeepAliveSession.startKeepAliveProcess(newInterval, this); + } catch (SipException e) { + Log.e(TAG, "onKeepAliveIntervalChanged()", e); + } + } + } + public void setListener(ISipSessionListener listener) { synchronized (SipService.this) { mProxy.setListener(listener); @@ -955,13 +983,14 @@ public final class SipService extends ISipService.Stub { } // timeout handler: re-register + @Override public void run() { synchronized (SipService.this) { if (!mRunning) return; mErrorCode = SipErrorCode.NO_ERROR; mErrorMessage = null; - if (DEBUG) Log.d(TAG, "~~~ registering"); + if (DEBUG) Log.d(TAG, "registering"); if (mConnected) { mMyWakeLock.acquire(mSession); mSession.register(EXPIRY_TIME); @@ -969,22 +998,6 @@ public final class SipService extends ISipService.Stub { } } - 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); @@ -1030,7 +1043,6 @@ public final class SipService extends ISipService.Stub { mProxy.onRegistrationDone(session, duration); if (duration > 0) { - mSession.clearReRegisterRequired(); mExpiryTime = SystemClock.elapsedRealtime() + (duration * 1000); @@ -1043,13 +1055,17 @@ public final class SipService extends ISipService.Stub { } restart(duration); - if (isBehindNAT(mLocalIp) || - mSession.getLocalProfile().getSendKeepAlive()) { - if (mKeepAliveProcess == null) { - mKeepAliveProcess = - new KeepAliveProcess(mSession); + SipProfile localProfile = mSession.getLocalProfile(); + if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp) + || localProfile.getSendKeepAlive())) { + mKeepAliveSession = mSession.duplicate(); + Log.d(TAG, "start keepalive"); + try { + mKeepAliveSession.startKeepAliveProcess( + getKeepAliveInterval(), this); + } catch (SipException e) { + Log.e(TAG, "AutoRegistrationProcess", e); } - mKeepAliveProcess.start(); } } mMyWakeLock.release(session); @@ -1103,10 +1119,6 @@ public final class SipService extends ISipService.Stub { private void restartLater() { mRegistered = false; restart(backoffDuration()); - if (mKeepAliveProcess != null) { - mKeepAliveProcess.stop(); - mKeepAliveProcess = null; - } } } diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java index 4837eb908024..cc3e4109b138 100644 --- a/voip/java/com/android/server/sip/SipSessionGroup.java +++ b/voip/java/com/android/server/sip/SipSessionGroup.java @@ -28,6 +28,7 @@ import android.net.sip.ISipSessionListener; import android.net.sip.SipErrorCode; import android.net.sip.SipProfile; import android.net.sip.SipSession; +import android.net.sip.SipSessionAdapter; import android.text.TextUtils; import android.util.Log; @@ -89,6 +90,7 @@ class SipSessionGroup implements SipListener { private static final String THREAD_POOL_SIZE = "1"; private static final int EXPIRY_TIME = 3600; // in seconds private static final int CANCEL_CALL_TIMER = 3; // in seconds + private static final int KEEPALIVE_TIMEOUT = 3; // in seconds private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds private static final EventObject DEREGISTER = new EventObject("Deregister"); @@ -107,6 +109,7 @@ class SipSessionGroup implements SipListener { private SipSessionImpl mCallReceiverSession; private String mLocalIp; + private SipWakeupTimer mWakeupTimer; private SipWakeLock mWakeLock; // call-id-to-SipSession map @@ -119,13 +122,21 @@ class SipSessionGroup implements SipListener { * @throws IOException if cannot assign requested address */ public SipSessionGroup(String localIp, SipProfile myself, String password, - SipWakeLock wakeLock) throws SipException, IOException { + SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException, + IOException { mLocalProfile = myself; mPassword = password; + mWakeupTimer = timer; mWakeLock = wakeLock; reset(localIp); } + // TODO: remove this method once SipWakeupTimer can better handle variety + // of timeout values + void setWakeupTimer(SipWakeupTimer timer) { + mWakeupTimer = timer; + } + synchronized void reset(String localIp) throws SipException, IOException { mLocalIp = localIp; if (localIp == null) return; @@ -382,6 +393,12 @@ class SipSessionGroup implements SipListener { } } + static interface KeepAliveProcessCallback { + /** Invoked when the response of keeping alive comes back. */ + void onResponse(boolean portChanged); + void onError(int errorCode, String description); + } + class SipSessionImpl extends ISipSession.Stub { SipProfile mPeerProfile; SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); @@ -392,12 +409,10 @@ class SipSessionGroup implements SipListener { ClientTransaction mClientTransaction; String mPeerSessionDescription; boolean mInCall; - SessionTimer mTimer; + SessionTimer mSessionTimer; int mAuthenticationRetryCount; - // for registration - boolean mReRegisterFlag = false; - int mRPort = 0; + private KeepAliveProcess mKeepAliveProcess; // lightweight timer class SessionTimer { @@ -512,7 +527,9 @@ class SipSessionGroup implements SipListener { try { processCommand(command); } catch (Throwable e) { - Log.w(TAG, "command error: " + command, e); + Log.w(TAG, "command error: " + command + ": " + + mLocalProfile.getUriString(), + getRootCause(e)); onError(e); } } @@ -553,34 +570,6 @@ class SipSessionGroup implements SipListener { doCommandAsync(DEREGISTER); } - public boolean isReRegisterRequired() { - return mReRegisterFlag; - } - - public void clearReRegisterRequired() { - mReRegisterFlag = false; - } - - public void sendKeepAlive() { - mState = SipSession.State.PINGING; - try { - processCommand(new OptionsCommand()); - for (int i = 0; i < 15; i++) { - if (SipSession.State.PINGING != mState) break; - Thread.sleep(200); - } - if (SipSession.State.PINGING == mState) { - // FIXME: what to do if server doesn't respond - reset(); - if (DEBUG) Log.w(TAG, "no response from ping"); - } - } catch (SipException e) { - Log.e(TAG, "sendKeepAlive failed", e); - } catch (InterruptedException e) { - Log.e(TAG, "sendKeepAlive interrupted", e); - } - } - private void processCommand(EventObject command) throws SipException { if (isLoggable(command)) Log.d(TAG, "process cmd: " + command); if (!process(command)) { @@ -612,6 +601,11 @@ class SipSessionGroup implements SipListener { synchronized (SipSessionGroup.this) { if (isClosed()) return false; + if (mKeepAliveProcess != null) { + // event consumed by keepalive process + if (mKeepAliveProcess.process(evt)) return true; + } + Dialog dialog = null; if (evt instanceof RequestEvent) { dialog = ((RequestEvent) evt).getDialog(); @@ -627,9 +621,6 @@ class SipSessionGroup implements SipListener { case SipSession.State.DEREGISTERING: processed = registeringToReady(evt); break; - case SipSession.State.PINGING: - processed = keepAliveProcess(evt); - break; case SipSession.State.READY_TO_CALL: processed = readyForCall(evt); break; @@ -754,10 +745,6 @@ class SipSessionGroup implements SipListener { case SipSession.State.OUTGOING_CALL_CANCELING: onError(SipErrorCode.TIME_OUT, event.toString()); break; - case SipSession.State.PINGING: - reset(); - mReRegisterFlag = true; - break; default: Log.d(TAG, " do nothing"); @@ -778,48 +765,6 @@ class SipSessionGroup implements SipListener { return expires; } - private boolean keepAliveProcess(EventObject evt) throws SipException { - if (evt instanceof OptionsCommand) { - mClientTransaction = mSipHelper.sendKeepAlive(mLocalProfile, - generateTag()); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - return true; - } else if (evt instanceof ResponseEvent) { - return parseOptionsResult(evt); - } - return false; - } - - private boolean parseOptionsResult(EventObject evt) { - if (expectResponse(Request.OPTIONS, evt)) { - ResponseEvent event = (ResponseEvent) evt; - int rPort = getRPortFromResponse(event.getResponse()); - if (rPort != -1) { - if (mRPort == 0) mRPort = rPort; - if (mRPort != rPort) { - mReRegisterFlag = true; - if (DEBUG) Log.w(TAG, String.format( - "rport is changed: %d <> %d", mRPort, rPort)); - mRPort = rPort; - } else { - if (DEBUG_PING) Log.w(TAG, "rport is the same: " + rPort); - } - } else { - if (DEBUG) Log.w(TAG, "peer did not respond rport"); - } - reset(); - return true; - } - return false; - } - - private int getRPortFromResponse(Response response) { - ViaHeader viaHeader = (ViaHeader)(response.getHeader( - SIPHeaderNames.VIA)); - return (viaHeader == null) ? -1 : viaHeader.getRPort(); - } - private boolean registeringToReady(EventObject evt) throws SipException { if (expectResponse(Request.REGISTER, evt)) { @@ -1138,15 +1083,15 @@ class SipSessionGroup implements SipListener { // timeout in seconds private void startSessionTimer(int timeout) { if (timeout > 0) { - mTimer = new SessionTimer(); - mTimer.start(timeout); + mSessionTimer = new SessionTimer(); + mSessionTimer.start(timeout); } } private void cancelSessionTimer() { - if (mTimer != null) { - mTimer.cancel(); - mTimer = null; + if (mSessionTimer != null) { + mSessionTimer.cancel(); + mSessionTimer = null; } } @@ -1272,6 +1217,168 @@ class SipSessionGroup implements SipListener { onRegistrationFailed(getErrorCode(statusCode), createErrorMessage(response)); } + + // Notes: SipSessionListener will be replaced by the keepalive process + // @param interval in seconds + public void startKeepAliveProcess(int interval, + KeepAliveProcessCallback callback) throws SipException { + synchronized (SipSessionGroup.this) { + startKeepAliveProcess(interval, mLocalProfile, callback); + } + } + + // Notes: SipSessionListener will be replaced by the keepalive process + // @param interval in seconds + public void startKeepAliveProcess(int interval, SipProfile peerProfile, + KeepAliveProcessCallback callback) throws SipException { + synchronized (SipSessionGroup.this) { + if (mKeepAliveProcess != null) { + throw new SipException("Cannot create more than one " + + "keepalive process in a SipSession"); + } + mPeerProfile = peerProfile; + mKeepAliveProcess = new KeepAliveProcess(); + mProxy.setListener(mKeepAliveProcess); + mKeepAliveProcess.start(interval, callback); + } + } + + public void stopKeepAliveProcess() { + synchronized (SipSessionGroup.this) { + if (mKeepAliveProcess != null) { + mKeepAliveProcess.stop(); + mKeepAliveProcess = null; + } + } + } + + class KeepAliveProcess extends SipSessionAdapter implements Runnable { + private static final String TAG = "SipKeepAlive"; + private boolean mRunning = false; + private KeepAliveProcessCallback mCallback; + + private boolean mPortChanged = false; + private int mRPort = 0; + + // @param interval in seconds + void start(int interval, KeepAliveProcessCallback callback) { + if (mRunning) return; + mRunning = true; + mCallback = new KeepAliveProcessCallbackProxy(callback); + mWakeupTimer.set(interval * 1000, this); + if (DEBUG) { + Log.d(TAG, "start keepalive:" + + mLocalProfile.getUriString()); + } + + // No need to run the first time in a separate thread for now + run(); + } + + // return true if the event is consumed + boolean process(EventObject evt) throws SipException { + if (mRunning && (mState == SipSession.State.PINGING)) { + if (evt instanceof ResponseEvent) { + if (parseOptionsResult(evt)) { + if (mPortChanged) { + stop(); + } else { + cancelSessionTimer(); + removeSipSession(SipSessionImpl.this); + } + mCallback.onResponse(mPortChanged); + return true; + } + } + } + return false; + } + + // SipSessionAdapter + // To react to the session timeout event and network error. + @Override + public void onError(ISipSession session, int errorCode, String message) { + stop(); + mCallback.onError(errorCode, message); + } + + // SipWakeupTimer timeout handler + // To send out keepalive message. + @Override + public void run() { + synchronized (SipSessionGroup.this) { + if (!mRunning) return; + + if (DEBUG_PING) { + Log.d(TAG, "keepalive: " + mLocalProfile.getUriString() + + " --> " + mPeerProfile); + } + try { + sendKeepAlive(); + } catch (Throwable t) { + Log.w(TAG, "keepalive error: " + ": " + + mLocalProfile.getUriString(), getRootCause(t)); + // It's possible that the keepalive process is being stopped + // during session.sendKeepAlive() so need to check mRunning + // again here. + if (mRunning) SipSessionImpl.this.onError(t); + } + } + } + + void stop() { + synchronized (SipSessionGroup.this) { + if (DEBUG) { + Log.d(TAG, "stop keepalive:" + mLocalProfile.getUriString() + + ",RPort=" + mRPort); + } + mRunning = false; + mWakeupTimer.cancel(this); + reset(); + } + } + + private void sendKeepAlive() throws SipException, InterruptedException { + synchronized (SipSessionGroup.this) { + mState = SipSession.State.PINGING; + mClientTransaction = mSipHelper.sendOptions( + mLocalProfile, mPeerProfile, generateTag()); + mDialog = mClientTransaction.getDialog(); + addSipSession(SipSessionImpl.this); + + startSessionTimer(KEEPALIVE_TIMEOUT); + // when timed out, onError() will be called with SipErrorCode.TIME_OUT + } + } + + private boolean parseOptionsResult(EventObject evt) { + if (expectResponse(Request.OPTIONS, evt)) { + ResponseEvent event = (ResponseEvent) evt; + int rPort = getRPortFromResponse(event.getResponse()); + if (rPort != -1) { + if (mRPort == 0) mRPort = rPort; + if (mRPort != rPort) { + mPortChanged = true; + if (DEBUG) Log.d(TAG, String.format( + "rport is changed: %d <> %d", mRPort, rPort)); + mRPort = rPort; + } else { + if (DEBUG) Log.d(TAG, "rport is the same: " + rPort); + } + } else { + if (DEBUG) Log.w(TAG, "peer did not respond rport"); + } + return true; + } + return false; + } + + private int getRPortFromResponse(Response response) { + ViaHeader viaHeader = (ViaHeader)(response.getHeader( + SIPHeaderNames.VIA)); + return (viaHeader == null) ? -1 : viaHeader.getRPort(); + } + } } /** @@ -1363,15 +1470,16 @@ class SipSessionGroup implements SipListener { if (!isLoggable(s)) return false; if (evt == null) return false; - if (evt instanceof OptionsCommand) { - return DEBUG_PING; - } else if (evt instanceof ResponseEvent) { + if (evt instanceof ResponseEvent) { Response response = ((ResponseEvent) evt).getResponse(); if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) { return DEBUG_PING; } return DEBUG; } else if (evt instanceof RequestEvent) { + if (isRequestEvent(Request.OPTIONS, evt)) { + return DEBUG_PING; + } return DEBUG; } return false; @@ -1387,12 +1495,6 @@ class SipSessionGroup implements SipListener { } } - private class OptionsCommand extends EventObject { - public OptionsCommand() { - super(SipSessionGroup.this); - } - } - private class RegisterCommand extends EventObject { private int mDuration; @@ -1434,4 +1536,46 @@ class SipSessionGroup implements SipListener { return mTimeout; } } + + /** Class to help safely run KeepAliveProcessCallback in a different thread. */ + static class KeepAliveProcessCallbackProxy implements KeepAliveProcessCallback { + private KeepAliveProcessCallback mCallback; + + KeepAliveProcessCallbackProxy(KeepAliveProcessCallback callback) { + mCallback = callback; + } + + private void proxy(Runnable runnable) { + // One thread for each calling back. + // Note: Guarantee ordering if the issue becomes important. Currently, + // the chance of handling two callback events at a time is none. + new Thread(runnable, "SIP-KeepAliveProcessCallbackThread").start(); + } + + public void onResponse(final boolean portChanged) { + if (mCallback == null) return; + proxy(new Runnable() { + public void run() { + try { + mCallback.onResponse(portChanged); + } catch (Throwable t) { + Log.w(TAG, "onResponse", t); + } + } + }); + } + + public void onError(final int errorCode, final String description) { + if (mCallback == null) return; + proxy(new Runnable() { + public void run() { + try { + mCallback.onError(errorCode, description); + } catch (Throwable t) { + Log.w(TAG, "onError", t); + } + } + }); + } + } } |