Merge "Add CrossSimRedialingController" into udc-dev
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index a1ba39f..7311113 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -2464,14 +2464,8 @@
Log.i(this, "maybeReselectDomainForEmergencyCall "
+ "csCause=" + callFailCause + ", psCause=" + reasonInfo);
- // EMERGENCY_TEMP_FAILURE and EMERGENCY_PERM_FAILURE shall be handled after
- // reselecting new {@link Phone} in {@link #retryOutgoingOriginalConnection()}.
if (c.getOriginalConnection() != null
&& c.getOriginalConnection().getDisconnectCause()
- != android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE
- && c.getOriginalConnection().getDisconnectCause()
- != android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE
- && c.getOriginalConnection().getDisconnectCause()
!= android.telephony.DisconnectCause.LOCAL
&& c.getOriginalConnection().getDisconnectCause()
!= android.telephony.DisconnectCause.POWER_OFF) {
diff --git a/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java b/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java
new file mode 100644
index 0000000..f1bb78c
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * 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.
+ */
+
+package com.android.services.telephony.domainselection;
+
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.REDIAL_TIMER_DISABLED;
+import static android.telephony.PreciseDisconnectCause.EMERGENCY_PERM_FAILURE;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.SystemProperties;
+import android.telephony.Annotation.PreciseDisconnectCauses;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+
+import java.util.ArrayList;
+
+/** Controls the cross stack redialing. */
+public class CrossSimRedialingController extends Handler {
+ private static final String TAG = "CrossSimRedialingCtrl";
+ private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ private static final int LOG_SIZE = 50;
+
+ /** An interface of a helper to check emergency number. */
+ public interface EmergencyNumberHelper {
+ /**
+ * Returns whether the number is an emergency number in the given modem slot.
+ *
+ * @param slotId The slot id to be checked.
+ * @param number The number.
+ * @return {@code true} if the number is an emergency number in the given slot.
+ */
+ boolean isEmergencyNumber(int slotId, String number);
+ }
+
+ @VisibleForTesting
+ public static final int MSG_CROSS_STACK_TIMEOUT = 1;
+ @VisibleForTesting
+ public static final int MSG_QUICK_CROSS_STACK_TIMEOUT = 2;
+
+ private static final LocalLog sLocalLog = new LocalLog(LOG_SIZE);
+
+ private final ArrayList<Integer> mStackSelectionHistory = new ArrayList<>();
+ private final ArrayList<Integer> mPermanentRejectedSlots = new ArrayList<>();
+ private final TelephonyManager mTelephonyManager;
+
+ private EmergencyNumberHelper mEmergencyNumberHelper = new EmergencyNumberHelper() {
+ @Override
+ public boolean isEmergencyNumber(int slotId, String number) {
+ // TODO(b/258112541) Add System api to check emergency number per subscription.
+ try {
+ Phone phone = PhoneFactory.getPhone(slotId);
+ if (phone != null
+ && phone.getEmergencyNumberTracker() != null
+ && phone.getEmergencyNumberTracker().isEmergencyNumber(number)) {
+ return true;
+ }
+ } catch (IllegalStateException e) {
+ loge("isEmergencyNumber e=" + e);
+ }
+ return false;
+ }
+ };
+
+ private int mModemCount;
+
+ /** A cache of the carrier config {@link #KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT}. */
+ private int mCrossStackTimer;
+ /** A cache of the carrier config {@link #KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT}. */
+ private int mQuickCrossStackTimer;
+ /**
+ * A cache of the carrier config
+ * {@link #KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL}.
+ */
+ private boolean mStartQuickCrossStackTimerWhenInService;
+
+ private String mCallId;
+ private EmergencyCallDomainSelector mSelector;
+ private String mNumber;
+ private int mSlotId;
+ private int mSubId;
+
+ /**
+ * Creates an instance.
+ *
+ * @param context The Context this is associated with.
+ * @param looper The Looper to run the CrossSimRedialingController.
+ */
+ public CrossSimRedialingController(@NonNull Context context, @NonNull Looper looper) {
+ super(looper);
+
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ }
+
+ /** For unit test only */
+ @VisibleForTesting
+ public CrossSimRedialingController(@NonNull Context context, @NonNull Looper looper,
+ EmergencyNumberHelper emergencyNumberHelper) {
+ this(context, looper);
+
+ mEmergencyNumberHelper = emergencyNumberHelper;
+ }
+
+ /**
+ * Starts the timer.
+ *
+ * @param context The Context this is associated with.
+ * @param selector The instance of {@link EmergencyCallDomainSelector}.
+ * @param callId The call identifier.
+ * @param number The dialing number.
+ * @param inService Indiates that normal service is available.
+ * @param roaming Indicates that it's in roaming or non-domestic network.
+ * @param modemCount The number of active modem count
+ */
+ public void startTimer(@NonNull Context context,
+ @NonNull EmergencyCallDomainSelector selector,
+ @NonNull String callId, @NonNull String number,
+ boolean inService, boolean roaming, int modemCount) {
+ logi("startTimer callId=" + callId
+ + ", in service=" + inService + ", roaming=" + roaming);
+
+ if (!TextUtils.equals(mCallId, callId)) {
+ logi("startTimer callId changed");
+ mCallId = callId;
+ mStackSelectionHistory.clear();
+ mPermanentRejectedSlots.clear();
+ }
+ mSelector = selector;
+ mSlotId = selector.getSlotId();
+ mSubId = selector.getSubId();
+ mNumber = number;
+ mModemCount = modemCount;
+
+ updateCarrierConfiguration(context);
+
+ boolean firstAttempt = !mStackSelectionHistory.contains(mSlotId);
+ logi("startTimer slot=" + mSlotId + ", firstAttempt=" + firstAttempt);
+ mStackSelectionHistory.add(mSlotId);
+
+ if (firstAttempt && mQuickCrossStackTimer > REDIAL_TIMER_DISABLED && !roaming) {
+ if (inService || !mStartQuickCrossStackTimerWhenInService) {
+ logi("startTimer quick timer started");
+ sendEmptyMessageDelayed(MSG_QUICK_CROSS_STACK_TIMEOUT,
+ mQuickCrossStackTimer);
+ return;
+ }
+ }
+
+ if (mCrossStackTimer > REDIAL_TIMER_DISABLED) {
+ logi("startTimer timer started");
+ sendEmptyMessageDelayed(MSG_CROSS_STACK_TIMEOUT, mCrossStackTimer);
+ }
+ }
+
+ /** Stops the timers. */
+ public void stopTimer() {
+ logi("stopTimer");
+ removeMessages(MSG_CROSS_STACK_TIMEOUT);
+ removeMessages(MSG_QUICK_CROSS_STACK_TIMEOUT);
+ }
+
+ /**
+ * Informs the call failure.
+ * @param cause The call failure cause.
+ */
+ public void notifyCallFailure(@PreciseDisconnectCauses int cause) {
+ logi("notifyCallFailure cause=" + cause);
+ if (cause == EMERGENCY_PERM_FAILURE) {
+ mPermanentRejectedSlots.add(mSlotId);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_CROSS_STACK_TIMEOUT:
+ case MSG_QUICK_CROSS_STACK_TIMEOUT:
+ handleCrossStackTimeout();
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+ private void handleCrossStackTimeout() {
+ logi("handleCrossStackTimeout");
+
+ if (isThereOtherSlot()) {
+ mSelector.notifyCrossStackTimerExpired();
+ }
+ }
+
+ /**
+ * Returns whether there is another slot emergency capable.
+ *
+ * @return {@code true} if there is another slot emergency capable,
+ * {@code false} otherwise.
+ */
+ public boolean isThereOtherSlot() {
+ logi("isThereOtherSlot modemCount=" + mModemCount);
+ if (mModemCount < 2) return false;
+
+ for (int i = 0; i < mModemCount; i++) {
+ if (i == mSlotId) continue;
+
+ if (mPermanentRejectedSlots.contains(i)) {
+ logi("isThereOtherSlot index=" + i + ", permanent rejected");
+ continue;
+ }
+
+ int simState = mTelephonyManager.getSimState(i);
+ if (simState != TelephonyManager.SIM_STATE_READY) {
+ logi("isThereOtherSlot index=" + i + ", simState=" + simState);
+ continue;
+ }
+
+ if (mEmergencyNumberHelper.isEmergencyNumber(i, mNumber)) {
+ logi("isThereOtherSlot index=" + i + ", found");
+ return true;
+ } else {
+ logi("isThereOtherSlot index=" + i + ", not emergency number");
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Caches the configuration.
+ */
+ private void updateCarrierConfiguration(Context context) {
+ CarrierConfigManager configMgr = context.getSystemService(CarrierConfigManager.class);
+ PersistableBundle b = configMgr.getConfigForSubId(mSubId,
+ KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT,
+ KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT,
+ KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL);
+ if (b == null) {
+ b = CarrierConfigManager.getDefaultConfig();
+ }
+
+ mCrossStackTimer = b.getInt(KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT) * 1000;
+ mQuickCrossStackTimer =
+ b.getInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT) * 1000;
+ mStartQuickCrossStackTimerWhenInService =
+ b.getBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL);
+
+ logi("updateCarrierConfiguration "
+ + ", crossStackTimer=" + mCrossStackTimer
+ + ", quickCrossStackTimer=" + mQuickCrossStackTimer
+ + ", startQuickTimerInService=" + mStartQuickCrossStackTimerWhenInService);
+ }
+
+ /** Destroys the instance. */
+ public void destroy() {
+ if (DBG) logd("destroy");
+
+ removeMessages(MSG_CROSS_STACK_TIMEOUT);
+ removeMessages(MSG_QUICK_CROSS_STACK_TIMEOUT);
+ }
+
+ private void logd(String s) {
+ Log.d(TAG, "[" + mSlotId + "|" + mSubId + "] " + s);
+ }
+
+ private void logi(String s) {
+ Log.i(TAG, "[" + mSlotId + "|" + mSubId + "] " + s);
+ sLocalLog.log(s);
+ }
+
+ private void loge(String s) {
+ Log.e(TAG, "[" + mSlotId + "|" + mSubId + "] " + s);
+ sLocalLog.log(s);
+ }
+}
diff --git a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
index 41b5612..b1c288c 100644
--- a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
@@ -50,6 +50,8 @@
import static android.telephony.CarrierConfigManager.ImsWfc.KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL;
import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+import static android.telephony.PreciseDisconnectCause.EMERGENCY_PERM_FAILURE;
+import static android.telephony.PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE;
import android.annotation.NonNull;
import android.content.Context;
@@ -194,6 +196,9 @@
private boolean mIsScanRequested = false;
/** Indicates whether selected domain has been notified. */
private boolean mDomainSelected = false;
+ /** Indicates whether the cross sim redialing timer has expired. */
+ private boolean mCrossStackTimerExpired = false;
+
/**
* Indicates whether {@link #selectDomain(SelectionAttributes, TransportSelectionCallback)}
* is called or not.
@@ -201,11 +206,13 @@
private boolean mDomainSelectionRequested = false;
private final PowerManager.WakeLock mPartialWakeLock;
+ private final CrossSimRedialingController mCrossSimRedialingController;
/** Constructor. */
public EmergencyCallDomainSelector(Context context, int slotId, int subId,
@NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
- @NonNull DestroyListener destroyListener) {
+ @NonNull DestroyListener destroyListener,
+ @NonNull CrossSimRedialingController csrController) {
super(context, slotId, subId, looper, imsStateTracker, destroyListener, TAG);
mImsStateTracker.addBarringInfoListener(this);
@@ -214,6 +221,7 @@
PowerManager pm = context.getSystemService(PowerManager.class);
mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mCrossSimRedialingController = csrController;
acquireWakeLock();
}
@@ -320,6 +328,23 @@
private void reselectDomain() {
logi("reselectDomain tryCsWhenPsFails=" + mTryCsWhenPsFails);
+ int cause = mSelectionAttributes.getCsDisconnectCause();
+ mCrossSimRedialingController.notifyCallFailure(cause);
+
+ // TODO(b/258112541) make EMERGENCY_PERM_FAILURE and EMERGENCY_TEMP_FAILURE public api
+ if (cause == EMERGENCY_PERM_FAILURE
+ || cause == EMERGENCY_TEMP_FAILURE) {
+ logi("reselectDomain should redial on the other subscription");
+ terminateSelectionForCrossSimRedialing(cause == EMERGENCY_PERM_FAILURE);
+ return;
+ }
+
+ if (mCrossStackTimerExpired) {
+ logi("reselectDomain cross stack timer expired");
+ terminateSelectionForCrossSimRedialing(false);
+ return;
+ }
+
if (mIsTestEmergencyNumber) {
selectDomainForTestEmergencyNumber();
return;
@@ -389,6 +414,7 @@
logi("startDomainSelection modemCount=" + mModemCount);
updateCarrierConfiguration();
mDomainSelectionRequested = true;
+ startCrossStackTimer();
if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
selectDomain();
} else {
@@ -1217,7 +1243,10 @@
int simState = tm.getSimState(getSlotId());
if (simState != TelephonyManager.SIM_STATE_READY) {
logi("allowEmergencyCalls not ready, simState=" + simState + ", iso=" + iso);
- return false;
+ if (mCrossSimRedialingController.isThereOtherSlot()) {
+ return false;
+ }
+ logi("allowEmergencyCalls there is no other slot available");
}
}
@@ -1226,7 +1255,18 @@
private void terminateSelectionPermanentlyForSlot() {
logi("terminateSelectionPermanentlyForSlot");
- mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.EMERGENCY_PERM_FAILURE);
+ terminateSelection(true);
+ }
+
+ private void terminateSelectionForCrossSimRedialing(boolean permanent) {
+ logi("terminateSelectionForCrossSimRedialing perm=" + permanent);
+ terminateSelection(permanent);
+ }
+
+ private void terminateSelection(boolean permanent) {
+ mTransportSelectorCallback.onSelectionTerminated(permanent
+ ? DisconnectCause.EMERGENCY_PERM_FAILURE
+ : DisconnectCause.EMERGENCY_TEMP_FAILURE);
if (mIsScanRequested && mCancelSignal != null) {
mCancelSignal.cancel();
@@ -1234,6 +1274,41 @@
}
}
+ /** Starts the cross stack timer. */
+ public void startCrossStackTimer() {
+ boolean inService = false;
+ boolean inRoaming = false;
+
+ if (mModemCount == 1) return;
+
+ EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ if (regResult != null) {
+ int regState = regResult.getRegState();
+
+ if ((regResult.getDomain() > 0)
+ && (regState == REGISTRATION_STATE_HOME
+ || regState == REGISTRATION_STATE_ROAMING)) {
+ inService = true;
+ }
+ inRoaming = (regState == REGISTRATION_STATE_ROAMING) || isInRoaming();
+ }
+
+ mCrossSimRedialingController.startTimer(mContext, this, mSelectionAttributes.getCallId(),
+ mSelectionAttributes.getNumber(), inService, inRoaming, mModemCount);
+ }
+
+ /** Notifies that the cross stack redilaing timer has been expired. */
+ public void notifyCrossStackTimerExpired() {
+ logi("notifyCrossStackTimerExpired");
+
+ mCrossStackTimerExpired = true;
+ if (mDomainSelected) {
+ // When reselecting domain, terminateSelection will be called.
+ return;
+ }
+ terminateSelectionForCrossSimRedialing(false);
+ }
+
private static String arrayToString(int[] intArray, IntFunction<String> func) {
int length = intArray.length;
StringBuilder sb = new StringBuilder("{");
@@ -1304,6 +1379,7 @@
public void destroy() {
if (DBG) logd("destroy");
+ mCrossSimRedialingController.stopTimer();
releaseWakeLock();
mDestroyed = true;
diff --git a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
index 13db06b..3a8fc86 100644
--- a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
+++ b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
@@ -70,7 +70,8 @@
DomainSelectorBase create(Context context, int slotId, int subId,
@SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
@NonNull ImsStateTracker imsStateTracker,
- @NonNull DomainSelectorBase.DestroyListener listener);
+ @NonNull DomainSelectorBase.DestroyListener listener,
+ @NonNull CrossSimRedialingController crossSimRedialingController);
}
private static final class DefaultDomainSelectorFactory implements DomainSelectorFactory {
@@ -78,7 +79,8 @@
public DomainSelectorBase create(Context context, int slotId, int subId,
@SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
@NonNull ImsStateTracker imsStateTracker,
- @NonNull DomainSelectorBase.DestroyListener listener) {
+ @NonNull DomainSelectorBase.DestroyListener listener,
+ @NonNull CrossSimRedialingController crossSimRedialingController) {
DomainSelectorBase selector = null;
logi("create-DomainSelector: slotId=" + slotId + ", subId=" + subId
@@ -89,7 +91,7 @@
case SELECTOR_TYPE_CALLING:
if (isEmergency) {
selector = new EmergencyCallDomainSelector(context, slotId, subId, looper,
- imsStateTracker, listener);
+ imsStateTracker, listener, crossSimRedialingController);
} else {
selector = new NormalCallDomainSelector(context, slotId, subId, looper,
imsStateTracker, listener);
@@ -192,6 +194,7 @@
private final ImsStateTrackerFactory mImsStateTrackerFactory;
private final DomainSelectorFactory mDomainSelectorFactory;
private Handler mServiceHandler;
+ private CrossSimRedialingController mCrossSimRedialingController;
public TelephonyDomainSelectionService(Context context) {
this(context, ImsStateTracker::new, new DefaultDomainSelectorFactory());
@@ -221,6 +224,8 @@
loge("Adding OnSubscriptionChangedListener failed");
}
+ mCrossSimRedialingController = new CrossSimRedialingController(context, getLooper());
+
logi("TelephonyDomainSelectionService created");
}
@@ -258,6 +263,11 @@
sm.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
}
+ if (mCrossSimRedialingController != null) {
+ mCrossSimRedialingController.destroy();
+ mCrossSimRedialingController = null;
+ }
+
if (mServiceHandler != null) {
mServiceHandler.getLooper().quit();
mServiceHandler = null;
@@ -279,7 +289,8 @@
final boolean isEmergency = attr.isEmergency();
ImsStateTracker ist = getImsStateTracker(slotId);
DomainSelectorBase selector = mDomainSelectorFactory.create(mContext, slotId, subId,
- selectorType, isEmergency, getLooper(), ist, mDestroyListener);
+ selectorType, isEmergency, getLooper(), ist, mDestroyListener,
+ mCrossSimRedialingController);
if (selector != null) {
// Ensures that ImsStateTracker is started before selecting the domain if not started
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 50eca60..f115a43 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -2386,6 +2386,44 @@
}
@Test
+ public void testDomainSelectionTempFailure() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause =
+ com.android.internal.telephony.CallFailCause.EMERGENCY_TEMP_FAILURE;
+ int disconnectCause = android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE;
+ int selectedDomain = DOMAIN_CS;
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
+
+ doReturn(new CompletableFuture()).when(mEmergencyCallDomainSelectionConnection)
+ .reselectDomain(any());
+
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+ verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
+ }
+
+ @Test
+ public void testDomainSelectionPermFailure() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause =
+ com.android.internal.telephony.CallFailCause.EMERGENCY_PERM_FAILURE;
+ int disconnectCause = android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE;
+ int selectedDomain = DOMAIN_CS;
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
+
+ doReturn(new CompletableFuture()).when(mEmergencyCallDomainSelectionConnection)
+ .reselectDomain(any());
+
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+ verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
+ }
+
+ @Test
public void testDomainSelectionWithMmiCode() {
//UT domain selection should not be handled by new domain selector.
doNothing().when(mContext).startActivity(any());
diff --git a/tests/src/com/android/services/telephony/domainselection/CrossSimRedialingControllerTest.java b/tests/src/com/android/services/telephony/domainselection/CrossSimRedialingControllerTest.java
new file mode 100644
index 0000000..a32329d
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/CrossSimRedialingControllerTest.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * 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.
+ */
+
+package com.android.services.telephony.domainselection;
+
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.REDIAL_TIMER_DISABLED;
+import static android.telephony.PreciseDisconnectCause.EMERGENCY_PERM_FAILURE;
+import static android.telephony.PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE;
+
+import static com.android.services.telephony.domainselection.CrossSimRedialingController.MSG_CROSS_STACK_TIMEOUT;
+import static com.android.services.telephony.domainselection.CrossSimRedialingController.MSG_QUICK_CROSS_STACK_TIMEOUT;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.TestContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for CrossSimRedialingController
+ */
+public class CrossSimRedialingControllerTest {
+ private static final String TAG = "CrossSimRedialingControllerTest";
+
+ private static final int SLOT_0 = 0;
+ private static final int SLOT_1 = 1;
+
+ private static final String TELECOM_CALL_ID1 = "TC1";
+ private static final String TEST_EMERGENCY_NUMBER = "911";
+
+ @Mock private CarrierConfigManager mCarrierConfigManager;
+ @Mock private TelephonyManager mTelephonyManager;
+ @Mock private EmergencyCallDomainSelector mEcds;
+ @Mock private CrossSimRedialingController.EmergencyNumberHelper mEmergencyNumberHelper;
+
+ private Context mContext;
+
+ private HandlerThread mHandlerThread;
+ private TestableLooper mLooper;
+ private CrossSimRedialingController mCsrController;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = new TestContext() {
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ if (serviceClass == TelephonyManager.class) {
+ return Context.TELEPHONY_SERVICE;
+ } else if (serviceClass == CarrierConfigManager.class) {
+ return Context.CARRIER_CONFIG_SERVICE;
+ }
+ return super.getSystemServiceName(serviceClass);
+ }
+
+ @Override
+ public String getOpPackageName() {
+ return "";
+ }
+ };
+
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mHandlerThread = new HandlerThread("CrossSimRedialingControllerTest");
+ mHandlerThread.start();
+
+ try {
+ mLooper = new TestableLooper(mHandlerThread.getLooper());
+ } catch (Exception e) {
+ logd("Unable to create looper from handler.");
+ }
+
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ when(mTelephonyManager.createForSubscriptionId(anyInt()))
+ .thenReturn(mTelephonyManager);
+ when(mTelephonyManager.getNetworkCountryIso()).thenReturn("");
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+ doReturn(TelephonyManager.SIM_STATE_READY)
+ .when(mTelephonyManager).getSimState(anyInt());
+
+ mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+ doReturn(getDefaultPersistableBundle()).when(mCarrierConfigManager)
+ .getConfigForSubId(anyInt(), ArgumentMatchers.<String>any());
+
+ doReturn(true).when(mEmergencyNumberHelper).isEmergencyNumber(anyInt(), anyString());
+
+ doReturn(SLOT_0).when(mEcds).getSlotId();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mCsrController != null) {
+ mCsrController.destroy();
+ mCsrController = null;
+ }
+
+ if (mLooper != null) {
+ mLooper.destroy();
+ mLooper = null;
+ }
+ }
+
+ @Test
+ public void testDefaultStartTimerInService() throws Exception {
+ createController();
+
+ boolean inService = true;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertFalse(mCsrController.hasMessages(MSG_QUICK_CROSS_STACK_TIMEOUT));
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ mCsrController.sendEmptyMessage(MSG_CROSS_STACK_TIMEOUT);
+ processAllMessages();
+
+ verify(mEcds).notifyCrossStackTimerExpired();
+ }
+
+ @Test
+ public void testDefaultStartTimerInServiceRoaming() throws Exception {
+ createController();
+
+ boolean inService = true;
+ boolean inRoaming = true;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertFalse(mCsrController.hasMessages(MSG_QUICK_CROSS_STACK_TIMEOUT));
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+ }
+
+ @Test
+ public void testDefaultStartTimerOutOfService() throws Exception {
+ createController();
+
+ boolean inService = false;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertFalse(mCsrController.hasMessages(MSG_QUICK_CROSS_STACK_TIMEOUT));
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+ }
+
+ @Test
+ public void testDefaultStartTimerOutOfServiceRoaming() throws Exception {
+ createController();
+
+ boolean inService = false;
+ boolean inRoaming = true;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertFalse(mCsrController.hasMessages(MSG_QUICK_CROSS_STACK_TIMEOUT));
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+ }
+
+ @Test
+ public void testQuickStartTimerInService() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, 3);
+ doReturn(bundle).when(mCarrierConfigManager)
+ .getConfigForSubId(anyInt(), ArgumentMatchers.<String>any());
+
+ createController();
+
+ boolean inService = true;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_QUICK_CROSS_STACK_TIMEOUT));
+ assertFalse(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ mCsrController.sendEmptyMessage(MSG_QUICK_CROSS_STACK_TIMEOUT);
+ processAllMessages();
+
+ verify(mEcds).notifyCrossStackTimerExpired();
+ }
+
+ @Test
+ public void testQuickStartTimerInServiceRoaming() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, 3);
+ doReturn(bundle).when(mCarrierConfigManager)
+ .getConfigForSubId(anyInt(), ArgumentMatchers.<String>any());
+
+ createController();
+
+ boolean inService = true;
+ boolean inRoaming = true;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertFalse(mCsrController.hasMessages(MSG_QUICK_CROSS_STACK_TIMEOUT));
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+ }
+
+ @Test
+ public void testQuickStartTimerOutOfService() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, 3);
+ doReturn(bundle).when(mCarrierConfigManager)
+ .getConfigForSubId(anyInt(), ArgumentMatchers.<String>any());
+
+ createController();
+
+ boolean inService = false;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_QUICK_CROSS_STACK_TIMEOUT));
+ assertFalse(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+ }
+
+ @Test
+ public void testQuickStartTimerOutOfServiceRoaming() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, 3);
+ doReturn(bundle).when(mCarrierConfigManager)
+ .getConfigForSubId(anyInt(), ArgumentMatchers.<String>any());
+
+ createController();
+
+ boolean inService = false;
+ boolean inRoaming = true;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertFalse(mCsrController.hasMessages(MSG_QUICK_CROSS_STACK_TIMEOUT));
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+ }
+
+ @Test
+ public void testNoNormalStartTimerInService() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putInt(KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);
+ doReturn(bundle).when(mCarrierConfigManager)
+ .getConfigForSubId(anyInt(), ArgumentMatchers.<String>any());
+
+ createController();
+
+ boolean inService = true;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertFalse(mCsrController.hasMessages(MSG_QUICK_CROSS_STACK_TIMEOUT));
+ assertFalse(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+ }
+
+ @Test
+ public void testQuickWhenInServiceStartTimerOutOfService() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, 3);
+ bundle.putBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL, true);
+ doReturn(bundle).when(mCarrierConfigManager)
+ .getConfigForSubId(anyInt(), ArgumentMatchers.<String>any());
+
+ createController();
+
+ boolean inService = false;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertFalse(mCsrController.hasMessages(MSG_QUICK_CROSS_STACK_TIMEOUT));
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+ }
+
+ @Test
+ public void testQuickNoNormalStartTimerInService() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, 3);
+ bundle.putInt(KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);
+ doReturn(bundle).when(mCarrierConfigManager)
+ .getConfigForSubId(anyInt(), ArgumentMatchers.<String>any());
+
+ createController();
+
+ boolean inService = true;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_QUICK_CROSS_STACK_TIMEOUT));
+ assertFalse(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+ }
+
+ @Test
+ public void testDefaultSlot0ThenSlot1() throws Exception {
+ createController();
+
+ boolean inService = true;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ mCsrController.removeMessages(MSG_CROSS_STACK_TIMEOUT);
+ assertFalse(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ doReturn(SLOT_1).when(mEcds).getSlotId();
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+ }
+
+ @Test
+ public void testDefaultSlot0PermThenSlot1Timeout() throws Exception {
+ createController();
+
+ boolean inService = true;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ mCsrController.notifyCallFailure(EMERGENCY_PERM_FAILURE);
+ mCsrController.stopTimer();
+ assertFalse(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ doReturn(SLOT_1).when(mEcds).getSlotId();
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ mCsrController.sendEmptyMessage(MSG_CROSS_STACK_TIMEOUT);
+ processAllMessages();
+
+ verify(mEcds, times(0)).notifyCrossStackTimerExpired();
+ }
+
+ @Test
+ public void testDefaultSlot0TempThenSlot1Timeout() throws Exception {
+ createController();
+
+ boolean inService = true;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ mCsrController.notifyCallFailure(EMERGENCY_TEMP_FAILURE);
+ mCsrController.stopTimer();
+ assertFalse(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ doReturn(SLOT_1).when(mEcds).getSlotId();
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ mCsrController.sendEmptyMessage(MSG_CROSS_STACK_TIMEOUT);
+ processAllMessages();
+
+ verify(mEcds).notifyCrossStackTimerExpired();
+ }
+
+ @Test
+ public void testDefaultSlot0TempThenSlot1TimeoutNotEmergencyNumber() throws Exception {
+ createController();
+
+ boolean inService = true;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ mCsrController.notifyCallFailure(EMERGENCY_TEMP_FAILURE);
+ mCsrController.stopTimer();
+ assertFalse(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ doReturn(SLOT_1).when(mEcds).getSlotId();
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ doReturn(false).when(mEmergencyNumberHelper).isEmergencyNumber(anyInt(), anyString());
+ mCsrController.sendEmptyMessage(MSG_CROSS_STACK_TIMEOUT);
+ processAllMessages();
+
+ verify(mEcds, times(0)).notifyCrossStackTimerExpired();
+ }
+
+ @Test
+ public void testDefaultSlot0TempThenSlot1TimeoutPinLocked() throws Exception {
+ createController();
+
+ boolean inService = true;
+ boolean inRoaming = false;
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ mCsrController.notifyCallFailure(EMERGENCY_TEMP_FAILURE);
+ mCsrController.stopTimer();
+ assertFalse(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ doReturn(SLOT_1).when(mEcds).getSlotId();
+ mCsrController.startTimer(mContext, mEcds, TELECOM_CALL_ID1,
+ TEST_EMERGENCY_NUMBER, inService, inRoaming, 2);
+
+ assertTrue(mCsrController.hasMessages(MSG_CROSS_STACK_TIMEOUT));
+
+ doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
+ .when(mTelephonyManager).getSimState(anyInt());
+ mCsrController.sendEmptyMessage(MSG_CROSS_STACK_TIMEOUT);
+ processAllMessages();
+
+ verify(mEcds, times(0)).notifyCrossStackTimerExpired();
+ }
+
+ private void createController() throws Exception {
+ mCsrController = new CrossSimRedialingController(mContext,
+ mHandlerThread.getLooper(), mEmergencyNumberHelper);
+ }
+
+ private static PersistableBundle getDefaultPersistableBundle() {
+ return getPersistableBundle(0, 120, false);
+ }
+
+ private static PersistableBundle getPersistableBundle(
+ int quickTimer, int timer, boolean startQuickInService) {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, quickTimer);
+ bundle.putInt(KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT, timer);
+ bundle.putBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL,
+ startQuickInService);
+
+ return bundle;
+ }
+
+ private void processAllMessages() {
+ while (!mLooper.getLooper().getQueue().isIdle()) {
+ mLooper.processAllMessages();
+ }
+ }
+
+ private static void logd(String str) {
+ Log.d(TAG, str);
+ }
+}
diff --git a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
index 9badf69..0821943 100644
--- a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
@@ -91,6 +91,7 @@
import android.telephony.DomainSelectionService.SelectionAttributes;
import android.telephony.EmergencyRegResult;
import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PreciseDisconnectCause;
import android.telephony.TelephonyManager;
import android.telephony.TransportSelectorCallback;
import android.telephony.WwanSelectorCallback;
@@ -134,6 +135,7 @@
@Mock private ImsStateTracker mImsStateTracker;
@Mock private DomainSelectorBase.DestroyListener mDestroyListener;
@Mock private ProvisioningManager mProvisioningManager;
+ @Mock private CrossSimRedialingController mCsrdCtrl;
private Context mContext;
@@ -1144,6 +1146,7 @@
doReturn(2).when(mTelephonyManager).getActiveModemCount();
doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
.when(mTelephonyManager).getSimState(anyInt());
+ doReturn(true).when(mCsrdCtrl).isThereOtherSlot();
EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_UNKNOWN,
0, false, false, 0, 0, "", "", "jp");
@@ -1159,6 +1162,29 @@
}
@Test
+ public void testDualSimInvalidSubscriptionButNoOtherSlot() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+ doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
+ .when(mTelephonyManager).getSimState(anyInt());
+ doReturn(false).when(mCsrdCtrl).isThereOtherSlot();
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "", "jp");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(0))
+ .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_PERM_FAILURE));
+ verifyScanPsPreferred();
+ }
+
+ @Test
public void testEutranWithCsDomainOnly() throws Exception {
setupForHandleScanResult();
@@ -1479,6 +1505,149 @@
assertTrue(networks.indexOf(GERAN) < networks.indexOf(NGRAN));
}
+ @Test
+ public void testStartCrossStackTimer() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(
+ UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ processAllMessages();
+ verify(mCsrdCtrl).startTimer(any(), eq(mDomainSelector), any(),
+ any(), anyBoolean(), anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void testStopCrossStackTimerOnCancel() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ mDomainSelector.cancelSelection();
+
+ verify(mCsrdCtrl).stopTimer();
+ }
+
+ @Test
+ public void testStopCrossStackTimerOnFinish() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ mDomainSelector.finishSelection();
+
+ verify(mCsrdCtrl).stopTimer();
+ }
+
+ @Test
+ public void testCrossStackTimerTempFailure() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+
+ attr = new SelectionAttributes.Builder(SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
+ .setEmergency(true)
+ .setEmergencyRegResult(regResult)
+ .setCsDisconnectCause(PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE)
+ .build();
+
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verify(mCsrdCtrl).notifyCallFailure(eq(PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE));
+ }
+
+ @Test
+ public void testCrossStackTimerPermFailure() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+
+ attr = new SelectionAttributes.Builder(SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
+ .setEmergency(true)
+ .setEmergencyRegResult(regResult)
+ .setCsDisconnectCause(PreciseDisconnectCause.EMERGENCY_PERM_FAILURE)
+ .build();
+
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verify(mCsrdCtrl).notifyCallFailure(eq(PreciseDisconnectCause.EMERGENCY_PERM_FAILURE));
+ }
+
+ @Test
+ public void testCrossStackTimerExpired() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(
+ UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanPsPreferred();
+
+ mDomainSelector.notifyCrossStackTimerExpired();
+
+ verify(mTransportSelectorCallback)
+ .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_TEMP_FAILURE));
+ }
+
+ @Test
+ public void testCrossStackTimerExpiredAfterDomainSelected() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+
+ mDomainSelector.notifyCrossStackTimerExpired();
+
+ verify(mTransportSelectorCallback, times(0))
+ .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_TEMP_FAILURE));
+
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verify(mTransportSelectorCallback)
+ .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_TEMP_FAILURE));
+ }
+
private void setupForScanListTest(PersistableBundle bundle) throws Exception {
setupForScanListTest(bundle, false);
}
@@ -1552,7 +1721,7 @@
private void createSelector(int subId) throws Exception {
mDomainSelector = new EmergencyCallDomainSelector(
mContext, SLOT_0, subId, mHandlerThread.getLooper(),
- mImsStateTracker, mDestroyListener);
+ mImsStateTracker, mDestroyListener, mCsrdCtrl);
replaceInstance(DomainSelectorBase.class,
"mWwanSelectorCallback", mDomainSelector, mWwanSelectorCallback);
diff --git a/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java b/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java
index ace59e3..f340e94 100644
--- a/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java
@@ -77,7 +77,8 @@
public DomainSelectorBase create(Context context, int slotId, int subId,
@SelectorType int selectorType, boolean isEmergency,
@NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
- @NonNull DomainSelectorBase.DestroyListener listener) {
+ @NonNull DomainSelectorBase.DestroyListener listener,
+ @NonNull CrossSimRedialingController crossSimRedialingController) {
switch (selectorType) {
case DomainSelectionService.SELECTOR_TYPE_CALLING: // fallthrough
case DomainSelectionService.SELECTOR_TYPE_SMS: // fallthrough