Emergency call redial implementation

Handle EMERGENCY_PERM_FAILURE and
EMERGENCY_TEMP_FAILURE DisconnectCause
for redial Emergency call.

Bug: 27059146
Change-Id: Ie72ab2901ec05d972204ed11f115a05b79173c1d
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index 26690b9..a420100 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -128,7 +128,6 @@
             case android.telephony.DisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED:
             case android.telephony.DisconnectCause.DATA_DISABLED:
             case android.telephony.DisconnectCause.DATA_LIMIT_REACHED:
-            case android.telephony.DisconnectCause.DIALED_ON_WRONG_SLOT:
             case android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING:
             case android.telephony.DisconnectCause.IMEI_NOT_ACCEPTED:
             case android.telephony.DisconnectCause.WIFI_LOST:
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 064d1f1..6a14e88 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -94,6 +94,7 @@
     private static final int MSG_ON_HOLD_TONE = 14;
     private static final int MSG_CDMA_VOICE_PRIVACY_ON = 15;
     private static final int MSG_CDMA_VOICE_PRIVACY_OFF = 16;
+    private static final int MSG_HANGUP = 17;
 
     private final Handler mHandler = new Handler() {
         @Override
@@ -240,6 +241,10 @@
                     Log.d(this, "MSG_CDMA_VOICE_PRIVACY_OFF received");
                     setCdmaVoicePrivacy(false);
                     break;
+                case MSG_HANGUP:
+                    int cause = (int) msg.obj;
+                    hangup(cause);
+                    break;
             }
         }
     };
