summaryrefslogtreecommitdiff
path: root/voip/java/com/android/server/sip/SipSessionGroup.java
diff options
context:
space:
mode:
Diffstat (limited to 'voip/java/com/android/server/sip/SipSessionGroup.java')
-rw-r--r--voip/java/com/android/server/sip/SipSessionGroup.java1863
1 files changed, 0 insertions, 1863 deletions
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
deleted file mode 100644
index e820f356bea3..000000000000
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ /dev/null
@@ -1,1863 +0,0 @@
-/*
- * Copyright (C) 2010 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 gov.nist.javax.sip.clientauthutils.AccountManager;
-import gov.nist.javax.sip.clientauthutils.UserCredentials;
-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.telephony.Rlog;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.DatagramSocket;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.text.ParseException;
-import java.util.EventObject;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-import javax.sip.ClientTransaction;
-import javax.sip.Dialog;
-import javax.sip.DialogTerminatedEvent;
-import javax.sip.IOExceptionEvent;
-import javax.sip.ObjectInUseException;
-import javax.sip.RequestEvent;
-import javax.sip.ResponseEvent;
-import javax.sip.ServerTransaction;
-import javax.sip.SipException;
-import javax.sip.SipFactory;
-import javax.sip.SipListener;
-import javax.sip.SipProvider;
-import javax.sip.SipStack;
-import javax.sip.TimeoutEvent;
-import javax.sip.Transaction;
-import javax.sip.TransactionTerminatedEvent;
-import javax.sip.address.Address;
-import javax.sip.address.SipURI;
-import javax.sip.header.CSeqHeader;
-import javax.sip.header.ContactHeader;
-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.
- */
-class SipSessionGroup implements SipListener {
- private static final String TAG = "SipSession";
- private static final boolean DBG = false;
- private static final boolean DBG_PING = false;
- private static final String ANONYMOUS = "anonymous";
- // Limit the size of thread pool to 1 for the order issue when the phone is
- // waken up from sleep and there are many packets to be processed in the SIP
- // stack. Note: The default thread pool size in NIST SIP stack is -1 which is
- // unlimited.
- 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 END_CALL_TIMER = 3; // in seconds
- private static final int KEEPALIVE_TIMEOUT = 5; // 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");
- private static final EventObject END_CALL = new EventObject("End call");
-
- private final SipProfile mLocalProfile;
- private final String mPassword;
-
- private SipStack mSipStack;
- private SipHelper mSipHelper;
-
- // session that processes INVITE requests
- 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 profile the local profile with password crossed out
- * @param password the password of the profile
- * @throws SipException if cannot assign requested address
- */
- public SipSessionGroup(SipProfile profile, String password,
- SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException {
- mLocalProfile = profile;
- mPassword = password;
- mWakeupTimer = timer;
- mWakeLock = wakeLock;
- reset();
- }
-
- // TODO: remove this method once SipWakeupTimer can better handle variety
- // of timeout values
- void setWakeupTimer(SipWakeupTimer timer) {
- mWakeupTimer = timer;
- }
-
- synchronized void reset() throws SipException {
- Properties properties = new Properties();
-
- String protocol = mLocalProfile.getProtocol();
- int port = mLocalProfile.getPort();
- String server = mLocalProfile.getProxyAddress();
-
- if (!TextUtils.isEmpty(server)) {
- properties.setProperty("javax.sip.OUTBOUND_PROXY",
- server + ':' + port + '/' + protocol);
- } else {
- server = mLocalProfile.getSipDomain();
- }
- if (server.startsWith("[") && server.endsWith("]")) {
- server = server.substring(1, server.length() - 1);
- }
-
- String local = null;
- try {
- for (InetAddress remote : InetAddress.getAllByName(server)) {
- DatagramSocket socket = new DatagramSocket();
- socket.connect(remote, port);
- if (socket.isConnected()) {
- local = socket.getLocalAddress().getHostAddress();
- port = socket.getLocalPort();
- socket.close();
- break;
- }
- socket.close();
- }
- } catch (Exception e) {
- // ignore.
- }
- if (local == null) {
- // We are unable to reach the server. Just bail out.
- return;
- }
-
- close();
- mLocalIp = local;
-
- properties.setProperty("javax.sip.STACK_NAME", getStackName());
- properties.setProperty(
- "gov.nist.javax.sip.THREAD_POOL_SIZE", THREAD_POOL_SIZE);
- mSipStack = SipFactory.getInstance().createSipStack(properties);
- try {
- SipProvider provider = mSipStack.createSipProvider(
- mSipStack.createListeningPoint(local, port, protocol));
- provider.addSipListener(this);
- mSipHelper = new SipHelper(mSipStack, provider);
- } catch (SipException e) {
- throw e;
- } catch (Exception e) {
- throw new SipException("failed to initialize SIP stack", e);
- }
-
- if (DBG) log("reset: start stack for " + mLocalProfile.getUriString());
- mSipStack.start();
- }
-
- synchronized void onConnectivityChanged() {
- SipSessionImpl[] ss = mSessionMap.values().toArray(
- new SipSessionImpl[mSessionMap.size()]);
- // Iterate on the copied array instead of directly on mSessionMap to
- // avoid ConcurrentModificationException being thrown when
- // SipSessionImpl removes itself from mSessionMap in onError() in the
- // following loop.
- for (SipSessionImpl s : ss) {
- s.onError(SipErrorCode.DATA_CONNECTION_LOST,
- "data connection lost");
- }
- }
-
- synchronized void resetExternalAddress() {
- if (DBG) {
- log("resetExternalAddress: " + mSipStack);
- }
- mExternalIp = null;
- mExternalPort = 0;
- }
-
- public SipProfile getLocalProfile() {
- return mLocalProfile;
- }
-
- public String getLocalProfileUri() {
- return mLocalProfile.getUriString();
- }
-
- private String getStackName() {
- return "stack" + System.currentTimeMillis();
- }
-
- public synchronized void close() {
- if (DBG) log("close: " + mLocalProfile.getUriString());
- onConnectivityChanged();
- mSessionMap.clear();
- closeToNotReceiveCalls();
- if (mSipStack != null) {
- mSipStack.stop();
- mSipStack = null;
- mSipHelper = null;
- }
- resetExternalAddress();
- }
-
- public synchronized boolean isClosed() {
- return (mSipStack == null);
- }
-
- // For internal use, require listener not to block in callbacks.
- public synchronized void openToReceiveCalls(ISipSessionListener listener) {
- if (mCallReceiverSession == null) {
- mCallReceiverSession = new SipSessionCallReceiverImpl(listener);
- } else {
- mCallReceiverSession.setListener(listener);
- }
- }
-
- public synchronized void closeToNotReceiveCalls() {
- mCallReceiverSession = null;
- }
-
- public ISipSession createSession(ISipSessionListener listener) {
- return (isClosed() ? null : new SipSessionImpl(listener));
- }
-
- synchronized boolean containsSession(String callId) {
- return mSessionMap.containsKey(callId);
- }
-
- private synchronized SipSessionImpl getSipSession(EventObject event) {
- String key = SipHelper.getCallId(event);
- SipSessionImpl session = mSessionMap.get(key);
- if ((session != null) && isLoggable(session)) {
- if (DBG) log("getSipSession: event=" + key);
- if (DBG) log("getSipSession: active sessions:");
- for (String k : mSessionMap.keySet()) {
- if (DBG) log("getSipSession: ..." + k + ": " + mSessionMap.get(k));
- }
- }
- return ((session != null) ? session : mCallReceiverSession);
- }
-
- private synchronized void addSipSession(SipSessionImpl newSession) {
- removeSipSession(newSession);
- String key = newSession.getCallId();
- mSessionMap.put(key, newSession);
- if (isLoggable(newSession)) {
- if (DBG) log("addSipSession: key='" + key + "'");
- for (String k : mSessionMap.keySet()) {
- if (DBG) log("addSipSession: " + k + ": " + mSessionMap.get(k));
- }
- }
- }
-
- private synchronized void removeSipSession(SipSessionImpl session) {
- if (session == mCallReceiverSession) return;
- String key = session.getCallId();
- SipSessionImpl s = mSessionMap.remove(key);
- // sanity check
- if ((s != null) && (s != session)) {
- if (DBG) log("removeSession: " + session + " is not associated with key '"
- + key + "'");
- mSessionMap.put(key, s);
- for (Map.Entry<String, SipSessionImpl> entry
- : mSessionMap.entrySet()) {
- if (entry.getValue() == s) {
- key = entry.getKey();
- mSessionMap.remove(key);
- }
- }
- }
-
- if ((s != null) && isLoggable(s)) {
- if (DBG) log("removeSession: " + session + " @key '" + key + "'");
- for (String k : mSessionMap.keySet()) {
- if (DBG) log("removeSession: " + k + ": " + mSessionMap.get(k));
- }
- }
- }
-
- @Override
- public void processRequest(final RequestEvent event) {
- if (isRequestEvent(Request.INVITE, event)) {
- if (DBG) log("processRequest: mWakeLock.acquire got INVITE, thread:"
- + Thread.currentThread());
- // Acquire a wake lock and keep it for WAKE_LOCK_HOLDING_TIME;
- // should be large enough to bring up the app.
- mWakeLock.acquire(WAKE_LOCK_HOLDING_TIME);
- }
- process(event);
- }
-
- @Override
- public void processResponse(ResponseEvent event) {
- process(event);
- }
-
- @Override
- public void processIOException(IOExceptionEvent event) {
- process(event);
- }
-
- @Override
- public void processTimeout(TimeoutEvent event) {
- process(event);
- }
-
- @Override
- public void processTransactionTerminated(TransactionTerminatedEvent event) {
- process(event);
- }
-
- @Override
- public void processDialogTerminated(DialogTerminatedEvent event) {
- process(event);
- }
-
- private synchronized void process(EventObject event) {
- SipSessionImpl session = getSipSession(event);
- try {
- boolean isLoggable = isLoggable(session, event);
- boolean processed = (session != null) && session.process(event);
- if (isLoggable && processed) {
- log("process: event new state after: "
- + SipSession.State.toString(session.mState));
- }
- } catch (Throwable e) {
- loge("process: error event=" + event, getRootCause(e));
- session.onError(e);
- }
- }
-
- private String extractContent(Message message) {
- // Currently we do not support secure MIME bodies.
- byte[] bytes = message.getRawContent();
- if (bytes != null) {
- try {
- if (message instanceof SIPMessage) {
- return ((SIPMessage) message).getMessageContent();
- } else {
- return new String(bytes, "UTF-8");
- }
- } catch (UnsupportedEncodingException e) {
- }
- }
- 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;
- if (DBG) {
- log("extractExternalAddress: external addr " + externalIp + ":" + rport
- + " on " + mSipStack);
- }
- }
- }
-
- private Throwable getRootCause(Throwable exception) {
- Throwable cause = exception.getCause();
- while (cause != null) {
- exception = cause;
- cause = exception.getCause();
- }
- return exception;
- }
-
- 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 {
- private static final String SSCRI_TAG = "SipSessionCallReceiverImpl";
- private static final boolean SSCRI_DBG = true;
-
- 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 (SSCRI_DBG) {
- log("processNewInviteRequest: " + 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);
- }
-
- @Override
- public boolean process(EventObject evt) throws SipException {
- if (isLoggable(this, evt)) log("process: " + this + ": "
- + SipSession.State.toString(mState) + ": processing "
- + logEvt(evt));
- if (isRequestEvent(Request.INVITE, evt)) {
- processNewInviteRequest((RequestEvent) evt);
- return true;
- } else if (isRequestEvent(Request.OPTIONS, evt)) {
- mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
- return true;
- } else {
- return false;
- }
- }
-
- private void log(String s) {
- Rlog.d(SSCRI_TAG, s);
- }
- }
-
- 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 {
- private static final String SSI_TAG = "SipSessionImpl";
- private static final boolean SSI_DBG = true;
-
- SipProfile mPeerProfile;
- SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
- int mState = SipSession.State.READY_TO_CALL;
- RequestEvent mInviteReceived;
- Dialog mDialog;
- ServerTransaction mServerTransaction;
- ClientTransaction mClientTransaction;
- String mPeerSessionDescription;
- boolean mInCall;
- SessionTimer mSessionTimer;
- int mAuthenticationRetryCount;
-
- private SipKeepAlive mSipKeepAlive;
-
- private SipSessionImpl mSipSessionImpl;
-
- // the following three members are used for handling refer request.
- SipSessionImpl mReferSession;
- ReferredByHeader mReferredBy;
- String mReplaces;
-
- // lightweight timer
- class SessionTimer {
- private boolean mRunning = true;
-
- void start(final int timeout) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- sleep(timeout);
- if (mRunning) timeout();
- }
- }, "SipSessionTimerThread").start();
- }
-
- synchronized void cancel() {
- mRunning = false;
- this.notify();
- }
-
- private void timeout() {
- synchronized (SipSessionGroup.this) {
- onError(SipErrorCode.TIME_OUT, "Session timed out!");
- }
- }
-
- private synchronized void sleep(int timeout) {
- try {
- this.wait(timeout * 1000);
- } catch (InterruptedException e) {
- loge("session timer interrupted!", e);
- }
- }
- }
-
- public SipSessionImpl(ISipSessionListener listener) {
- setListener(listener);
- }
-
- SipSessionImpl duplicate() {
- return new SipSessionImpl(mProxy.getListener());
- }
-
- private void reset() {
- mInCall = false;
- removeSipSession(this);
- mPeerProfile = null;
- mState = SipSession.State.READY_TO_CALL;
- mInviteReceived = null;
- mPeerSessionDescription = null;
- mAuthenticationRetryCount = 0;
- mReferSession = null;
- mReferredBy = null;
- mReplaces = null;
-
- if (mDialog != null) mDialog.delete();
- mDialog = null;
-
- try {
- if (mServerTransaction != null) mServerTransaction.terminate();
- } catch (ObjectInUseException e) {
- // ignored
- }
- mServerTransaction = null;
-
- try {
- if (mClientTransaction != null) mClientTransaction.terminate();
- } catch (ObjectInUseException e) {
- // ignored
- }
- mClientTransaction = null;
-
- cancelSessionTimer();
-
- if (mSipSessionImpl != null) {
- mSipSessionImpl.stopKeepAliveProcess();
- mSipSessionImpl = null;
- }
- }
-
- @Override
- public boolean isInCall() {
- return mInCall;
- }
-
- @Override
- public String getLocalIp() {
- return mLocalIp;
- }
-
- @Override
- public SipProfile getLocalProfile() {
- return mLocalProfile;
- }
-
- @Override
- public SipProfile getPeerProfile() {
- return mPeerProfile;
- }
-
- @Override
- public String getCallId() {
- return SipHelper.getCallId(getTransaction());
- }
-
- private Transaction getTransaction() {
- if (mClientTransaction != null) return mClientTransaction;
- if (mServerTransaction != null) return mServerTransaction;
- return null;
- }
-
- @Override
- public int getState() {
- return mState;
- }
-
- @Override
- public void setListener(ISipSessionListener listener) {
- mProxy.setListener((listener instanceof SipSessionListenerProxy)
- ? ((SipSessionListenerProxy) listener).getListener()
- : listener);
- }
-
- // process the command in a new thread
- private void doCommandAsync(final EventObject command) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- processCommand(command);
- } catch (Throwable e) {
- loge("command error: " + command + ": "
- + mLocalProfile.getUriString(),
- getRootCause(e));
- onError(e);
- }
- }
- }, "SipSessionAsyncCmdThread").start();
- }
-
- @Override
- public void makeCall(SipProfile peerProfile, String sessionDescription,
- int timeout) {
- doCommandAsync(new MakeCallCommand(peerProfile, sessionDescription,
- timeout));
- }
-
- @Override
- public void answerCall(String sessionDescription, int timeout) {
- synchronized (SipSessionGroup.this) {
- if (mPeerProfile == null) return;
- doCommandAsync(new MakeCallCommand(mPeerProfile,
- sessionDescription, timeout));
- }
- }
-
- @Override
- public void endCall() {
- doCommandAsync(END_CALL);
- }
-
- @Override
- public void changeCall(String sessionDescription, int timeout) {
- synchronized (SipSessionGroup.this) {
- if (mPeerProfile == null) return;
- doCommandAsync(new MakeCallCommand(mPeerProfile,
- sessionDescription, timeout));
- }
- }
-
- @Override
- public void register(int duration) {
- doCommandAsync(new RegisterCommand(duration));
- }
-
- @Override
- public void unregister() {
- doCommandAsync(DEREGISTER);
- }
-
- private void processCommand(EventObject command) throws SipException {
- if (isLoggable(command)) log("process cmd: " + command);
- if (!process(command)) {
- onError(SipErrorCode.IN_PROGRESS,
- "cannot initiate a new transaction to execute: "
- + command);
- }
- }
-
- protected String generateTag() {
- // 32-bit randomness
- return String.valueOf((long) (Math.random() * 0x100000000L));
- }
-
- @Override
- public String toString() {
- try {
- String s = super.toString();
- return s.substring(s.indexOf("@")) + ":"
- + SipSession.State.toString(mState);
- } catch (Throwable e) {
- return super.toString();
- }
- }
-
- public boolean process(EventObject evt) throws SipException {
- if (isLoggable(this, evt)) log(" ~~~~~ " + this + ": "
- + SipSession.State.toString(mState) + ": processing "
- + logEvt(evt));
- synchronized (SipSessionGroup.this) {
- if (isClosed()) return false;
-
- if (mSipKeepAlive != null) {
- // event consumed by keepalive process
- if (mSipKeepAlive.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;
-
- boolean processed;
-
- switch (mState) {
- case SipSession.State.REGISTERING:
- case SipSession.State.DEREGISTERING:
- processed = registeringToReady(evt);
- break;
- case SipSession.State.READY_TO_CALL:
- processed = readyForCall(evt);
- break;
- case SipSession.State.INCOMING_CALL:
- processed = incomingCall(evt);
- break;
- case SipSession.State.INCOMING_CALL_ANSWERING:
- processed = incomingCallToInCall(evt);
- break;
- case SipSession.State.OUTGOING_CALL:
- case SipSession.State.OUTGOING_CALL_RING_BACK:
- processed = outgoingCall(evt);
- break;
- case SipSession.State.OUTGOING_CALL_CANCELING:
- processed = outgoingCallToReady(evt);
- break;
- case SipSession.State.IN_CALL:
- processed = inCall(evt);
- break;
- case SipSession.State.ENDING_CALL:
- processed = endingCall(evt);
- break;
- default:
- processed = false;
- }
- return (processed || processExceptions(evt));
- }
- }
-
- private boolean processExceptions(EventObject evt) throws SipException {
- if (isRequestEvent(Request.BYE, evt)) {
- // terminate the call whenever a BYE is received
- mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
- endCallNormally();
- return true;
- } else if (isRequestEvent(Request.CANCEL, evt)) {
- mSipHelper.sendResponse((RequestEvent) evt,
- Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST);
- return true;
- } else if (evt instanceof TransactionTerminatedEvent) {
- if (isCurrentTransaction((TransactionTerminatedEvent) evt)) {
- if (evt instanceof TimeoutEvent) {
- processTimeout((TimeoutEvent) evt);
- } else {
- processTransactionTerminated(
- (TransactionTerminatedEvent) evt);
- }
- return true;
- }
- } else if (isRequestEvent(Request.OPTIONS, evt)) {
- mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
- return true;
- } else if (evt instanceof DialogTerminatedEvent) {
- processDialogTerminated((DialogTerminatedEvent) evt);
- return true;
- }
- return false;
- }
-
- private void processDialogTerminated(DialogTerminatedEvent event) {
- if (mDialog == event.getDialog()) {
- onError(new SipException("dialog terminated"));
- } else {
- if (SSI_DBG) log("not the current dialog; current=" + mDialog
- + ", terminated=" + event.getDialog());
- }
- }
-
- private boolean isCurrentTransaction(TransactionTerminatedEvent event) {
- Transaction current = event.isServerTransaction()
- ? mServerTransaction
- : mClientTransaction;
- Transaction target = event.isServerTransaction()
- ? event.getServerTransaction()
- : event.getClientTransaction();
-
- if ((current != target) && (mState != SipSession.State.PINGING)) {
- if (SSI_DBG) log("not the current transaction; current="
- + toString(current) + ", target=" + toString(target));
- return false;
- } else if (current != null) {
- if (SSI_DBG) log("transaction terminated: " + toString(current));
- return true;
- } else {
- // no transaction; shouldn't be here; ignored
- return true;
- }
- }
-
- private String toString(Transaction transaction) {
- if (transaction == null) return "null";
- Request request = transaction.getRequest();
- Dialog dialog = transaction.getDialog();
- CSeqHeader cseq = (CSeqHeader) request.getHeader(CSeqHeader.NAME);
- return String.format("req=%s,%s,s=%s,ds=%s,", request.getMethod(),
- cseq.getSeqNumber(), transaction.getState(),
- ((dialog == null) ? "-" : dialog.getState()));
- }
-
- private void processTransactionTerminated(
- TransactionTerminatedEvent event) {
- switch (mState) {
- case SipSession.State.IN_CALL:
- case SipSession.State.READY_TO_CALL:
- if (SSI_DBG) log("Transaction terminated; do nothing");
- break;
- default:
- if (SSI_DBG) log("Transaction terminated early: " + this);
- onError(SipErrorCode.TRANSACTION_TERMINTED,
- "transaction terminated");
- }
- }
-
- private void processTimeout(TimeoutEvent event) {
- if (SSI_DBG) log("processing Timeout...");
- switch (mState) {
- case SipSession.State.REGISTERING:
- case SipSession.State.DEREGISTERING:
- reset();
- mProxy.onRegistrationTimeout(this);
- break;
- case SipSession.State.INCOMING_CALL:
- case SipSession.State.INCOMING_CALL_ANSWERING:
- case SipSession.State.OUTGOING_CALL:
- case SipSession.State.OUTGOING_CALL_CANCELING:
- onError(SipErrorCode.TIME_OUT, event.toString());
- break;
-
- default:
- if (SSI_DBG) log(" do nothing");
- break;
- }
- }
-
- private int getExpiryTime(Response response) {
- int time = -1;
- ContactHeader contact = (ContactHeader) response.getHeader(ContactHeader.NAME);
- if (contact != null) {
- time = contact.getExpires();
- }
- ExpiresHeader expires = (ExpiresHeader) response.getHeader(ExpiresHeader.NAME);
- if (expires != null && (time < 0 || time > expires.getExpires())) {
- time = expires.getExpires();
- }
- if (time <= 0) {
- time = EXPIRY_TIME;
- }
- expires = (ExpiresHeader) response.getHeader(MinExpiresHeader.NAME);
- if (expires != null && time < expires.getExpires()) {
- time = expires.getExpires();
- }
- if (SSI_DBG) {
- log("Expiry time = " + time);
- }
- return time;
- }
-
- private boolean registeringToReady(EventObject evt)
- throws SipException {
- if (expectResponse(Request.REGISTER, evt)) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
-
- int statusCode = response.getStatusCode();
- switch (statusCode) {
- case Response.OK:
- int state = mState;
- onRegistrationDone((state == SipSession.State.REGISTERING)
- ? getExpiryTime(((ResponseEvent) evt).getResponse())
- : -1);
- return true;
- case Response.UNAUTHORIZED:
- case Response.PROXY_AUTHENTICATION_REQUIRED:
- handleAuthentication(event);
- return true;
- default:
- if (statusCode >= 500) {
- onRegistrationFailed(response);
- return true;
- }
- }
- }
- return false;
- }
-
- private boolean handleAuthentication(ResponseEvent event)
- throws SipException {
- Response response = event.getResponse();
- String nonce = getNonceFromResponse(response);
- if (nonce == null) {
- onError(SipErrorCode.SERVER_ERROR,
- "server does not provide challenge");
- return false;
- } else if (mAuthenticationRetryCount < 2) {
- mClientTransaction = mSipHelper.handleChallenge(
- event, getAccountManager());
- mDialog = mClientTransaction.getDialog();
- mAuthenticationRetryCount++;
- if (isLoggable(this, event)) {
- if (SSI_DBG) log(" authentication retry count="
- + mAuthenticationRetryCount);
- }
- return true;
- } else {
- if (crossDomainAuthenticationRequired(response)) {
- onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION,
- getRealmFromResponse(response));
- } else {
- onError(SipErrorCode.INVALID_CREDENTIALS,
- "incorrect username or password");
- }
- return false;
- }
- }
-
- private boolean crossDomainAuthenticationRequired(Response response) {
- String realm = getRealmFromResponse(response);
- if (realm == null) realm = "";
- return !mLocalProfile.getSipDomain().trim().equals(realm.trim());
- }
-
- private AccountManager getAccountManager() {
- return new AccountManager() {
- @Override
- public UserCredentials getCredentials(ClientTransaction
- challengedTransaction, String realm) {
- return new UserCredentials() {
- @Override
- public String getUserName() {
- String username = mLocalProfile.getAuthUserName();
- return (!TextUtils.isEmpty(username) ? username :
- mLocalProfile.getUserName());
- }
-
- @Override
- public String getPassword() {
- return mPassword;
- }
-
- @Override
- public String getSipDomain() {
- return mLocalProfile.getSipDomain();
- }
- };
- }
- };
- }
-
- private String getRealmFromResponse(Response response) {
- WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader(
- SIPHeaderNames.WWW_AUTHENTICATE);
- if (wwwAuth != null) return wwwAuth.getRealm();
- ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader(
- SIPHeaderNames.PROXY_AUTHENTICATE);
- return (proxyAuth == null) ? null : proxyAuth.getRealm();
- }
-
- private String getNonceFromResponse(Response response) {
- WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader(
- SIPHeaderNames.WWW_AUTHENTICATE);
- if (wwwAuth != null) return wwwAuth.getNonce();
- ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader(
- SIPHeaderNames.PROXY_AUTHENTICATE);
- 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();
- 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());
- mProxy.onCalling(this);
- return true;
- } else if (evt instanceof RegisterCommand) {
- mState = SipSession.State.REGISTERING;
- int duration = ((RegisterCommand) evt).getDuration();
- mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
- generateTag(), duration);
- mDialog = mClientTransaction.getDialog();
- addSipSession(this);
- mProxy.onRegistering(this);
- return true;
- } else if (DEREGISTER == evt) {
- mState = SipSession.State.DEREGISTERING;
- mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
- generateTag(), 0);
- mDialog = mClientTransaction.getDialog();
- addSipSession(this);
- mProxy.onRegistering(this);
- return true;
- }
- return false;
- }
-
- private boolean incomingCall(EventObject evt) throws SipException {
- // expect MakeCallCommand(answering) , END_CALL cmd , Cancel
- if (evt instanceof MakeCallCommand) {
- // answer call
- mState = SipSession.State.INCOMING_CALL_ANSWERING;
- mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived,
- mLocalProfile,
- ((MakeCallCommand) evt).getSessionDescription(),
- mServerTransaction,
- mExternalIp, mExternalPort);
- startSessionTimer(((MakeCallCommand) evt).getTimeout());
- return true;
- } else if (END_CALL == evt) {
- mSipHelper.sendInviteBusyHere(mInviteReceived,
- mServerTransaction);
- endCallNormally();
- return true;
- } else if (isRequestEvent(Request.CANCEL, evt)) {
- RequestEvent event = (RequestEvent) evt;
- mSipHelper.sendResponse(event, Response.OK);
- mSipHelper.sendInviteRequestTerminated(
- mInviteReceived.getRequest(), mServerTransaction);
- endCallNormally();
- return true;
- }
- return false;
- }
-
- private boolean incomingCallToInCall(EventObject evt) {
- // expect ACK, CANCEL request
- if (isRequestEvent(Request.ACK, evt)) {
- 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
- // Final response has been sent; do nothing here.
- return true;
- }
- return false;
- }
-
- private boolean outgoingCall(EventObject evt) throws SipException {
- if (expectResponse(Request.INVITE, evt)) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
-
- int statusCode = response.getStatusCode();
- switch (statusCode) {
- case Response.RINGING:
- case Response.CALL_IS_BEING_FORWARDED:
- case Response.QUEUED:
- case Response.SESSION_PROGRESS:
- // feedback any provisional responses (except TRYING) as
- // ring back for better UX
- if (mState == SipSession.State.OUTGOING_CALL) {
- mState = SipSession.State.OUTGOING_CALL_RING_BACK;
- cancelSessionTimer();
- mProxy.onRingingBack(this);
- }
- 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(true);
- return true;
- case Response.UNAUTHORIZED:
- case Response.PROXY_AUTHENTICATION_REQUIRED:
- if (handleAuthentication(event)) {
- addSipSession(this);
- }
- return true;
- case Response.REQUEST_PENDING:
- // TODO: 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);
- return true;
- } else if (statusCode >= 300) {
- // TODO: handle 3xx (redirect)
- } else {
- return true;
- }
- }
- return false;
- } else if (END_CALL == evt) {
- // RFC says that UA should not send out cancel when no
- // response comes back yet. We are cheating for not checking
- // response.
- mState = SipSession.State.OUTGOING_CALL_CANCELING;
- mSipHelper.sendCancel(mClientTransaction);
- startSessionTimer(CANCEL_CALL_TIMER);
- return true;
- } else if (isRequestEvent(Request.INVITE, evt)) {
- // Call self? Send BUSY HERE so server may redirect the call to
- // voice mailbox.
- RequestEvent event = (RequestEvent) evt;
- mSipHelper.sendInviteBusyHere(event,
- event.getServerTransaction());
- return true;
- }
- return false;
- }
-
- private boolean outgoingCallToReady(EventObject evt)
- throws SipException {
- if (evt instanceof ResponseEvent) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
- int statusCode = response.getStatusCode();
- if (expectResponse(Request.CANCEL, evt)) {
- if (statusCode == Response.OK) {
- // do nothing; wait for REQUEST_TERMINATED
- return true;
- }
- } else if (expectResponse(Request.INVITE, evt)) {
- switch (statusCode) {
- case Response.OK:
- outgoingCall(evt); // abort Cancel
- return true;
- case Response.REQUEST_TERMINATED:
- endCallNormally();
- return true;
- }
- } else {
- return false;
- }
-
- if (statusCode >= 400) {
- onError(response);
- return true;
- }
- } else if (evt instanceof TransactionTerminatedEvent) {
- // rfc3261#section-14.1:
- // if re-invite gets timed out, terminate the dialog; but
- // re-invite is not reliable, just let it go and pretend
- // nothing happened.
- onError(new SipException("timed out"));
- }
- 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
- if (END_CALL == evt) {
- // rfc3261#section-15.1.1
- mState = SipSession.State.ENDING_CALL;
- mSipHelper.sendBye(mDialog);
- mProxy.onCallEnded(this);
- startSessionTimer(END_CALL_TIMER);
- return true;
- } else if (isRequestEvent(Request.INVITE, evt)) {
- // got Re-INVITE
- mState = SipSession.State.INCOMING_CALL;
- RequestEvent event = mInviteReceived = (RequestEvent) evt;
- mPeerSessionDescription = extractContent(event.getRequest());
- mServerTransaction = null;
- mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
- return true;
- } else if (isRequestEvent(Request.BYE, evt)) {
- 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;
- mClientTransaction = mSipHelper.sendReinvite(mDialog,
- ((MakeCallCommand) evt).getSessionDescription());
- startSessionTimer(((MakeCallCommand) evt).getTimeout());
- return true;
- } else if (evt instanceof ResponseEvent) {
- if (expectResponse(Request.NOTIFY, evt)) return true;
- }
- return false;
- }
-
- private boolean endingCall(EventObject evt) throws SipException {
- if (expectResponse(Request.BYE, evt)) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
-
- int statusCode = response.getStatusCode();
- switch (statusCode) {
- case Response.UNAUTHORIZED:
- case Response.PROXY_AUTHENTICATION_REQUIRED:
- if (handleAuthentication(event)) {
- return true;
- } else {
- // can't authenticate; pass through to end session
- }
- }
- cancelSessionTimer();
- reset();
- return true;
- }
- return false;
- }
-
- // timeout in seconds
- private void startSessionTimer(int timeout) {
- if (timeout > 0) {
- mSessionTimer = new SessionTimer();
- mSessionTimer.start(timeout);
- }
- }
-
- private void cancelSessionTimer() {
- if (mSessionTimer != null) {
- mSessionTimer.cancel();
- mSessionTimer = null;
- }
- }
-
- private String createErrorMessage(Response response) {
- return String.format("%s (%d)", response.getReasonPhrase(),
- response.getStatusCode());
- }
-
- private void enableKeepAlive() {
- if (mSipSessionImpl != null) {
- mSipSessionImpl.stopKeepAliveProcess();
- } else {
- mSipSessionImpl = duplicate();
- }
- try {
- mSipSessionImpl.startKeepAliveProcess(
- INCALL_KEEPALIVE_INTERVAL, mPeerProfile, null);
- } catch (SipException e) {
- loge("keepalive cannot be enabled; ignored", e);
- mSipSessionImpl.stopKeepAliveProcess();
- }
- }
-
- private void establishCall(boolean enableKeepAlive) {
- mState = SipSession.State.IN_CALL;
- cancelSessionTimer();
- if (!mInCall && enableKeepAlive) enableKeepAlive();
- mInCall = true;
- mProxy.onCallEstablished(this, mPeerSessionDescription);
- }
-
- private void endCallNormally() {
- reset();
- mProxy.onCallEnded(this);
- }
-
- private void endCallOnError(int errorCode, String message) {
- reset();
- mProxy.onError(this, errorCode, message);
- }
-
- private void endCallOnBusy() {
- reset();
- mProxy.onCallBusy(this);
- }
-
- private void onError(int errorCode, String message) {
- cancelSessionTimer();
- switch (mState) {
- case SipSession.State.REGISTERING:
- case SipSession.State.DEREGISTERING:
- onRegistrationFailed(errorCode, message);
- break;
- default:
- endCallOnError(errorCode, message);
- }
- }
-
-
- private void onError(Throwable exception) {
- exception = getRootCause(exception);
- onError(getErrorCode(exception), exception.toString());
- }
-
- private void onError(Response response) {
- int statusCode = response.getStatusCode();
- if (!mInCall && (statusCode == Response.BUSY_HERE)) {
- endCallOnBusy();
- } else {
- onError(getErrorCode(statusCode), createErrorMessage(response));
- }
- }
-
- private int getErrorCode(int responseStatusCode) {
- switch (responseStatusCode) {
- case Response.TEMPORARILY_UNAVAILABLE:
- case Response.FORBIDDEN:
- case Response.GONE:
- case Response.NOT_FOUND:
- case Response.NOT_ACCEPTABLE:
- case Response.NOT_ACCEPTABLE_HERE:
- return SipErrorCode.PEER_NOT_REACHABLE;
-
- case Response.REQUEST_URI_TOO_LONG:
- case Response.ADDRESS_INCOMPLETE:
- case Response.AMBIGUOUS:
- return SipErrorCode.INVALID_REMOTE_URI;
-
- case Response.REQUEST_TIMEOUT:
- return SipErrorCode.TIME_OUT;
-
- default:
- if (responseStatusCode < 500) {
- return SipErrorCode.CLIENT_ERROR;
- } else {
- return SipErrorCode.SERVER_ERROR;
- }
- }
- }
-
- private int getErrorCode(Throwable exception) {
- String message = exception.getMessage();
- if (exception instanceof UnknownHostException) {
- return SipErrorCode.SERVER_UNREACHABLE;
- } else if (exception instanceof IOException) {
- return SipErrorCode.SOCKET_ERROR;
- } else {
- return SipErrorCode.CLIENT_ERROR;
- }
- }
-
- private void onRegistrationDone(int duration) {
- reset();
- mProxy.onRegistrationDone(this, duration);
- }
-
- private void onRegistrationFailed(int errorCode, String message) {
- reset();
- mProxy.onRegistrationFailed(this, errorCode, message);
- }
-
- private void onRegistrationFailed(Response response) {
- int statusCode = response.getStatusCode();
- 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 (mSipKeepAlive != null) {
- throw new SipException("Cannot create more than one "
- + "keepalive process in a SipSession");
- }
- mPeerProfile = peerProfile;
- mSipKeepAlive = new SipKeepAlive();
- mProxy.setListener(mSipKeepAlive);
- mSipKeepAlive.start(interval, callback);
- }
- }
-
- public void stopKeepAliveProcess() {
- synchronized (SipSessionGroup.this) {
- if (mSipKeepAlive != null) {
- mSipKeepAlive.stop();
- mSipKeepAlive = null;
- }
- }
- }
-
- class SipKeepAlive extends SipSessionAdapter implements Runnable {
- private static final String SKA_TAG = "SipKeepAlive";
- private static final boolean SKA_DBG = true;
-
- 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 (SKA_DBG) {
- log("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) {
- 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 (DBG_PING) {
- String peerUri = (mPeerProfile == null)
- ? "null"
- : mPeerProfile.getUriString();
- log("keepalive: " + mLocalProfile.getUriString()
- + " --> " + peerUri + ", interval=" + mInterval);
- }
- try {
- sendKeepAlive();
- } catch (Throwable t) {
- if (SKA_DBG) {
- loge("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 (SKA_DBG) {
- log("stop keepalive:" + mLocalProfile.getUriString()
- + ",RPort=" + mRPort);
- }
- mRunning = false;
- mWakeupTimer.cancel(this);
- reset();
- }
- }
-
- private void sendKeepAlive() throws SipException {
- 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 (SKA_DBG) log(String.format(
- "rport is changed: %d <> %d", mRPort, rPort));
- mRPort = rPort;
- } else {
- if (SKA_DBG) log("rport is the same: " + rPort);
- }
- } else {
- if (SKA_DBG) log("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();
- }
-
- private void log(String s) {
- Rlog.d(SKA_TAG, s);
- }
- }
-
- private void log(String s) {
- Rlog.d(SSI_TAG, s);
- }
- }
-
- /**
- * @return true if the event is a request event matching the specified
- * method; false otherwise
- */
- private static boolean isRequestEvent(String method, EventObject event) {
- try {
- if (event instanceof RequestEvent) {
- RequestEvent requestEvent = (RequestEvent) event;
- return method.equals(requestEvent.getRequest().getMethod());
- }
- } catch (Throwable e) {
- }
- return false;
- }
-
- private static String getCseqMethod(Message message) {
- return ((CSeqHeader) message.getHeader(CSeqHeader.NAME)).getMethod();
- }
-
- /**
- * @return true if the event is a response event and the CSeqHeader method
- * match the given arguments; false otherwise
- */
- private static boolean expectResponse(
- String expectedMethod, EventObject evt) {
- if (evt instanceof ResponseEvent) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
- return expectedMethod.equalsIgnoreCase(getCseqMethod(response));
- }
- return false;
- }
-
- private static SipProfile createPeerProfile(HeaderAddress header)
- throws SipException {
- try {
- Address address = header.getAddress();
- SipURI uri = (SipURI) address.getURI();
- String username = uri.getUser();
- if (username == null) username = ANONYMOUS;
- int port = uri.getPort();
- SipProfile.Builder builder =
- new SipProfile.Builder(username, uri.getHost())
- .setDisplayName(address.getDisplayName());
- if (port > 0) builder.setPort(port);
- return builder.build();
- } catch (IllegalArgumentException e) {
- throw new SipException("createPeerProfile()", e);
- } catch (ParseException e) {
- throw new SipException("createPeerProfile()", e);
- }
- }
-
- private static boolean isLoggable(SipSessionImpl s) {
- if (s != null) {
- switch (s.mState) {
- case SipSession.State.PINGING:
- return DBG_PING;
- }
- }
- return DBG;
- }
-
- private static boolean isLoggable(EventObject evt) {
- return isLoggable(null, evt);
- }
-
- private static boolean isLoggable(SipSessionImpl s, EventObject evt) {
- if (!isLoggable(s)) return false;
- if (evt == null) return false;
-
- if (evt instanceof ResponseEvent) {
- Response response = ((ResponseEvent) evt).getResponse();
- if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) {
- return DBG_PING;
- }
- return DBG;
- } else if (evt instanceof RequestEvent) {
- if (isRequestEvent(Request.OPTIONS, evt)) {
- return DBG_PING;
- }
- return DBG;
- }
- return false;
- }
-
- private static String logEvt(EventObject evt) {
- if (evt instanceof RequestEvent) {
- return ((RequestEvent) evt).getRequest().toString();
- } else if (evt instanceof ResponseEvent) {
- return ((ResponseEvent) evt).getResponse().toString();
- } else {
- return evt.toString();
- }
- }
-
- private class RegisterCommand extends EventObject {
- private int mDuration;
-
- public RegisterCommand(int duration) {
- super(SipSessionGroup.this);
- mDuration = duration;
- }
-
- public int getDuration() {
- return mDuration;
- }
- }
-
- private class MakeCallCommand extends EventObject {
- private String mSessionDescription;
- private int mTimeout; // in seconds
-
- public MakeCallCommand(SipProfile peerProfile,
- String sessionDescription, int timeout) {
- super(peerProfile);
- mSessionDescription = sessionDescription;
- mTimeout = timeout;
- }
-
- public SipProfile getPeerProfile() {
- return (SipProfile) getSource();
- }
-
- public String getSessionDescription() {
- return mSessionDescription;
- }
-
- public int getTimeout() {
- return mTimeout;
- }
- }
-
- /** Class to help safely run KeepAliveProcessCallback in a different thread. */
- static class KeepAliveProcessCallbackProxy implements KeepAliveProcessCallback {
- private static final String KAPCP_TAG = "KeepAliveProcessCallbackProxy";
- 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();
- }
-
- @Override
- public void onResponse(final boolean portChanged) {
- if (mCallback == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mCallback.onResponse(portChanged);
- } catch (Throwable t) {
- loge("onResponse", t);
- }
- }
- });
- }
-
- @Override
- public void onError(final int errorCode, final String description) {
- if (mCallback == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mCallback.onError(errorCode, description);
- } catch (Throwable t) {
- loge("onError", t);
- }
- }
- });
- }
-
- private void loge(String s, Throwable t) {
- Rlog.e(KAPCP_TAG, s, t);
- }
- }
-
- private void log(String s) {
- Rlog.d(TAG, s);
- }
-
- private void loge(String s, Throwable t) {
- Rlog.e(TAG, s, t);
- }
-}