Adding disconnect cause and DISCONNECTED state to Calls

Used for allowing UI to display UI when a call disconnects

Change-Id: I352d0be5a7be3237328b310e1c3d2c587f488d22
diff --git a/common/src/com/android/services/telephony/common/Call.java b/common/src/com/android/services/telephony/common/Call.java
index 25ea085..470381d 100644
--- a/common/src/com/android/services/telephony/common/Call.java
+++ b/common/src/com/android/services/telephony/common/Call.java
@@ -54,6 +54,59 @@
 
         // An active phone call placed on hold.
         public static final int ONHOLD = 6;
+
+        // State after a call disconnects.
+        public static final int DISCONNECTED = 7;
+    }
+
+    /**
+     * Copy of states found in Connection object since Connection object is not available to the UI
+     * code.
+     * TODO: Consider cutting this down to only the types used by the UI.
+     * TODO: Consider adding a CUSTOM cause type and a customDisconnect member variable to
+     *       the Call object.  This would allow OEMs to extend the cause list without
+     *       needing to alter our implementation.
+     */
+    public enum DisconnectCause {
+        NOT_DISCONNECTED,               /* has not yet disconnected */
+        INCOMING_MISSED,                /* an incoming call that was missed and never answered */
+        NORMAL,                         /* normal; remote */
+        LOCAL,                          /* normal; local hangup */
+        BUSY,                           /* outgoing call to busy line */
+        CONGESTION,                     /* outgoing call to congested network */
+        MMI,                            /* not presently used; dial() returns null */
+        INVALID_NUMBER,                 /* invalid dial string */
+        NUMBER_UNREACHABLE,             /* cannot reach the peer */
+        SERVER_UNREACHABLE,             /* cannot reach the server */
+        INVALID_CREDENTIALS,            /* invalid credentials */
+        OUT_OF_NETWORK,                 /* calling from out of network is not allowed */
+        SERVER_ERROR,                   /* server error */
+        TIMED_OUT,                      /* client timed out */
+        LOST_SIGNAL,
+        LIMIT_EXCEEDED,                 /* eg GSM ACM limit exceeded */
+        INCOMING_REJECTED,              /* an incoming call that was rejected */
+        POWER_OFF,                      /* radio is turned off explicitly */
+        OUT_OF_SERVICE,                 /* out of service */
+        ICC_ERROR,                      /* No ICC, ICC locked, or other ICC error */
+        CALL_BARRED,                    /* call was blocked by call barring */
+        FDN_BLOCKED,                    /* call was blocked by fixed dial number */
+        CS_RESTRICTED,                  /* call was blocked by restricted all voice access */
+        CS_RESTRICTED_NORMAL,           /* call was blocked by restricted normal voice access */
+        CS_RESTRICTED_EMERGENCY,        /* call was blocked by restricted emergency voice access */
+        UNOBTAINABLE_NUMBER,            /* Unassigned number (3GPP TS 24.008 table 10.5.123) */
+        CDMA_LOCKED_UNTIL_POWER_CYCLE,  /* MS is locked until next power cycle */
+        CDMA_DROP,
+        CDMA_INTERCEPT,                 /* INTERCEPT order received, MS state idle entered */
+        CDMA_REORDER,                   /* MS has been redirected, call is cancelled */
+        CDMA_SO_REJECT,                 /* service option rejection */
+        CDMA_RETRY_ORDER,               /* requested service is rejected, retry delay is set */
+        CDMA_ACCESS_FAILURE,
+        CDMA_PREEMPTED,
+        CDMA_NOT_EMERGENCY,              /* not an emergency call */
+        CDMA_ACCESS_BLOCKED,            /* Access Blocked by CDMA network */
+        ERROR_UNSPECIFIED,
+
+        UNKNOWN                         /* Disconnect cause doesn't map to any above */
     }
 
     private static final Map<Integer, String> STATE_MAP = ImmutableMap.<Integer, String>builder()
@@ -64,6 +117,7 @@
             .put(Call.State.INCOMING, "INCOMING")
             .put(Call.State.ONHOLD, "ONHOLD")
             .put(Call.State.INVALID, "INVALID")
