summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Hung-ying Tyan <tyanh@google.com> 2011-06-22 21:38:27 -0700
committer Android (Google) Code Review <android-gerrit@google.com> 2011-06-22 21:38:27 -0700
commitea380219dfc5a343a4062cc1613101d845bf9047 (patch)
tree21708613254f9b19535d34c17083286dd485e5a2
parentd9b0ee029fa4d098c7597b4074b9c59e558e6c38 (diff)
parent4a267a9158a62010cd76ab93681586ea8e3d6015 (diff)
Merge "Move the keepalive process to SipSessionImpl and make it reusable."
-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 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);
+ }
+ }
+ });
+ }
+ }
}