Merge changes from topics "msim_tests_fixes", "redial_emer_phone_account"
am: 8823dd0a36
Change-Id: I42951ff00a5c58697714b26414669f6882a74940
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;
+ }
}