@@ -258,7 +263,7 @@
      */
     public abstract static class TelephonyConnectionListener {
         public void onOriginalConnectionConfigured(TelephonyConnection c) {}
-        public void onOriginalConnectionRetry(TelephonyConnection c) {}
+        public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) {}
     }
 
     private final PostDialListener mPostDialListener = new PostDialListener() {
@@ -544,7 +549,7 @@
     @Override
     public void onDisconnect() {
         Log.v(this, "onDisconnect");
-        hangup(android.telephony.DisconnectCause.LOCAL);
+        mHandler.obtainMessage(MSG_HANGUP, android.telephony.DisconnectCause.LOCAL).sendToTarget();
     }
 
     /**
@@ -579,7 +584,7 @@
     @Override
     public void onAbort() {
         Log.v(this, "onAbort");
-        hangup(android.telephony.DisconnectCause.LOCAL);
+        mHandler.obtainMessage(MSG_HANGUP, android.telephony.DisconnectCause.LOCAL).sendToTarget();
     }
 
     @Override
@@ -608,7 +613,8 @@
     public void onReject() {
         Log.v(this, "onReject");
         if (isValidRingingCall()) {
-            hangup(android.telephony.DisconnectCause.INCOMING_REJECTED);
+            mHandler.obtainMessage(MSG_HANGUP, android.telephony.DisconnectCause.INCOMING_REJECTED)
+                    .sendToTarget();
         }
         super.onReject();
     }
@@ -1093,8 +1099,8 @@
         if (mOriginalConnection != null) {
             try {
                 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
-                // connection.hangup(). Without this change, the party originating the call will not
-                // get sent to voicemail if the user opts to reject the call.
+                // connection.hangup(). Without this change, the party originating the call
+                // will not get sent to voicemail if the user opts to reject the call.
                 if (isValidRingingCall()) {
                     Call call = getCall();
                     if (call != null) {
@@ -1103,10 +1109,10 @@
                         Log.w(this, "Attempting to hangup a connection without backing call.");
                     }
                 } else {
-                    // We still prefer to call connection.hangup() for non-ringing calls in order
-                    // to support hanging-up specific calls within a conference call. If we invoked
-                    // call.hangup() while in a conference, we would end up hanging up the entire
-                    // conference call instead of the specific connection.
+                    // We still prefer to call connection.hangup() for non-ringing calls
+                    // in order to support hanging-up specific calls within a conference call.
+                    // If we invoked call.hangup() while in a conference, we would end up
+                    // hanging up the entire conference call instead of the specific connection.
                     mOriginalConnection.hangup();
                 }
             } catch (CallStateException e) {
@@ -1297,6 +1303,7 @@
         } else {
             newState = mOriginalConnection.getState();
         }
+        int cause = mOriginalConnection.getDisconnectCause();
         Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this);
 
         if (mConnectionState != newState) {
@@ -1323,13 +1330,18 @@
                     setRinging();
                     break;
                 case DISCONNECTED:
-                    // We can get into a situation where the radio wants us to redial the same
-                    // emergency call on the other available slot. This will not set the state to
-                    // disconnected and will instead tell the TelephonyConnectionService to create
-                    // a new originalConnection using the new Slot.
-                    if (mOriginalConnection.getDisconnectCause() ==
-                            DisconnectCause.DIALED_ON_WRONG_SLOT) {
-                        fireOnOriginalConnectionRetryDial();
+                    if (shouldTreatAsEmergencyCall()
+                            && (cause
+                            == android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE
+                            || cause
+                            == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE)) {
+                        // We can get into a situation where the radio wants us to redial the
+                        // same emergency call on the other available slot. This will not set
+                        // the state to disconnected and will instead tell the
+                        // TelephonyConnectionService to
+                        // create a new originalConnection using the new Slot.
+                        fireOnOriginalConnectionRetryDial(cause
+                                == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE);
                     } else {
                         setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
                                 mOriginalConnection.getDisconnectCause(),
@@ -1728,9 +1740,9 @@
         }
     }
 
-    private final void fireOnOriginalConnectionRetryDial() {
+    private final void fireOnOriginalConnectionRetryDial(boolean isPermanentFailure) {
         for (TelephonyConnectionListener l : mTelephonyListeners) {
-            l.onOriginalConnectionRetry(this);
+            l.onOriginalConnectionRetry(this, isPermanentFailure);
         }
     }
 
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 53bfd68..50bb096 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -61,6 +61,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.List;
 import java.util.regex.Pattern;
 
@@ -238,8 +239,8 @@
         }
 
         @Override
-        public void onOriginalConnectionRetry(TelephonyConnection c) {
-            retryOutgoingOriginalConnection(c);
+        public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) {
+            retryOutgoingOriginalConnection(c, isPermanentFailure);
         }
     };
 
@@ -895,7 +896,8 @@
 
     // Check the mEmergencyRetryCache to see if it contains the TelephonyConnection. If it doesn't,
     // then it is stale. Create a new one!
-    private void updateCachedConnectionPhonePair(TelephonyConnection c) {
+    private void updateCachedConnectionPhonePair(TelephonyConnection c,
+            boolean isPermanentFailure) {
         if (mEmergencyRetryCache == null) {
             Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache");
             mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
@@ -905,36 +907,44 @@
             if (cachedConnection.get() != c) {
                 Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating.");
                 mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
+            } else {
+                List<Phone> cachedPhones = mEmergencyRetryCache.second;
+                Phone phoneUsed = c.getPhone();
+                if (phoneUsed == null) {
+                    return;
+                }
+                // c.getPhone() can return ImsPhone object also, But here we are interested in
+                // GsmCdmaPhone object, hence get corresponding GsmCdmaPhone object from
+                // PhoneFactory.
+                Phone PhoneToBeUpdated = PhoneFactory.getPhone(phoneUsed.getPhoneId());
+                // Remove phone used from the list, but for temporary fail cause, it will be added
+                // back to list further in this method. However in case of permanent failure, the
+                // phone shouldn't be reused, hence it will not be added back again.
+                cachedPhones.remove(PhoneToBeUpdated);
+                Log.i(this, "updateCachedConnectionPhonePair, isPermanentFailure:"
+                        + isPermanentFailure);
+                if (!isPermanentFailure) {
+                    // In case of temporary failure, add the phone back, this will result adding it
+                    // to tail of list mEmergencyRetryCache.second, giving other phone more
+                    // priority and that is what we want.
+                    cachedPhones.add(PhoneToBeUpdated);
+                }
             }
         }
     }
 
-    /**
-     * Returns the first Phone that has not been used yet to place the call. Any Phones that have
-     * been used to place a call will have already been removed from mEmergencyRetryCache.second.
-     * The phone that it excluded will be removed from mEmergencyRetryCache.second in this method.
-     * @param phoneToExclude The Phone object that will be removed from our cache of available
-     * phones.
-     * @return the first Phone that is available to be used to retry the call.
-     */
-    private Phone getPhoneForRedial(Phone phoneToExclude) {
-        List<Phone> cachedPhones = mEmergencyRetryCache.second;
-        if (cachedPhones.contains(phoneToExclude)) {
-            Log.i(this, "getPhoneForRedial, removing Phone[" + phoneToExclude.getPhoneId() +
-                    "] from the available Phone cache.");
-            cachedPhones.remove(phoneToExclude);
-        }
-        return cachedPhones.isEmpty() ? null : cachedPhones.get(0);
-    }
-
-    private void retryOutgoingOriginalConnection(TelephonyConnection c) {
-        updateCachedConnectionPhonePair(c);
-        Phone newPhoneToUse = getPhoneForRedial(c.getPhone());
+    private void retryOutgoingOriginalConnection(
+            TelephonyConnection c, boolean isPermanentFailure) {
+        int phoneId = c.getPhone() == null ? -1 : c.getPhone().getPhoneId();
+        updateCachedConnectionPhonePair(c, isPermanentFailure);
+        Phone newPhoneToUse = (mEmergencyRetryCache.second != null) ?
+                mEmergencyRetryCache.second.get(0) : null;
         if (newPhoneToUse != null) {
             int videoState = c.getVideoState();
             Bundle connExtras = c.getExtras();
             Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse);
             c.clearOriginalConnection();
+            if (phoneId != newPhoneToUse.getPhoneId()) updatePhoneAccount(c, newPhoneToUse);
             placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras);
         } else {
             // We have run out of Phones to use. Disconnect the call and destroy the connection.
@@ -945,6 +955,16 @@
         }
     }
 
+    private void updatePhoneAccount(TelephonyConnection connection, Phone phone) {
+        PhoneAccountHandle pHandle = PhoneUtils.makePstnPhoneAccountHandle(phone);
+        // For ECall handling on MSIM, till the request reaches here(i.e PhoneApp)
+        // we dont know on which phone account ECall can be placed, once after deciding
+        // the phone account for ECall we should inform Telecomm so that
+        // the proper sub information will be displayed on InCallUI.
+        Log.i(this, "updatePhoneAccount setPhoneAccountHandle, account = " + pHandle);
+        connection.notifyPhoneAccountChanged(pHandle);
+    }
+
     private void placeOutgoingConnection(
             TelephonyConnection connection, Phone phone, ConnectionRequest request) {
         placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras());