am 0793586b: am f8c1f129: am e1d27154: am f87743e7: Merge "Prevent NullPointerException cases while using SipService."
* commit '0793586bf8f4dce71d0b4d7ff2f212129b3f76fe':
Prevent NullPointerException cases while using SipService.
diff --git a/java/android/net/sip/ISipSessionListener.aidl b/java/android/net/sip/ISipSessionListener.aidl
index 5920bca..690700c 100644
--- a/java/android/net/sip/ISipSessionListener.aidl
+++ b/java/android/net/sip/ISipSessionListener.aidl
@@ -72,6 +72,14 @@
void onCallBusy(in ISipSession session);
/**
+ * Called when the call is being transferred to a new one.
+ *
+ * @param newSession the new session that the call will be transferred to
+ * @param sessionDescription the new peer's session description
+ */
+ void onCallTransferring(in ISipSession newSession, String sessionDescription);
+
+ /**
* Called when an error occurs during session initialization and
* termination.
*
diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java
index b46f826..fcdbd2c 100644
--- a/java/android/net/sip/SipAudioCall.java
+++ b/java/android/net/sip/SipAudioCall.java
@@ -26,6 +26,7 @@
import android.net.wifi.WifiManager;
import android.os.Message;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.Log;
import java.io.IOException;
@@ -56,6 +57,7 @@
private static final boolean RELEASE_SOCKET = true;
private static final boolean DONT_RELEASE_SOCKET = false;
private static final int SESSION_TIMEOUT = 5; // in seconds
+ private static final int TRANSFER_TIMEOUT = 15; // in seconds
/** Listener for events relating to a SIP call, such as when a call is being
* recieved ("on ringing") or a call is outgoing ("on calling").
@@ -170,6 +172,7 @@
private SipProfile mLocalProfile;
private SipAudioCall.Listener mListener;
private SipSession mSipSession;
+ private SipSession mTransferringSession;
private long mSessionId = System.currentTimeMillis();
private String mPeerSd;
@@ -347,6 +350,27 @@
}
}
+ private synchronized void transferToNewSession() {
+ if (mTransferringSession == null) return;
+ SipSession origin = mSipSession;
+ mSipSession = mTransferringSession;
+ mTransferringSession = null;
+
+ // stop the replaced call.
+ if (mAudioStream != null) {
+ mAudioStream.join(null);
+ } else {
+ try {
+ mAudioStream = new AudioStream(InetAddress.getByName(
+ getLocalIp()));
+ } catch (Throwable t) {
+ Log.i(TAG, "transferToNewSession(): " + t);
+ }
+ }
+ if (origin != null) origin.endCall();
+ startAudio();
+ }
+
private SipSession.Listener createListener() {
return new SipSession.Listener() {
@Override
@@ -378,6 +402,7 @@
@Override
public void onRinging(SipSession session,
SipProfile peerProfile, String sessionDescription) {
+ // this callback is triggered only for reinvite.
synchronized (SipAudioCall.this) {
if ((mSipSession == null) || !mInCall
|| !session.getCallId().equals(
@@ -404,6 +429,13 @@
mPeerSd = sessionDescription;
Log.v(TAG, "onCallEstablished()" + mPeerSd);
+ // TODO: how to notify the UI that the remote party is changed
+ if ((mTransferringSession != null)
+ && (session == mTransferringSession)) {
+ transferToNewSession();
+ return;
+ }
+
Listener listener = mListener;
if (listener != null) {
try {
@@ -420,7 +452,17 @@
@Override
public void onCallEnded(SipSession session) {
- Log.d(TAG, "sip call ended: " + session);
+ Log.d(TAG, "sip call ended: " + session + " mSipSession:" + mSipSession);
+ // reset the trasnferring session if it is the one.
+ if (session == mTransferringSession) {
+ mTransferringSession = null;
+ return;
+ }
+ // or ignore the event if the original session is being
+ // transferred to the new one.
+ if ((mTransferringSession != null) ||
+ (session != mSipSession)) return;
+
Listener listener = mListener;
if (listener != null) {
try {
@@ -489,6 +531,26 @@
public void onRegistrationDone(SipSession session, int duration) {
// irrelevant
}
+
+ @Override
+ public void onCallTransferring(SipSession newSession,
+ String sessionDescription) {
+ Log.v(TAG, "onCallTransferring mSipSession:"
+ + mSipSession + " newSession:" + newSession);
+ mTransferringSession = newSession;
+ try {
+ if (sessionDescription == null) {
+ newSession.makeCall(newSession.getPeerProfile(),
+ createOffer().encode(), TRANSFER_TIMEOUT);
+ } else {
+ String answer = createAnswer(sessionDescription).encode();
+ newSession.answerCall(answer, SESSION_TIMEOUT);
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, "onCallTransferring()", e);
+ newSession.endCall();
+ }
+ }
};
}
@@ -675,6 +737,7 @@
}
private SimpleSessionDescription createAnswer(String offerSd) {
+ if (TextUtils.isEmpty(offerSd)) return createOffer();
SimpleSessionDescription offer =
new SimpleSessionDescription(offerSd);
SimpleSessionDescription answer =
diff --git a/java/android/net/sip/SipSession.java b/java/android/net/sip/SipSession.java
index 5629b3c..5ba1626 100644
--- a/java/android/net/sip/SipSession.java
+++ b/java/android/net/sip/SipSession.java
@@ -160,6 +160,17 @@
}
/**
+ * Called when the call is being transferred to a new one.
+ *
+ * @hide
+ * @param newSession the new session that the call will be transferred to
+ * @param sessionDescription the new peer's session description
+ */
+ public void onCallTransferring(SipSession newSession,
+ String sessionDescription) {
+ }
+
+ /**
* Called when an error occurs during session initialization and
* termination.
*
@@ -489,6 +500,16 @@
}
}
+ public void onCallTransferring(ISipSession session,
+ String sessionDescription) {
+ if (mListener != null) {
+ mListener.onCallTransferring(
+ new SipSession(session, SipSession.this.mListener),
+ sessionDescription);
+
+ }
+ }
+
public void onCallChangeFailed(ISipSession session, int errorCode,
String message) {
if (mListener != null) {
diff --git a/java/android/net/sip/SipSessionAdapter.java b/java/android/net/sip/SipSessionAdapter.java
index 86aca37..f538983 100644
--- a/java/android/net/sip/SipSessionAdapter.java
+++ b/java/android/net/sip/SipSessionAdapter.java
@@ -42,6 +42,10 @@
public void onCallBusy(ISipSession session) {
}
+ public void onCallTransferring(ISipSession session,
+ String sessionDescription) {
+ }
+
public void onCallChangeFailed(ISipSession session, int errorCode,
String message) {
}
diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java
index 4ee86b6..dc628e0 100644
--- a/java/com/android/server/sip/SipHelper.java
+++ b/java/com/android/server/sip/SipHelper.java
@@ -19,6 +19,9 @@
import gov.nist.javax.sip.SipStackExt;
import gov.nist.javax.sip.clientauthutils.AccountManager;
import gov.nist.javax.sip.clientauthutils.AuthenticationHelper;
+import gov.nist.javax.sip.header.extensions.ReferencesHeader;
+import gov.nist.javax.sip.header.extensions.ReferredByHeader;
+import gov.nist.javax.sip.header.extensions.ReplacesHeader;
import android.net.sip.SipProfile;
import android.util.Log;
@@ -71,6 +74,7 @@
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;
@@ -149,9 +153,17 @@
private ContactHeader createContactHeader(SipProfile profile)
throws ParseException, SipException {
- ListeningPoint lp = getListeningPoint();
- SipURI contactURI =
- createSipUri(profile.getUserName(), profile.getProtocol(), lp);
+ return createContactHeader(profile, null, 0);
+ }
+
+ private ContactHeader createContactHeader(SipProfile profile,
+ String ip, int port) throws ParseException,
+ SipException {
+ SipURI contactURI = (ip == null)
+ ? createSipUri(profile.getUserName(), profile.getProtocol(),
+ getListeningPoint())
+ : createSipUri(profile.getUserName(), profile.getProtocol(),
+ ip, port);
Address contactAddress = mAddressFactory.createAddress(contactURI);
contactAddress.setDisplayName(profile.getDisplayName());
@@ -167,9 +179,14 @@
private SipURI createSipUri(String username, String transport,
ListeningPoint lp) throws ParseException {
- SipURI uri = mAddressFactory.createSipURI(username, lp.getIPAddress());
+ return createSipUri(username, transport, lp.getIPAddress(), lp.getPort());
+ }
+
+ private SipURI createSipUri(String username, String transport,
+ String ip, int port) throws ParseException {
+ SipURI uri = mAddressFactory.createSipURI(username, ip);
try {
- uri.setPort(lp.getPort());
+ uri.setPort(port);
uri.setTransportParam(transport);
} catch (InvalidArgumentException e) {
throw new RuntimeException(e);
@@ -177,17 +194,19 @@
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,27 +268,37 @@
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 {
+ String sessionDescription, String tag, ReferredByHeader referredBy,
+ String replaces) 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);
+ if (referredBy != null) request.addHeader(referredBy);
+ if (replaces != null) {
+ request.addHeader(mHeaderFactory.createHeader(
+ ReplacesHeader.NAME, replaces));
+ }
request.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
"application", "sdp"));
-
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
if (DEBUG) Log.d(TAG, "send INVITE: " + request);
@@ -305,7 +334,7 @@
}
}
- private ServerTransaction getServerTransaction(RequestEvent event)
+ public ServerTransaction getServerTransaction(RequestEvent event)
throws SipException {
ServerTransaction transaction = event.getServerTransaction();
if (transaction == null) {
@@ -344,13 +373,14 @@
*/
public ServerTransaction sendInviteOk(RequestEvent event,
SipProfile localProfile, String sessionDescription,
- ServerTransaction inviteTransaction)
- throws SipException {
+ ServerTransaction inviteTransaction, String externalIp,
+ int externalPort) throws SipException {
try {
Request request = event.getRequest();
Response response = mMessageFactory.createResponse(Response.OK,
request);
- response.addHeader(createContactHeader(localProfile));
+ response.addHeader(createContactHeader(localProfile, externalIp,
+ externalPort));
response.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
"application", "sdp"));
@@ -419,15 +449,38 @@
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);
}
}
+ public void sendReferNotify(Dialog dialog, String content)
+ throws SipException {
+ try {
+ Request request = dialog.createRequest(Request.NOTIFY);
+ request.addHeader(mHeaderFactory.createSubscriptionStateHeader(
+ "active;expires=60"));
+ // set content here
+ request.setContent(content,
+ mHeaderFactory.createContentTypeHeader(
+ "message", "sipfrag"));
+ request.addHeader(mHeaderFactory.createEventHeader(
+ ReferencesHeader.REFER));
+ if (DEBUG) Log.d(TAG, "send NOTIFY: " + request);
+ dialog.sendRequest(mSipProvider.getNewClientTransaction(request));
+ } catch (ParseException e) {
+ throw new SipException("sendReferNotify()", e);
+ }
+ }
+
public void sendInviteRequestTerminated(Request inviteRequest,
ServerTransaction inviteTransaction) throws SipException {
try {
diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java
index dc66989..c553947 100644
--- a/java/com/android/server/sip/SipService.java
+++ b/java/com/android/server/sip/SipService.java
@@ -60,6 +60,7 @@
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeSet;
+import java.util.concurrent.Executor;
import javax.sip.SipException;
/**
@@ -68,22 +69,26 @@
public final class SipService extends ISipService.Stub {
static final String TAG = "SipService";
static final boolean DEBUGV = false;
- private static final boolean DEBUG = false;
- private static final boolean DEBUG_TIMER = 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 static final int DEFAULT_MAX_KEEPALIVE_INTERVAL = 120; // in seconds
private Context mContext;
private String mLocalIp;
private String mNetworkType;
private boolean mConnected;
- private WakeupTimer mTimer;
+ private SipWakeupTimer mTimer;
private WifiScanProcess mWifiScanProcess;
private WifiManager.WifiLock mWifiLock;
private boolean mWifiOnly;
+ private BroadcastReceiver mWifiStateReceiver = null;
- private MyExecutor mExecutor;
+ private IntervalMeasurementProcess mIntervalMeasurementProcess;
+
+ private MyExecutor mExecutor = new MyExecutor();
// SipProfile URI --> group
private Map<String, SipSessionGroupExt> mSipGroups =
@@ -96,6 +101,8 @@
private ConnectivityReceiver mConnectivityReceiver;
private boolean mWifiEnabled;
private SipWakeLock mMyWakeLock;
+ private int mKeepAliveInterval;
+ private int mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
/**
* Starts the SIP service. Do nothing if the SIP API is not supported on the
@@ -116,53 +123,54 @@
mMyWakeLock = new SipWakeLock((PowerManager)
context.getSystemService(Context.POWER_SERVICE));
- mTimer = new WakeupTimer(context);
+ mTimer = new SipWakeupTimer(context, mExecutor);
mWifiOnly = SipManager.isSipWifiOnly(context);
}
- private BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
- int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
- WifiManager.WIFI_STATE_UNKNOWN);
- synchronized (SipService.this) {
- switch (state) {
- case WifiManager.WIFI_STATE_ENABLED:
- mWifiEnabled = true;
- if (anyOpenedToReceiveCalls()) grabWifiLock();
- break;
- case WifiManager.WIFI_STATE_DISABLED:
- mWifiEnabled = false;
- releaseWifiLock();
- break;
+ private BroadcastReceiver createWifiBroadcastReceiver() {
+ return new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
+ int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN);
+ synchronized (SipService.this) {
+ switch (state) {
+ case WifiManager.WIFI_STATE_ENABLED:
+ mWifiEnabled = true;
+ if (anyOpenedToReceiveCalls()) grabWifiLock();
+ break;
+ case WifiManager.WIFI_STATE_DISABLED:
+ mWifiEnabled = false;
+ releaseWifiLock();
+ break;
+ }
}
}
}
- }
+ };
};
private void registerReceivers() {
mContext.registerReceiver(mConnectivityReceiver,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
- mContext.registerReceiver(mWifiStateReceiver,
- new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));
+ if (SipManager.isSipWifiOnly(mContext)) {
+ mWifiStateReceiver = createWifiBroadcastReceiver();
+ mContext.registerReceiver(mWifiStateReceiver,
+ new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));
+ }
if (DEBUG) Log.d(TAG, " +++ register receivers");
}
private void unregisterReceivers() {
mContext.unregisterReceiver(mConnectivityReceiver);
- mContext.unregisterReceiver(mWifiStateReceiver);
+ if (SipManager.isSipWifiOnly(mContext)) {
+ mContext.unregisterReceiver(mWifiStateReceiver);
+ }
if (DEBUG) Log.d(TAG, " --- unregister receivers");
}
- private MyExecutor getExecutor() {
- // create mExecutor lazily
- if (mExecutor == null) mExecutor = new MyExecutor();
- return mExecutor;
- }
-
public synchronized SipProfile[] getListOfProfiles() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.USE_SIP, null);
@@ -382,7 +390,7 @@
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);
@@ -393,7 +401,7 @@
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();
@@ -441,12 +449,15 @@
if (connected) {
mLocalIp = determineLocalIp();
+ mKeepAliveInterval = -1;
+ mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
for (SipSessionGroupExt group : mSipGroups.values()) {
group.onConnectivityChanged(true);
}
if (isWifi && (mWifiLock != null)) stopWifiScanner();
} else {
mMyWakeLock.reset(); // in case there's a leak
+ stopPortMappingMeasurement();
if (isWifi && (mWifiLock != null)) startWifiScanner();
}
} catch (SipException e) {
@@ -454,6 +465,48 @@
}
}
+ private void stopPortMappingMeasurement() {
+ if (mIntervalMeasurementProcess != null) {
+ mIntervalMeasurementProcess.stop();
+ mIntervalMeasurementProcess = null;
+ }
+ }
+
+ private void startPortMappingLifetimeMeasurement(
+ SipProfile localProfile) {
+ startPortMappingLifetimeMeasurement(localProfile,
+ DEFAULT_MAX_KEEPALIVE_INTERVAL);
+ }
+
+ private void startPortMappingLifetimeMeasurement(
+ SipProfile localProfile, int maxInterval) {
+ if ((mIntervalMeasurementProcess == null)
+ && (mKeepAliveInterval == -1)
+ && isBehindNAT(mLocalIp)) {
+ Log.d(TAG, "start NAT port mapping timeout measurement on "
+ + localProfile.getUriString());
+
+ int minInterval = mLastGoodKeepAliveInterval;
+ if (minInterval >= maxInterval) {
+ // If mLastGoodKeepAliveInterval also does not work, reset it
+ // to the default min
+ minInterval = mLastGoodKeepAliveInterval
+ = DEFAULT_KEEPALIVE_INTERVAL;
+ Log.d(TAG, " reset min interval to " + minInterval);
+ }
+ mIntervalMeasurementProcess = new IntervalMeasurementProcess(
+ localProfile, minInterval, maxInterval);
+ mIntervalMeasurementProcess.start();
+ }
+ }
+
+ private void restartPortMappingLifetimeMeasurement(
+ SipProfile localProfile, int maxInterval) {
+ stopPortMappingMeasurement();
+ mKeepAliveInterval = -1;
+ startPortMappingLifetimeMeasurement(localProfile, maxInterval);
+ }
+
private synchronized void addPendingSession(ISipSession session) {
try {
cleanUpPendingSessions();
@@ -490,6 +543,33 @@
return false;
}
+ private synchronized void onKeepAliveIntervalChanged() {
+ for (SipSessionGroupExt group : mSipGroups.values()) {
+ group.onKeepAliveIntervalChanged();
+ }
+ }
+
+ private int getKeepAliveInterval() {
+ return (mKeepAliveInterval < 0)
+ ? mLastGoodKeepAliveInterval
+ : 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;
@@ -517,6 +597,16 @@
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
@@ -524,7 +614,7 @@
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?");
@@ -687,60 +777,170 @@
}
}
- // 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 class IntervalMeasurementProcess implements Runnable,
+ SipSessionGroup.KeepAliveProcessCallback {
+ private static final String TAG = "SipKeepAliveInterval";
+ private static final int MIN_INTERVAL = 5; // in seconds
+ private static final int PASS_THRESHOLD = 10;
+ private static final int MAX_RETRY_COUNT = 5;
+ private static final int NAT_MEASUREMENT_RETRY_INTERVAL = 120; // in seconds
+ private SipSessionGroupExt mGroup;
private SipSessionGroup.SipSessionImpl mSession;
- private boolean mRunning = false;
+ private int mMinInterval;
+ private int mMaxInterval;
+ private int mInterval;
+ private int mPassCount = 0;
- public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
- mSession = session;
+ public IntervalMeasurementProcess(SipProfile localProfile,
+ int minInterval, int maxInterval) {
+ mMaxInterval = maxInterval;
+ mMinInterval = minInterval;
+ mInterval = (maxInterval + minInterval) / 2;
+
+ // Don't start measurement if the interval is too small
+ if (mInterval < DEFAULT_KEEPALIVE_INTERVAL) {
+ Log.w(TAG, "interval is too small; measurement aborted; "
+ + "maxInterval=" + mMaxInterval);
+ return;
+ } else if (checkTermination()) {
+ Log.w(TAG, "interval is too small; measurement aborted; "
+ + "interval=[" + mMinInterval + "," + mMaxInterval
+ + "]");
+ return;
+ }
+
+ try {
+ 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));
+ } catch (Exception e) {
+ Log.w(TAG, "start interval measurement error: " + e);
+ }
}
public void start() {
- if (mRunning) return;
- mRunning = true;
- mTimer.set(INTERVAL * 1000, this);
- }
-
- // timeout handler
- public void run() {
synchronized (SipService.this) {
- if (!mRunning) return;
-
- if (DEBUGV) Log.v(TAG, "~~~ keepalive: "
- + mSession.getLocalProfile().getUriString());
- SipSessionGroup.SipSessionImpl session = mSession.duplicate();
+ Log.d(TAG, "start measurement w interval=" + mInterval);
+ if (mSession == null) {
+ mSession = (SipSessionGroup.SipSessionImpl)
+ mGroup.createSession(null);
+ }
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);
- }
- } catch (Throwable t) {
- Log.w(TAG, "keepalive error: " + t);
+ mSession.startKeepAliveProcess(mInterval, this);
+ } catch (SipException e) {
+ Log.e(TAG, "start()", e);
}
}
}
public void stop() {
- if (DEBUGV && (mSession != null)) Log.v(TAG, "stop keepalive:"
- + mSession.getLocalProfile().getUriString());
- mRunning = false;
- mSession = null;
+ synchronized (SipService.this) {
+ if (mSession != null) {
+ mSession.stopKeepAliveProcess();
+ mSession = null;
+ }
+ mTimer.cancel(this);
+ }
+ }
+
+ private void restart() {
+ synchronized (SipService.this) {
+ // Return immediately if the measurement process is stopped
+ if (mSession == null) return;
+
+ Log.d(TAG, "restart measurement w interval=" + mInterval);
+ try {
+ mSession.stopKeepAliveProcess();
+ mPassCount = 0;
+ mSession.startKeepAliveProcess(mInterval, this);
+ } catch (SipException e) {
+ Log.e(TAG, "restart()", e);
+ }
+ }
+ }
+
+ private boolean checkTermination() {
+ return ((mMaxInterval - mMinInterval) < MIN_INTERVAL);
+ }
+
+ // SipSessionGroup.KeepAliveProcessCallback
+ @Override
+ public void onResponse(boolean portChanged) {
+ synchronized (SipService.this) {
+ if (!portChanged) {
+ if (++mPassCount != PASS_THRESHOLD) return;
+ // update the interval, since the current interval is good to
+ // keep the port mapping.
+ if (mKeepAliveInterval > 0) {
+ mLastGoodKeepAliveInterval = mKeepAliveInterval;
+ }
+ 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 (checkTermination()) {
+ // update mKeepAliveInterval and stop measurement.
+ stop();
+ // If all the measurements failed, we still set it to
+ // mMinInterval; If mMinInterval still doesn't work, a new
+ // measurement with min interval=DEFAULT_KEEPALIVE_INTERVAL
+ // will be conducted.
+ mKeepAliveInterval = mMinInterval;
+ if (DEBUG) {
+ Log.d(TAG, "measured keepalive interval: "
+ + mKeepAliveInterval);
+ }
+ } else {
+ // calculate the new interval and continue.
+ mInterval = (mMaxInterval + mMinInterval) / 2;
+ if (DEBUG) {
+ Log.d(TAG, "current interval: " + mKeepAliveInterval
+ + ", test new interval: " + mInterval);
+ }
+ restart();
+ }
+ }
+ }
+
+ // SipSessionGroup.KeepAliveProcessCallback
+ @Override
+ public void onError(int errorCode, String description) {
+ Log.w(TAG, "interval measurement error: " + description);
+ restartLater();
+ }
+
+ // timeout handler
+ @Override
+ public void run() {
mTimer.cancel(this);
+ restart();
+ }
+
+ private void restartLater() {
+ synchronized (SipService.this) {
+ int interval = NAT_MEASUREMENT_RETRY_INTERVAL;
+ Log.d(TAG, "Retry measurement " + interval + "s later.");
+ mTimer.cancel(this);
+ mTimer.set(interval * 1000, this);
+ }
}
}
private class AutoRegistrationProcess extends SipSessionAdapter
- implements Runnable {
+ implements Runnable, SipSessionGroup.KeepAliveProcessCallback {
+ private static final int MIN_KEEPALIVE_SUCCESS_COUNT = 10;
+ 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;
@@ -748,6 +948,8 @@
private String mErrorMessage;
private boolean mRunning = false;
+ private int mKeepAliveSuccessCount = 0;
+
private String getAction() {
return toString();
}
@@ -766,11 +968,84 @@
// 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");
}
}
+ private void startKeepAliveProcess(int interval) {
+ Log.d(TAG, "start keepalive w interval=" + interval);
+ if (mKeepAliveSession == null) {
+ mKeepAliveSession = mSession.duplicate();
+ } else {
+ mKeepAliveSession.stopKeepAliveProcess();
+ }
+ try {
+ mKeepAliveSession.startKeepAliveProcess(interval, this);
+ } catch (SipException e) {
+ Log.e(TAG, "failed to start keepalive w interval=" + interval,
+ e);
+ }
+ }
+
+ private void stopKeepAliveProcess() {
+ if (mKeepAliveSession != null) {
+ mKeepAliveSession.stopKeepAliveProcess();
+ mKeepAliveSession = null;
+ }
+ mKeepAliveSuccessCount = 0;
+ }
+
+ // SipSessionGroup.KeepAliveProcessCallback
+ @Override
+ public void onResponse(boolean portChanged) {
+ synchronized (SipService.this) {
+ if (portChanged) {
+ int interval = getKeepAliveInterval();
+ if (mKeepAliveSuccessCount < MIN_KEEPALIVE_SUCCESS_COUNT) {
+ Log.i(TAG, "keepalive doesn't work with interval "
+ + interval + ", past success count="
+ + mKeepAliveSuccessCount);
+ if (interval > DEFAULT_KEEPALIVE_INTERVAL) {
+ restartPortMappingLifetimeMeasurement(
+ mSession.getLocalProfile(), interval);
+ mKeepAliveSuccessCount = 0;
+ }
+ } else {
+ Log.i(TAG, "keep keepalive going with interval "
+ + interval + ", past success count="
+ + mKeepAliveSuccessCount);
+ mKeepAliveSuccessCount /= 2;
+ }
+ } else {
+ // Start keep-alive interval measurement on the first
+ // successfully kept-alive SipSessionGroup
+ startPortMappingLifetimeMeasurement(
+ mSession.getLocalProfile());
+ mKeepAliveSuccessCount++;
+ }
+
+ if (!mRunning || !portChanged) return;
+
+ // The keep alive process is stopped when port is changed;
+ // Nullify the session so that the process can be restarted
+ // again when the re-registration is done
+ mKeepAliveSession = null;
+
+ // 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);
+ onResponse(true); // re-register immediately
+ }
+
public void stop() {
if (!mRunning) return;
mRunning = false;
@@ -781,15 +1056,23 @@
}
mTimer.cancel(this);
- if (mKeepAliveProcess != null) {
- mKeepAliveProcess.stop();
- mKeepAliveProcess = null;
- }
+ stopKeepAliveProcess();
mRegistered = false;
setListener(mProxy.getListener());
}
+ public void onKeepAliveIntervalChanged() {
+ if (mKeepAliveSession != null) {
+ int newInterval = getKeepAliveInterval();
+ if (DEBUGV) {
+ Log.v(TAG, "restart keepalive w interval=" + newInterval);
+ }
+ mKeepAliveSuccessCount = 0;
+ startKeepAliveProcess(newInterval);
+ }
+ }
+
public void setListener(ISipSessionListener listener) {
synchronized (SipService.this) {
mProxy.setListener(listener);
@@ -836,13 +1119,14 @@
}
// 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);
@@ -850,22 +1134,6 @@
}
}
- 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);
@@ -911,7 +1179,6 @@
mProxy.onRegistrationDone(session, duration);
if (duration > 0) {
- mSession.clearReRegisterRequired();
mExpiryTime = SystemClock.elapsedRealtime()
+ (duration * 1000);
@@ -924,13 +1191,10 @@
}
restart(duration);
- if (isBehindNAT(mLocalIp) ||
- mSession.getLocalProfile().getSendKeepAlive()) {
- if (mKeepAliveProcess == null) {
- mKeepAliveProcess =
- new KeepAliveProcess(mSession);
- }
- mKeepAliveProcess.start();
+ SipProfile localProfile = mSession.getLocalProfile();
+ if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp)
+ || localProfile.getSendKeepAlive())) {
+ startKeepAliveProcess(getKeepAliveInterval());
}
}
mMyWakeLock.release(session);
@@ -984,10 +1248,6 @@
private void restartLater() {
mRegistered = false;
restart(backoffDuration());
- if (mKeepAliveProcess != null) {
- mKeepAliveProcess.stop();
- mKeepAliveProcess = null;
- }
}
}
@@ -998,7 +1258,7 @@
@Override
public void onReceive(final Context context, final Intent intent) {
// Run the handler in MyExecutor to be protected by wake lock
- getExecutor().execute(new Runnable() {
+ mExecutor.execute(new Runnable() {
public void run() {
onReceiveInternal(context, intent);
}
@@ -1102,7 +1362,7 @@
@Override
public void run() {
// delegate to mExecutor
- getExecutor().execute(new Runnable() {
+ mExecutor.execute(new Runnable() {
public void run() {
realRun();
}
@@ -1127,300 +1387,6 @@
}
}
- /**
- * Timer that can schedule events to occur even when the device is in sleep.
- * Only used internally in this package.
- */
- class WakeupTimer extends BroadcastReceiver {
- private static final String TAG = "_SIP.WkTimer_";
- private static final String TRIGGER_TIME = "TriggerTime";
-
- private Context mContext;
- private AlarmManager mAlarmManager;
-
- // runnable --> time to execute in SystemClock
- private TreeSet<MyEvent> mEventQueue =
- new TreeSet<MyEvent>(new MyEventComparator());
-
- private PendingIntent mPendingIntent;
-
- public WakeupTimer(Context context) {
- mContext = context;
- mAlarmManager = (AlarmManager)
- context.getSystemService(Context.ALARM_SERVICE);
-
- IntentFilter filter = new IntentFilter(getAction());
- context.registerReceiver(this, filter);
- }
-
- /**
- * Stops the timer. No event can be scheduled after this method is called.
- */
- public synchronized void stop() {
- mContext.unregisterReceiver(this);
- if (mPendingIntent != null) {
- mAlarmManager.cancel(mPendingIntent);
- mPendingIntent = null;
- }
- mEventQueue.clear();
- mEventQueue = null;
- }
-
- private synchronized boolean stopped() {
- if (mEventQueue == null) {
- Log.w(TAG, "Timer stopped");
- return true;
- } else {
- return false;
- }
- }
-
- private void cancelAlarm() {
- mAlarmManager.cancel(mPendingIntent);
- mPendingIntent = null;
- }
-
- private void recalculatePeriods() {
- if (mEventQueue.isEmpty()) return;
-
- MyEvent firstEvent = mEventQueue.first();
- int minPeriod = firstEvent.mMaxPeriod;
- long minTriggerTime = firstEvent.mTriggerTime;
- for (MyEvent e : mEventQueue) {
- e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod;
- int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod
- - minTriggerTime);
- interval = interval / minPeriod * minPeriod;
- e.mTriggerTime = minTriggerTime + interval;
- }
- TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(
- mEventQueue.comparator());
- newQueue.addAll((Collection<MyEvent>) mEventQueue);
- mEventQueue.clear();
- mEventQueue = newQueue;
- if (DEBUG_TIMER) {
- Log.d(TAG, "queue re-calculated");
- printQueue();
- }
- }
-
- // Determines the period and the trigger time of the new event and insert it
- // to the queue.
- private void insertEvent(MyEvent event) {
- long now = SystemClock.elapsedRealtime();
- if (mEventQueue.isEmpty()) {
- event.mTriggerTime = now + event.mPeriod;
- mEventQueue.add(event);
- return;
- }
- MyEvent firstEvent = mEventQueue.first();
- int minPeriod = firstEvent.mPeriod;
- if (minPeriod <= event.mMaxPeriod) {
- event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod;
- int interval = event.mMaxPeriod;
- interval -= (int) (firstEvent.mTriggerTime - now);
- interval = interval / minPeriod * minPeriod;
- event.mTriggerTime = firstEvent.mTriggerTime + interval;
- mEventQueue.add(event);
- } else {
- long triggerTime = now + event.mPeriod;
- if (firstEvent.mTriggerTime < triggerTime) {
- event.mTriggerTime = firstEvent.mTriggerTime;
- event.mLastTriggerTime -= event.mPeriod;
- } else {
- event.mTriggerTime = triggerTime;
- }
- mEventQueue.add(event);
- recalculatePeriods();
- }
- }
-
- /**
- * Sets a periodic timer.
- *
- * @param period the timer period; in milli-second
- * @param callback is called back when the timer goes off; the same callback
- * can be specified in multiple timer events
- */
- public synchronized void set(int period, Runnable callback) {
- if (stopped()) return;
-
- long now = SystemClock.elapsedRealtime();
- MyEvent event = new MyEvent(period, callback, now);
- insertEvent(event);
-
- if (mEventQueue.first() == event) {
- if (mEventQueue.size() > 1) cancelAlarm();
- scheduleNext();
- }
-
- long triggerTime = event.mTriggerTime;
- if (DEBUG_TIMER) {
- Log.d(TAG, " add event " + event + " scheduled at "
- + showTime(triggerTime) + " at " + showTime(now)
- + ", #events=" + mEventQueue.size());
- printQueue();
- }
- }
-
- /**
- * Cancels all the timer events with the specified callback.
- *
- * @param callback the callback
- */
- public synchronized void cancel(Runnable callback) {
- if (stopped() || mEventQueue.isEmpty()) return;
- if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback);
-
- MyEvent firstEvent = mEventQueue.first();
- for (Iterator<MyEvent> iter = mEventQueue.iterator();
- iter.hasNext();) {
- MyEvent event = iter.next();
- if (event.mCallback == callback) {
- iter.remove();
- if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event);
- }
- }
- if (mEventQueue.isEmpty()) {
- cancelAlarm();
- } else if (mEventQueue.first() != firstEvent) {
- cancelAlarm();
- firstEvent = mEventQueue.first();
- firstEvent.mPeriod = firstEvent.mMaxPeriod;
- firstEvent.mTriggerTime = firstEvent.mLastTriggerTime
- + firstEvent.mPeriod;
- recalculatePeriods();
- scheduleNext();
- }
- if (DEBUG_TIMER) {
- Log.d(TAG, "after cancel:");
- printQueue();
- }
- }
-
- private void scheduleNext() {
- if (stopped() || mEventQueue.isEmpty()) return;
-
- if (mPendingIntent != null) {
- throw new RuntimeException("pendingIntent is not null!");
- }
-
- MyEvent event = mEventQueue.first();
- Intent intent = new Intent(getAction());
- intent.putExtra(TRIGGER_TIME, event.mTriggerTime);
- PendingIntent pendingIntent = mPendingIntent =
- PendingIntent.getBroadcast(mContext, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- event.mTriggerTime, pendingIntent);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- // This callback is already protected by AlarmManager's wake lock.
- String action = intent.getAction();
- if (getAction().equals(action)
- && intent.getExtras().containsKey(TRIGGER_TIME)) {
- mPendingIntent = null;
- long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L);
- execute(triggerTime);
- } else {
- Log.d(TAG, "unrecognized intent: " + intent);
- }
- }
-
- private void printQueue() {
- int count = 0;
- for (MyEvent event : mEventQueue) {
- Log.d(TAG, " " + event + ": scheduled at "
- + showTime(event.mTriggerTime) + ": last at "
- + showTime(event.mLastTriggerTime));
- if (++count >= 5) break;
- }
- if (mEventQueue.size() > count) {
- Log.d(TAG, " .....");
- } else if (count == 0) {
- Log.d(TAG, " <empty>");
- }
- }
-
- private synchronized void execute(long triggerTime) {
- if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = "
- + showTime(triggerTime) + ": " + mEventQueue.size());
- if (stopped() || mEventQueue.isEmpty()) return;
-
- for (MyEvent event : mEventQueue) {
- if (event.mTriggerTime != triggerTime) break;
- if (DEBUG_TIMER) Log.d(TAG, "execute " + event);
-
- event.mLastTriggerTime = event.mTriggerTime;
- event.mTriggerTime += event.mPeriod;
-
- // run the callback in the handler thread to prevent deadlock
- getExecutor().execute(event.mCallback);
- }
- if (DEBUG_TIMER) {
- Log.d(TAG, "after timeout execution");
- printQueue();
- }
- scheduleNext();
- }
-
- private String getAction() {
- return toString();
- }
-
- private String showTime(long time) {
- int ms = (int) (time % 1000);
- int s = (int) (time / 1000);
- int m = s / 60;
- s %= 60;
- return String.format("%d.%d.%d", m, s, ms);
- }
- }
-
- private static class MyEvent {
- int mPeriod;
- int mMaxPeriod;
- long mTriggerTime;
- long mLastTriggerTime;
- Runnable mCallback;
-
- MyEvent(int period, Runnable callback, long now) {
- mPeriod = mMaxPeriod = period;
- mCallback = callback;
- mLastTriggerTime = now;
- }
-
- @Override
- public String toString() {
- String s = super.toString();
- s = s.substring(s.indexOf("@"));
- return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":"
- + toString(mCallback);
- }
-
- private String toString(Object o) {
- String s = o.toString();
- int index = s.indexOf("$");
- if (index > 0) s = s.substring(index + 1);
- return s;
- }
- }
-
- private static class MyEventComparator implements Comparator<MyEvent> {
- public int compare(MyEvent e1, MyEvent e2) {
- if (e1 == e2) return 0;
- int diff = e1.mMaxPeriod - e2.mMaxPeriod;
- if (diff == 0) diff = -1;
- return diff;
- }
-
- public boolean equals(Object that) {
- return (this == that);
- }
- }
-
private static Looper createLooper() {
HandlerThread thread = new HandlerThread("SipService.Executor");
thread.start();
@@ -1429,12 +1395,13 @@
// Executes immediate tasks in a single thread.
// Hold/release wake lock for running tasks
- private class MyExecutor extends Handler {
+ private class MyExecutor extends Handler implements Executor {
MyExecutor() {
super(createLooper());
}
- void execute(Runnable task) {
+ @Override
+ public void execute(Runnable task) {
mMyWakeLock.acquire(task);
Message.obtain(this, 0/* don't care */, task).sendToTarget();
}
diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java
index aa616a9..48d9b17 100644
--- a/java/com/android/server/sip/SipSessionGroup.java
+++ b/java/com/android/server/sip/SipSessionGroup.java
@@ -18,16 +18,22 @@
import gov.nist.javax.sip.clientauthutils.AccountManager;
import gov.nist.javax.sip.clientauthutils.UserCredentials;
-import gov.nist.javax.sip.header.SIPHeaderNames;
import gov.nist.javax.sip.header.ProxyAuthenticate;
+import gov.nist.javax.sip.header.ReferTo;
+import gov.nist.javax.sip.header.SIPHeaderNames;
+import gov.nist.javax.sip.header.StatusLine;
import gov.nist.javax.sip.header.WWWAuthenticate;
+import gov.nist.javax.sip.header.extensions.ReferredByHeader;
+import gov.nist.javax.sip.header.extensions.ReplacesHeader;
import gov.nist.javax.sip.message.SIPMessage;
+import gov.nist.javax.sip.message.SIPResponse;
import android.net.sip.ISipSession;
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;
@@ -68,12 +74,15 @@
import javax.sip.header.CSeqHeader;
import javax.sip.header.ExpiresHeader;
import javax.sip.header.FromHeader;
+import javax.sip.header.HeaderAddress;
import javax.sip.header.MinExpiresHeader;
+import javax.sip.header.ReferToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Message;
import javax.sip.message.Request;
import javax.sip.message.Response;
+
/**
* Manages {@link ISipSession}'s for a SIP account.
*/
@@ -89,6 +98,8 @@
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 int INCALL_KEEPALIVE_INTERVAL = 10; // in seconds
private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds
private static final EventObject DEREGISTER = new EventObject("Deregister");
@@ -107,25 +118,38 @@
private SipSessionImpl mCallReceiverSession;
private String mLocalIp;
+ private SipWakeupTimer mWakeupTimer;
private SipWakeLock mWakeLock;
// call-id-to-SipSession map
private Map<String, SipSessionImpl> mSessionMap =
new HashMap<String, SipSessionImpl>();
+ // external address observed from any response
+ private String mExternalIp;
+ private int mExternalPort;
+
/**
* @param myself the local profile with password crossed out
* @param password the password of the profile
* @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;
@@ -161,6 +185,8 @@
mCallReceiverSession = null;
mSessionMap.clear();
+
+ resetExternalAddress();
}
synchronized void onConnectivityChanged() {
@@ -176,6 +202,12 @@
}
}
+ synchronized void resetExternalAddress() {
+ Log.d(TAG, " reset external addr on " + mSipStack);
+ mExternalIp = null;
+ mExternalPort = 0;
+ }
+
public SipProfile getLocalProfile() {
return mLocalProfile;
}
@@ -349,29 +381,108 @@
return null;
}
+ private void extractExternalAddress(ResponseEvent evt) {
+ Response response = evt.getResponse();
+ ViaHeader viaHeader = (ViaHeader)(response.getHeader(
+ SIPHeaderNames.VIA));
+ if (viaHeader == null) return;
+ int rport = viaHeader.getRPort();
+ String externalIp = viaHeader.getReceived();
+ if ((rport > 0) && (externalIp != null)) {
+ mExternalIp = externalIp;
+ mExternalPort = rport;
+ Log.d(TAG, " got external addr " + externalIp + ":" + rport
+ + " on " + mSipStack);
+ }
+ }
+
+ private SipSessionImpl createNewSession(RequestEvent event,
+ ISipSessionListener listener, ServerTransaction transaction,
+ int newState) throws SipException {
+ SipSessionImpl newSession = new SipSessionImpl(listener);
+ newSession.mServerTransaction = transaction;
+ newSession.mState = newState;
+ newSession.mDialog = newSession.mServerTransaction.getDialog();
+ newSession.mInviteReceived = event;
+ newSession.mPeerProfile = createPeerProfile((HeaderAddress)
+ event.getRequest().getHeader(FromHeader.NAME));
+ newSession.mPeerSessionDescription =
+ extractContent(event.getRequest());
+ return newSession;
+ }
+
private class SipSessionCallReceiverImpl extends SipSessionImpl {
public SipSessionCallReceiverImpl(ISipSessionListener listener) {
super(listener);
}
+ private int processInviteWithReplaces(RequestEvent event,
+ ReplacesHeader replaces) {
+ String callId = replaces.getCallId();
+ SipSessionImpl session = mSessionMap.get(callId);
+ if (session == null) {
+ return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
+ }
+
+ Dialog dialog = session.mDialog;
+ if (dialog == null) return Response.DECLINE;
+
+ if (!dialog.getLocalTag().equals(replaces.getToTag()) ||
+ !dialog.getRemoteTag().equals(replaces.getFromTag())) {
+ // No match is found, returns 481.
+ return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
+ }
+
+ ReferredByHeader referredBy = (ReferredByHeader) event.getRequest()
+ .getHeader(ReferredByHeader.NAME);
+ if ((referredBy == null) ||
+ !dialog.getRemoteParty().equals(referredBy.getAddress())) {
+ return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
+ }
+ return Response.OK;
+ }
+
+ private void processNewInviteRequest(RequestEvent event)
+ throws SipException {
+ ReplacesHeader replaces = (ReplacesHeader) event.getRequest()
+ .getHeader(ReplacesHeader.NAME);
+ SipSessionImpl newSession = null;
+ if (replaces != null) {
+ int response = processInviteWithReplaces(event, replaces);
+ if (DEBUG) {
+ Log.v(TAG, "ReplacesHeader: " + replaces
+ + " response=" + response);
+ }
+ if (response == Response.OK) {
+ SipSessionImpl replacedSession =
+ mSessionMap.get(replaces.getCallId());
+ // got INVITE w/ replaces request.
+ newSession = createNewSession(event,
+ replacedSession.mProxy.getListener(),
+ mSipHelper.getServerTransaction(event),
+ SipSession.State.INCOMING_CALL);
+ newSession.mProxy.onCallTransferring(newSession,
+ newSession.mPeerSessionDescription);
+ } else {
+ mSipHelper.sendResponse(event, response);
+ }
+ } else {
+ // New Incoming call.
+ newSession = createNewSession(event, mProxy,
+ mSipHelper.sendRinging(event, generateTag()),
+ SipSession.State.INCOMING_CALL);
+ mProxy.onRinging(newSession, newSession.mPeerProfile,
+ newSession.mPeerSessionDescription);
+ }
+ if (newSession != null) addSipSession(newSession);
+ }
+
public boolean process(EventObject evt) throws SipException {
if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": "
+ SipSession.State.toString(mState) + ": processing "
+ log(evt));
if (isRequestEvent(Request.INVITE, evt)) {
- RequestEvent event = (RequestEvent) evt;
- SipSessionImpl newSession = new SipSessionImpl(mProxy);
- newSession.mState = SipSession.State.INCOMING_CALL;
- newSession.mServerTransaction = mSipHelper.sendRinging(event,
- generateTag());
- newSession.mDialog = newSession.mServerTransaction.getDialog();
- newSession.mInviteReceived = event;
- newSession.mPeerProfile = createPeerProfile(event.getRequest());
- newSession.mPeerSessionDescription =
- extractContent(event.getRequest());
- addSipSession(newSession);
- mProxy.onRinging(newSession, newSession.mPeerProfile,
- newSession.mPeerSessionDescription);
+ processNewInviteRequest((RequestEvent) evt);
return true;
} else if (isRequestEvent(Request.OPTIONS, evt)) {
mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
@@ -382,6 +493,12 @@
}
}
+ 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 +509,17 @@
ClientTransaction mClientTransaction;
String mPeerSessionDescription;
boolean mInCall;
- SessionTimer mTimer;
+ SessionTimer mSessionTimer;
int mAuthenticationRetryCount;
- // for registration
- boolean mReRegisterFlag = false;
- int mRPort;
+ private KeepAliveProcess mKeepAliveProcess;
+
+ private SipSessionImpl mKeepAliveSession;
+
+ // the following three members are used for handling refer request.
+ SipSessionImpl mReferSession;
+ ReferredByHeader mReferredBy;
+ String mReplaces;
// lightweight timer
class SessionTimer {
@@ -447,8 +569,10 @@
mState = SipSession.State.READY_TO_CALL;
mInviteReceived = null;
mPeerSessionDescription = null;
- mRPort = 0;
mAuthenticationRetryCount = 0;
+ mReferSession = null;
+ mReferredBy = null;
+ mReplaces = null;
if (mDialog != null) mDialog.delete();
mDialog = null;
@@ -468,6 +592,11 @@
mClientTransaction = null;
cancelSessionTimer();
+
+ if (mKeepAliveSession != null) {
+ mKeepAliveSession.stopKeepAliveProcess();
+ mKeepAliveSession = null;
+ }
}
public boolean isInCall() {
@@ -513,7 +642,9 @@
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);
}
}
@@ -529,12 +660,8 @@
public void answerCall(String sessionDescription, int timeout) {
synchronized (SipSessionGroup.this) {
if (mPeerProfile == null) return;
- try {
- processCommand(new MakeCallCommand(mPeerProfile,
- sessionDescription, timeout));
- } catch (SipException e) {
- onError(e);
- }
+ doCommandAsync(new MakeCallCommand(mPeerProfile,
+ sessionDescription, timeout));
}
}
@@ -558,34 +685,6 @@
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)) {
@@ -617,11 +716,17 @@
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();
} else if (evt instanceof ResponseEvent) {
dialog = ((ResponseEvent) evt).getDialog();
+ extractExternalAddress((ResponseEvent) evt);
}
if (dialog != null) mDialog = dialog;
@@ -632,9 +737,6 @@
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;
@@ -759,10 +861,6 @@
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");
@@ -783,48 +881,6 @@
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)) {
@@ -930,15 +986,26 @@
return (proxyAuth == null) ? null : proxyAuth.getNonce();
}
+ private String getResponseString(int statusCode) {
+ StatusLine statusLine = new StatusLine();
+ statusLine.setStatusCode(statusCode);
+ statusLine.setReasonPhrase(SIPResponse.getReasonPhrase(statusCode));
+ return statusLine.encode();
+ }
+
private boolean readyForCall(EventObject evt) throws SipException {
// expect MakeCallCommand, RegisterCommand, DEREGISTER
if (evt instanceof MakeCallCommand) {
mState = SipSession.State.OUTGOING_CALL;
MakeCallCommand cmd = (MakeCallCommand) evt;
mPeerProfile = cmd.getPeerProfile();
- mClientTransaction = mSipHelper.sendInvite(mLocalProfile,
- mPeerProfile, cmd.getSessionDescription(),
- generateTag());
+ if (mReferSession != null) {
+ mSipHelper.sendReferNotify(mReferSession.mDialog,
+ getResponseString(Response.TRYING));
+ }
+ mClientTransaction = mSipHelper.sendInvite(
+ mLocalProfile, mPeerProfile, cmd.getSessionDescription(),
+ generateTag(), mReferredBy, mReplaces);
mDialog = mClientTransaction.getDialog();
addSipSession(this);
startSessionTimer(cmd.getTimeout());
@@ -973,7 +1040,8 @@
mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived,
mLocalProfile,
((MakeCallCommand) evt).getSessionDescription(),
- mServerTransaction);
+ mServerTransaction,
+ mExternalIp, mExternalPort);
startSessionTimer(((MakeCallCommand) evt).getTimeout());
return true;
} else if (END_CALL == evt) {
@@ -996,7 +1064,13 @@
throws SipException {
// expect ACK, CANCEL request
if (isRequestEvent(Request.ACK, evt)) {
- establishCall();
+ String sdp = extractContent(((RequestEvent) evt).getRequest());
+ if (sdp != null) mPeerSessionDescription = sdp;
+ if (mPeerSessionDescription == null) {
+ onError(SipErrorCode.CLIENT_ERROR, "peer sdp is empty");
+ } else {
+ establishCall(false);
+ }
return true;
} else if (isRequestEvent(Request.CANCEL, evt)) {
// http://tools.ietf.org/html/rfc3261#section-9.2
@@ -1026,9 +1100,15 @@
}
return true;
case Response.OK:
+ if (mReferSession != null) {
+ mSipHelper.sendReferNotify(mReferSession.mDialog,
+ getResponseString(Response.OK));
+ // since we don't need to remember the session anymore.
+ mReferSession = null;
+ }
mSipHelper.sendInviteAck(event, mDialog);
mPeerSessionDescription = extractContent(response);
- establishCall();
+ establishCall(true);
return true;
case Response.UNAUTHORIZED:
case Response.PROXY_AUTHENTICATION_REQUIRED:
@@ -1041,6 +1121,10 @@
// rfc3261#section-14.1; re-schedule invite
return true;
default:
+ if (mReferSession != null) {
+ mSipHelper.sendReferNotify(mReferSession.mDialog,
+ getResponseString(Response.SERVICE_UNAVAILABLE));
+ }
if (statusCode >= 400) {
// error: an ack is sent automatically by the stack
onError(response);
@@ -1109,6 +1193,38 @@
return false;
}
+ private boolean processReferRequest(RequestEvent event)
+ throws SipException {
+ try {
+ ReferToHeader referto = (ReferToHeader) event.getRequest()
+ .getHeader(ReferTo.NAME);
+ Address address = referto.getAddress();
+ SipURI uri = (SipURI) address.getURI();
+ String replacesHeader = uri.getHeader(ReplacesHeader.NAME);
+ String username = uri.getUser();
+ if (username == null) {
+ mSipHelper.sendResponse(event, Response.BAD_REQUEST);
+ return false;
+ }
+ // send notify accepted
+ mSipHelper.sendResponse(event, Response.ACCEPTED);
+ SipSessionImpl newSession = createNewSession(event,
+ this.mProxy.getListener(),
+ mSipHelper.getServerTransaction(event),
+ SipSession.State.READY_TO_CALL);
+ newSession.mReferSession = this;
+ newSession.mReferredBy = (ReferredByHeader) event.getRequest()
+ .getHeader(ReferredByHeader.NAME);
+ newSession.mReplaces = replacesHeader;
+ newSession.mPeerProfile = createPeerProfile(referto);
+ newSession.mProxy.onCallTransferring(newSession,
+ null);
+ return true;
+ } catch (IllegalArgumentException e) {
+ throw new SipException("createPeerProfile()", e);
+ }
+ }
+
private boolean inCall(EventObject evt) throws SipException {
// expect END_CALL cmd, BYE request, hold call (MakeCallCommand)
// OK retransmission is handled in SipStack
@@ -1129,6 +1245,8 @@
mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
endCallNormally();
return true;
+ } else if (isRequestEvent(Request.REFER, evt)) {
+ return processReferRequest((RequestEvent) evt);
} else if (evt instanceof MakeCallCommand) {
// to change call
mState = SipSession.State.OUTGOING_CALL;
@@ -1136,6 +1254,8 @@
((MakeCallCommand) evt).getSessionDescription());
startSessionTimer(((MakeCallCommand) evt).getTimeout());
return true;
+ } else if (evt instanceof ResponseEvent) {
+ if (expectResponse(Request.NOTIFY, evt)) return true;
}
return false;
}
@@ -1143,15 +1263,15 @@
// 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;
}
}
@@ -1160,10 +1280,26 @@
response.getStatusCode());
}
- private void establishCall() {
+ private void enableKeepAlive() {
+ if (mKeepAliveSession != null) {
+ mKeepAliveSession.stopKeepAliveProcess();
+ } else {
+ mKeepAliveSession = duplicate();
+ }
+ try {
+ mKeepAliveSession.startKeepAliveProcess(
+ INCALL_KEEPALIVE_INTERVAL, mPeerProfile, null);
+ } catch (SipException e) {
+ Log.w(TAG, "keepalive cannot be enabled; ignored", e);
+ mKeepAliveSession.stopKeepAliveProcess();
+ }
+ }
+
+ private void establishCall(boolean enableKeepAlive) {
mState = SipSession.State.IN_CALL;
- mInCall = true;
cancelSessionTimer();
+ if (!mInCall && enableKeepAlive) enableKeepAlive();
+ mInCall = true;
mProxy.onCallEstablished(this, mPeerSessionDescription);
}
@@ -1277,6 +1413,174 @@
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;
+ private int mInterval; // just for debugging
+
+ // @param interval in seconds
+ void start(int interval, KeepAliveProcessCallback callback) {
+ if (mRunning) return;
+ mRunning = true;
+ mInterval = interval;
+ 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) {
+ resetExternalAddress();
+ 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) {
+ String peerUri = (mPeerProfile == null)
+ ? "null"
+ : mPeerProfile.getUriString();
+ Log.d(TAG, "keepalive: " + mLocalProfile.getUriString()
+ + " --> " + peerUri + ", interval=" + mInterval);
+ }
+ 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();
+ }
+ }
}
/**
@@ -1328,12 +1632,10 @@
return false;
}
- private static SipProfile createPeerProfile(Request request)
+ private static SipProfile createPeerProfile(HeaderAddress header)
throws SipException {
try {
- FromHeader fromHeader =
- (FromHeader) request.getHeader(FromHeader.NAME);
- Address address = fromHeader.getAddress();
+ Address address = header.getAddress();
SipURI uri = (SipURI) address.getURI();
String username = uri.getUser();
if (username == null) username = ANONYMOUS;
@@ -1368,15 +1670,16 @@
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;
@@ -1392,12 +1695,6 @@
}
}
- private class OptionsCommand extends EventObject {
- public OptionsCommand() {
- super(SipSessionGroup.this);
- }
- }
-
private class RegisterCommand extends EventObject {
private int mDuration;
@@ -1439,4 +1736,46 @@
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);
+ }
+ }
+ });
+ }
+ }
}
diff --git a/java/com/android/server/sip/SipSessionListenerProxy.java b/java/com/android/server/sip/SipSessionListenerProxy.java
index f8be0a8..8655a3a 100644
--- a/java/com/android/server/sip/SipSessionListenerProxy.java
+++ b/java/com/android/server/sip/SipSessionListenerProxy.java
@@ -110,6 +110,20 @@
});
}
+ public void onCallTransferring(final ISipSession newSession,
+ final String sessionDescription) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onCallTransferring(newSession, sessionDescription);
+ } catch (Throwable t) {
+ handle(t, "onCallTransferring()");
+ }
+ }
+ });
+ }
+
public void onCallBusy(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
diff --git a/java/com/android/server/sip/SipWakeupTimer.java b/java/com/android/server/sip/SipWakeupTimer.java
new file mode 100644
index 0000000..00d47ac
--- /dev/null
+++ b/java/com/android/server/sip/SipWakeupTimer.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.sip;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.TreeSet;
+import java.util.concurrent.Executor;
+import javax.sip.SipException;
+
+/**
+ * Timer that can schedule events to occur even when the device is in sleep.
+ */
+class SipWakeupTimer extends BroadcastReceiver {
+ private static final String TAG = "_SIP.WkTimer_";
+ private static final String TRIGGER_TIME = "TriggerTime";
+ private static final boolean DEBUG_TIMER = SipService.DEBUG && false;
+
+ private Context mContext;
+ private AlarmManager mAlarmManager;
+
+ // runnable --> time to execute in SystemClock
+ private TreeSet<MyEvent> mEventQueue =
+ new TreeSet<MyEvent>(new MyEventComparator());
+
+ private PendingIntent mPendingIntent;
+
+ private Executor mExecutor;
+
+ public SipWakeupTimer(Context context, Executor executor) {
+ mContext = context;
+ mAlarmManager = (AlarmManager)
+ context.getSystemService(Context.ALARM_SERVICE);
+
+ IntentFilter filter = new IntentFilter(getAction());
+ context.registerReceiver(this, filter);
+ mExecutor = executor;
+ }
+
+ /**
+ * Stops the timer. No event can be scheduled after this method is called.
+ */
+ public synchronized void stop() {
+ mContext.unregisterReceiver(this);
+ if (mPendingIntent != null) {
+ mAlarmManager.cancel(mPendingIntent);
+ mPendingIntent = null;
+ }
+ mEventQueue.clear();
+ mEventQueue = null;
+ }
+
+ private boolean stopped() {
+ if (mEventQueue == null) {
+ Log.w(TAG, "Timer stopped");
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void cancelAlarm() {
+ mAlarmManager.cancel(mPendingIntent);
+ mPendingIntent = null;
+ }
+
+ private void recalculatePeriods() {
+ if (mEventQueue.isEmpty()) return;
+
+ MyEvent firstEvent = mEventQueue.first();
+ int minPeriod = firstEvent.mMaxPeriod;
+ long minTriggerTime = firstEvent.mTriggerTime;
+ for (MyEvent e : mEventQueue) {
+ e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod;
+ int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod
+ - minTriggerTime);
+ interval = interval / minPeriod * minPeriod;
+ e.mTriggerTime = minTriggerTime + interval;
+ }
+ TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(
+ mEventQueue.comparator());
+ newQueue.addAll((Collection<MyEvent>) mEventQueue);
+ mEventQueue.clear();
+ mEventQueue = newQueue;
+ if (DEBUG_TIMER) {
+ Log.d(TAG, "queue re-calculated");
+ printQueue();
+ }
+ }
+
+ // Determines the period and the trigger time of the new event and insert it
+ // to the queue.
+ private void insertEvent(MyEvent event) {
+ long now = SystemClock.elapsedRealtime();
+ if (mEventQueue.isEmpty()) {
+ event.mTriggerTime = now + event.mPeriod;
+ mEventQueue.add(event);
+ return;
+ }
+ MyEvent firstEvent = mEventQueue.first();
+ int minPeriod = firstEvent.mPeriod;
+ if (minPeriod <= event.mMaxPeriod) {
+ event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod;
+ int interval = event.mMaxPeriod;
+ interval -= (int) (firstEvent.mTriggerTime - now);
+ interval = interval / minPeriod * minPeriod;
+ event.mTriggerTime = firstEvent.mTriggerTime + interval;
+ mEventQueue.add(event);
+ } else {
+ long triggerTime = now + event.mPeriod;
+ if (firstEvent.mTriggerTime < triggerTime) {
+ event.mTriggerTime = firstEvent.mTriggerTime;
+ event.mLastTriggerTime -= event.mPeriod;
+ } else {
+ event.mTriggerTime = triggerTime;
+ }
+ mEventQueue.add(event);
+ recalculatePeriods();
+ }
+ }
+
+ /**
+ * Sets a periodic timer.
+ *
+ * @param period the timer period; in milli-second
+ * @param callback is called back when the timer goes off; the same callback
+ * can be specified in multiple timer events
+ */
+ public synchronized void set(int period, Runnable callback) {
+ if (stopped()) return;
+
+ long now = SystemClock.elapsedRealtime();
+ MyEvent event = new MyEvent(period, callback, now);
+ insertEvent(event);
+
+ if (mEventQueue.first() == event) {
+ if (mEventQueue.size() > 1) cancelAlarm();
+ scheduleNext();
+ }
+
+ long triggerTime = event.mTriggerTime;
+ if (DEBUG_TIMER) {
+ Log.d(TAG, " add event " + event + " scheduled on "
+ + showTime(triggerTime) + " at " + showTime(now)
+ + ", #events=" + mEventQueue.size());
+ printQueue();
+ }
+ }
+
+ /**
+ * Cancels all the timer events with the specified callback.
+ *
+ * @param callback the callback
+ */
+ public synchronized void cancel(Runnable callback) {
+ if (stopped() || mEventQueue.isEmpty()) return;
+ if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback);
+
+ MyEvent firstEvent = mEventQueue.first();
+ for (Iterator<MyEvent> iter = mEventQueue.iterator();
+ iter.hasNext();) {
+ MyEvent event = iter.next();
+ if (event.mCallback == callback) {
+ iter.remove();
+ if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event);
+ }
+ }
+ if (mEventQueue.isEmpty()) {
+ cancelAlarm();
+ } else if (mEventQueue.first() != firstEvent) {
+ cancelAlarm();
+ firstEvent = mEventQueue.first();
+ firstEvent.mPeriod = firstEvent.mMaxPeriod;
+ firstEvent.mTriggerTime = firstEvent.mLastTriggerTime
+ + firstEvent.mPeriod;
+ recalculatePeriods();
+ scheduleNext();
+ }
+ if (DEBUG_TIMER) {
+ Log.d(TAG, "after cancel:");
+ printQueue();
+ }
+ }
+
+ private void scheduleNext() {
+ if (stopped() || mEventQueue.isEmpty()) return;
+
+ if (mPendingIntent != null) {
+ throw new RuntimeException("pendingIntent is not null!");
+ }
+
+ MyEvent event = mEventQueue.first();
+ Intent intent = new Intent(getAction());
+ intent.putExtra(TRIGGER_TIME, event.mTriggerTime);
+ PendingIntent pendingIntent = mPendingIntent =
+ PendingIntent.getBroadcast(mContext, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ event.mTriggerTime, pendingIntent);
+ }
+
+ @Override
+ public synchronized void onReceive(Context context, Intent intent) {
+ // This callback is already protected by AlarmManager's wake lock.
+ String action = intent.getAction();
+ if (getAction().equals(action)
+ && intent.getExtras().containsKey(TRIGGER_TIME)) {
+ mPendingIntent = null;
+ long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L);
+ execute(triggerTime);
+ } else {
+ Log.d(TAG, "unrecognized intent: " + intent);
+ }
+ }
+
+ private void printQueue() {
+ int count = 0;
+ for (MyEvent event : mEventQueue) {
+ Log.d(TAG, " " + event + ": scheduled at "
+ + showTime(event.mTriggerTime) + ": last at "
+ + showTime(event.mLastTriggerTime));
+ if (++count >= 5) break;
+ }
+ if (mEventQueue.size() > count) {
+ Log.d(TAG, " .....");
+ } else if (count == 0) {
+ Log.d(TAG, " <empty>");
+ }
+ }
+
+ private void execute(long triggerTime) {
+ if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = "
+ + showTime(triggerTime) + ": " + mEventQueue.size());
+ if (stopped() || mEventQueue.isEmpty()) return;
+
+ for (MyEvent event : mEventQueue) {
+ if (event.mTriggerTime != triggerTime) continue;
+ if (DEBUG_TIMER) Log.d(TAG, "execute " + event);
+
+ event.mLastTriggerTime = triggerTime;
+ event.mTriggerTime += event.mPeriod;
+
+ // run the callback in the handler thread to prevent deadlock
+ mExecutor.execute(event.mCallback);
+ }
+ if (DEBUG_TIMER) {
+ Log.d(TAG, "after timeout execution");
+ printQueue();
+ }
+ scheduleNext();
+ }
+
+ private String getAction() {
+ return toString();
+ }
+
+ private String showTime(long time) {
+ int ms = (int) (time % 1000);
+ int s = (int) (time / 1000);
+ int m = s / 60;
+ s %= 60;
+ return String.format("%d.%d.%d", m, s, ms);
+ }
+
+ private static class MyEvent {
+ int mPeriod;
+ int mMaxPeriod;
+ long mTriggerTime;
+ long mLastTriggerTime;
+ Runnable mCallback;
+
+ MyEvent(int period, Runnable callback, long now) {
+ mPeriod = mMaxPeriod = period;
+ mCallback = callback;
+ mLastTriggerTime = now;
+ }
+
+ @Override
+ public String toString() {
+ String s = super.toString();
+ s = s.substring(s.indexOf("@"));
+ return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":"
+ + toString(mCallback);
+ }
+
+ private String toString(Object o) {
+ String s = o.toString();
+ int index = s.indexOf("$");
+ if (index > 0) s = s.substring(index + 1);
+ return s;
+ }
+ }
+
+ // Sort the events by mMaxPeriod so that the first event can be used to
+ // align events with larger periods
+ private static class MyEventComparator implements Comparator<MyEvent> {
+ public int compare(MyEvent e1, MyEvent e2) {
+ if (e1 == e2) return 0;
+ int diff = e1.mMaxPeriod - e2.mMaxPeriod;
+ if (diff == 0) diff = -1;
+ return diff;
+ }
+
+ public boolean equals(Object that) {
+ return (this == that);
+ }
+ }
+}
diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk
index 76c43ba..0815294 100644
--- a/jni/rtp/Android.mk
+++ b/jni/rtp/Android.mk
@@ -37,9 +37,9 @@
libcutils \
libutils \
libmedia \
- libstagefright
+ libstagefright_amrnb_common
-LOCAL_STATIC_LIBRARIES := libgsm
+LOCAL_STATIC_LIBRARIES := libgsm libstagefright_amrnbdec libstagefright_amrnbenc
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
@@ -49,10 +49,11 @@
frameworks/base/media/libstagefright/codecs/amrnb/enc/include \
frameworks/base/media/libstagefright/codecs/amrnb/enc/src \
frameworks/base/media/libstagefright/codecs/amrnb/dec/include \
- frameworks/base/media/libstagefright/codecs/amrnb/dec/src
+ frameworks/base/media/libstagefright/codecs/amrnb/dec/src \
+ system/media/audio_effects/include
LOCAL_CFLAGS += -fvisibility=hidden
-LOCAL_PRELINK_MODULE := false
+
include $(BUILD_SHARED_LIBRARY)
diff --git a/jni/rtp/AudioCodec.cpp b/jni/rtp/AudioCodec.cpp
index 2267ea0..c75fbc9 100644
--- a/jni/rtp/AudioCodec.cpp
+++ b/jni/rtp/AudioCodec.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include <string.h>
+#include <strings.h>
#include "AudioCodec.h"
diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp
index 41fedce..529b425 100644
--- a/jni/rtp/AudioGroup.cpp
+++ b/jni/rtp/AudioGroup.cpp
@@ -40,6 +40,9 @@
#include <media/AudioRecord.h>
#include <media/AudioTrack.h>
#include <media/mediarecorder.h>
+#include <media/AudioEffect.h>
+#include <audio_effects/effect_aec.h>
+#include <system/audio.h>
#include "jni.h"
#include "JNIHelp.h"
@@ -479,6 +482,7 @@
bool sendDtmf(int event);
bool add(AudioStream *stream);
bool remove(int socket);
+ bool platformHasAec() { return mPlatformHasAec; }
private:
enum {
@@ -489,6 +493,8 @@
LAST_MODE = 3,
};
+ bool checkPlatformAec();
+
AudioStream *mChain;
int mEventQueue;
volatile int mDtmfEvent;
@@ -497,6 +503,7 @@
int mSampleRate;
int mSampleCount;
int mDeviceSocket;
+ bool mPlatformHasAec;
class NetworkThread : public Thread
{
@@ -548,6 +555,7 @@
mDeviceSocket = -1;
mNetworkThread = new NetworkThread(this);
mDeviceThread = new DeviceThread(this);
+ mPlatformHasAec = checkPlatformAec();
}
AudioGroup::~AudioGroup()
@@ -628,10 +636,6 @@
if (mode == NORMAL && !strcmp(value, "herring")) {
mode = ECHO_SUPPRESSION;
}
- if (mode == ECHO_SUPPRESSION && AudioSystem::getParameters(
- 0, String8("ec_supported")) == "ec_supported=yes") {
- mode = NORMAL;
- }
if (mMode == mode) {
return true;
}
@@ -757,6 +761,25 @@
return true;
}
+bool AudioGroup::checkPlatformAec()
+{
+ effect_descriptor_t fxDesc;
+ uint32_t numFx;
+
+ if (AudioEffect::queryNumberEffects(&numFx) != NO_ERROR) {
+ return false;
+ }
+ for (uint32_t i = 0; i < numFx; i++) {
+ if (AudioEffect::queryEffect(i, &fxDesc) != NO_ERROR) {
+ continue;
+ }
+ if (memcmp(&fxDesc.type, FX_IID_AEC, sizeof(effect_uuid_t)) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool AudioGroup::DeviceThread::threadLoop()
{
int mode = mGroup->mMode;
@@ -767,10 +790,10 @@
// Find out the frame count for AudioTrack and AudioRecord.
int output = 0;
int input = 0;
- if (AudioTrack::getMinFrameCount(&output, AudioSystem::VOICE_CALL,
+ if (AudioTrack::getMinFrameCount(&output, AUDIO_STREAM_VOICE_CALL,
sampleRate) != NO_ERROR || output <= 0 ||
AudioRecord::getMinFrameCount(&input, sampleRate,
- AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) {
+ AUDIO_FORMAT_PCM_16_BIT, 1) != NO_ERROR || input <= 0) {
LOGE("cannot compute frame count");
return false;
}
@@ -787,19 +810,15 @@
// Initialize AudioTrack and AudioRecord.
AudioTrack track;
AudioRecord record;
- if (track.set(AudioSystem::VOICE_CALL, sampleRate, AudioSystem::PCM_16_BIT,
- AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || record.set(
- AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AudioSystem::PCM_16_BIT,
- AudioSystem::CHANNEL_IN_MONO, input) != NO_ERROR) {
+ if (track.set(AUDIO_STREAM_VOICE_CALL, sampleRate, AUDIO_FORMAT_PCM_16_BIT,
+ AUDIO_CHANNEL_OUT_MONO, output) != NO_ERROR || record.set(
+ AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AUDIO_FORMAT_PCM_16_BIT,
+ AUDIO_CHANNEL_IN_MONO, input) != NO_ERROR) {
LOGE("cannot initialize audio device");
return false;
}
LOGD("latency: output %d, input %d", track.latency(), record.latency());
- // Initialize echo canceler.
- EchoSuppressor echo(sampleCount,
- (track.latency() + record.latency()) * sampleRate / 1000);
-
// Give device socket a reasonable buffer size.
setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output));
setsockopt(deviceSocket, SOL_SOCKET, SO_SNDBUF, &output, sizeof(output));
@@ -808,6 +827,33 @@
char c;
while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1);
+ // check if platform supports echo cancellation and do not active local echo suppression in
+ // this case
+ EchoSuppressor *echo = NULL;
+ AudioEffect *aec = NULL;
+ if (mode == ECHO_SUPPRESSION) {
+ if (mGroup->platformHasAec()) {
+ aec = new AudioEffect(FX_IID_AEC,
+ NULL,
+ 0,
+ 0,
+ 0,
+ record.getSessionId(),
+ record.getInput());
+ status_t status = aec->initCheck();
+ if (status == NO_ERROR || status == ALREADY_EXISTS) {
+ aec->setEnabled(true);
+ } else {
+ delete aec;
+ aec = NULL;
+ }
+ }
+ // Create local echo suppressor if platform AEC cannot be used.
+ if (aec == NULL) {
+ echo = new EchoSuppressor(sampleCount,
+ (track.latency() + record.latency()) * sampleRate / 1000);
+ }
+ }
// Start AudioRecord before AudioTrack. This prevents AudioTrack from being
// disabled due to buffer underrun while waiting for AudioRecord.
if (mode != MUTED) {
@@ -841,7 +887,7 @@
track.releaseBuffer(&buffer);
} else if (status != TIMED_OUT && status != WOULD_BLOCK) {
LOGE("cannot write to AudioTrack");
- return true;
+ goto exit;
}
}
@@ -857,7 +903,7 @@
record.releaseBuffer(&buffer);
} else if (status != TIMED_OUT && status != WOULD_BLOCK) {
LOGE("cannot read from AudioRecord");
- return true;
+ goto exit;
}
}
}
@@ -868,15 +914,18 @@
}
if (mode != MUTED) {
- if (mode == NORMAL) {
- send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
- } else {
- echo.run(output, input);
- send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
+ if (echo != NULL) {
+ LOGV("echo->run()");
+ echo->run(output, input);
}
+ send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
}
}
- return false;
+
+exit:
+ delete echo;
+ delete aec;
+ return true;
}
//------------------------------------------------------------------------------