+            .put(Call.State.DISCONNECTED, "DISCONNECTED")
             .build();
 
     // Number presentation type for caller id display
@@ -82,6 +136,7 @@
     private int mNumberPresentation = PRESENTATION_ALLOWED;
     private int mCnapNamePresentation = PRESENTATION_ALLOWED;
     private String mCnapName = "";
+    private DisconnectCause mDisconnectCause;
 
     public Call(int callId) {
         mCallId = callId;
@@ -131,6 +186,18 @@
         mCnapName = cnapName;
     }
 
+    public DisconnectCause getDisconnectCause() {
+        if (mState == State.DISCONNECTED || mState == State.IDLE) {
+            return mDisconnectCause;
+        }
+
+        return DisconnectCause.NOT_DISCONNECTED;
+    }
+
+    public void setDisconnectCause(DisconnectCause cause) {
+        mDisconnectCause = cause;
+    }
+
     /**
      * Parcelable implementation
      */
@@ -143,6 +210,7 @@
         dest.writeInt(mNumberPresentation);
         dest.writeInt(mCnapNamePresentation);
         dest.writeString(mCnapName);
+        dest.writeString(getDisconnectCause().toString());
     }
 
     @Override
@@ -169,6 +237,7 @@
         mNumberPresentation = in.readInt();
         mCnapNamePresentation = in.readInt();
         mCnapName = in.readString();
+        mDisconnectCause = DisconnectCause.valueOf(in.readString());
     }
 
     @Override
@@ -178,6 +247,8 @@
         buffer.append(mCallId);
         buffer.append(", state: ");
         buffer.append(STATE_MAP.get(mState));
+        buffer.append(", disconnect_cause: ");
+        buffer.append(getDisconnectCause().toString());
         return buffer.toString();
     }
 }
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9255cf6..651f2e2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -43,54 +43,6 @@
     <!-- In-call screen: status label for a call that's on hold -->
     <string name="onHold">On hold</string>
     <!-- Possible error messages with outgoing calls --><skip/>
-    <!-- In-call screen: call failure reason (busy) -->
-    <string name="callFailed_userBusy">Line busy</string>
-    <!-- In-call screen: call failure reason (network congestion) -->
-    <string name="callFailed_congestion">Network busy</string>
-    <!-- In-call screen: call failure reason (client timed out) -->
-    <string name="callFailed_timedOut">No response, timed out</string>
-    <!-- In-call screen: call failure reason (server unreachable) -->
-    <string name="callFailed_server_unreachable">Server unreachable</string>
-    <!-- In-call screen: call failure reason (peer unreachable) -->
-    <string name="callFailed_number_unreachable">Number unreachable</string>
-    <!-- In-call screen: call failure reason (incorrect username or password) -->
-    <string name="callFailed_invalid_credentials">Incorrect username or password</string>
-    <!-- In-call screen: call failure reason (calling from out of network is not allowed) -->
-    <string name="callFailed_out_of_network">Called from out-of-network</string>
-    <!-- In-call screen: call failure reason (server error) -->
-    <string name="callFailed_server_error">Server error. Try again later.</string>
-    <!-- In-call screen: call failure reason (no signal) -->
-    <string name="callFailed_noSignal">No signal</string>
-    <!-- In-call screen: call failure reason (GSM ACM limit exceeded) -->
-    <string name="callFailed_limitExceeded">ACM limit exceeded</string>
-    <!-- In-call screen: call failure reason (radio is off) -->
-    <string name="callFailed_powerOff">Radio off</string>
-    <!-- In-call screen: call failure reason (SIM error) -->
-    <string name="callFailed_simError">No SIM or SIM error</string>
-    <!-- In-call screen: call failure reason (out of service) -->
-    <string name="callFailed_outOfService">Out of service area</string>
-    <!-- In-call screen: call failure reason (call denied because of current FDN setting) -->
-    <string name="callFailed_fdn_only">Outgoing calls are restricted by FDN.</string>
-    <!-- In-call screen: call failure reason (call denied because call barring is on) -->
-    <string name="callFailed_cb_enabled">You can\'t make outgoing calls while call barring is on.</string>
-    <!-- In-call screen: call failure reason (call denied because domain specific access control is on) -->
-    <string name="callFailed_dsac_restricted">All calls are restricted by access control.</string>
-    <!-- In-call screen: call failure reason (Emergency call denied because domain specific access control is on)-->
-    <string name="callFailed_dsac_restricted_emergency">Emergency calls are restricted by access control.</string>
-    <!-- In-call screen: call failure reason (Normal call denied because domain specific access control is on)-->
-    <string name="callFailed_dsac_restricted_normal">Normal calls are restricted by access control.</string>
-    <!-- In-call screen: call failure reason (Dialed number doesn't exist) -->
-    <string name="callFailed_unobtainable_number">Invalid number</string>
-    <!-- In-call screen: status label for a conference call -->
-    <string name="confCall">Conference call</string>
-    <!-- In-call screen: call lost dialog text -->
-    <string name="call_lost">Call has been lost.</string>
-
-    <!-- Positive button label ("OK") used in several dialogs in the phone UI [CHAR LIMIT=10] -->
-    <string name="ok">OK</string>
-
-    <!-- MMI dialog strings -->
-    <!-- Dialog label when an MMI code starts running -->
     <string name="mmiStarted">MMI code started</string>
     <!-- Dialog label when a USSD code starts running -->
     <string name="ussdRunning">USSD code running\u2026</string>
diff --git a/src/com/android/phone/CallModeler.java b/src/com/android/phone/CallModeler.java
index f309fa0..7f0c5c5 100644
--- a/src/com/android/phone/CallModeler.java
+++ b/src/com/android/phone/CallModeler.java
@@ -19,10 +19,13 @@
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
 
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.internal.telephony.CallManager;
 import com.android.internal.telephony.Connection;
@@ -86,7 +89,7 @@
         mCallStateMonitor.addListener(this);
     }
 
