Merge changes from topics "msim_tests_fixes", "redial_emer_phone_account"

* changes:
  Adds tests for MSIM redial and fixes bug
  Emergency call redial implementation
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..ded2468 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -61,7 +61,9 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Queue;
 import java.util.regex.Pattern;
 
 /**
@@ -126,7 +128,8 @@
     // 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;
+    @VisibleForTesting
+    public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache;
 
     /**
      * Keeps track of the status of a SIM slot.
@@ -238,8 +241,8 @@
         }
 
         @Override
-        public void onOriginalConnectionRetry(TelephonyConnection c) {
-            retryOutgoingOriginalConnection(c);
+        public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) {
+            retryOutgoingOriginalConnection(c, isPermanentFailure);
         }
     };
 
@@ -887,54 +890,69 @@
         return result;
     }
 
-    private Pair<WeakReference<TelephonyConnection>, List<Phone>> makeCachedConnectionPhonePair(
+    private Pair<WeakReference<TelephonyConnection>, Queue<Phone>> makeCachedConnectionPhonePair(
             TelephonyConnection c) {
-        List<Phone> phones = new ArrayList<>(Arrays.asList(mPhoneFactoryProxy.getPhones()));
+        Queue<Phone> phones = new LinkedList<>(Arrays.asList(mPhoneFactoryProxy.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) {
+    // Update the mEmergencyRetryCache by removing the Phone used to call the last failed emergency
+    // number and then moving it to the back of the queue if it is not a permanent failure cause
+    // from the modem.
+    private void updateCachedConnectionPhonePair(TelephonyConnection c,
+            boolean isPermanentFailure) {
+        // No cache exists, create a new one.
         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);
-            }
+        // Cache is stale, create a new one with the new TelephonyConnection.
+        } else if (mEmergencyRetryCache.first.get() != c) {
+            Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating.");
+            mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
+        }
+
+        Queue<Phone> cachedPhones = mEmergencyRetryCache.second;
+        Phone phoneUsed = c.getPhone();
+        if (phoneUsed == null) {
+            return;
+        }
+        // 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(phoneUsed);
+        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.offer(phoneUsed);
         }
     }
 
     /**
-     * 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.
+     * Updates a cache containing all of the slots that are available for redial at any point.
+     *
+     * - If a Connection returns with the disconnect cause EMERGENCY_TEMP_FAILURE, keep that phone
+     * in the cache, but move it to the lowest priority in the list. Then, place the emergency call
+     * on the next phone in the list.
+     * - If a Connection returns with the disconnect cause EMERGENCY_PERM_FAILURE, remove that phone
+     * from the cache and pull another phone from the cache to place the emergency call.
+     *
+     * This will continue until there are no more slots to dial on.
      */
-    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());
+    @VisibleForTesting
+    public void retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure) {
+        int phoneId = (c.getPhone() == null) ? -1 : c.getPhone().getPhoneId();
+        updateCachedConnectionPhonePair(c, isPermanentFailure);
+        // Pull next phone to use from the cache or null if it is empty
+        Phone newPhoneToUse = (mEmergencyRetryCache.second != null)
+                ? mEmergencyRetryCache.second.peek() : 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 +963,15 @@
         }
     }
 
+    private void updatePhoneAccount(TelephonyConnection connection, Phone phone) {
+        PhoneAccountHandle pHandle = PhoneUtils.makePstnPhoneAccountHandle(phone);
+        // For ECall handling on MSIM, until the request reaches here (i.e PhoneApp), we don't know
+        // on which phone account ECall can be placed. After deciding, we should notify Telecom of
+        // the change so that the proper PhoneAccount can be displayed.
+        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());
diff --git a/tests/src/com/android/TelephonyTestBase.java b/tests/src/com/android/TelephonyTestBase.java
index 044f26b..a61ffe9 100644
--- a/tests/src/com/android/TelephonyTestBase.java
+++ b/tests/src/com/android/TelephonyTestBase.java
@@ -39,6 +39,14 @@
         // Set up the looper if it does not exist on the test thread.
         if (Looper.myLooper() == null) {
             Looper.prepare();
+            // Wait until the looper is not null anymore
+            for(int i = 0; i < 5; i++) {
+                if (Looper.myLooper() != null) {
+                    break;
+                }
+                Looper.prepare();
+                Thread.sleep(100);
+            }
         }
     }
 
