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

View File

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

View File

@@ -39,6 +39,7 @@ public abstract class Connection {
CONGESTION, /* outgoing call to congested network */
MMI, /* not presently used; dial() returns null */
INVALID_NUMBER, /* invalid dial string */
INVALID_CREDENTIALS, /* invalid credentials */
LOST_SIGNAL,
LIMIT_EXCEEDED, /* eg GSM ACM limit exceeded */
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.AudioStream;
import android.net.sip.SipAudioCall;
import android.net.sip.SipErrorCode;
import android.net.sip.SipManager;
import android.net.sip.SipProfile;
import android.net.sip.SipSessionState;
@@ -633,16 +634,15 @@ public class SipPhone extends SipPhoneBase {
}
@Override
protected void onError(String errorMessage) {
Log.w(LOG_TAG, "SIP error: " + errorMessage);
protected void onError(DisconnectCause cause) {
Log.w(LOG_TAG, "SIP error: " + cause);
if (mSipAudioCall.isInCall()) {
// Don't end the call when in call.
// TODO: how to deliver the error to PhoneApp
return;
}
// FIXME: specify error
onCallEnded(DisconnectCause.ERROR_UNSPECIFIED);
onCallEnded(cause);
}
};
@@ -807,7 +807,7 @@ public class SipPhone extends SipPhoneBase {
private abstract class SipAudioCallAdapter extends SipAudioCall.Adapter {
protected abstract void onCallEnded(Connection.DisconnectCause cause);
protected abstract void onError(String errorMessage);
protected abstract void onError(Connection.DisconnectCause cause);
@Override
public void onCallEnded(SipAudioCall call) {
@@ -820,8 +820,24 @@ public class SipPhone extends SipPhoneBase {
}
@Override
public void onError(SipAudioCall call, String errorMessage) {
onError(errorMessage);
public void onError(SipAudioCall call, String errorCode,
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.
*
* @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
*/
void onError(in ISipSession session, String errorClass,
void onError(in ISipSession session, String errorCode,
String errorMessage);
/**
* Called when an error occurs during session modification negotiation.
*
* @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
*/
void onCallChangeFailed(in ISipSession session, String errorClass,
void onCallChangeFailed(in ISipSession session, String errorCode,
String errorMessage);
/**
@@ -111,10 +111,10 @@ interface ISipSessionListener {
* Called when the registration fails.
*
* @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
*/
void onRegistrationFailed(in ISipSession session, String errorClass,
void onRegistrationFailed(in ISipSession session, String errorCode,
String errorMessage);
/**

View File

@@ -90,9 +90,10 @@ public interface SipAudioCall {
* Called when an error occurs.
*
* @param call the call object that carries out the audio call
* @param errorCode error code defined in {@link SipErrorCode}
* @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) {
onChanged(call);
}
public void onError(SipAudioCall call, String errorMessage) {
public void onError(SipAudioCall call, String errorCode,
String errorMessage) {
onChanged(call);
}
}

View File

@@ -108,7 +108,8 @@ public class SipAudioCallImpl extends SipSessionAdapter
listener.onCalling(this);
break;
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) {
Log.e(TAG, "setListener()", t);
@@ -275,14 +276,13 @@ public class SipAudioCallImpl extends SipSessionAdapter
}
@Override
public void onCallChangeFailed(ISipSession session,
String className, String message) {
public void onCallChangeFailed(ISipSession session, String errorCode,
String message) {
Log.d(TAG, "sip call change failed: " + message);
Listener listener = mListener;
if (listener != null) {
try {
listener.onError(SipAudioCallImpl.this,
className + ": " + message);
listener.onError(SipAudioCallImpl.this, errorCode, message);
} catch (Throwable t) {
Log.e(TAG, "onCallBusy()", t);
}
@@ -290,17 +290,16 @@ public class SipAudioCallImpl extends SipSessionAdapter
}
@Override
public void onError(ISipSession session, String className,
public void onError(ISipSession session, String errorCode,
String message) {
Log.d(TAG, "sip session error: " + className + ": " + message);
Log.d(TAG, "sip session error: " + errorCode + ": " + message);
synchronized (this) {
if (!isInCall()) close(true);
}
Listener listener = mListener;
if (listener != null) {
try {
listener.onError(SipAudioCallImpl.this,
className + ": " + message);
listener.onError(SipAudioCallImpl.this, errorCode, message);
} catch (Throwable 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.
*
* @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
*/
void onRegistrationFailed(String localProfileUri, String errorClass,
void onRegistrationFailed(String localProfileUri, String errorCode,
String errorMessage);
}

View File

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