am ca3c24db: Merge "SIP: add SipErrorCode for error feedback." into gingerbread

Merge commit 'ca3c24db3ae4b7a513f1ca76b1e7a3f56a020680' into gingerbread-plus-aosp

* commit 'ca3c24db3ae4b7a513f1ca76b1e7a3f56a020680':
  SIP: add SipErrorCode for error feedback.
This commit is contained in:
Hung-ying Tyan
2010-09-10 02:23:04 -07:00
committed by Android Git Automerger
10 changed files with 213 additions and 64 deletions

View File

@@ -25,6 +25,7 @@ import gov.nist.javax.sip.message.SIPMessage;
import android.net.sip.ISipSession; import android.net.sip.ISipSession;
import android.net.sip.ISipSessionListener; import android.net.sip.ISipSessionListener;
import android.net.sip.SessionDescription; import android.net.sip.SessionDescription;
import android.net.sip.SipErrorCode;
import android.net.sip.SipProfile; import android.net.sip.SipProfile;
import android.net.sip.SipSessionAdapter; import android.net.sip.SipSessionAdapter;
import android.net.sip.SipSessionState; import android.net.sip.SipSessionState;
@@ -34,6 +35,7 @@ import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.UnknownHostException;
import java.text.ParseException; import java.text.ParseException;
import java.util.Collection; import java.util.Collection;
import java.util.EventObject; import java.util.EventObject;
@@ -60,6 +62,7 @@ import javax.sip.TimeoutEvent;
import javax.sip.Transaction; import javax.sip.Transaction;
import javax.sip.TransactionState; import javax.sip.TransactionState;
import javax.sip.TransactionTerminatedEvent; import javax.sip.TransactionTerminatedEvent;
import javax.sip.TransactionUnavailableException;
import javax.sip.address.Address; import javax.sip.address.Address;
import javax.sip.address.SipURI; import javax.sip.address.SipURI;
import javax.sip.header.CSeqHeader; import javax.sip.header.CSeqHeader;
@@ -77,6 +80,7 @@ import javax.sip.message.Response;
class SipSessionGroup implements SipListener { class SipSessionGroup implements SipListener {
private static final String TAG = "SipSession"; private static final String TAG = "SipSession";
private static final String ANONYMOUS = "anonymous"; private static final String ANONYMOUS = "anonymous";
private static final String SERVER_ERROR_PREFIX = "Response: ";
private static final int EXPIRY_TIME = 3600; private static final int EXPIRY_TIME = 3600;
private static final EventObject DEREGISTER = new EventObject("Deregister"); private static final EventObject DEREGISTER = new EventObject("Deregister");
@@ -282,7 +286,7 @@ class SipSessionGroup implements SipListener {
Log.d(TAG, "event not processed: " + event); Log.d(TAG, "event not processed: " + event);
} }
} catch (Throwable e) { } catch (Throwable e) {
Log.e(TAG, "event process error: " + event, e); Log.w(TAG, "event process error: " + event, e);
session.onError(e); session.onError(e);
} }
} }
@@ -407,6 +411,7 @@ class SipSessionGroup implements SipListener {
try { try {
processCommand(command); processCommand(command);
} catch (SipException e) { } catch (SipException e) {
Log.w(TAG, "command error: " + command, e);
// TODO: find a better way to do this // TODO: find a better way to do this
if ((command instanceof RegisterCommand) if ((command instanceof RegisterCommand)
|| (command == DEREGISTER)) { || (command == DEREGISTER)) {
@@ -603,7 +608,7 @@ class SipSessionGroup implements SipListener {
case INCOMING_CALL: case INCOMING_CALL:
case INCOMING_CALL_ANSWERING: case INCOMING_CALL_ANSWERING:
case OUTGOING_CALL_CANCELING: case OUTGOING_CALL_CANCELING:
endCallOnError(new SipException("timed out")); endCallOnError(SipErrorCode.TIME_OUT, event.toString());
break; break;
case PINGING: case PINGING:
reset(); reset();
@@ -691,15 +696,11 @@ class SipSessionGroup implements SipListener {
return true; return true;
case Response.UNAUTHORIZED: case Response.UNAUTHORIZED:
case Response.PROXY_AUTHENTICATION_REQUIRED: case Response.PROXY_AUTHENTICATION_REQUIRED:
String nonce = getNonceFromResponse(response); if (!handleAuthentication(event)) {
if (((nonce != null) && nonce.equals(mLastNonce)) ||
(nonce == mLastNonce)) {
Log.v(TAG, "Incorrect username/password"); Log.v(TAG, "Incorrect username/password");
reset(); reset();
onRegistrationFailed(createCallbackException(response)); onRegistrationFailed(SipErrorCode.INVALID_CREDENTIALS,
} else { "incorrect username or password");
mSipHelper.handleChallenge(event, getAccountManager());
mLastNonce = nonce;
} }
return true; return true;
default: default:
@@ -713,6 +714,23 @@ class SipSessionGroup implements SipListener {
return false; return false;
} }
private boolean handleAuthentication(ResponseEvent event)
throws SipException {
Response response = event.getResponse();
String nonce = getNonceFromResponse(response);
if (((nonce != null) && nonce.equals(mLastNonce)) ||
(nonce == mLastNonce)) {
Log.v(TAG, "Incorrect username/password");
return false;
} else {
mClientTransaction = mSipHelper.handleChallenge(
event, getAccountManager());
mDialog = mClientTransaction.getDialog();
mLastNonce = nonce;
return true;
}
}
private AccountManager getAccountManager() { private AccountManager getAccountManager() {
return new AccountManager() { return new AccountManager() {
public UserCredentials getCredentials(ClientTransaction public UserCredentials getCredentials(ClientTransaction
@@ -833,14 +851,12 @@ class SipSessionGroup implements SipListener {
establishCall(); establishCall();
return true; return true;
case Response.PROXY_AUTHENTICATION_REQUIRED: case Response.PROXY_AUTHENTICATION_REQUIRED:
mClientTransaction = mSipHelper.handleChallenge( if (handleAuthentication(event)) {
(ResponseEvent) evt, getAccountManager()); addSipSession(this);
mDialog = mClientTransaction.getDialog(); } else {
addSipSession(this); endCallOnError(SipErrorCode.INVALID_CREDENTIALS,
return true; "incorrect username or password");
case Response.BUSY_HERE: }
reset();
mProxy.onCallBusy(this);
return true; return true;
case Response.REQUEST_PENDING: case Response.REQUEST_PENDING:
// TODO: // TODO:
@@ -849,7 +865,7 @@ class SipSessionGroup implements SipListener {
default: default:
if (statusCode >= 400) { if (statusCode >= 400) {
// error: an ack is sent automatically by the stack // error: an ack is sent automatically by the stack
onError(createCallbackException(response)); onError(response);
return true; return true;
} else if (statusCode >= 300) { } else if (statusCode >= 300) {
// TODO: handle 3xx (redirect) // TODO: handle 3xx (redirect)
@@ -933,9 +949,13 @@ class SipSessionGroup implements SipListener {
return false; return false;
} }
private String createErrorMessage(Response response) {
return String.format(SERVER_ERROR_PREFIX + "%s (%d)",
response.getReasonPhrase(), response.getStatusCode());
}
private Exception createCallbackException(Response response) { private Exception createCallbackException(Response response) {
return new SipException(String.format("Response: %s (%d)", return new SipException(createErrorMessage(response));
response.getReasonPhrase(), response.getStatusCode()));
} }
private void establishCall() { private void establishCall() {
@@ -946,8 +966,9 @@ class SipSessionGroup implements SipListener {
private void fallbackToPreviousInCall(Throwable exception) { private void fallbackToPreviousInCall(Throwable exception) {
mState = SipSessionState.IN_CALL; mState = SipSessionState.IN_CALL;
mProxy.onCallChangeFailed(this, exception.getClass().getName(), exception = getRootCause(exception);
exception.getMessage()); mProxy.onCallChangeFailed(this, getErrorCode(exception).toString(),
exception.toString());
} }
private void endCallNormally() { private void endCallNormally() {
@@ -956,9 +977,18 @@ class SipSessionGroup implements SipListener {
} }
private void endCallOnError(Throwable exception) { private void endCallOnError(Throwable exception) {
exception = getRootCause(exception);
endCallOnError(getErrorCode(exception), exception.toString());
}
private void endCallOnError(SipErrorCode errorCode, String message) {
reset(); reset();
mProxy.onError(this, exception.getClass().getName(), mProxy.onError(this, errorCode.toString(), message);
exception.getMessage()); }
private void endCallOnBusy() {
reset();
mProxy.onCallBusy(this);
} }
private void onError(Throwable exception) { private void onError(Throwable exception) {
@@ -969,13 +999,72 @@ class SipSessionGroup implements SipListener {
} }
} }
private void onError(Response response) {
if (mInCall) {
fallbackToPreviousInCall(createCallbackException(response));
} else {
int statusCode = response.getStatusCode();
if ((statusCode == Response.TEMPORARILY_UNAVAILABLE)
|| (statusCode == Response.BUSY_HERE)) {
endCallOnBusy();
} else {
endCallOnError(getErrorCode(statusCode),
createErrorMessage(response));
}
}
}
private SipErrorCode getErrorCode(int responseStatusCode) {
switch (responseStatusCode) {
case Response.NOT_FOUND:
case Response.ADDRESS_INCOMPLETE:
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 Throwable getRootCause(Throwable exception) {
Throwable cause = exception.getCause();
while (cause != null) {
exception = cause;
cause = exception.getCause();
}
return exception;
}
private SipErrorCode getErrorCode(Throwable exception) {
String message = exception.getMessage();
if (exception instanceof UnknownHostException) {
return SipErrorCode.INVALID_REMOTE_URI;
} else if (exception instanceof IOException) {
return SipErrorCode.SOCKET_ERROR;
} else if (message.startsWith(SERVER_ERROR_PREFIX)) {
return SipErrorCode.SERVER_ERROR;
} else {
return SipErrorCode.CLIENT_ERROR;
}
}
private void onRegistrationDone(int duration) { private void onRegistrationDone(int duration) {
mProxy.onRegistrationDone(this, duration); mProxy.onRegistrationDone(this, duration);
} }
private void onRegistrationFailed(SipErrorCode errorCode,
String message) {
mProxy.onRegistrationFailed(this, errorCode.toString(), message);
}
private void onRegistrationFailed(Throwable exception) { private void onRegistrationFailed(Throwable exception) {
mProxy.onRegistrationFailed(this, exception.getClass().getName(), exception = getRootCause(exception);
exception.getMessage()); onRegistrationFailed(getErrorCode(exception),
exception.toString());
} }
} }

View File

@@ -124,12 +124,12 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub {
} }
public void onCallChangeFailed(final ISipSession session, public void onCallChangeFailed(final ISipSession session,
final String className, final String message) { final String errorCode, final String message) {
if (mListener == null) return; if (mListener == null) return;
proxy(new Runnable() { proxy(new Runnable() {
public void run() { public void run() {
try { try {
mListener.onCallChangeFailed(session, className, message); mListener.onCallChangeFailed(session, errorCode, message);
} catch (Throwable t) { } catch (Throwable t) {
handle(t, "onCallChangeFailed()"); handle(t, "onCallChangeFailed()");
} }
@@ -137,13 +137,13 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub {
}); });
} }
public void onError(final ISipSession session, final String className, public void onError(final ISipSession session, final String errorCode,
final String message) { final String message) {
if (mListener == null) return; if (mListener == null) return;
proxy(new Runnable() { proxy(new Runnable() {
public void run() { public void run() {
try { try {
mListener.onError(session, className, message); mListener.onError(session, errorCode, message);
} catch (Throwable t) { } catch (Throwable t) {
handle(t, "onError()"); handle(t, "onError()");
} }
@@ -179,12 +179,12 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub {
} }
public void onRegistrationFailed(final ISipSession session, public void onRegistrationFailed(final ISipSession session,
final String className, final String message) { final String errorCode, final String message) {
if (mListener == null) return; if (mListener == null) return;
proxy(new Runnable() { proxy(new Runnable() {
public void run() { public void run() {
try { try {
mListener.onRegistrationFailed(session, className, message); mListener.onRegistrationFailed(session, errorCode, message);
} catch (Throwable t) { } catch (Throwable t) {
handle(t, "onRegistrationFailed()"); handle(t, "onRegistrationFailed()");
} }

View File

@@ -39,6 +39,7 @@ public abstract class Connection {
CONGESTION, /* outgoing call to congested network */ CONGESTION, /* outgoing call to congested network */
MMI, /* not presently used; dial() returns null */ MMI, /* not presently used; dial() returns null */
INVALID_NUMBER, /* invalid dial string */ INVALID_NUMBER, /* invalid dial string */
INVALID_CREDENTIALS, /* invalid credentials */
LOST_SIGNAL, LOST_SIGNAL,
LIMIT_EXCEEDED, /* eg GSM ACM limit exceeded */ LIMIT_EXCEEDED, /* eg GSM ACM limit exceeded */
INCOMING_REJECTED, /* an incoming call that was rejected */ INCOMING_REJECTED, /* an incoming call that was rejected */

View File

@@ -23,6 +23,7 @@ import android.net.Uri;
import android.net.rtp.AudioGroup; import android.net.rtp.AudioGroup;
import android.net.rtp.AudioStream; import android.net.rtp.AudioStream;
import android.net.sip.SipAudioCall; import android.net.sip.SipAudioCall;
import android.net.sip.SipErrorCode;
import android.net.sip.SipManager; import android.net.sip.SipManager;
import android.net.sip.SipProfile; import android.net.sip.SipProfile;
import android.net.sip.SipSessionState; import android.net.sip.SipSessionState;
@@ -633,16 +634,15 @@ public class SipPhone extends SipPhoneBase {
} }
@Override @Override
protected void onError(String errorMessage) { protected void onError(DisconnectCause cause) {
Log.w(LOG_TAG, "SIP error: " + errorMessage); Log.w(LOG_TAG, "SIP error: " + cause);
if (mSipAudioCall.isInCall()) { if (mSipAudioCall.isInCall()) {
// Don't end the call when in call. // Don't end the call when in call.
// TODO: how to deliver the error to PhoneApp // TODO: how to deliver the error to PhoneApp
return; return;
} }
// FIXME: specify error onCallEnded(cause);
onCallEnded(DisconnectCause.ERROR_UNSPECIFIED);
} }
}; };
@@ -807,7 +807,7 @@ public class SipPhone extends SipPhoneBase {
private abstract class SipAudioCallAdapter extends SipAudioCall.Adapter { private abstract class SipAudioCallAdapter extends SipAudioCall.Adapter {
protected abstract void onCallEnded(Connection.DisconnectCause cause); protected abstract void onCallEnded(Connection.DisconnectCause cause);
protected abstract void onError(String errorMessage); protected abstract void onError(Connection.DisconnectCause cause);
@Override @Override
public void onCallEnded(SipAudioCall call) { public void onCallEnded(SipAudioCall call) {
@@ -820,8 +820,24 @@ public class SipPhone extends SipPhoneBase {
} }
@Override @Override
public void onError(SipAudioCall call, String errorMessage) { public void onError(SipAudioCall call, String errorCode,
onError(errorMessage); String errorMessage) {
switch (Enum.valueOf(SipErrorCode.class, errorCode)) {
case INVALID_REMOTE_URI:
onError(Connection.DisconnectCause.INVALID_NUMBER);
break;
case TIME_OUT:
onError(Connection.DisconnectCause.CONGESTION);
break;
case INVALID_CREDENTIALS:
onError(Connection.DisconnectCause.INVALID_CREDENTIALS);
break;
case SOCKET_ERROR:
case SERVER_ERROR:
case CLIENT_ERROR:
default:
onError(Connection.DisconnectCause.ERROR_UNSPECIFIED);
}
} }
} }
} }

View File

@@ -76,20 +76,20 @@ interface ISipSessionListener {
* termination. * termination.
* *
* @param session the session object that carries out the transaction * @param session the session object that carries out the transaction
* @param errorClass name of the exception class * @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message * @param errorMessage error message
*/ */
void onError(in ISipSession session, String errorClass, void onError(in ISipSession session, String errorCode,
String errorMessage); String errorMessage);
/** /**
* Called when an error occurs during session modification negotiation. * Called when an error occurs during session modification negotiation.
* *
* @param session the session object that carries out the transaction * @param session the session object that carries out the transaction
* @param errorClass name of the exception class * @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message * @param errorMessage error message
*/ */
void onCallChangeFailed(in ISipSession session, String errorClass, void onCallChangeFailed(in ISipSession session, String errorCode,
String errorMessage); String errorMessage);
/** /**
@@ -111,10 +111,10 @@ interface ISipSessionListener {
* Called when the registration fails. * Called when the registration fails.
* *
* @param session the session object that carries out the transaction * @param session the session object that carries out the transaction
* @param errorClass name of the exception class * @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message * @param errorMessage error message
*/ */
void onRegistrationFailed(in ISipSession session, String errorClass, void onRegistrationFailed(in ISipSession session, String errorCode,
String errorMessage); String errorMessage);
/** /**

View File

@@ -90,9 +90,10 @@ public interface SipAudioCall {
* Called when an error occurs. * Called when an error occurs.
* *
* @param call the call object that carries out the audio call * @param call the call object that carries out the audio call
* @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message * @param errorMessage error message
*/ */
void onError(SipAudioCall call, String errorMessage); void onError(SipAudioCall call, String errorCode, String errorMessage);
} }
/** /**
@@ -126,7 +127,8 @@ public interface SipAudioCall {
public void onCallHeld(SipAudioCall call) { public void onCallHeld(SipAudioCall call) {
onChanged(call); onChanged(call);
} }
public void onError(SipAudioCall call, String errorMessage) { public void onError(SipAudioCall call, String errorCode,
String errorMessage) {
onChanged(call); onChanged(call);
} }
} }

View File

@@ -108,7 +108,8 @@ public class SipAudioCallImpl extends SipSessionAdapter
listener.onCalling(this); listener.onCalling(this);
break; break;
default: default:
listener.onError(this, "wrong state to attach call: " + state); listener.onError(this, SipErrorCode.CLIENT_ERROR.toString(),
"wrong state to attach call: " + state);
} }
} catch (Throwable t) { } catch (Throwable t) {
Log.e(TAG, "setListener()", t); Log.e(TAG, "setListener()", t);
@@ -275,14 +276,13 @@ public class SipAudioCallImpl extends SipSessionAdapter
} }
@Override @Override
public void onCallChangeFailed(ISipSession session, public void onCallChangeFailed(ISipSession session, String errorCode,
String className, String message) { String message) {
Log.d(TAG, "sip call change failed: " + message); Log.d(TAG, "sip call change failed: " + message);
Listener listener = mListener; Listener listener = mListener;
if (listener != null) { if (listener != null) {
try { try {
listener.onError(SipAudioCallImpl.this, listener.onError(SipAudioCallImpl.this, errorCode, message);
className + ": " + message);
} catch (Throwable t) { } catch (Throwable t) {
Log.e(TAG, "onCallBusy()", t); Log.e(TAG, "onCallBusy()", t);
} }
@@ -290,17 +290,16 @@ public class SipAudioCallImpl extends SipSessionAdapter
} }
@Override @Override
public void onError(ISipSession session, String className, public void onError(ISipSession session, String errorCode,
String message) { String message) {
Log.d(TAG, "sip session error: " + className + ": " + message); Log.d(TAG, "sip session error: " + errorCode + ": " + message);
synchronized (this) { synchronized (this) {
if (!isInCall()) close(true); if (!isInCall()) close(true);
} }
Listener listener = mListener; Listener listener = mListener;
if (listener != null) { if (listener != null) {
try { try {
listener.onError(SipAudioCallImpl.this, listener.onError(SipAudioCallImpl.this, errorCode, message);
className + ": " + message);
} catch (Throwable t) { } catch (Throwable t) {
Log.e(TAG, "onError()", t); Log.e(TAG, "onError()", t);
} }

View File

@@ -0,0 +1,45 @@
/*
* 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 android.net.sip;
/**
* Defines error code returned in
* {@link SipRegistrationListener#onRegistrationFailed(String, String, String)},
* {@link ISipSessionListener#onError(ISipSession, String, String)},
* {@link ISipSessionListener#onCallChangeFailed(ISipSession, String, String)} and
* {@link ISipSessionListener#onRegistrationFailed(ISipSession, String, String)}.
* @hide
*/
public enum SipErrorCode {
/** When some socket error occurs. */
SOCKET_ERROR,
/** When server responds with an error. */
SERVER_ERROR,
/** When some error occurs on the device, possibly due to a bug. */
CLIENT_ERROR,
/** When the transaction gets timed out. */
TIME_OUT,
/** When the remote URI is not valid. */
INVALID_REMOTE_URI,
/** When invalid credentials are provided. */
INVALID_CREDENTIALS;
}

View File

@@ -40,9 +40,9 @@ public interface SipRegistrationListener {
* Called when the registration fails. * Called when the registration fails.
* *
* @param localProfileUri the URI string of the SIP profile to register with * @param localProfileUri the URI string of the SIP profile to register with
* @param errorClass name of the exception class * @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message * @param errorMessage error message
*/ */
void onRegistrationFailed(String localProfileUri, String errorClass, void onRegistrationFailed(String localProfileUri, String errorCode,
String errorMessage); String errorMessage);
} }

View File

@@ -42,14 +42,11 @@ public class SipSessionAdapter extends ISipSessionListener.Stub {
public void onCallBusy(ISipSession session) { public void onCallBusy(ISipSession session) {
} }
public void onCallChanged(ISipSession session, byte[] sessionDescription) { public void onCallChangeFailed(ISipSession session, String errorCode,
}
public void onCallChangeFailed(ISipSession session, String className,
String message) { String message) {
} }
public void onError(ISipSession session, String className, String message) { public void onError(ISipSession session, String errorCode, String message) {
} }
public void onRegistering(ISipSession session) { public void onRegistering(ISipSession session) {
@@ -58,7 +55,7 @@ public class SipSessionAdapter extends ISipSessionListener.Stub {
public void onRegistrationDone(ISipSession session, int duration) { public void onRegistrationDone(ISipSession session, int duration) {
} }
public void onRegistrationFailed(ISipSession session, String className, public void onRegistrationFailed(ISipSession session, String errorCode,
String message) { String message) {
} }