diff --git a/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java b/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
index 3d88af7..229bdee 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
@@ -34,7 +34,6 @@
 import org.junit.Test;
 
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 /**
@@ -48,8 +47,8 @@
 
     private TelecomAccountRegistry mTelecomAccountRegistry;
 
-    private MockTelephonyConnection mMockTelephonyConnectionA;
-    private MockTelephonyConnection mMockTelephonyConnectionB;
+    private TestTelephonyConnection mTestTelephonyConnectionA;
+    private TestTelephonyConnection mTestTelephonyConnectionB;
 
     private ImsConferenceController mControllerTest;
 
@@ -60,8 +59,8 @@
             Looper.prepare();
         }
         mTelecomAccountRegistry = TelecomAccountRegistry.getInstance(null);
-        mMockTelephonyConnectionA = new MockTelephonyConnection();
-        mMockTelephonyConnectionB = new MockTelephonyConnection();
+        mTestTelephonyConnectionA = new TestTelephonyConnection();
+        mTestTelephonyConnectionB = new TestTelephonyConnection();
 
         mControllerTest = new ImsConferenceController(mTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy);
@@ -80,25 +79,25 @@
     @SmallTest
     public void testConferenceable() {
 
-        mControllerTest.add(mMockTelephonyConnectionB);
-        mControllerTest.add(mMockTelephonyConnectionA);
+        mControllerTest.add(mTestTelephonyConnectionB);
+        mControllerTest.add(mTestTelephonyConnectionA);
 
-        mMockTelephonyConnectionA.setActive();
-        mMockTelephonyConnectionB.setOnHold();
+        mTestTelephonyConnectionA.setActive();
+        mTestTelephonyConnectionB.setOnHold();
 
-        assertTrue(mMockTelephonyConnectionA.getConferenceables()
-                .contains(mMockTelephonyConnectionB));
-        assertTrue(mMockTelephonyConnectionB.getConferenceables()
-                .contains(mMockTelephonyConnectionA));
+        assertTrue(mTestTelephonyConnectionA.getConferenceables()
+                .contains(mTestTelephonyConnectionB));
+        assertTrue(mTestTelephonyConnectionB.getConferenceables()
+                .contains(mTestTelephonyConnectionA));
 
         // verify addConference method is never called
         verify(mMockTelephonyConnectionServiceProxy, never())
                 .addConference(any(ImsConference.class));
 
         // call A removed
-        mControllerTest.remove(mMockTelephonyConnectionA);
-        assertFalse(mMockTelephonyConnectionB.getConferenceables()
-                .contains(mMockTelephonyConnectionA));
+        mControllerTest.remove(mTestTelephonyConnectionA);
+        assertFalse(mTestTelephonyConnectionB.getConferenceables()
+                .contains(mTestTelephonyConnectionA));
     }
 
     /**
@@ -114,18 +113,18 @@
     @SmallTest
     public void testMergeMultiPartyCalls() {
 
-        when(mMockTelephonyConnectionA.mMockRadioConnection.getPhoneType())
+        when(mTestTelephonyConnectionA.mMockRadioConnection.getPhoneType())
                 .thenReturn(PhoneConstants.PHONE_TYPE_IMS);
-        when(mMockTelephonyConnectionB.mMockRadioConnection.getPhoneType())
+        when(mTestTelephonyConnectionB.mMockRadioConnection.getPhoneType())
                 .thenReturn(PhoneConstants.PHONE_TYPE_IMS);
-        when(mMockTelephonyConnectionA.mMockRadioConnection.isMultiparty()).thenReturn(true);
-        when(mMockTelephonyConnectionB.mMockRadioConnection.isMultiparty()).thenReturn(true);
+        when(mTestTelephonyConnectionA.mMockRadioConnection.isMultiparty()).thenReturn(true);
+        when(mTestTelephonyConnectionB.mMockRadioConnection.isMultiparty()).thenReturn(true);
 
-        mControllerTest.add(mMockTelephonyConnectionB);
-        mControllerTest.add(mMockTelephonyConnectionA);
+        mControllerTest.add(mTestTelephonyConnectionB);
+        mControllerTest.add(mTestTelephonyConnectionA);
 
-        mMockTelephonyConnectionA.setActive();
-        mMockTelephonyConnectionB.setOnHold();
+        mTestTelephonyConnectionA.setActive();
+        mTestTelephonyConnectionB.setOnHold();
 
         verify(mMockTelephonyConnectionServiceProxy, times(2))
                 .addConference(any(ImsConference.class));
diff --git a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
index 739359a..275bcc6 100644
--- a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
@@ -25,7 +25,6 @@
 import org.junit.Test;
 
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.ArgumentCaptor;
 
@@ -53,8 +52,8 @@
     @Mock
     private Conference.Listener mMockListener;
 
-    private MockTelephonyConnection mMockTelephonyConnectionA;
-    private MockTelephonyConnection mMockTelephonyConnectionB;
+    private TestTelephonyConnection mTestTelephonyConnectionA;
+    private TestTelephonyConnection mTestTelephonyConnectionB;
 
     private TelephonyConferenceController mControllerTest;
 
@@ -64,8 +63,8 @@
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
-        mMockTelephonyConnectionA = new MockTelephonyConnection();
-        mMockTelephonyConnectionB = new MockTelephonyConnection();
+        mTestTelephonyConnectionA = new TestTelephonyConnection();
+        mTestTelephonyConnectionB = new TestTelephonyConnection();
 
         mControllerTest = new TelephonyConferenceController(mMockTelephonyConnectionServiceProxy);
     }
@@ -84,33 +83,33 @@
     @SmallTest
     public void testConferenceable() {
 
-        when(mMockTelephonyConnectionA.mMockRadioConnection.getCall()
+        when(mTestTelephonyConnectionA.mMockRadioConnection.getCall()
                 .isMultiparty()).thenReturn(false);
-        when(mMockTelephonyConnectionB.mMockRadioConnection.getCall()
+        when(mTestTelephonyConnectionB.mMockRadioConnection.getCall()
                 .isMultiparty()).thenReturn(false);
 
         // add telephony connection B
-        mControllerTest.add(mMockTelephonyConnectionB);
+        mControllerTest.add(mTestTelephonyConnectionB);
 
         // add telephony connection A
-        mControllerTest.add(mMockTelephonyConnectionA);
+        mControllerTest.add(mTestTelephonyConnectionA);
 
-        mMockTelephonyConnectionA.setActive();
-        mMockTelephonyConnectionB.setOnHold();
+        mTestTelephonyConnectionA.setActive();
+        mTestTelephonyConnectionB.setOnHold();
 
-        assertTrue(mMockTelephonyConnectionA.getConferenceables()
-                .contains(mMockTelephonyConnectionB));
-        assertTrue(mMockTelephonyConnectionB.getConferenceables()
-                .contains(mMockTelephonyConnectionA));
+        assertTrue(mTestTelephonyConnectionA.getConferenceables()
+                .contains(mTestTelephonyConnectionB));
+        assertTrue(mTestTelephonyConnectionB.getConferenceables()
+                .contains(mTestTelephonyConnectionA));
 
         // verify addConference method is never called
         verify(mMockTelephonyConnectionServiceProxy, never())
                 .addConference(any(TelephonyConference.class));
 
         // call A removed
-        mControllerTest.remove(mMockTelephonyConnectionA);
-        assertFalse(mMockTelephonyConnectionB.getConferenceables()
-                .contains(mMockTelephonyConnectionA));
+        mControllerTest.remove(mTestTelephonyConnectionA);
+        assertFalse(mTestTelephonyConnectionB.getConferenceables()
+                .contains(mTestTelephonyConnectionA));
     }
 
     /**
@@ -129,31 +128,31 @@
     public void testMergeMultiPartyCalls() {
 
         // set isMultiparty() true to create the same senario of merge behaviour
-        when(mMockTelephonyConnectionA.mMockRadioConnection.getCall()
+        when(mTestTelephonyConnectionA.mMockRadioConnection.getCall()
                 .isMultiparty()).thenReturn(true);
-        when(mMockTelephonyConnectionB.mMockRadioConnection.getCall()
+        when(mTestTelephonyConnectionB.mMockRadioConnection.getCall()
                 .isMultiparty()).thenReturn(true);
 
         // Add connections into connection Service
         Collection<Connection> allConnections = new ArrayList<Connection>();
-        allConnections.add(mMockTelephonyConnectionA);
-        allConnections.add(mMockTelephonyConnectionB);
+        allConnections.add(mTestTelephonyConnectionA);
+        allConnections.add(mTestTelephonyConnectionB);
         when(mMockTelephonyConnectionServiceProxy.getAllConnections())
                 .thenReturn(allConnections);
 
         // add telephony connection B
-        mControllerTest.add(mMockTelephonyConnectionB);
+        mControllerTest.add(mTestTelephonyConnectionB);
 
         // add telephony connection A
-        mControllerTest.add(mMockTelephonyConnectionA);
+        mControllerTest.add(mTestTelephonyConnectionA);
 
-        mMockTelephonyConnectionA.setActive();
-        mMockTelephonyConnectionB.setOnHold();
+        mTestTelephonyConnectionA.setActive();
+        mTestTelephonyConnectionB.setOnHold();
 
-        assertTrue(mMockTelephonyConnectionA.getConferenceables()
-                .contains(mMockTelephonyConnectionB));
-        assertTrue(mMockTelephonyConnectionB.getConferenceables()
-                .contains(mMockTelephonyConnectionA));
+        assertTrue(mTestTelephonyConnectionA.getConferenceables()
+                .contains(mTestTelephonyConnectionB));
+        assertTrue(mTestTelephonyConnectionB.getConferenceables()
+                .contains(mTestTelephonyConnectionA));
 
         // capture the argument in the addConference method, and verify it is called
         ArgumentCaptor<TelephonyConference> argumentCaptor = ArgumentCaptor.
@@ -166,9 +165,9 @@
         verify(mMockListener, never()).onDestroyed(any(Conference.class));
 
         // call A removed
-        mControllerTest.remove(mMockTelephonyConnectionA);
-        assertFalse(mMockTelephonyConnectionB.getConferenceables()
-                .contains(mMockTelephonyConnectionA));
+        mControllerTest.remove(mTestTelephonyConnectionA);
+        assertFalse(mTestTelephonyConnectionB.getConferenceables()
+                .contains(mTestTelephonyConnectionA));
 
         //onDestroy should be called during the destroy
         verify(mMockListener).onDestroyed(any(Conference.class));
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 45f74df..eb8c48a 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -16,13 +16,19 @@
 
 package com.android.services.telephony;
 
+import android.net.Uri;
+import android.telecom.DisconnectCause;
+import android.telecom.TelecomManager;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
+import android.support.test.filters.FlakyTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.TelephonyTestBase;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
 
 import org.junit.After;
@@ -31,9 +37,20 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -491,6 +508,250 @@
         assertEquals(slot0Phone, resultPhone);
     }
 
+    /**
+     * The modem has returned a temporary error when placing an emergency call on a phone with one
+     * SIM slot.
+     *
+     * Verify that dial is called on the same phone again when retryOutgoingOriginalConnection is
+     * called.
+     */
+    @Test
+    @FlakyTest
+    @SmallTest
+    public void testRetryOutgoingOriginalConnection_redialTempFailOneSlot() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        Phone slot0Phone = c.getPhone();
+        when(slot0Phone.getPhoneId()).thenReturn(SLOT_0_PHONE_ID);
+        List<Phone> phones = new ArrayList<>(1);
+        phones.add(slot0Phone);
+        setPhones(phones);
+        c.setAddress(Uri.parse("tel:+16505551212"), TelecomManager.PRESENTATION_ALLOWED);
+
+        mTestConnectionService.retryOutgoingOriginalConnection(c, false /*isPermanentFailure*/);
+
+        // We never need to be notified in telecom that the PhoneAccount has changed, because it
+        // was redialed on the same slot
+        assertEquals(0, c.getNotifyPhoneAccountChangedCount());
+        try {
+            verify(slot0Phone).dial(anyString(), any(), anyInt(), any());
+        } catch (CallStateException e) {
+            // This shouldn't happen
+            fail();
+        }
+    }
+
+    /**
+     * The modem has returned a permanent failure when placing an emergency call on a phone with one
+     * SIM slot.
+     *
+     * Verify that the connection is set to disconnected with an error disconnect cause and dial is
+     * not called.
+     */
+    @Test
+    @FlakyTest
+    @SmallTest
+    public void testRetryOutgoingOriginalConnection_redialPermFailOneSlot() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        Phone slot0Phone = c.getPhone();
+        when(slot0Phone.getPhoneId()).thenReturn(SLOT_0_PHONE_ID);
+        List<Phone> phones = new ArrayList<>(1);
+        phones.add(slot0Phone);
+        setPhones(phones);
+        c.setAddress(Uri.parse("tel:+16505551212"), TelecomManager.PRESENTATION_ALLOWED);
+
+        mTestConnectionService.retryOutgoingOriginalConnection(c, true /*isPermanentFailure*/);
+
+        // We never need to be notified in telecom that the PhoneAccount has changed, because it
+        // was never redialed
+        assertEquals(0, c.getNotifyPhoneAccountChangedCount());
+        try {
+            verify(slot0Phone, never()).dial(anyString(), any(), anyInt(), any());
+        } catch (CallStateException e) {
+            // This shouldn't happen
+            fail();
+        }
+        assertEquals(c.getState(), android.telecom.Connection.STATE_DISCONNECTED);
+        assertEquals(c.getDisconnectCause().getCode(), DisconnectCause.ERROR);
+    }
+
+    /**
+     * The modem has returned a temporary failure when placing an emergency call on a phone with two
+     * SIM slots.
+     *
+     * Verify that the emergency call is dialed on the other slot and telecom is notified of the new
+     * PhoneAccount.
+     */
+    @Test
+    @FlakyTest
+    @SmallTest
+    public void testRetryOutgoingOriginalConnection_redialTempFailTwoSlot() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        Phone slot0Phone = c.getPhone();
+        when(slot0Phone.getPhoneId()).thenReturn(SLOT_0_PHONE_ID);
+        Phone slot1Phone = makeTestPhone(SLOT_1_PHONE_ID, ServiceState.STATE_OUT_OF_SERVICE,
+                false /*isEmergencyOnly*/);
+        setPhonesDialConnection(slot1Phone, c.getOriginalConnection());
+        c.setAddress(Uri.parse("tel:+16505551212"), TelecomManager.PRESENTATION_ALLOWED);
+        List<Phone> phones = new ArrayList<>(2);
+        phones.add(slot0Phone);
+        phones.add(slot1Phone);
+        setPhones(phones);
+
+        mTestConnectionService.retryOutgoingOriginalConnection(c, false /*isPermanentFailure*/);
+
+        // The cache should still contain all of the Phones, since it was a temporary failure.
+        assertEquals(2, mTestConnectionService.mEmergencyRetryCache.second.size());
+        // We need to be notified in Telecom that the PhoneAccount has changed, because it was
+        // redialed on another slot
+        assertEquals(1, c.getNotifyPhoneAccountChangedCount());
+        try {
+            verify(slot1Phone).dial(anyString(), any(), anyInt(), any());
+        } catch (CallStateException e) {
+            // This shouldn't happen
+            fail();
+        }
+    }
+
+    /**
+     * The modem has returned a temporary failure when placing an emergency call on a phone with two
+     * SIM slots.
+     *
+     * Verify that the emergency call is dialed on the other slot and telecom is notified of the new
+     * PhoneAccount.
+     */
+    @Test
+    @FlakyTest
+    @SmallTest
+    public void testRetryOutgoingOriginalConnection_redialPermFailTwoSlot() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        Phone slot0Phone = c.getPhone();
+        when(slot0Phone.getPhoneId()).thenReturn(SLOT_0_PHONE_ID);
+        Phone slot1Phone = makeTestPhone(SLOT_1_PHONE_ID, ServiceState.STATE_OUT_OF_SERVICE,
+                false /*isEmergencyOnly*/);
+        setPhonesDialConnection(slot1Phone, c.getOriginalConnection());
+        c.setAddress(Uri.parse("tel:+16505551212"), TelecomManager.PRESENTATION_ALLOWED);
+        List<Phone> phones = new ArrayList<>(2);
+        phones.add(slot0Phone);
+        phones.add(slot1Phone);
+        setPhones(phones);
+
+        mTestConnectionService.retryOutgoingOriginalConnection(c, true /*isPermanentFailure*/);
+
+        // The cache should only contain the slot1Phone.
+        assertEquals(1, mTestConnectionService.mEmergencyRetryCache.second.size());
+        // We need to be notified in Telecom that the PhoneAccount has changed, because it was
+        // redialed on another slot
+        assertEquals(1, c.getNotifyPhoneAccountChangedCount());
+        try {
+            verify(slot1Phone).dial(anyString(), any(), anyInt(), any());
+        } catch (CallStateException e) {
+            // This shouldn't happen
+            fail();
+        }
+    }
+
+    /**
+     * The modem has returned a temporary failure twice while placing an emergency call on a phone
+     * with two SIM slots.
+     *
+     * Verify that the emergency call is dialed on slot 1 and then on slot 0 and telecom is
+     * notified of this twice.
+     */
+    @Test
+    @FlakyTest
+    @SmallTest
+    public void testRetryOutgoingOriginalConnection_redialTempFailTwoSlot_twoFailure() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        Phone slot0Phone = c.getPhone();
+        when(slot0Phone.getPhoneId()).thenReturn(SLOT_0_PHONE_ID);
+        Phone slot1Phone = makeTestPhone(SLOT_1_PHONE_ID, ServiceState.STATE_OUT_OF_SERVICE,
+                false /*isEmergencyOnly*/);
+        setPhonesDialConnection(slot1Phone, c.getOriginalConnection());
+        c.setAddress(Uri.parse("tel:+16505551212"), TelecomManager.PRESENTATION_ALLOWED);
+        List<Phone> phones = new ArrayList<>(2);
+        phones.add(slot0Phone);
+        phones.add(slot1Phone);
+        setPhones(phones);
+
+        // First Temporary failure
+        mTestConnectionService.retryOutgoingOriginalConnection(c, false /*isPermanentFailure*/);
+        // Set the Phone to the new phone that was just used to dial.
+        c.setMockPhone(slot1Phone);
+        // The cache should still contain all of the Phones, since it was a temporary failure.
+        assertEquals(2, mTestConnectionService.mEmergencyRetryCache.second.size());
+        // Make sure slot 1 is next in the queue.
+        assertEquals(slot1Phone, mTestConnectionService.mEmergencyRetryCache.second.peek());
+        // Second Temporary failure
+        mTestConnectionService.retryOutgoingOriginalConnection(c, false /*isPermanentFailure*/);
+        // Set the Phone to the new phone that was just used to dial.
+        c.setMockPhone(slot0Phone);
+        // The cache should still contain all of the Phones, since it was a temporary failure.
+        assertEquals(2, mTestConnectionService.mEmergencyRetryCache.second.size());
+        // Make sure slot 0 is next in the queue.
+        assertEquals(slot0Phone, mTestConnectionService.mEmergencyRetryCache.second.peek());
+
+        // We need to be notified in Telecom that the PhoneAccount has changed, because it was
+        // redialed on another slot
+        assertEquals(2, c.getNotifyPhoneAccountChangedCount());
+        try {
+            verify(slot0Phone).dial(anyString(), any(), anyInt(), any());
+            verify(slot1Phone).dial(anyString(), any(), anyInt(), any());
+        } catch (CallStateException e) {
+            // This shouldn't happen
+            fail();
+        }
+    }
+
+    /**
+     * The modem has returned a permanent failure twice while placing an emergency call on a phone
+     * with two SIM slots.
+     *
+     * Verify that the emergency call is dialed on slot 1 and then disconnected and telecom is
+     * notified of the change to slot 1.
+     */
+    @Test
+    @FlakyTest
+    @SmallTest
+    public void testRetryOutgoingOriginalConnection_redialPermFailTwoSlot_twoFailure() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        Phone slot0Phone = c.getPhone();
+        when(slot0Phone.getPhoneId()).thenReturn(SLOT_0_PHONE_ID);
+        Phone slot1Phone = makeTestPhone(SLOT_1_PHONE_ID, ServiceState.STATE_OUT_OF_SERVICE,
+                false /*isEmergencyOnly*/);
+        setPhonesDialConnection(slot1Phone, c.getOriginalConnection());
+        c.setAddress(Uri.parse("tel:+16505551212"), TelecomManager.PRESENTATION_ALLOWED);
+        List<Phone> phones = new ArrayList<>(2);
+        phones.add(slot0Phone);
+        phones.add(slot1Phone);
+        setPhones(phones);
+
+        // First Permanent failure
+        mTestConnectionService.retryOutgoingOriginalConnection(c, true /*isPermanentFailure*/);
+        // Set the Phone to the new phone that was just used to dial.
+        c.setMockPhone(slot1Phone);
+        // The cache should only contain one phone
+        assertEquals(1, mTestConnectionService.mEmergencyRetryCache.second.size());
+        // Make sure slot 1 is next in the queue.
+        assertEquals(slot1Phone, mTestConnectionService.mEmergencyRetryCache.second.peek());
+        // Second Permanent failure
+        mTestConnectionService.retryOutgoingOriginalConnection(c, true /*isPermanentFailure*/);
+        // The cache should be empty
+        assertEquals(true, mTestConnectionService.mEmergencyRetryCache.second.isEmpty());
+
+        assertEquals(c.getState(), android.telecom.Connection.STATE_DISCONNECTED);
+        assertEquals(c.getDisconnectCause().getCode(), DisconnectCause.ERROR);
+        // We need to be notified in Telecom that the PhoneAccount has changed, because it was
+        // redialed on another slot
+        assertEquals(1, c.getNotifyPhoneAccountChangedCount());
+        try {
+            verify(slot1Phone).dial(anyString(), any(), anyInt(), any());
+            verify(slot0Phone, never()).dial(anyString(), any(), anyInt(), any());
+        } catch (CallStateException e) {
+            // This shouldn't happen
+            fail();
+        }
+    }
+
     private Phone makeTestPhone(int phoneId, int serviceState, boolean isEmergencyOnly) {
         Phone phone = mock(Phone.class);
         ServiceState testServiceState = new ServiceState();
@@ -524,4 +785,17 @@
     private void setDefaultPhone(Phone phone) {
         when(mPhoneFactoryProxy.getDefaultPhone()).thenReturn(phone);
     }
+
+    private void setPhones(List<Phone> phones) {
+        when(mPhoneFactoryProxy.getPhones()).thenReturn(phones.toArray(new Phone[phones.size()]));
+    }
+
+    private void setPhonesDialConnection(Phone phone, Connection c) {
+        try {
+            when(phone.dial(anyString(), anyInt())).thenReturn(c);
+        } catch (CallStateException e) {
+            // this shouldn't happen
+            fail();
+        }
+    }
 }
