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.
diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java
index 70ff9ea..2f7ddc4 100644
--- a/services/java/com/android/server/sip/SipSessionGroup.java
+++ b/services/java/com/android/server/sip/SipSessionGroup.java
@@ -25,6 +25,7 @@
 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 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.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 @@
 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 @@
                 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 @@
                         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 @@
             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 @@
                     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 @@
             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 @@
                     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 @@
                 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 @@
             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 @@
 
         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 @@
         }
 
         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 @@
             }
         }
 
+        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());
         }
     }
 
diff --git a/services/java/com/android/server/sip/SipSessionListenerProxy.java b/services/java/com/android/server/sip/SipSessionListenerProxy.java
index 7234196..747d79f 100644
--- a/services/java/com/android/server/sip/SipSessionListenerProxy.java
+++ b/services/java/com/android/server/sip/SipSessionListenerProxy.java
@@ -124,12 +124,12 @@
     }
 
     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 @@
         });
     }
 
-    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 @@
     }
 
     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()");
                 }
diff --git a/telephony/java/com/android/internal/telephony/Connection.java b/telephony/java/com/android/internal/telephony/Connection.java
index 11d0b1b..e5456e4 100644
--- a/telephony/java/com/android/internal/telephony/Connection.java
+++ b/telephony/java/com/android/internal/telephony/Connection.java
@@ -39,6 +39,7 @@
         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 */
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index 6eb619a..bf1b939 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -23,6 +23,7 @@
 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 @@
             }
 
             @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 @@
 
     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 @@
         }
 
         @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);
+            }
         }
     }
 }
diff --git a/voip/java/android/net/sip/ISipSessionListener.aidl b/voip/java/android/net/sip/ISipSessionListener.aidl
index c552a57..0a6220b 100644
--- a/voip/java/android/net/sip/ISipSessionListener.aidl
+++ b/voip/java/android/net/sip/ISipSessionListener.aidl
@@ -76,20 +76,20 @@
      * 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 @@
      * 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);
 
     /**
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index 8254543..02f82b3 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -90,9 +90,10 @@
          * 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 void onCallHeld(SipAudioCall call) {
             onChanged(call);
         }
-        public void onError(SipAudioCall call, String errorMessage) {
+        public void onError(SipAudioCall call, String errorCode,
+                String errorMessage) {
             onChanged(call);
         }
     }
diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java
index a312f83..2e2ca5b 100644
--- a/voip/java/android/net/sip/SipAudioCallImpl.java
+++ b/voip/java/android/net/sip/SipAudioCallImpl.java
@@ -108,7 +108,8 @@
                 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 @@
     }
 
     @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 @@
     }
 
     @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);
             }
diff --git a/voip/java/android/net/sip/SipErrorCode.java b/voip/java/android/net/sip/SipErrorCode.java
new file mode 100644
index 0000000..2eb67e8
--- /dev/null
+++ b/voip/java/android/net/sip/SipErrorCode.java
@@ -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;
+}
diff --git a/voip/java/android/net/sip/SipRegistrationListener.java b/voip/java/android/net/sip/SipRegistrationListener.java
index 63faaf8..22488d7 100644
--- a/voip/java/android/net/sip/SipRegistrationListener.java
+++ b/voip/java/android/net/sip/SipRegistrationListener.java
@@ -40,9 +40,9 @@
      * 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);
 }
diff --git a/voip/java/android/net/sip/SipSessionAdapter.java b/voip/java/android/net/sip/SipSessionAdapter.java
index 770d4eb..6020f2c 100644
--- a/voip/java/android/net/sip/SipSessionAdapter.java
+++ b/voip/java/android/net/sip/SipSessionAdapter.java
@@ -42,14 +42,11 @@
     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 void onRegistrationDone(ISipSession session, int duration) {
     }
 
-    public void onRegistrationFailed(ISipSession session, String className,
+    public void onRegistrationFailed(ISipSession session, String errorCode,
             String message) {
     }