Merge changes from topics "msim_tests_fixes", "redial_emer_phone_account" am: 8823dd0a36 am: 552799c7f9
am: 258efa1829

Change-Id: I9d01ea6f88b9c7205d2f512bc9eb6ca6c18823ee
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 50bb096..ded2468 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -61,8 +61,9 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Objects;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Queue;
 import java.util.regex.Pattern;
 
 /**
@@ -127,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.
@@ -888,57 +890,63 @@
         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!
+    // 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);
-            } 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);
-                }
-            }
+        // 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);
         }
     }
 
-    private void retryOutgoingOriginalConnection(
-            TelephonyConnection c, boolean isPermanentFailure) {
-        int phoneId = c.getPhone() == null ? -1 : c.getPhone().getPhoneId();
+    /**
+     * 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.
+     */
+    @VisibleForTesting
+    public 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;
+        // 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();
@@ -957,10 +965,9 @@
 
     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.
+        // 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);
     }
diff --git a/tests/src/com/android/TelephonyTestBase.java b/tests/src/com/android/TelephonyTestBase.java
index edf575b..7b7bef2 100644
--- a/tests/src/com/android/TelephonyTestBase.java
+++ b/tests/src/com/android/TelephonyTestBase.java
@@ -40,6 +40,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;
+    }
 }