diff --git a/tests/src/com/android/services/telephony/MockTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
similarity index 73%
rename from tests/src/com/android/services/telephony/MockTelephonyConnection.java
rename to tests/src/com/android/services/telephony/TestTelephonyConnection.java
index 634cbb5..ea0f965 100644
--- a/tests/src/com/android/services/telephony/MockTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -11,11 +11,14 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.services.telephony;
 
+import android.telecom.PhoneAccountHandle;
+
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import com.android.internal.telephony.Call;
@@ -28,7 +31,7 @@
  * Mock Telephony Connection used in TelephonyConferenceController.java for testing purpose
  */
 
-public class MockTelephonyConnection extends TelephonyConnection {
+public class TestTelephonyConnection extends TelephonyConnection {
 
     @Mock
     com.android.internal.telephony.Connection mMockRadioConnection;
@@ -36,18 +39,19 @@
     @Mock
     Call mMockCall;
 
-    @Mock
-    Phone mMockPhone;
+    private Phone mMockPhone;
+    private int mNotifyPhoneAccountChangedCount = 0;
 
     @Override
     public com.android.internal.telephony.Connection getOriginalConnection() {
         return mMockRadioConnection;
     }
 
-    public MockTelephonyConnection() {
+    public TestTelephonyConnection() {
         super(null, null, false);
         MockitoAnnotations.initMocks(this);
 
+        mMockPhone = mock(Phone.class);
         // Set up mMockRadioConnection and mMockPhone to contain an active call
         when(mMockRadioConnection.getState()).thenReturn(Call.State.ACTIVE);
         when(mMockRadioConnection.getCall()).thenReturn(mMockCall);
@@ -60,6 +64,10 @@
         return true;
     }
 
+    public void setMockPhone(Phone newPhone) {
+        mMockPhone = newPhone;
+    }
+
     @Override
     public Phone getPhone() {
         return mMockPhone;
@@ -69,4 +77,12 @@
         return this;
     }
 
+    @Override
+    public void notifyPhoneAccountChanged(PhoneAccountHandle pHandle) {
+        mNotifyPhoneAccountChangedCount++;
+    }
+
+    public int getNotifyPhoneAccountChangedCount() {
+        return mNotifyPhoneAccountChangedCount;
+    }
 }