SIP: enhance timeout and registration status feedback.

http://b/issue?id=2984419
http://b/issue?id=2991065

Change-Id: I2d3b1dd3a70079ff347f7256f4684aea07847f4e
diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java
index eee97c3..c3786f5 100644
--- a/services/java/com/android/server/sip/SipService.java
+++ b/services/java/com/android/server/sip/SipService.java
@@ -27,6 +27,7 @@
 import android.net.sip.ISipService;
 import android.net.sip.ISipSession;
 import android.net.sip.ISipSessionListener;
+import android.net.sip.SipErrorCode;
 import android.net.sip.SipManager;
 import android.net.sip.SipProfile;
 import android.net.sip.SipSessionAdapter;
@@ -528,6 +529,8 @@
         private int mBackoff = 1;
         private boolean mRegistered;
         private long mExpiryTime;
+        private SipErrorCode mErrorCode;
+        private String mErrorMessage;
 
         private String getAction() {
             return toString();
@@ -551,15 +554,25 @@
         }
 
         public void stop() {
+            stop(false);
+        }
+
+        private void stopButKeepStates() {
+            stop(true);
+        }
+
+        private void stop(boolean keepStates) {
             if (mSession == null) return;
-            if (mConnected) mSession.unregister();
+            if (mConnected && mRegistered) mSession.unregister();
             mTimer.cancel(this);
             if (mKeepAliveProcess != null) {
                 mKeepAliveProcess.stop();
                 mKeepAliveProcess = null;
             }
-            mSession = null;
-            mRegistered = false;
+            if (!keepStates) {
+                mSession = null;
+                mRegistered = false;
+            }
         }
 
         private boolean isStopped() {
@@ -568,20 +581,33 @@
 
         public void setListener(ISipSessionListener listener) {
             Log.v(TAG, "setListener(): " + listener);
-            mProxy.setListener(listener);
-            if (mSession == null) return;
+            synchronized (SipService.this) {
+                mProxy.setListener(listener);
+                if (mSession == null) return;
 
-            try {
-                if ((mSession != null) && SipSessionState.REGISTERING.equals(
-                        mSession.getState())) {
-                    mProxy.onRegistering(mSession);
-                } else if (mRegistered) {
-                    int duration = (int)
-                            (mExpiryTime - SystemClock.elapsedRealtime());
-                    mProxy.onRegistrationDone(mSession, duration);
+                try {
+                    SipSessionState state = (mSession == null)
+                            ? SipSessionState.READY_TO_CALL
+                            : Enum.valueOf(
+                                    SipSessionState.class, mSession.getState());
+                    if ((state == SipSessionState.REGISTERING)
+                            || (state == SipSessionState.DEREGISTERING)) {
+                        mProxy.onRegistering(mSession);
+                    } else if (mRegistered) {
+                        int duration = (int)
+                                (mExpiryTime - SystemClock.elapsedRealtime());
+                        mProxy.onRegistrationDone(mSession, duration);
+                    } else if (mErrorCode != null) {
+                        if (mErrorCode == SipErrorCode.TIME_OUT) {
+                            mProxy.onRegistrationTimeout(mSession);
+                        } else {
+                            mProxy.onRegistrationFailed(mSession,
+                                    mErrorCode.toString(), mErrorMessage);
+                        }
+                    }
+                } catch (Throwable t) {
+                    Log.w(TAG, "setListener(): " + t);
                 }
-            } catch (Throwable t) {
-                Log.w(TAG, "setListener(): " + t);
             }
         }
 
@@ -590,6 +616,8 @@
         }
 
         public void run() {
+            mErrorCode = null;
+            mErrorMessage = null;
             Log.v(TAG, "  ~~~ registering");
             synchronized (SipService.this) {
                 if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME);
@@ -634,11 +662,7 @@
             synchronized (SipService.this) {
                 if (!isStopped() && (session != mSession)) return;
                 mRegistered = false;
-                try {
-                    mProxy.onRegistering(session);
-                } catch (Throwable t) {
-                    Log.w(TAG, "onRegistering()", t);
-                }
+                mProxy.onRegistering(session);
             }
         }
 
@@ -647,11 +671,9 @@
             Log.v(TAG, "onRegistrationDone(): " + session + ": " + mSession);
             synchronized (SipService.this) {
                 if (!isStopped() && (session != mSession)) return;
-                try {
-                    mProxy.onRegistrationDone(session, duration);
-                } catch (Throwable t) {
-                    Log.w(TAG, "onRegistrationDone()", t);
-                }
+
+                mProxy.onRegistrationDone(session, duration);
+
                 if (isStopped()) return;
 
                 if (duration > 0) {
@@ -687,19 +709,25 @@
         }
 
         @Override
-        public void onRegistrationFailed(ISipSession session, String className,
-                String message) {
+        public void onRegistrationFailed(ISipSession session,
+                String errorCodeString, String message) {
+            SipErrorCode errorCode =
+                    Enum.valueOf(SipErrorCode.class, errorCodeString);
             Log.v(TAG, "onRegistrationFailed(): " + session + ": " + mSession
-                    + ": " + className + ": " + message);
+                    + ": " + errorCode + ": " + message);
             synchronized (SipService.this) {
                 if (!isStopped() && (session != mSession)) return;
-                try {
-                    mProxy.onRegistrationFailed(session, className, message);
-                } catch (Throwable t) {
-                    Log.w(TAG, "onRegistrationFailed(): " + t);
-                }
+                mErrorCode = errorCode;
+                mErrorMessage = message;
+                mProxy.onRegistrationFailed(session, errorCode.toString(),
+                        message);
 
-                if (!isStopped()) onError();
+                if (errorCode == SipErrorCode.INVALID_CREDENTIALS) {
+                    Log.d(TAG, "   pause auto-registration");
+                    stopButKeepStates();
+                } else if (!isStopped()) {
+                    onError();
+                }
             }
         }
 
@@ -708,11 +736,8 @@
             Log.v(TAG, "onRegistrationTimeout(): " + session + ": " + mSession);
             synchronized (SipService.this) {
                 if (!isStopped() && (session != mSession)) return;
-                try {
-                    mProxy.onRegistrationTimeout(session);
-                } catch (Throwable t) {
-                    Log.w(TAG, "onRegistrationTimeout(): " + t);
-                }
+                mErrorCode = SipErrorCode.TIME_OUT;
+                mProxy.onRegistrationTimeout(session);
 
                 if (!isStopped()) {
                     mRegistered = false;
diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java
index 2f7ddc4..da8e9b8 100644
--- a/services/java/com/android/server/sip/SipSessionGroup.java
+++ b/services/java/com/android/server/sip/SipSessionGroup.java
@@ -412,13 +412,7 @@
                             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)) {
-                                onRegistrationFailed(e);
-                            } else {
-                                onError(e);
-                            }
+                            onError(e);
                         }
                     }
             }).start();
