Merge "Move the keepalive process to SipSessionImpl and make it reusable."

This commit is contained in:
Hung-ying Tyan
2011-06-22 21:38:27 -07:00
committed by Android (Google) Code Review
3 changed files with 450 additions and 281 deletions

View File

@@ -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);

View File

@@ -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);
synchronized (SipService.this) {
mSession.stopKeepAliveProcess();
}
}
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();
}
}
public void run() {
synchronized (SipService.this) {
if (!mRunning) return;
try {
mSession.sendKeepAlive();
calculateNewInterval();
} catch (Throwable t) {
mSession.stopKeepAliveProcess();
mSession.startKeepAliveProcess(mInterval, this);
} catch (SipException e) {
Log.e(TAG, "restart()", e);
}
}
}
// SipSessionGroup.KeepAliveProcessCallback
@Override
public void onResponse(boolean portChanged) {
synchronized (SipService.this) {
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);
}
onKeepAliveIntervalChanged();
} else {
// Since the rport is changed, shorten the interval.
mMaxInterval = mInterval;
}
if ((mMaxInterval - mMinInterval) < MIN_INTERVAL) {
// update mKeepAliveInterval and stop measurement.
stop();
Log.w(TAG, "interval measurement error: " + t);
mKeepAliveInterval = mMinInterval;
if (DEBUG) {
Log.d(TAG, "measured keepalive interval: "
+ mKeepAliveInterval);
}
} 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();
}
}
}
}
// 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 onError(int errorCode, String description) {
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 (mKeepAliveInterval > mInterval) {
mInterval = mKeepAliveInterval;
restart(mInterval);
}
} catch (Throwable t) {
Log.w(TAG, "keepalive error: " + t);
}
Log.w(TAG, "interval measurement error: " + description);
}
}
public void stop() {
if (DEBUGV && (mSession != null)) Log.v(TAG, "stop keepalive:"
+ mSession.getLocalProfile().getUriString());
mRunning = false;
mSession = null;
mTimer.cancel(this);
}
}
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;
}
}
}

View File

@@ -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);
}
}
});
}
}
}