summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHung-ying Tyan <tyanh@google.com>2011-06-22 16:42:38 +0800
committerHung-ying Tyan <tyanh@google.com>2011-06-23 11:41:37 +0800
commit4a267a9158a62010cd76ab93681586ea8e3d6015 (patch)
tree123128c507d1e3cabff75914bdcc9bd7623ee1eb
parentac320b224590c8cdea93a50338aaef5faa1f2466 (diff)
downloadframeworks_base-4a267a9158a62010cd76ab93681586ea8e3d6015.zip
frameworks_base-4a267a9158a62010cd76ab93681586ea8e3d6015.tar.gz
frameworks_base-4a267a9158a62010cd76ab93681586ea8e3d6015.tar.bz2
Move the keepalive process to SipSessionImpl and make it reusable.
Reuse the new component in the original keepalive process and the NAT port mapping timeout measurement process. This is the foundation for fixing the following bug. Bug: 3464181 Change-Id: If7e951c000503fa64843942ad062c4d853e20c8d
-rw-r--r--voip/java/com/android/server/sip/SipHelper.java51
-rw-r--r--voip/java/com/android/server/sip/SipService.java334
-rw-r--r--voip/java/com/android/server/sip/SipSessionGroup.java338
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 4ee86b6..018e6de 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 5ad5d26..802e56d 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 4837eb9..cc3e410 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);
+ }
+ }
+ });
+ }
+ }
}