@@ -480,7 +474,9 @@
 
         private void processCommand(EventObject command) throws SipException {
             if (!process(command)) {
-                throw new SipException("wrong state to execute: " + command);
+                onError(SipErrorCode.IN_PROGRESS,
+                        "cannot initiate a new transaction to execute: "
+                        + command);
             }
         }
 
@@ -562,11 +558,8 @@
                 if (evt instanceof TimeoutEvent) {
                     processTimeout((TimeoutEvent) evt);
                 } else {
-                    Log.d(TAG, "Transaction terminated:" + this);
-                    if (!SipSessionState.IN_CALL.equals(mState)) {
-                        removeSipSession(this);
-                    }
-                    return true;
+                    processTransactionTerminated(
+                            (TransactionTerminatedEvent) evt);
                 }
                 return true;
             } else if (evt instanceof DialogTerminatedEvent) {
@@ -585,6 +578,20 @@
             }
         }
 
+        private void processTransactionTerminated(
+                TransactionTerminatedEvent event) {
+            switch (mState) {
+                case IN_CALL:
+                case READY_TO_CALL:
+                    Log.d(TAG, "Transaction terminated; do nothing");
+                    break;
+                default:
+                    Log.d(TAG, "Transaction terminated early: " + this);
+                    onError(SipErrorCode.TRANSACTION_TERMINTED,
+                            "transaction terminated");
+            }
+        }
+
         private void processTimeout(TimeoutEvent event) {
             Log.d(TAG, "processing Timeout..." + event);
             Transaction current = event.isServerTransaction()
@@ -600,25 +607,26 @@
                 return;
             }
             switch (mState) {
-            case REGISTERING:
-            case DEREGISTERING:
-                reset();
-                mProxy.onRegistrationTimeout(this);
-                break;
-            case INCOMING_CALL:
-            case INCOMING_CALL_ANSWERING:
-            case OUTGOING_CALL_CANCELING:
-                endCallOnError(SipErrorCode.TIME_OUT, event.toString());
-                break;
-            case PINGING:
-                reset();
-                mReRegisterFlag = true;
-                mState = SipSessionState.READY_TO_CALL;
-                break;
+                case REGISTERING:
+                case DEREGISTERING:
+                    reset();
+                    mProxy.onRegistrationTimeout(this);
+                    break;
+                case INCOMING_CALL:
+                case INCOMING_CALL_ANSWERING:
+                case OUTGOING_CALL:
+                case OUTGOING_CALL_CANCELING:
+                    onError(SipErrorCode.TIME_OUT, event.toString());
+                    break;
+                case PINGING:
+                    reset();
+                    mReRegisterFlag = true;
+                    mState = SipSessionState.READY_TO_CALL;
+                    break;
 
-            default:
-                // do nothing
-                break;
+                default:
+                    Log.d(TAG, "   do nothing");
+                    break;
             }
         }
 
