Merge "Move the keepalive process to SipSessionImpl and make it reusable."
This commit is contained in:
committed by
Android (Google) Code Review
commit
ea380219df
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user