summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHung-ying Tyan <tyanh@google.com>2010-03-18 12:50:20 +0800
committerHung-ying Tyan <tyanh@google.com>2010-03-18 13:31:33 +0800
commit1e6173f5990bc75a0e63a59997253586c49e289a (patch)
treeb1658c808e17421fc3aef8f16d1db1da1845515f
parent0869c36bd47b60c25920751178b02987a3cfd2d7 (diff)
downloadnist-sip-1e6173f5990bc75a0e63a59997253586c49e289a.tar.gz
SIP: allocate UDP port for each SIP account.
+ add SipSessionGroup and move SipSessionImpl out of SipSessionLayer
-rw-r--r--res/values/strings.xml2
-rw-r--r--src/android/net/sip/SipHelper.java4
-rw-r--r--src/android/net/sip/SipSessionGroup.java797
-rw-r--r--src/android/net/sip/SipSessionLayer.java811
-rw-r--r--src/com/android/sip/SipMain.java39
5 files changed, 860 insertions, 793 deletions
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 44554e8..c187508 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Sip Joy</string>
- <string name="dev_pref_title">SIP test (build 106)</string>
+ <string name="dev_pref_title">SIP test (build 107)</string>
<string name="peer_title">Who to call?</string>
<string name="peer_summary">test@192.168.147.113:5060</string>
<string name="server_address_title">Server address</string>
diff --git a/src/android/net/sip/SipHelper.java b/src/android/net/sip/SipHelper.java
index 4447515..976cde1 100644
--- a/src/android/net/sip/SipHelper.java
+++ b/src/android/net/sip/SipHelper.java
@@ -86,10 +86,6 @@ class SipHelper {
mMessageFactory = sipFactory.createMessageFactory();
}
- public SipStack getSipStack() {
- return mSipStack;
- }
-
private FromHeader createFromHeader(SipProfile profile, String tag)
throws ParseException {
return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag);
diff --git a/src/android/net/sip/SipSessionGroup.java b/src/android/net/sip/SipSessionGroup.java
new file mode 100644
index 0000000..426d6a8
--- /dev/null
+++ b/src/android/net/sip/SipSessionGroup.java
@@ -0,0 +1,797 @@
+/*
+ * Copyright (C) 2009 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 android.net.sip;
+
+import android.util.Log;
+
+import java.text.ParseException;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.TooManyListenersException;
+
+import javax.sip.ClientTransaction;
+import javax.sip.Dialog;
+import javax.sip.DialogTerminatedEvent;
+import javax.sip.IOExceptionEvent;
+import javax.sip.InvalidArgumentException;
+import javax.sip.ListeningPoint;
+import javax.sip.RequestEvent;
+import javax.sip.ResponseEvent;
+import javax.sip.ServerTransaction;
+import javax.sip.SipException;
+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.ExpiresHeader;
+import javax.sip.header.FromHeader;
+import javax.sip.message.Message;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+/**
+ * Manages SipSession's for a SIP account.
+ */
+class SipSessionGroup implements SipListener {
+ private static final String TAG = SipSessionGroup.class.getSimpleName();
+ private static final int EXPIRY_TIME = 3600;
+
+ private static final EventObject REGISTER = new EventObject("Register");
+ private static final EventObject DEREGISTER = new EventObject("Deregister");
+ private static final EventObject END_CALL = new EventObject("End call");
+ private static final EventObject HOLD_CALL = new EventObject("Hold call");
+ private static final EventObject CONTINUE_CALL
+ = new EventObject("Continue call");
+
+ private SipHelper mSipHelper;
+ private SipProfile mLocalProfile;
+
+ // default session that processes all the un-attended messages received
+ // from the UDP port
+ private SipSessionImpl mDefaultSession;
+
+ // call-id-to-SipSession map
+ private Map<String, SipSessionImpl> mSessionMap =
+ new HashMap<String, SipSessionImpl>();
+
+ SipSessionGroup(SipStack stack, SipProvider provider, SipProfile myself,
+ SipSessionListener listener) throws SipException {
+ try {
+ provider.addSipListener(this);
+ } catch (TooManyListenersException e) {
+ // must never happen
+ throw new SipException("SipSessionGroup constructor", e);
+ }
+ mSipHelper = new SipHelper(stack, provider);
+ mLocalProfile = myself;
+ mDefaultSession = new SipSessionImpl(listener);
+ }
+
+ SipSession getDefaultSession() {
+ return mDefaultSession;
+ }
+
+ private synchronized SipSessionImpl getSipSession(EventObject event) {
+ String[] keys = mSipHelper.getPossibleSessionKeys(event);
+ Log.v(TAG, " possible keys " + keys.length + " for evt: " + event);
+ for (String key : keys) Log.v(TAG, " '" + key + "'");
+ Log.v(TAG, " active sessions:");
+ for (String k : mSessionMap.keySet()) {
+ Log.v(TAG, " ----- '" + k + "': " + mSessionMap.get(k));
+ }
+ for (String uri : mSipHelper.getPossibleSessionKeys(event)) {
+ SipSessionImpl callSession = mSessionMap.get(uri);
+ // return first match
+ if (callSession != null) return callSession;
+ }
+ return mDefaultSession;
+ }
+
+ private synchronized void addSipSession(SipSessionImpl newSession) {
+ String key = mSipHelper.getSessionKey(newSession);
+ Log.v(TAG, " +++++ add a session with key: '" + key + "'");
+ mSessionMap.put(key, newSession);
+ for (String k : mSessionMap.keySet()) {
+ Log.v(TAG, " ..... " + k + ": " + mSessionMap.get(k));
+ }
+ }
+
+ private synchronized void removeSipSession(SipSessionImpl session) {
+ String key = mSipHelper.getSessionKey(session);
+ SipSessionImpl s = mSessionMap.remove(key);
+ // sanity check
+ if ((s != null) && (s != session)) {
+ Log.w(TAG, "session " + session + " is not associated with key '"
+ + key + "'");
+ mSessionMap.put(key, s);
+ } else {
+ Log.v(TAG, " remove session " + session + " with key '" + key
+ + "'");
+ }
+ for (String k : mSessionMap.keySet()) {
+ Log.v(TAG, " ..... " + k + ": " + mSessionMap.get(k));
+ }
+ }
+
+ public void processRequest(RequestEvent event) {
+ process(event);
+ }
+
+ public void processResponse(ResponseEvent event) {
+ process(event);
+ }
+
+ public void processIOException(IOExceptionEvent event) {
+ // TODO: find proper listener to pass the exception
+ process(event);
+ }
+
+ public void processTimeout(TimeoutEvent event) {
+ // TODO: find proper listener to pass the exception
+ process(event);
+ }
+
+ public void processTransactionTerminated(TransactionTerminatedEvent event) {
+ // TODO: clean up if session is in in-transaction states
+ //process(event);
+ }
+
+ public void processDialogTerminated(DialogTerminatedEvent event) {
+ process(event);
+ }
+
+ private void process(EventObject event) {
+ try {
+ getSipSession(event).process(event);
+ } catch (Exception e) {
+ // TODO: suppress stack trace after code gets stabilized
+ Log.e(TAG, "event not processed: " + event, e);
+ }
+ }
+
+ private class SipSessionImpl implements SipSession {
+ private SipProfile mPeerProfile;
+ private SipSessionListener mListener;
+ private SipSessionState mState = SipSessionState.READY_FOR_CALL;
+ private RequestEvent mInviteReceived;
+ private Dialog mDialog;
+ private ServerTransaction mServerTransaction;
+ private ClientTransaction mClientTransaction;
+ private byte[] mPeerSessionDescription;
+
+ SipSessionImpl(SipSessionListener listener) {
+ mListener = listener;
+ }
+
+ private void reset() {
+ mPeerProfile = null;
+ mState = SipSessionState.READY_FOR_CALL;
+ mInviteReceived = null;
+ mDialog = null;
+ mServerTransaction = null;
+ mClientTransaction = null;
+ mPeerSessionDescription = null;
+ }
+
+ public SipProfile getLocalProfile() {
+ return mLocalProfile;
+ }
+
+ public synchronized SipProfile getPeerProfile() {
+ return mPeerProfile;
+ }
+
+ public synchronized Dialog getDialog() {
+ return mDialog;
+ }
+
+ public synchronized SipSessionState getState() {
+ return mState;
+ }
+
+ public void makeCall(SipProfile peerProfile,
+ SessionDescription sessionDescription) throws SipException {
+ process(new MakeCallCommand(peerProfile, sessionDescription));
+ }
+
+ public void answerCall(SessionDescription sessionDescription)
+ throws SipException {
+ process(new MakeCallCommand(mPeerProfile, sessionDescription));
+ }
+
+ public void endCall() throws SipException {
+ process(END_CALL);
+ }
+
+ // http://www.tech-invite.com/Ti-sip-service-1.html#fig5
+ public void changeCall(SessionDescription sessionDescription)
+ throws SipException {
+ process(new MakeCallCommand(mPeerProfile, sessionDescription));
+ }
+
+ public void register() throws SipException {
+ process(REGISTER);
+ }
+
+ public void deRegister() throws SipException {
+ process(DEREGISTER);
+ }
+
+ private void scheduleNextRegistration(
+ ExpiresHeader expiresHeader) {
+ int expires = EXPIRY_TIME;
+ if (expiresHeader != null) expires = expiresHeader.getExpires();
+ Log.v(TAG, "Refresh registration " + expires + "s later.");
+ new Timer().schedule(new TimerTask() {
+ public void run() {
+ try {
+ process(REGISTER);
+ } catch (SipException e) {
+ Log.e(TAG, "", e);
+ }
+ }}, expires * 1000L);
+ }
+
+ private String generateTag() {
+ // TODO: based on myself's profile
+ return String.valueOf((long) (Math.random() * 1000000L));
+ }
+
+ private String log(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();
+ }
+ }
+
+ public String toString() {
+ try {
+ String s = super.toString();
+ return s.substring(s.indexOf("@")) + ":" + mState;
+ } catch (Throwable e) {
+ return super.toString();
+ }
+ }
+
+ synchronized void process(EventObject evt) throws SipException {
+ Log.v(TAG, " ~~~~~ " + this + ": " + mState + ": processing "
+ + log(evt));
+ boolean processed;
+
+ switch (mState) {
+ case REGISTERING:
+ case DEREGISTERING:
+ processed = registeringToReady(evt);
+ break;
+ case READY_FOR_CALL:
+ processed = readyForCall(evt);
+ break;
+ case INCOMING_CALL:
+ processed = incomingCall(evt);
+ break;
+ case INCOMING_CALL_ANSWERING:
+ processed = incomingCallToInCall(evt);
+ break;
+ case OUTGOING_CALL:
+ case OUTGOING_CALL_RING_BACK:
+ processed = outgoingCall(evt);
+ break;
+ case OUTGOING_CALL_CANCELING:
+ processed = outgoingCallToReady(evt);
+ break;
+ case IN_CALL:
+ processed = inCall(evt);
+ break;
+ case IN_CALL_ANSWERING:
+ processed = inCallAnsweringToInCall(evt);
+ break;
+ case IN_CALL_CHANGING:
+ processed = inCallChanging(evt);
+ break;
+ case IN_CALL_CHANGING_CANCELING:
+ processed = inCallChangingToInCall(evt);
+ break;
+ default:
+ processed = false;
+ }
+ if (!processed && !processExceptions(evt)) {
+ throw new SipException("event not processed");
+ } else {
+ Log.v(TAG, " ~~~~~ new state: " + mState);
+ }
+ }
+
+ private boolean processExceptions(EventObject evt) throws SipException {
+ // process INVITE and CANCEL
+ if (isRequestEvent(Request.INVITE, evt)) {
+ mSipHelper.sendResponse((RequestEvent) evt, Response.BUSY_HERE);
+ 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 (evt instanceof TimeoutEvent) {
+ processTimeout((TimeoutEvent) evt);
+ } else {
+ // TODO: any clean up?
+ }
+ return true;
+ } else if (evt instanceof DialogTerminatedEvent) {
+ processDialogTerminated((DialogTerminatedEvent) evt);
+ return true;
+ }
+ return false;
+ }
+
+ private void processDialogTerminated(DialogTerminatedEvent event) {
+ if (mDialog == event.getDialog()) {
+ endCall(isInCall());
+ } else {
+ Log.d(TAG, "not the current dialog; current=" + mDialog
+ + ", terminated=" + event.getDialog());
+ }
+ }
+
+ private void processTimeout(TimeoutEvent event) {
+ Log.d(TAG, "processing Timeout..." + event);
+ Transaction current = event.isServerTransaction()
+ ? mServerTransaction
+ : mClientTransaction;
+ Transaction target = event.isServerTransaction()
+ ? event.getServerTransaction()
+ : event.getClientTransaction();
+
+ if (current != target) {
+ Log.d(TAG, "not the current transaction; current=" + current
+ + ", timed out=" + target);
+ return;
+ }
+ switch (mState) {
+ case REGISTERING:
+ case INCOMING_CALL:
+ case INCOMING_CALL_ANSWERING:
+ case OUTGOING_CALL:
+ case OUTGOING_CALL_RING_BACK:
+ case OUTGOING_CALL_CANCELING:
+ case IN_CALL_CHANGING:
+ // rfc3261#section-14.1
+ // if re-invite gets timed out, terminate the dialog
+ endCallOnError((mState == SipSessionState.IN_CALL_CHANGING),
+ new SipException("timed out"));
+ break;
+ case IN_CALL_ANSWERING:
+ case IN_CALL_CHANGING_CANCELING:
+ mState = SipSessionState.IN_CALL;
+ mListener.onError(this, new SipException("timed out"));
+ break;
+ default:
+ // do nothing
+ break;
+ }
+ }
+
+ private boolean registeringToReady(EventObject evt)
+ throws SipException {
+ if (expectResponse(Response.OK, Request.REGISTER, evt)) {
+ if (mState == SipSessionState.REGISTERING) {
+ scheduleNextRegistration((ExpiresHeader)
+ ((ResponseEvent) evt).getResponse().getHeader(
+ ExpiresHeader.NAME));
+ }
+ reset();
+ mListener.onRegistrationDone(this);
+ return true;
+ }
+ if (expectResponse(Response.UNAUTHORIZED, Request.REGISTER, evt)
+ || expectResponse(Response.PROXY_AUTHENTICATION_REQUIRED,
+ Request.REGISTER, evt)) {
+ mSipHelper.handleChallenge((ResponseEvent)evt, mLocalProfile);
+ return true;
+ }
+ //TODO: handle error conditions
+ return false;
+ }
+
+ private boolean readyForCall(EventObject evt) throws SipException {
+ // expect MakeCallCommand, Invite
+ if (evt instanceof MakeCallCommand) {
+ MakeCallCommand cmd = (MakeCallCommand) evt;
+ mPeerProfile = cmd.getPeerProfile();
+ SessionDescription sessionDescription =
+ cmd.getSessionDescription();
+ mClientTransaction = mSipHelper.sendInvite(mLocalProfile,
+ mPeerProfile, sessionDescription, generateTag());
+ mDialog = mClientTransaction.getDialog();
+ mState = SipSessionState.OUTGOING_CALL;
+ return true;
+ } else if (isRequestEvent(Request.INVITE, evt)) {
+ RequestEvent event = (RequestEvent) evt;
+ mServerTransaction = mSipHelper.sendRinging(event);
+ mDialog = mServerTransaction.getDialog();
+ mInviteReceived = event;
+ mPeerProfile = createPeerProfile(event.getRequest());
+ mState = SipSessionState.INCOMING_CALL;
+ mPeerSessionDescription = event.getRequest().getRawContent();
+ mListener.onRinging(SipSessionImpl.this, mPeerProfile,
+ mPeerSessionDescription);
+ return true;
+ } else if (REGISTER == evt) {
+ mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
+ generateTag(), EXPIRY_TIME);
+ mDialog = mClientTransaction.getDialog();
+ mState = SipSessionState.REGISTERING;
+ return true;
+ } else if (DEREGISTER == evt) {
+ mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
+ generateTag(), 0);
+ mState = SipSessionState.DEREGISTERING;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean incomingCall(EventObject evt) throws SipException {
+ // expect MakeCallCommand(answering) , END_CALL cmd , Cancel
+ if (evt instanceof MakeCallCommand) {
+ // answer call
+ mSipHelper.sendInviteOk(mInviteReceived, mLocalProfile,
+ ((MakeCallCommand) evt).getSessionDescription(),
+ generateTag(), mServerTransaction);
+ mState = SipSessionState.INCOMING_CALL_ANSWERING;
+ return true;
+ } else if (END_CALL == evt) {
+ mSipHelper.sendInviteBusyHere(mInviteReceived,
+ mServerTransaction);
+ endCall(false);
+ return true;
+ } else if (isRequestEvent(Request.CANCEL, evt)) {
+ RequestEvent event = (RequestEvent) evt;
+ mSipHelper.sendResponse(event, Response.OK);
+ mSipHelper.sendInviteRequestTerminated(
+ mInviteReceived.getRequest(), mServerTransaction);
+ endCall(false);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean incomingCallToInCall(EventObject evt)
+ throws SipException {
+ // expect ACK, CANCEL request
+ if (isRequestEvent(Request.ACK, evt)) {
+ establishCall(false);
+ return true;
+ } else if (isRequestEvent(Request.CANCEL, evt)) {
+ RequestEvent event = (RequestEvent) evt;
+ // TODO: what to do here? what happens when racing between
+ // OK-to-invite from callee and Cancel from caller
+ 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:
+ if (mState == SipSessionState.OUTGOING_CALL) {
+ mState = SipSessionState.OUTGOING_CALL_RING_BACK;
+ mListener.onRingingBack(this);
+ }
+ return true;
+ case Response.OK:
+ mSipHelper.sendInviteAck(event, mDialog);
+ mPeerSessionDescription = response.getRawContent();
+ establishCall(false);
+ return true;
+ default:
+ if (statusCode >= 400) {
+ // error: an ack is sent automatically by the stack
+ endCallOnError(false,
+ createCallbackException(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.
+ mSipHelper.sendCancel(mClientTransaction);
+ mState = SipSessionState.OUTGOING_CALL_CANCELING;
+ 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)) {
+ if (statusCode == Response.REQUEST_TERMINATED) {
+ endCall(false);
+ return true;
+ }
+ } else {
+ return false;
+ }
+
+ if (statusCode >= 400) {
+ endCallOnError(true, createCallbackException(response));
+ return true;
+ }
+ }
+ return false;
+ }
+
+ 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
+ mSipHelper.sendBye(mDialog);
+ endCall(true);
+ return true;
+ } else if (isRequestEvent(Request.INVITE, evt)) {
+ // got Re-INVITE
+ RequestEvent event = (RequestEvent) evt;
+ mSipHelper.sendReInviteOk(event, mLocalProfile);
+ mState = SipSessionState.IN_CALL_ANSWERING;
+ mPeerSessionDescription = event.getRequest().getRawContent();
+ mListener.onCallChanged(this, mPeerSessionDescription);
+ return true;
+ } else if (isRequestEvent(Request.BYE, evt)) {
+ mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
+ endCall(true);
+ return true;
+ } else if (evt instanceof MakeCallCommand) {
+ // to change call
+ mClientTransaction = mSipHelper.sendReinvite(mDialog,
+ ((MakeCallCommand) evt).getSessionDescription());
+ mState = SipSessionState.IN_CALL_CHANGING;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean inCallChanging(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.OK:
+ mSipHelper.sendInviteAck(event, mDialog);
+ establishCall(true);
+ return true;
+ case Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST:
+ case Response.REQUEST_TIMEOUT:
+ // rfc3261#section-14.1: re-invite failed; terminate
+ // the dialog
+ endCallOnError(true, createCallbackException(response));
+ return true;
+ case Response.REQUEST_PENDING:
+ // TODO:
+ // rfc3261#section-14.1; re-schedule invite
+ return true;
+ default:
+ if (statusCode >= 400) {
+ // error: an ack is sent automatically by the stack
+ mState = SipSessionState.IN_CALL;
+ mListener.onError(this,
+ createCallbackException(response));
+ return true;
+ } else if (statusCode >= 300) {
+ // TODO: handle 3xx (redirect)
+ } else {
+ return true;
+ }
+ }
+ return false;
+ } else if (END_CALL == evt) {
+ mSipHelper.sendCancel(mClientTransaction);
+ mState = SipSessionState.IN_CALL_CHANGING_CANCELING;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean inCallChangingToInCall(EventObject evt)
+ throws SipException {
+ if (expectResponse(Response.OK, Request.CANCEL, evt)) {
+ // do nothing; wait for REQUEST_TERMINATED
+ return true;
+ } else if (expectResponse(Response.OK, Request.INVITE, evt)) {
+ inCallChanging(evt); // abort Cancel
+ return true;
+ } else if (expectResponse(Response.REQUEST_TERMINATED,
+ Request.INVITE, evt)) {
+ establishCall(true);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean inCallAnsweringToInCall(EventObject evt) {
+ // expect ACK
+ if (isRequestEvent(Request.ACK, evt)) {
+ establishCall(true);
+ return true;
+ }
+ return false;
+ }
+
+ private Exception createCallbackException(Response response) {
+ return new SipException("Got response: " + response.getStatusCode()
+ + " " + response.getReasonPhrase());
+ }
+
+ private void establishCall(boolean inCall) {
+ if (inCall) {
+ mState = SipSessionState.IN_CALL;
+ mListener.onCallEstablished(this, mPeerSessionDescription);
+ } else {
+ SipSessionImpl newSession = createInCallSipSession();
+ addSipSession(newSession);
+ reset();
+ newSession.establishCall(true);
+ }
+ }
+
+ private void endCall(boolean inCall) {
+ if (inCall) removeSipSession(this);
+ reset();
+ mListener.onCallEnded(this);
+ }
+
+ private void endCallOnError(
+ boolean terminating, Throwable throwable) {
+ if (terminating) removeSipSession(this);
+ reset();
+ mListener.onError(this, throwable);
+ }
+
+ private boolean isInCall() {
+ return (mState.compareTo(SipSessionState.IN_CALL) >= 0);
+ }
+
+ private SipSessionImpl createInCallSipSession() {
+ SipSessionImpl newSession = new SipSessionImpl(mListener);
+ newSession.mPeerProfile = mPeerProfile;
+ newSession.mState = SipSessionState.IN_CALL;
+ newSession.mInviteReceived = mInviteReceived;
+ newSession.mDialog = mDialog;
+ newSession.mPeerSessionDescription = mPeerSessionDescription;
+ return newSession;
+ }
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * @return true if the event is a response event and the response code and
+ * CSeqHeader method match the given arguments; false otherwise
+ */
+ private static boolean expectResponse(
+ int responseCode, String expectedMethod, EventObject evt) {
+ if (evt instanceof ResponseEvent) {
+ ResponseEvent event = (ResponseEvent) evt;
+ Response response = event.getResponse();
+ if (response.getStatusCode() == responseCode) {
+ return expectedMethod.equalsIgnoreCase(getCseqMethod(response));
+ }
+ }
+ return false;
+ }
+
+ private static SipProfile createPeerProfile(Request request)
+ throws SipException {
+ try {
+ FromHeader fromHeader =
+ (FromHeader) request.getHeader(FromHeader.NAME);
+ Address address = fromHeader.getAddress();
+ SipURI uri = (SipURI) address.getURI();
+ return new SipProfile.Builder(uri.getUser(), uri.getHost())
+ .setPort(uri.getPort())
+ .setDisplayName(address.getDisplayName())
+ .build();
+ } catch (InvalidArgumentException e) {
+ throw new SipException("createPeerProfile()", e);
+ } catch (ParseException e) {
+ throw new SipException("createPeerProfile()", e);
+ }
+ }
+
+ private class MakeCallCommand extends EventObject {
+ private SessionDescription mSessionDescription;
+
+ MakeCallCommand(SipProfile peerProfile,
+ SessionDescription sessionDescription) {
+ super(peerProfile);
+ mSessionDescription = sessionDescription;
+ }
+
+ SipProfile getPeerProfile() {
+ return (SipProfile) getSource();
+ }
+
+ SessionDescription getSessionDescription() {
+ return mSessionDescription;
+ }
+ }
+}
diff --git a/src/android/net/sip/SipSessionLayer.java b/src/android/net/sip/SipSessionLayer.java
index 7b6c31e..adfd165 100644
--- a/src/android/net/sip/SipSessionLayer.java
+++ b/src/android/net/sip/SipSessionLayer.java
@@ -20,92 +20,63 @@ import android.util.Log;
import java.io.IOException;
import java.net.DatagramSocket;
-import java.text.ParseException;
-import java.util.EventObject;
+import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.TooManyListenersException;
-import javax.sip.ClientTransaction;
-import javax.sip.Dialog;
-import javax.sip.DialogTerminatedEvent;
-import javax.sip.IOExceptionEvent;
-import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
-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.ExpiresHeader;
-import javax.sip.header.FromHeader;
-import javax.sip.message.Message;
-import javax.sip.message.Request;
-import javax.sip.message.Response;
/**
* @hide
* Creates and manages multiple {@link SipSession}.
*/
-public class SipSessionLayer implements SipListener {
+public class SipSessionLayer {
private static final String TAG = SipSessionLayer.class.getSimpleName();
- private static final int EXPIRY_TIME = 3600;
+ private static final String STACK_NAME = "A SIP STACK";
- private static final EventObject REGISTER = new EventObject("Register");
- private static final EventObject DEREGISTER = new EventObject("Deregister");
- private static final EventObject END_CALL = new EventObject("End call");
- private static final EventObject HOLD_CALL = new EventObject("Hold call");
- private static final EventObject CONTINUE_CALL
- = new EventObject("Continue call");
+ private SipStack mSipStack;
+ private String mMyIp;
+ private Map<String, SipSessionGroup> mGroupMap =
+ new HashMap<String, SipSessionGroup>();
- private SipHelper mSipHelper;
-
- // myself@domain-to-SipSession map
- private Map<String, SipSessionImpl> mSessionMap =
- new HashMap<String, SipSessionImpl>();
-
- public void open(String myIp) throws SipException {
- open(myIp, allocateLocalPort(), "Android SIP Stack");
- }
-
- private void open(String myIp, int port, String stackName)
- throws SipException {
- if (mSipHelper != null) {
+ public SipSessionLayer() throws SipException {
+ if (mSipStack != null) {
throw new SipException("Call close() before open it again");
}
+ try {
+ mMyIp = getMyIp();
+ } catch (IOException e) {
+ throw new SipException("SipSessionLayer constructor", e);
+ }
SipFactory sipFactory = SipFactory.getInstance();
Properties properties = new Properties();
- properties.setProperty("javax.sip.STACK_NAME", stackName);
+ properties.setProperty("javax.sip.STACK_NAME", STACK_NAME);
+ SipStack stack = mSipStack = sipFactory.createSipStack(properties);
+ stack.start();
+ }
+
+ public String getLocalIp() {
+ if (mMyIp != null) return mMyIp;
try {
- SipStack stack = sipFactory.createSipStack(properties);
- SipProvider sipProvider = stack.createSipProvider(
- stack.createListeningPoint(myIp, port, ListeningPoint.UDP));
- sipProvider.addListeningPoint(
- stack.createListeningPoint(myIp, port, ListeningPoint.TCP));
- sipProvider.addSipListener(this);
- mSipHelper = new SipHelper(stack, sipProvider);
- stack.start();
- } catch (TooManyListenersException e) {
- // must never happen
- Log.e(TAG, "", e);
- } catch (SipException e) {
- Log.e(TAG, "", e);
+ return getMyIp();
+ } catch (IOException e) {
+ Log.w(TAG, "getLocalIp(): " + e);
+ return "127.0.0.1";
}
}
- private int allocateLocalPort() throws SipException {
+ private String getMyIp() throws IOException {
+ DatagramSocket s = new DatagramSocket();
+ s.connect(InetAddress.getByName("www.google.com"), 80);
+ return s.getLocalAddress().getHostAddress();
+ }
+
+ private static int allocateLocalPort() throws SipException {
try {
DatagramSocket s = new DatagramSocket();
int localPort = s.getLocalPort();
@@ -117,716 +88,28 @@ public class SipSessionLayer implements SipListener {
}
public synchronized void close() {
- if (mSipHelper != null) {
- mSipHelper.getSipStack().stop();
- mSipHelper = null;
+ if (mSipStack != null) {
+ mSipStack.stop();
+ mSipStack = null;
}
+ // remove all the groups
}
- public synchronized SipSession createSipSession(SipProfile myself,
+ private synchronized SipSessionGroup createGroup(SipProfile myself,
SipSessionListener listener) throws SipException {
- SipSessionImpl session = new SipSessionImpl(myself, listener);
- String key = mSipHelper.getSessionKey(session);
- Log.v(TAG, "add a session with key: '" + key + "'");
- mSessionMap.put(key, session);
- for (String k : mSessionMap.keySet()) {
- Log.v(TAG, " ----- " + k + ": " + mSessionMap.get(k));
- }
- return session;
- }
-
- private synchronized SipSessionImpl getSipSession(EventObject event) {
- String[] keys = mSipHelper.getPossibleSessionKeys(event);
- Log.v(TAG, " possible keys " + keys.length + " for event: " + event);
- for (String key : keys) Log.v(TAG, " '" + key + "'");
- Log.v(TAG, " active sessions:");
- for (String k : mSessionMap.keySet()) {
- Log.v(TAG, " ----- '" + k + "': " + mSessionMap.get(k));
- }
- for (String uri : mSipHelper.getPossibleSessionKeys(event)) {
- SipSessionImpl callSession = mSessionMap.get(uri);
- // return first match
- if (callSession != null) return callSession;
- }
- return null;
- }
-
- private synchronized void addSipSession(SipSessionImpl newSession) {
- String key = mSipHelper.getSessionKey(newSession);
- Log.v(TAG, " +++++ add a session with key: '" + key + "'");
- mSessionMap.put(key, newSession);
- for (String k : mSessionMap.keySet()) {
- Log.v(TAG, " ..... " + k + ": " + mSessionMap.get(k));
- }
- }
-
- private synchronized void removeSipSession(SipSessionImpl session) {
- String key = mSipHelper.getSessionKey(session);
- SipSessionImpl s = mSessionMap.remove(key);
- // sanity check
- if ((s != null) && (s != session)) {
- Log.w(TAG, "session " + session + " is not associated with key '"
- + key + "'");
- mSessionMap.put(key, s);
- } else {
- Log.v(TAG, " remove session " + session + " with key '" + key
- + "'");
- }
- for (String k : mSessionMap.keySet()) {
- Log.v(TAG, " ..... " + k + ": " + mSessionMap.get(k));
- }
- }
-
- public void processRequest(RequestEvent event) {
- process(event);
- }
-
- public void processResponse(ResponseEvent event) {
- process(event);
- }
-
- public void processIOException(IOExceptionEvent event) {
- // TODO: find proper listener to pass the exception
- process(event);
- }
-
- public void processTimeout(TimeoutEvent event) {
- // TODO: find proper listener to pass the exception
- process(event);
- }
-
- public void processTransactionTerminated(TransactionTerminatedEvent event) {
- // TODO: clean up if session is in in-transaction states
- //process(event);
- }
-
- public void processDialogTerminated(DialogTerminatedEvent event) {
- process(event);
- }
-
- private void process(EventObject event) {
- try {
- getSipSession(event).process(event);
- } catch (Exception e) {
- // TODO: suppress stack trace after code gets stabilized
- Log.e(TAG, "event not processed: " + event, e);
- }
- }
-
- /**
- * @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;
- }
-
- /**
- * @return true if the event is a response event and the response code and
- * CSeqHeader method match the given arguments; false otherwise
- */
- private static boolean expectResponse(
- int responseCode, String expectedMethod, EventObject evt) {
- if (evt instanceof ResponseEvent) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
- if (response.getStatusCode() == responseCode) {
- return expectedMethod.equalsIgnoreCase(getCseqMethod(response));
- }
- }
- return false;
- }
+ SipSessionGroup group = mGroupMap.get(myself.getUri().toString());
+ if (group != null) return group;
- private static SipProfile createPeerProfile(Request request)
- throws SipException {
- try {
- FromHeader fromHeader =
- (FromHeader) request.getHeader(FromHeader.NAME);
- Address address = fromHeader.getAddress();
- SipURI uri = (SipURI) address.getURI();
- return new SipProfile.Builder(uri.getUser(), uri.getHost())
- .setPort(uri.getPort())
- .setDisplayName(address.getDisplayName())
- .build();
- } catch (InvalidArgumentException e) {
- throw new SipException("createPeerProfile()", e);
- } catch (ParseException e) {
- throw new SipException("createPeerProfile()", e);
- }
+ SipStack stack = mSipStack;
+ SipProvider provider = stack.createSipProvider(
+ stack.createListeningPoint(mMyIp, allocateLocalPort(),
+ ListeningPoint.UDP));
+ return new SipSessionGroup(stack, provider, myself, listener);
}
- private class SipSessionImpl implements SipSession {
- private SipProfile mLocalProfile;
- private SipProfile mPeerProfile;
- private SipSessionListener mListener;
- private SipSessionState mState = SipSessionState.READY_FOR_CALL;
- private RequestEvent mInviteReceived;
- private Dialog mDialog;
- private ServerTransaction mServerTransaction;
- private ClientTransaction mClientTransaction;
- private byte[] mPeerSessionDescription;
-
- SipSessionImpl(SipProfile myself, SipSessionListener listener) {
- mLocalProfile = myself;
- mListener = listener;
- }
-
- private void reset() {
- mPeerProfile = null;
- mState = SipSessionState.READY_FOR_CALL;
- mInviteReceived = null;
- mDialog = null;
- mServerTransaction = null;
- mClientTransaction = null;
- mPeerSessionDescription = null;
- }
-
- public SipProfile getLocalProfile() {
- return mLocalProfile;
- }
-
- public synchronized SipProfile getPeerProfile() {
- return mPeerProfile;
- }
- public synchronized Dialog getDialog() {
- return mDialog;
- }
-
- public synchronized SipSessionState getState() {
- return mState;
- }
-
- public void makeCall(SipProfile peerProfile,
- SessionDescription sessionDescription) throws SipException {
- process(new MakeCallCommand(peerProfile, sessionDescription));
- }
-
- public void answerCall(SessionDescription sessionDescription)
- throws SipException {
- process(new MakeCallCommand(mPeerProfile, sessionDescription));
- }
-
- public void endCall() throws SipException {
- process(END_CALL);
- }
-
- // http://www.tech-invite.com/Ti-sip-service-1.html#fig5
- public void changeCall(SessionDescription sessionDescription)
- throws SipException {
- process(new MakeCallCommand(mPeerProfile, sessionDescription));
- }
-
- public void register() throws SipException {
- process(REGISTER);
- }
-
- public void deRegister() throws SipException {
- process(DEREGISTER);
- }
-
- private void scheduleNextRegistration(
- ExpiresHeader expiresHeader) {
- int expires = EXPIRY_TIME;
- if (expiresHeader != null) expires = expiresHeader.getExpires();
- Log.v(TAG, "Refresh registration " + expires + "s later.");
- new Timer().schedule(new TimerTask() {
- public void run() {
- try {
- process(REGISTER);
- } catch (SipException e) {
- Log.e(TAG, "", e);
- }
- }}, expires * 1000L);
- }
-
- private String generateTag() {
- // TODO: based on myself's profile
- return String.valueOf((long) (Math.random() * 1000000L));
- }
-
- private String log(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();
- }
- }
-
- public String toString() {
- try {
- String s = super.toString();
- return s.substring(s.indexOf("@")) + ":" + mState;
- } catch (Throwable e) {
- return super.toString();
- }
- }
-
- synchronized void process(EventObject evt) throws SipException {
- Log.v(TAG, " ~~~~~ " + this + ": " + mState + ": processing " + log(evt));
- boolean processed;
-
- switch (mState) {
- case REGISTERING:
- case DEREGISTERING:
- processed = registeringToReady(evt);
- break;
- case READY_FOR_CALL:
- processed = readyForCall(evt);
- break;
- case INCOMING_CALL:
- processed = incomingCall(evt);
- break;
- case INCOMING_CALL_ANSWERING:
- processed = incomingCallToInCall(evt);
- break;
- case OUTGOING_CALL:
- case OUTGOING_CALL_RING_BACK:
- processed = outgoingCall(evt);
- break;
- case OUTGOING_CALL_CANCELING:
- processed = outgoingCallToReady(evt);
- break;
- case IN_CALL:
- processed = inCall(evt);
- break;
- case IN_CALL_ANSWERING:
- processed = inCallAnsweringToInCall(evt);
- break;
- case IN_CALL_CHANGING:
- processed = inCallChanging(evt);
- break;
- case IN_CALL_CHANGING_CANCELING:
- processed = inCallChangingToInCall(evt);
- break;
- default:
- processed = false;
- }
- if (!processed && !processExceptions(evt)) {
- throw new SipException("event not processed");
- } else {
- Log.v(TAG, " ~~~~~ new state: " + mState);
- }
- }
-
- private boolean processExceptions(EventObject evt) throws SipException {
- // process INVITE and CANCEL
- if (isRequestEvent(Request.INVITE, evt)) {
- mSipHelper.sendResponse((RequestEvent) evt, Response.BUSY_HERE);
- 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 (evt instanceof TimeoutEvent) {
- processTimeout((TimeoutEvent) evt);
- } else {
- // TODO: any clean up?
- }
- return true;
- } else if (evt instanceof DialogTerminatedEvent) {
- processDialogTerminated((DialogTerminatedEvent) evt);
- return true;
- }
- return false;
- }
-
- private void processDialogTerminated(DialogTerminatedEvent event) {
- if (mDialog == event.getDialog()) {
- endCall(isInCall());
- } else {
- Log.d(TAG, "not the current dialog; current=" + mDialog
- + ", terminated=" + event.getDialog());
- }
- }
-
- private void processTimeout(TimeoutEvent event) {
- Log.d(TAG, "processing Timeout..." + event);
- Transaction current = event.isServerTransaction()
- ? mServerTransaction
- : mClientTransaction;
- Transaction target = event.isServerTransaction()
- ? event.getServerTransaction()
- : event.getClientTransaction();
-
- if (current != target) {
- Log.d(TAG, "not the current transaction; current=" + current
- + ", timed out=" + target);
- return;
- }
- switch (mState) {
- case REGISTERING:
- case INCOMING_CALL:
- case INCOMING_CALL_ANSWERING:
- case OUTGOING_CALL:
- case OUTGOING_CALL_RING_BACK:
- case OUTGOING_CALL_CANCELING:
- case IN_CALL_CHANGING:
- // rfc3261#section-14.1
- // if re-invite gets timed out, terminate the dialog
- endCallOnError((mState == SipSessionState.IN_CALL_CHANGING),
- new SipException("timed out"));
- break;
- case IN_CALL_ANSWERING:
- case IN_CALL_CHANGING_CANCELING:
- mState = SipSessionState.IN_CALL;
- mListener.onError(this, new SipException("timed out"));
- break;
- default:
- // do nothing
- break;
- }
- }
-
- private boolean registeringToReady(EventObject evt)
- throws SipException {
- if (expectResponse(Response.OK, Request.REGISTER, evt)) {
- if (mState == SipSessionState.REGISTERING) {
- scheduleNextRegistration((ExpiresHeader)
- ((ResponseEvent) evt).getResponse().getHeader(
- ExpiresHeader.NAME));
- }
- reset();
- mListener.onRegistrationDone(this);
- return true;
- }
- if (expectResponse(Response.UNAUTHORIZED, Request.REGISTER, evt)
- || expectResponse(Response.PROXY_AUTHENTICATION_REQUIRED,
- Request.REGISTER, evt)) {
- mSipHelper.handleChallenge((ResponseEvent)evt, mLocalProfile);
- return true;
- }
- //TODO: handle error conditions
- return false;
- }
-
- private boolean readyForCall(EventObject evt) throws SipException {
- // expect MakeCallCommand, Invite
- if (evt instanceof MakeCallCommand) {
- MakeCallCommand cmd = (MakeCallCommand) evt;
- mPeerProfile = cmd.getPeerProfile();
- SessionDescription sessionDescription = cmd.getSessionDescription();
- mClientTransaction = mSipHelper.sendInvite(mLocalProfile,
- mPeerProfile, sessionDescription, generateTag());
- mDialog = mClientTransaction.getDialog();
- mState = SipSessionState.OUTGOING_CALL;
- return true;
- } else if (isRequestEvent(Request.INVITE, evt)) {
- RequestEvent event = (RequestEvent) evt;
- mServerTransaction = mSipHelper.sendRinging(event);
- mDialog = mServerTransaction.getDialog();
- mInviteReceived = event;
- mPeerProfile = createPeerProfile(event.getRequest());
- mState = SipSessionState.INCOMING_CALL;
- mPeerSessionDescription = event.getRequest().getRawContent();
- mListener.onRinging(SipSessionImpl.this, mPeerProfile,
- mPeerSessionDescription);
- return true;
- } else if (REGISTER == evt) {
- mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
- generateTag(), EXPIRY_TIME);
- mDialog = mClientTransaction.getDialog();
- mState = SipSessionState.REGISTERING;
- return true;
- } else if (DEREGISTER == evt) {
- mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
- generateTag(), 0);
- mState = SipSessionState.DEREGISTERING;
- return true;
- }
- return false;
- }
-
- private boolean incomingCall(EventObject evt) throws SipException {
- // expect MakeCallCommand(answering) , END_CALL cmd , Cancel request
- if (evt instanceof MakeCallCommand) {
- // answer call
- mSipHelper.sendInviteOk(mInviteReceived, mLocalProfile,
- ((MakeCallCommand) evt).getSessionDescription(),
- generateTag(), mServerTransaction);
- mState = SipSessionState.INCOMING_CALL_ANSWERING;
- return true;
- } else if (END_CALL == evt) {
- mSipHelper.sendInviteBusyHere(mInviteReceived,
- mServerTransaction);
- endCall(false);
- return true;
- } else if (isRequestEvent(Request.CANCEL, evt)) {
- RequestEvent event = (RequestEvent) evt;
- mSipHelper.sendResponse(event, Response.OK);
- mSipHelper.sendInviteRequestTerminated(
- mInviteReceived.getRequest(),
- mServerTransaction);
- endCall(false);
- return true;
- }
- return false;
- }
-
- private boolean incomingCallToInCall(EventObject evt)
- throws SipException {
- // expect ACK, CANCEL request
- if (isRequestEvent(Request.ACK, evt)) {
- establishCall(false);
- return true;
- } else if (isRequestEvent(Request.CANCEL, evt)) {
- RequestEvent event = (RequestEvent) evt;
- // TODO: what to do here? what happens when racing between
- // OK-to-invite from callee and Cancel from caller
- 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:
- if (mState == SipSessionState.OUTGOING_CALL) {
- mState = SipSessionState.OUTGOING_CALL_RING_BACK;
- mListener.onRingingBack(this);
- }
- return true;
- case Response.OK:
- mSipHelper.sendInviteAck(event, mDialog);
- mPeerSessionDescription = response.getRawContent();
- establishCall(false);
- return true;
- default:
- if (statusCode >= 400) {
- // error: an ack is sent automatically by the stack
- endCallOnError(false,
- createCallbackException(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.
- mSipHelper.sendCancel(mClientTransaction);
- mState = SipSessionState.OUTGOING_CALL_CANCELING;
- return true;
- }
- return false;
- }
-
- private boolean outgoingCallToReady(EventObject evt)
- throws SipException {
- if (expectResponse(Response.OK, Request.CANCEL, evt)) {
- // do nothing; wait for REQUEST_TERMINATED
- return true;
- } else if (expectResponse(Request.INVITE, evt)) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
-
- int statusCode = response.getStatusCode();
- switch (statusCode) {
- case Response.REQUEST_TERMINATED:
- endCall(false);
- return true;
- case Response.REQUEST_TIMEOUT:
- case Response.SERVER_INTERNAL_ERROR:
- endCallOnError(true, createCallbackException(response));
- return true;
- }
- }
- return false;
- }
-
- 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
- mSipHelper.sendBye(mDialog);
- endCall(true);
- return true;
- } else if (isRequestEvent(Request.INVITE, evt)) {
- // got Re-INVITE
- RequestEvent event = (RequestEvent) evt;
- mSipHelper.sendReInviteOk(event, mLocalProfile);
- mState = SipSessionState.IN_CALL_ANSWERING;
- mPeerSessionDescription = event.getRequest().getRawContent();
- mListener.onCallChanged(this, mPeerSessionDescription);
- return true;
- } else if (isRequestEvent(Request.BYE, evt)) {
- mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
- endCall(true);
- return true;
- } else if (evt instanceof MakeCallCommand) {
- // to change call
- mClientTransaction = mSipHelper.sendReinvite(mDialog,
- ((MakeCallCommand) evt).getSessionDescription());
- mState = SipSessionState.IN_CALL_CHANGING;
- return true;
- }
- return false;
- }
-
- private boolean inCallChanging(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.OK:
- mSipHelper.sendInviteAck(event, mDialog);
- establishCall(true);
- return true;
- case Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST:
- case Response.REQUEST_TIMEOUT:
- // rfc3261#section-14.1: re-invite failed; terminate dialog
- endCallOnError(true, createCallbackException(response));
- return true;
- case Response.REQUEST_PENDING:
- // TODO:
- // rfc3261#section-14.1; re-schedule invite
- return true;
- default:
- if (statusCode >= 400) {
- // error: an ack is sent automatically by the stack
- mState = SipSessionState.IN_CALL;
- mListener.onError(this, createCallbackException(response));
- return true;
- } else if (statusCode >= 300) {
- // TODO: handle 3xx (redirect)
- } else {
- return true;
- }
- }
- return false;
- } else if (END_CALL == evt) {
- mSipHelper.sendCancel(mClientTransaction);
- mState = SipSessionState.IN_CALL_CHANGING_CANCELING;
- return true;
- }
- return false;
- }
-
- private boolean inCallChangingToInCall(EventObject evt)
- throws SipException {
- if (expectResponse(Response.OK, Request.CANCEL, evt)) {
- // do nothing; wait for REQUEST_TERMINATED
- return true;
- } else if (expectResponse(Response.OK, Request.INVITE, evt)) {
- inCallChanging(evt); // abort Cancel
- return true;
- } else if (expectResponse(Response.REQUEST_TERMINATED,
- Request.INVITE, evt)) {
- establishCall(true);
- return true;
- }
- return false;
- }
-
- private boolean inCallAnsweringToInCall(EventObject evt) {
- // expect ACK
- if (isRequestEvent(Request.ACK, evt)) {
- establishCall(true);
- return true;
- }
- return false;
- }
-
- private Exception createCallbackException(Response response) {
- return new SipException("Got response: " + response.getStatusCode()
- + " " + response.getReasonPhrase());
- }
-
- private void establishCall(boolean inCall) {
- if (inCall) {
- mState = SipSessionState.IN_CALL;
- mListener.onCallEstablished(this, mPeerSessionDescription);
- } else {
- SipSessionImpl newSession = createInCallSipSession();
- addSipSession(newSession);
- reset();
- newSession.establishCall(true);
- }
- }
-
- private void endCall(boolean inCall) {
- if (inCall) removeSipSession(this);
- reset();
- mListener.onCallEnded(this);
- }
-
- private void endCallOnError(boolean terminating, Throwable throwable) {
- if (terminating) removeSipSession(this);
- reset();
- mListener.onError(this, throwable);
- }
-
- private boolean isInCall() {
- return (mState.compareTo(SipSessionState.IN_CALL) >= 0);
- }
-
- private SipSessionImpl createInCallSipSession() {
- SipSessionImpl newSession =
- new SipSessionImpl(mLocalProfile, mListener);
- newSession.mPeerProfile = mPeerProfile;
- newSession.mState = SipSessionState.IN_CALL;
- newSession.mInviteReceived = mInviteReceived;
- newSession.mDialog = mDialog;
- newSession.mPeerSessionDescription = mPeerSessionDescription;
- return newSession;
- }
- }
-
- private class MakeCallCommand extends EventObject {
- private SessionDescription mSessionDescription;
-
- MakeCallCommand(SipProfile peerProfile,
- SessionDescription sessionDescription) {
- super(peerProfile);
- mSessionDescription = sessionDescription;
- }
-
- SipProfile getPeerProfile() {
- return (SipProfile) getSource();
- }
-
- SessionDescription getSessionDescription() {
- return mSessionDescription;
- }
+ public synchronized SipSession createSipSession(SipProfile myself,
+ SipSessionListener listener) throws SipException {
+ return createGroup(myself, listener).getDefaultSession();
}
}
diff --git a/src/com/android/sip/SipMain.java b/src/com/android/sip/SipMain.java
index 7dc4b62..cb866a2 100644
--- a/src/com/android/sip/SipMain.java
+++ b/src/com/android/sip/SipMain.java
@@ -50,7 +50,6 @@ import gov.nist.javax.sdp.fields.SDPKeywords;
import java.io.IOException;
import java.net.DatagramSocket;
-import java.net.InetAddress;
import java.text.ParseException;
import javax.sdp.SdpException;
import javax.sip.SipException;
@@ -87,6 +86,13 @@ public class SipMain extends PreferenceActivity
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.dev_pref);
+ try {
+ setupSipStack();
+ } catch (SipException e) {
+ Log.e(TAG, "register()", e);
+ return;
+ }
+
mCallStatus = getPreferenceScreen().findPreference("call_status");
mPeerUri = setupEditTextPreference("peer");
mServerUri = setupEditTextPreference("server_address");
@@ -138,6 +144,8 @@ public class SipMain extends PreferenceActivity
mSipSession = null;
}
stopAudioCall();
+ stopRingbackPlayer();
+ stopRinging();
}
public boolean onPreferenceChange(Preference pref, Object newValue) {
@@ -334,28 +342,18 @@ public class SipMain extends PreferenceActivity
};
}
- private SipSession createSipSession() {
- try {
- return mSipSessionLayer.createSipSession(createLocalSipProfile(),
- createSipSessionListener());
- } catch (SipException e) {
- // TODO: toast
- Log.e(TAG, "createSipSession()", e);
- return null;
- }
- }
-
private void setupSipStack() throws SipException {
- if (mSipSession == null) {
+ if (mSipSessionLayer == null) {
mSipSessionLayer = new SipSessionLayer();
- mSipSessionLayer.open(getLocalIp());
- mSipSession = createSipSession();
}
}
private void register() {
try {
- setupSipStack();
+ if (mSipSession == null) {
+ mSipSession = mSipSessionLayer.createSipSession(
+ createLocalSipProfile(), createSipSessionListener());
+ }
mSipSession.register();
setCallStatus();
} catch (SipException e) {
@@ -597,14 +595,7 @@ public class SipMain extends PreferenceActivity
}
private String getLocalIp() {
- try {
- DatagramSocket s = new DatagramSocket();
- s.connect(InetAddress.getByName("www.google.com"), 80);
- return s.getLocalAddress().getHostAddress();
- } catch (IOException e) {
- Log.w(TAG, "getLocalIp(): " + e);
- return "127.0.0.1";
- }
+ return mSipSessionLayer.getLocalIp();
}
private void setValue(EditTextPreference pref, String value) {