@@ -665,7 +673,7 @@
                 } else {
                     Log.w(TAG, "peer did not respect our rport request");
                 }
-                mState = SipSessionState.READY_TO_CALL;
+                reset();
                 return true;
             }
             return false;
@@ -687,7 +695,6 @@
                 switch (statusCode) {
                 case Response.OK:
                     SipSessionState state = mState;
-                    reset();
                     onRegistrationDone((state == SipSessionState.REGISTERING)
                             ? getExpiryTime(((ResponseEvent) evt).getResponse())
                             : -1);
@@ -698,15 +705,13 @@
                 case Response.PROXY_AUTHENTICATION_REQUIRED:
                     if (!handleAuthentication(event)) {
                         Log.v(TAG, "Incorrect username/password");
-                        reset();
                         onRegistrationFailed(SipErrorCode.INVALID_CREDENTIALS,
                                 "incorrect username or password");
                     }
                     return true;
                 default:
                     if (statusCode >= 500) {
-                        reset();
-                        onRegistrationFailed(createCallbackException(response));
+                        onRegistrationFailed(response);
                         return true;
                     }
                 }
@@ -720,7 +725,6 @@
             String nonce = getNonceFromResponse(response);
             if (((nonce != null) && nonce.equals(mLastNonce)) ||
                     (nonce == mLastNonce)) {
-                Log.v(TAG, "Incorrect username/password");
                 return false;
             } else {
                 mClientTransaction = mSipHelper.handleChallenge(
@@ -906,7 +910,7 @@
                 }
 
                 if (statusCode >= 400) {
-                    onError(createCallbackException(response));
+                    onError(response);
                     return true;
                 }
             } else if (evt instanceof TransactionTerminatedEvent) {
@@ -954,10 +958,6 @@
                     response.getReasonPhrase(), response.getStatusCode());
         }
 
-        private Exception createCallbackException(Response response) {
-            return new SipException(createErrorMessage(response));
-        }
-
         private void establishCall() {
             mState = SipSessionState.IN_CALL;
             mInCall = true;
@@ -965,22 +965,22 @@
         }
 
         private void fallbackToPreviousInCall(Throwable exception) {
-            mState = SipSessionState.IN_CALL;
             exception = getRootCause(exception);
-            mProxy.onCallChangeFailed(this, getErrorCode(exception).toString(),
+            fallbackToPreviousInCall(getErrorCode(exception),
                     exception.toString());
         }
 
+        private void fallbackToPreviousInCall(SipErrorCode errorCode,
+                String message) {
+            mState = SipSessionState.IN_CALL;
+            mProxy.onCallChangeFailed(this, errorCode.toString(), message);
+        }
+
         private void endCallNormally() {
             reset();
             mProxy.onCallEnded(this);
         }
 
-        private void endCallOnError(Throwable exception) {
-            exception = getRootCause(exception);
-            endCallOnError(getErrorCode(exception), exception.toString());
-        }
-
         private void endCallOnError(SipErrorCode errorCode, String message) {
             reset();
             mProxy.onError(this, errorCode.toString(), message);
@@ -991,26 +991,34 @@
             mProxy.onCallBusy(this);
         }
 
-        private void onError(Throwable exception) {
-            if (mInCall) {
-                fallbackToPreviousInCall(exception);
-            } else {
-                endCallOnError(exception);
+        private void onError(SipErrorCode errorCode, String message) {
+            switch (mState) {
+                case REGISTERING:
+                case DEREGISTERING:
+                    onRegistrationFailed(errorCode, message);
+                    break;
+                default:
+                    if (mInCall) {
+                        fallbackToPreviousInCall(errorCode, message);
+                    } else {
+                        endCallOnError(errorCode, message);
+                    }
             }
         }
 
+
+        private void onError(Throwable exception) {
+            exception = getRootCause(exception);
+            onError(getErrorCode(exception), exception.toString());
+        }
+
         private void onError(Response response) {
-            if (mInCall) {
-                fallbackToPreviousInCall(createCallbackException(response));
+            int statusCode = response.getStatusCode();
+            if (!mInCall && ((statusCode == Response.TEMPORARILY_UNAVAILABLE)
+                    || (statusCode == Response.BUSY_HERE))) {
+                endCallOnBusy();
             } else {
-                int statusCode = response.getStatusCode();
-                if ((statusCode == Response.TEMPORARILY_UNAVAILABLE)
-                        || (statusCode == Response.BUSY_HERE)) {
-                    endCallOnBusy();
-                } else {
-                    endCallOnError(getErrorCode(statusCode),
-                            createErrorMessage(response));
-                }
+                onError(getErrorCode(statusCode), createErrorMessage(response));
             }
         }
 
@@ -1053,19 +1061,29 @@
         }
 
         private void onRegistrationDone(int duration) {
+            reset();
             mProxy.onRegistrationDone(this, duration);
         }
 
         private void onRegistrationFailed(SipErrorCode errorCode,
                 String message) {
+            reset();
             mProxy.onRegistrationFailed(this, errorCode.toString(), message);
         }
 
         private void onRegistrationFailed(Throwable exception) {
+            reset();
             exception = getRootCause(exception);
             onRegistrationFailed(getErrorCode(exception),
                     exception.toString());
         }
+
+        private void onRegistrationFailed(Response response) {
+            reset();
+            int statusCode = response.getStatusCode();
+            onRegistrationFailed(getErrorCode(statusCode),
+                    createErrorMessage(response));
+        }
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/Connection.java b/telephony/java/com/android/internal/telephony/Connection.java
index e5456e4..c20c200 100644
--- a/telephony/java/com/android/internal/telephony/Connection.java
+++ b/telephony/java/com/android/internal/telephony/Connection.java
@@ -40,6 +40,7 @@
         MMI,                            /* not presently used; dial() returns null */
         INVALID_NUMBER,                 /* invalid dial string */
         INVALID_CREDENTIALS,            /* invalid credentials */
+        TIMED_OUT,                      /* client timed out */
         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 fd90610..2c99145 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -826,7 +826,8 @@
                     onError(Connection.DisconnectCause.INVALID_NUMBER);
                     break;
                 case TIME_OUT:
-                    onError(Connection.DisconnectCause.CONGESTION);
+                case TRANSACTION_TERMINTED:
+                    onError(Connection.DisconnectCause.TIMED_OUT);
                     break;
                 case INVALID_CREDENTIALS:
                     onError(Connection.DisconnectCause.INVALID_CREDENTIALS);
diff --git a/voip/java/android/net/sip/SipErrorCode.java b/voip/java/android/net/sip/SipErrorCode.java
index 2eb67e8..8624811 100644
--- a/voip/java/android/net/sip/SipErrorCode.java
+++ b/voip/java/android/net/sip/SipErrorCode.java
@@ -31,6 +31,9 @@
     /** When server responds with an error. */
     SERVER_ERROR,
 
+    /** When transaction is terminated unexpectedly. */
+    TRANSACTION_TERMINTED,
+
     /** When some error occurs on the device, possibly due to a bug. */
     CLIENT_ERROR,
 
@@ -41,5 +44,8 @@
     INVALID_REMOTE_URI,
 
     /** When invalid credentials are provided. */
-    INVALID_CREDENTIALS;
+    INVALID_CREDENTIALS,
+
+    /** The client is in a transaction and cannot initiate a new one. */
+    IN_PROGRESS;
 }