-    @Override
+    //@Override
     public void handleMessage(Message msg) {
         switch(msg.what) {
             case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
@@ -143,6 +146,8 @@
     private void onNewRingingConnection(AsyncResult r) {
         final Connection conn = (Connection) r.result;
         final Call call = getCallFromConnection(conn, true);
+
+        updateCallFromConnection(call, conn);
         call.setState(Call.State.INCOMING);
 
         for (int i = 0; i < mListeners.size(); ++i) {
@@ -156,7 +161,9 @@
     private void onDisconnect(AsyncResult r) {
         final Connection conn = (Connection) r.result;
         final Call call = getCallFromConnection(conn, false);
-        call.setState(Call.State.IDLE);
+
+        updateCallFromConnection(call, conn);
+        call.setState(Call.State.DISCONNECTED);
 
         if (call != null) {
             mCallMap.remove(conn);
@@ -193,22 +200,71 @@
         // Cycle through all the Connections on all the Calls. Update our Call objects
         // to reflect any new state and send the updated Call objects to the handler service.
         for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
-            final int state = translateStateFromTelephony(telephonyCall.getState());
 
             for (Connection connection : telephonyCall.getConnections()) {
                 // new connections return a Call with INVALID state, which does not translate to
-                // a state in the Connection object.  This ensures that staleness check below
-                // fails and we always add the item to the update list if it is new.
+                // a state in the internal.telephony.Call object.  This ensures that staleness
+                // check below fails and we always add the item to the update list if it is new.
                 final Call call = getCallFromConnection(connection, true);
 
-                if (fullUpdate || call.getState() != state) {
-                    call.setState(state);
+                boolean changed = updateCallFromConnection(call, connection);
+
+                if (fullUpdate || changed) {
                     out.add(call);
                 }
             }
         }
     }
 
+    /**
+     * Updates the Call properties to match the state of the connection object
+     * that it represents.
+     */
+    private boolean updateCallFromConnection(Call call, Connection connection) {
+        boolean changed = false;
+
+        com.android.internal.telephony.Call telephonyCall = connection.getCall();
+        final int newState = translateStateFromTelephony(telephonyCall.getState());
+
+        if (call.getState() != newState) {
+            call.setState(newState);
+            changed = true;
+        }
+
+        final String oldNumber = call.getNumber();
+        if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(connection.getAddress())) {
+            call.setNumber(connection.getAddress());
+            changed = true;
+        }
+
+        final Call.DisconnectCause newDisconnectCause =
+                translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
+        if (call.getDisconnectCause() != newDisconnectCause) {
+            call.setDisconnectCause(newDisconnectCause);
+            changed = true;
+        }
+
+        final int newNumberPresentation = connection.getNumberPresentation();
+        if (call.getNumberPresentation() != newNumberPresentation) {
+            call.setNumberPresentation(newNumberPresentation);
+            changed = true;
+        }
+
+        final int newCnapNamePresentation = connection.getCnapNamePresentation();
+        if (call.getCnapNamePresentation() != newCnapNamePresentation) {
+            call.setCnapNamePresentation(newCnapNamePresentation);
+            changed = true;
+        }
+
+        final String oldCnapName = call.getCnapName();
+        if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
+            call.setCnapName(connection.getCnapName());
+            changed = true;
+        }
+
+        return changed;
+    }
+
     private int translateStateFromTelephony(com.android.internal.telephony.Call.State teleState) {
         int retval = State.IDLE;
         switch (teleState) {
@@ -228,15 +284,86 @@
             case HOLDING:
                 retval = State.ONHOLD;
                 break;
+            case DISCONNECTED:
+            case DISCONNECTING:
+                retval = State.DISCONNECTED;
             default:
         }
 
         return retval;
     }
 
+    private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
+            ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
+                .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
+                .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
+                .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
+                        Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
+                .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
+                        Call.DisconnectCause.CDMA_ACCESS_FAILURE)
+                .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
+                .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
+                .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
+                        Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
+                .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
+                        Call.DisconnectCause.CDMA_NOT_EMERGENCY)
+                .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
+                .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
+                .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
+                        Call.DisconnectCause.CDMA_RETRY_ORDER)
+                .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
+                .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
+                .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
+                .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
+                        Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
+                .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
+                        Call.DisconnectCause.CS_RESTRICTED_NORMAL)
+                .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
+                        Call.DisconnectCause.ERROR_UNSPECIFIED)
+                .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
+                .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
+                .put(Connection.DisconnectCause.INCOMING_MISSED,
+                        Call.DisconnectCause.INCOMING_MISSED)
+                .put(Connection.DisconnectCause.INCOMING_REJECTED,
+                        Call.DisconnectCause.INCOMING_REJECTED)
+                .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
+                        Call.DisconnectCause.INVALID_CREDENTIALS)
+                .put(Connection.DisconnectCause.INVALID_NUMBER,
+                        Call.DisconnectCause.INVALID_NUMBER)
+                .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
+                .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
+                .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
+                .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
+                .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
+                .put(Connection.DisconnectCause.NOT_DISCONNECTED,
+                        Call.DisconnectCause.NOT_DISCONNECTED)
+                .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
+                        Call.DisconnectCause.NUMBER_UNREACHABLE)
+                .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
+                .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
+                .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
+                .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
+                .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
+                        Call.DisconnectCause.SERVER_UNREACHABLE)
+                .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
+                .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
+                        Call.DisconnectCause.UNOBTAINABLE_NUMBER)
+                .build();
+
+    private Call.DisconnectCause translateDisconnectCauseFromTelephony(
+            Connection.DisconnectCause causeSource) {
+
+        if (CAUSE_MAP.containsKey(causeSource)) {
+            return CAUSE_MAP.get(causeSource);
+        }
+
+        return Call.DisconnectCause.UNKNOWN;
+    }
+
     /**
-     * Gets an existing callId for a connection, or creates one
-     * if none exists.
+     * Gets an existing callId for a connection, or creates one if none exists.
+     * This function does NOT set any of the Connection data onto the Call class.
+     * A separate call to updateCallFromConnection must be made for that purpose.
      */
     private Call getCallFromConnection(Connection conn, boolean createIfMissing) {
         Call call = null;
@@ -262,10 +389,7 @@
                         mCallMap.containsValue(callId));
 
                 call = new Call(callId);
-                call.setNumber(conn.getAddress());
-                call.setNumberPresentation(conn.getNumberPresentation());
-                call.setCnapNamePresentation(conn.getCnapNamePresentation());
-                call.setCnapName(conn.getCnapName());
+
                 mCallMap.put(conn, call);
             }
         }