Redial on the other slot upon radio disconnect

The radio now supports the DIALED_ON_WRONG_SLOT DisconnectCause for
emergency calls. If the radio needs the emergency call to be redialed on
another slot, it will send this DisconnectCause.

This change adds the functionality to support the new DisconnectCause
and perform the redial silently in Telephony.

Bug: 31498841
Change-Id: I6a8fa79fe33c04a00c457e33e3eeb182c1a6a821
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index bcc22fd..3e2eb65 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -128,6 +128,7 @@
             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:
                 return DisconnectCause.ERROR;
 
             case android.telephony.DisconnectCause.DIALED_MMI:
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index ac117b3..365f99e 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -33,6 +33,7 @@
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.CarrierConfigManager;
+import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.util.Pair;
@@ -254,6 +255,7 @@
      */
     public abstract static class TelephonyConnectionListener {
         public void onOriginalConnectionConfigured(TelephonyConnection c) {}
+        public void onOriginalConnectionRetry(TelephonyConnection c) {}
     }
 
     private final PostDialListener mPostDialListener = new PostDialListener() {
@@ -1266,10 +1268,19 @@
                     setRinging();
                     break;
                 case DISCONNECTED:
-                    setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
-                            mOriginalConnection.getDisconnectCause(),
-                            mOriginalConnection.getVendorDisconnectCause()));
-                    close();
+                    // 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();
+                    } else {
+                        setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
+                                mOriginalConnection.getDisconnectCause(),
+                                mOriginalConnection.getVendorDisconnectCause()));
+                        close();
+                    }
                     break;
                 case DISCONNECTING:
                     break;
@@ -1647,6 +1658,12 @@
         }
     }
 
+    private final void fireOnOriginalConnectionRetryDial() {
+        for (TelephonyConnectionListener l : mTelephonyListeners) {
+            l.onOriginalConnectionRetry(this);
+        }
+    }
+
     /**
      * Handles exiting ECM mode.
      */
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index c26090e..5a4c727 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -37,6 +37,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
@@ -52,7 +53,9 @@
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.regex.Pattern;
@@ -77,6 +80,13 @@
     private EmergencyCallHelper mEmergencyCallHelper;
     private EmergencyTonePlayer mEmergencyTonePlayer;
 
+    // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has
+    // already tried to connect with. There should be only one TelephonyConnection trying to place a
+    // call at one time. We also only access this cache from a TelephonyConnection that wishes to
+    // redial, so we use a WeakReference that will become stale once the TelephonyConnection is
+    // destroyed.
+    private Pair<WeakReference<TelephonyConnection>, List<Phone>> mEmergencyRetryCache;
+
     /**
      * A listener to actionable events specific to the TelephonyConnection.
      */
@@ -86,6 +96,11 @@
         public void onOriginalConnectionConfigured(TelephonyConnection c) {
             addConnectionToConferenceController(c);
         }
+
+        @Override
+        public void onOriginalConnectionRetry(TelephonyConnection c) {
+            retryOutgoingOriginalConnection(c);
+        }
     };
 
     @Override
@@ -617,14 +632,78 @@
         return result;
     }
 
+    private Pair<WeakReference<TelephonyConnection>, List<Phone>> makeCachedConnectionPhonePair(
+            TelephonyConnection c) {
+        List<Phone> phones = new ArrayList<>(Arrays.asList(PhoneFactory.getPhones()));
+        return new Pair<>(new WeakReference<>(c), phones);
+    }
+
+    // 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) {
+        if (mEmergencyRetryCache == null) {
+            Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache");
+            mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
+        } else {
+            // Check to see if old cache is stale. If it is, replace it
+            WeakReference<TelephonyConnection> cachedConnection = mEmergencyRetryCache.first;
+            if (cachedConnection.get() != c) {
+                Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating.");
+                mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
+            }
+        }
+    }
+
+    /**
+     * 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());
+        if (newPhoneToUse != null) {
+            int videoState = c.getVideoState();
+            Bundle connExtras = c.getExtras();
+            Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse);
+            c.clearOriginalConnection();
+            placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras);
+        } else {
+            // We have run out of Phones to use. Disconnect the call and destroy the connection.
+            Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting.");
+            c.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
+            c.clearOriginalConnection();
+            c.destroy();
+        }
+    }
+
     private void placeOutgoingConnection(
             TelephonyConnection connection, Phone phone, ConnectionRequest request) {
+        placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras());
+    }
+
+    private void placeOutgoingConnection(
+            TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
         String number = connection.getAddress().getSchemeSpecificPart();
 
-        com.android.internal.telephony.Connection originalConnection;
+        com.android.internal.telephony.Connection originalConnection = null;
         try {
-            originalConnection =
-                    phone.dial(number, null, request.getVideoState(), request.getExtras());
+            if (phone != null) {
+                originalConnection = phone.dial(number, null, videoState, extras);
+            }
         } catch (CallStateException e) {
             Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
             int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE;