Merge "Cleanup satellite messaging pipeline." into udc-dev
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 8368dfc..b541c17 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -51,7 +51,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.ICancellationSignal;
@@ -149,7 +148,6 @@
 import android.telephony.satellite.ISatellitePositionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteStateCallback;
-import android.telephony.satellite.PointingInfo;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteDatagramCallback;
@@ -198,7 +196,6 @@
 import com.android.internal.telephony.ProxyController;
 import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.RILConstants;
-import com.android.internal.telephony.RILUtils;
 import com.android.internal.telephony.RadioInterfaceCapabilityController;
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.SmsApplication;
@@ -266,7 +263,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
@@ -6764,7 +6760,7 @@
                 mApp, subId, "getAllowedNetworkTypesForReason");
         final long identity = Binder.clearCallingIdentity();
         try {
-            return getPhoneFromSubId(subId).getAllowedNetworkTypes(reason);
+            return getPhoneFromSubIdOrDefault(subId).getAllowedNetworkTypes(reason);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/src/com/android/phone/TimeConsumingPreferenceActivity.java b/src/com/android/phone/TimeConsumingPreferenceActivity.java
index d21f6a8..1fe548c 100644
--- a/src/com/android/phone/TimeConsumingPreferenceActivity.java
+++ b/src/com/android/phone/TimeConsumingPreferenceActivity.java
@@ -192,7 +192,12 @@
         if (mIsForeground) {
             showDialog(error);
         }
-        preference.setEnabled(false);
+
+        //If the error is due to RESPONSE_ERROR, do not disable the item so end user
+        //can continue to interact with it.
+        if (error != RESPONSE_ERROR) {
+            preference.setEnabled(false);
+        }
     }
 
     @Override
diff --git a/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
index 2546023..8288c43 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
@@ -45,7 +45,7 @@
     private static final String ENTITLEMENT_STATUS_KEY = "EntitlementStatus";
     private static final String PROVISION_STATUS_KEY = "ProvStatus";
     private static final String SERVICE_FLOW_URL_KEY = "ServiceFlow_URL";
-    private static final String PROVISION_TIME_LEFT_KEY = "ProvisionTimeLeft";
+    private static final String SERVICE_FLOW_USERDATA_KEY = "ServiceFlow_UserData";
     private static final String DEFAULT_EAP_AKA_RESPONSE = "Default EAP AKA response";
     /**
      * UUID to report an anomaly if an unexpected error is received during entitlement check.
@@ -100,8 +100,7 @@
         requestBuilder.setTerminalModel("modelY");
         requestBuilder.setTerminalSoftwareVersion("versionZ");
         requestBuilder.setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
-        requestBuilder.setNetworkIdentifier(
-                TelephonyManager.convertPremiumCapabilityToString(capability));
+        requestBuilder.setBoostType(getBoostTypeFromPremiumCapability(capability));
         ServiceEntitlementRequest request = requestBuilder.build();
         PremiumNetworkEntitlementResponse premiumNetworkEntitlementResponse =
                 new PremiumNetworkEntitlementResponse();
@@ -109,7 +108,7 @@
         String response = null;
         try {
             response = mServiceEntitlement.queryEntitlementStatus(
-                    ServiceEntitlement.APP_PREMIUM_NETWORK_SLICE,
+                    ServiceEntitlement.APP_DATA_PLAN_BOOST,
                     request);
         } catch (ServiceEntitlementException e) {
             Log.e(TAG, "queryEntitlementStatus failed", e);
@@ -123,10 +122,9 @@
             JSONObject jsonAuthResponse = new JSONObject(response);
             String entitlementStatus = null;
             String provisionStatus = null;
-            String provisionTimeLeft = null;
-            if (jsonAuthResponse.has(ServiceEntitlement.APP_PREMIUM_NETWORK_SLICE)) {
+            if (jsonAuthResponse.has(ServiceEntitlement.APP_DATA_PLAN_BOOST)) {
                 JSONObject jsonToken = jsonAuthResponse.getJSONObject(
-                        ServiceEntitlement.APP_PREMIUM_NETWORK_SLICE);
+                        ServiceEntitlement.APP_DATA_PLAN_BOOST);
                 if (jsonToken.has(ENTITLEMENT_STATUS_KEY)) {
                     entitlementStatus = jsonToken.getString(ENTITLEMENT_STATUS_KEY);
                     if (entitlementStatus == null) {
@@ -142,20 +140,17 @@
                                 Integer.parseInt(provisionStatus);
                     }
                 }
-                if (jsonToken.has(PROVISION_TIME_LEFT_KEY)) {
-                    provisionTimeLeft = jsonToken.getString(PROVISION_TIME_LEFT_KEY);
-                    if (provisionTimeLeft != null) {
-                        premiumNetworkEntitlementResponse.mProvisionTimeLeft =
-                                Integer.parseInt(provisionTimeLeft);
-                    }
-                }
                 if (jsonToken.has(SERVICE_FLOW_URL_KEY)) {
                     premiumNetworkEntitlementResponse.mServiceFlowURL =
                             jsonToken.getString(SERVICE_FLOW_URL_KEY);
                 }
+                if (jsonToken.has(SERVICE_FLOW_USERDATA_KEY)) {
+                    premiumNetworkEntitlementResponse.mServiceFlowUserData =
+                            jsonToken.getString(SERVICE_FLOW_USERDATA_KEY);
+                }
+            } else {
+                Log.e(TAG, "queryEntitlementStatus failed with no app");
             }
-
-
         } catch (JSONException e) {
             Log.e(TAG, "queryEntitlementStatus failed", e);
             reportAnomaly(UUID_ENTITLEMENT_CHECK_UNEXPECTED_ERROR,
@@ -194,4 +189,12 @@
         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY,
                 BYPASS_EAP_AKA_AUTH_FOR_SLICE_PURCHASE_ENABLED, false);
     }
+
+    @NonNull private String getBoostTypeFromPremiumCapability(
+            @TelephonyManager.PremiumCapability int capability) {
+        if (capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY) {
+            return "0" /* REALTIME_INTERACTIVE_TRAFFIC */;
+        }
+        return "";
+    }
 }
diff --git a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
index 4e63e35..242ca69 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
@@ -30,40 +30,39 @@
     public static final int PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING = 3;
     public static final int PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED = 4;
 
-    @IntDef(prefix = {"PREMIUM_NETWORK_ENTITLEMENT_STATUS_"},
-            value = {
-                    PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED,
-                    PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED,
-                    PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCOMPATIBLE,
-                    PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING,
-                    PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED
-            })
+    @IntDef(prefix = {"PREMIUM_NETWORK_ENTITLEMENT_STATUS_"}, value = {
+            PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED,
+            PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED,
+            PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCOMPATIBLE,
+            PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING,
+            PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED
+    })
     public @interface PremiumNetworkEntitlementStatus {}
 
     public static final int PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED = 0;
     public static final int PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED = 1;
-    public static final int PREMIUM_NETWORK_PROVISION_STATUS_NOT_REQUIRED = 2;
+    public static final int PREMIUM_NETWORK_PROVISION_STATUS_NOT_AVAILABLE = 2;
     public static final int PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS = 3;
 
-    @IntDef(prefix = {"PREMIUM_NETWORK_PROVISION_STATUS_"},
-            value = {
-                    PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED,
-                    PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED,
-                    PREMIUM_NETWORK_PROVISION_STATUS_NOT_REQUIRED,
-                    PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS
-            })
+    @IntDef(prefix = {"PREMIUM_NETWORK_PROVISION_STATUS_"}, value = {
+            PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED,
+            PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED,
+            PREMIUM_NETWORK_PROVISION_STATUS_NOT_AVAILABLE,
+            PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS
+    })
     public @interface PremiumNetworkProvisionStatus {}
 
     @PremiumNetworkEntitlementStatus public int mEntitlementStatus;
     @PremiumNetworkProvisionStatus public int mProvisionStatus;
-    public int mProvisionTimeLeft;
     @NonNull public String mServiceFlowURL;
+    @NonNull public String mServiceFlowUserData;
 
     /**
      * @return {@code true} if the premium network is provisioned and {@code false} otherwise.
      */
     public boolean isProvisioned() {
         return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED
+                || mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED
                 || mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED;
     }
 
@@ -83,7 +82,10 @@
     public boolean isPremiumNetworkCapabilityAllowed() {
         switch (mEntitlementStatus) {
             case PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCOMPATIBLE:
-            case PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED:
+                return false;
+        }
+        switch (mProvisionStatus) {
+            case PREMIUM_NETWORK_PROVISION_STATUS_NOT_AVAILABLE:
                 return false;
         }
         return true;
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index c62b4fa..654fb93 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -980,23 +980,25 @@
             // event package; some carriers are known to keep a disconnected participant around in
             // subsequent CEP updates with a state of disconnected, even though its no longer part
             // of the conference.
-            // Note: We consider 0 to still be a single party conference since some carriers will
-            // send a conference event package with JUST the host in it when the conference is
-            // disconnected.  We don't want to change back to conference mode prior to disconnection
-            // or we will not log the call.
-            boolean isSinglePartyConference = participants.stream()
+            final long numActiveCepParticipantsOtherThanHost = participants.stream()
                     .filter(p -> {
                         Pair<Uri, Uri> pIdent = new Pair<>(p.getHandle(), p.getEndpoint());
                         return !Objects.equals(mHostParticipantIdentity, pIdent)
                                 && p.getState() != Connection.STATE_DISCONNECTED;
                     })
-                    .count() <= 1;
+                    .count();
+            // We consider 0 to still be a single party conference since some carriers
+            // will send a conference event package with JUST the host in it when the conference
+            // is disconnected.  We don't want to change back to conference mode prior to
+            // disconnection or we will not log the call.
+            final boolean isCepForSinglePartyConference =
+                    numActiveCepParticipantsOtherThanHost <= 1;
 
             // We will only process the CEP data if:
             // 1. We're not emulating a single party call.
             // 2. We're emulating a single party call and the CEP contains more than just the
             //    single party
-            if ((!isMultiparty() && !isSinglePartyConference)
+            if ((!isMultiparty() && !isCepForSinglePartyConference)
                     || isMultiparty()) {
                 // Add any new participants and update existing.
                 for (ConferenceParticipant participant : participants) {
@@ -1082,15 +1084,17 @@
 
             int newParticipantCount = mConferenceParticipantConnections.size();
             Log.v(this, "handleConferenceParticipantsUpdate: oldParticipantCount=%d, "
-                            + "newParticipantcount=%d", oldParticipantCount, newParticipantCount);
-            // If the single party call emulation fature flag is enabled, we can potentially treat
+                            + "newParticipantCount=%d, isMultiPty=%b, cepParticipantCt=%d",
+                    oldParticipantCount, newParticipantCount, isMultiparty(),
+                    numActiveCepParticipantsOtherThanHost);
+            // If the single party call emulation feature flag is enabled, we can potentially treat
             // the conference as a single party call when there is just one participant.
             if (mFeatureFlagProxy.isUsingSinglePartyCallEmulation() &&
                     !mConferenceHost.isAdhocConferenceCall()) {
                 if (oldParticipantCount != 1 && newParticipantCount == 1) {
                     // If number of participants goes to 1, emulate a single party call.
                     startEmulatingSinglePartyCall();
-                } else if (!isMultiparty() && !isSinglePartyConference) {
+                } else if (!isMultiparty() && !isCepForSinglePartyConference) {
                     // Number of participants increased, so stop emulating a single party call.
                     stopEmulatingSinglePartyCall();
                 }
@@ -1108,8 +1112,8 @@
                     // OR if the conference had a single participant and is emulating a standalone
                     // call.
                     && (oldParticipantCount > 0 || !isMultiparty())
-                    // AND the CEP says there is nobody left any more.
-                    && newParticipantCount == 0) {
+                    // AND the CEP says there is nobody left anymore.
+                    && numActiveCepParticipantsOtherThanHost == 0) {
                 Log.i(this, "handleConferenceParticipantsUpdate: empty conference; "
                         + "local disconnect.");
                 onDisconnect();
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index a04fe33..7311113 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -83,6 +83,7 @@
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 import com.android.internal.telephony.imsphone.ImsPhoneMmiCode;
+import com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.phone.FrameworksUtils;
@@ -209,6 +210,7 @@
     public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache;
     private DeviceState mDeviceState = new DeviceState();
     private EmergencyStateTracker mEmergencyStateTracker;
+    private SatelliteSOSMessageRecommender mSatelliteSOSMessageRecommender;
     private DomainSelectionResolver mDomainSelectionResolver;
     private EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
     private TelephonyConnection mEmergencyConnection;
@@ -572,6 +574,34 @@
                 }
             };
 
+    private final TelephonyConnection.TelephonyConnectionListener
+            mEmergencyConnectionSatelliteListener =
+            new TelephonyConnection.TelephonyConnectionListener() {
+                @Override
+                public void onStateChanged(Connection connection,
+                        @Connection.ConnectionState int state) {
+                    if (connection == null) {
+                        Log.d(this,
+                                "onStateChanged for satellite listener: connection is null");
+                        return;
+                    }
+                    if (mSatelliteSOSMessageRecommender == null) {
+                        Log.d(this, "onStateChanged for satellite listener: "
+                                + "mSatelliteSOSMessageRecommender is null");
+                        return;
+                    }
+
+                    TelephonyConnection c = (TelephonyConnection) connection;
+                    mSatelliteSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+                            c.getTelecomCallId(), state);
+                    if (state == Connection.STATE_DISCONNECTED
+                            || state == Connection.STATE_ACTIVE) {
+                        c.removeTelephonyConnectionListener(mEmergencyConnectionSatelliteListener);
+                        mSatelliteSOSMessageRecommender = null;
+                    }
+                }
+            };
+
     /**
      * A listener for calls.
      */
@@ -2011,12 +2041,14 @@
                         }
                     });
         }
+
         final com.android.internal.telephony.Connection originalConnection;
         try {
             if (phone != null) {
                 boolean isEmergency = mTelephonyManagerProxy.isCurrentEmergencyNumber(number);
                 Log.i(this, "placeOutgoingConnection isEmergency=" + isEmergency);
                 if (isEmergency) {
+                    handleEmergencyCallStartedForSatelliteSOSMessageRecommender(connection, phone);
                     if (!getAllConnections().isEmpty()) {
                         if (!shouldHoldForEmergencyCall(phone)) {
                             // If we do not support holding ongoing calls for an outgoing
@@ -2432,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) {
@@ -2578,6 +2604,7 @@
 
         mIsEmergencyCallPending = true;
         c.addTelephonyConnectionListener(mEmergencyConnectionListener);
+        handleEmergencyCallStartedForSatelliteSOSMessageRecommender(c, phone);
 
         if (mEmergencyStateTracker == null) {
             mEmergencyStateTracker = EmergencyStateTracker.getInstance();
@@ -2748,6 +2775,12 @@
         return mEmergencyConnectionListener;
     }
 
+    @VisibleForTesting
+    public TelephonyConnection.TelephonyConnectionListener
+            getEmergencyConnectionSatelliteListener() {
+        return mEmergencyConnectionSatelliteListener;
+    }
+
     private boolean isVideoCallHoldAllowed(Phone phone) {
          CarrierConfigManager cfgManager = (CarrierConfigManager)
                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
@@ -3898,4 +3931,14 @@
         }
         return NetworkRegistrationInfo.DOMAIN_UNKNOWN;
     }
+
+    private void handleEmergencyCallStartedForSatelliteSOSMessageRecommender(
+            @NonNull TelephonyConnection connection, @NonNull Phone phone) {
+        if (mSatelliteSOSMessageRecommender == null) {
+            mSatelliteSOSMessageRecommender = new SatelliteSOSMessageRecommender(
+                    phone.getContext().getMainLooper());
+        }
+        connection.addTelephonyConnectionListener(mEmergencyConnectionSatelliteListener);
+        mSatelliteSOSMessageRecommender.onEmergencyCallStarted(connection, phone);
+    }
 }
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/phone/slice/SlicePurchaseControllerTest.java b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
index 921babb..544d7ea 100644
--- a/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
+++ b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
@@ -715,7 +715,7 @@
         doReturn(TelephonyManager.NETWORK_TYPE_NR).when(mServiceState).getDataNetworkType();
         // entitlement check passed
         mEntitlementResponse.mEntitlementStatus =
-                PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED;
+                PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED;
 
         // send purchase request
         mSlicePurchaseController.purchasePremiumCapability(
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 9d2f5ac..5123858 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -590,7 +590,7 @@
 
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
-                null /* phoneAccountHandle */, () -> false /* featureFlagProxy */,
+                null /* phoneAccountHandle */, () -> true /* isUsingSinglePartyCallEmulation */,
                 new ImsConference.CarrierConfiguration.Builder()
                         .setShouldLocalDisconnectEmptyConference(true)
                         .build());
@@ -622,6 +622,109 @@
     }
 
     /**
+     * Preconditions: both single party emulation and local disconnect of empty conferences is
+     * enabled.
+     * Tests the case where we receive a repeat with the same single-party data that caused a
+     * conference to be treated as a single party; we need to verify that we do not disconnect the
+     * conference locally in this case.
+     * @throws Exception
+     */
+    @Test
+    @SmallTest
+    public void testNoLocalDisconnectSinglePartyConferenceOnRepeatedCep() throws Exception {
+        when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+                .thenReturn(false);
+
+        ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+                mMockTelephonyConnectionServiceProxy, mConferenceHost,
+                null /* phoneAccountHandle */, () -> true /* isUsingSinglePartyCallEmulation */,
+                new ImsConference.CarrierConfiguration.Builder()
+                        .setShouldLocalDisconnectEmptyConference(true)
+                        .build());
+
+        ConferenceParticipant participant1 = new ConferenceParticipant(
+                Uri.parse("tel:6505551212"),
+                "A",
+                Uri.parse("sip:[email protected]"),
+                Connection.STATE_ACTIVE,
+                Call.Details.DIRECTION_INCOMING);
+        ConferenceParticipant participant2 = new ConferenceParticipant(
+                Uri.parse("tel:6505551213"),
+                "A",
+                Uri.parse("sip:[email protected]"),
+                Connection.STATE_ACTIVE,
+                Call.Details.DIRECTION_INCOMING);
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1, participant2));
+        assertEquals(2, imsConference.getNumberOfParticipants());
+
+        // Drop to 1 participant which enters single party mode.
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1));
+        assertEquals(0, imsConference.getNumberOfParticipants());
+
+        // Get a repeat CEP with the same participant data; we should still be in single party mode
+        // but we should NOT disconnect the conference.
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1));
+        assertEquals(0, imsConference.getNumberOfParticipants());
+        verify(mConferenceHost.mMockCall, never()).hangup();
+    }
+
+    /**
+     * An extension of {@link #testNoLocalDisconnectSinglePartyConferenceOnRepeatedCep()} where we
+     * get a repeated CEP with the same single party state, but then finally get a CEP with no
+     * participants anymore.  In this case we do expect a local disconnect as the final state.
+     * @throws Exception
+     */
+    @Test
+    @SmallTest
+    public void testLocalDisconnectSinglePartyConferenceOnRepeatedCep() throws Exception {
+        when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+                .thenReturn(false);
+
+        ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+                mMockTelephonyConnectionServiceProxy, mConferenceHost,
+                null /* phoneAccountHandle */, () -> true /* isUsingSinglePartyCallEmulation */,
+                new ImsConference.CarrierConfiguration.Builder()
+                        .setShouldLocalDisconnectEmptyConference(true)
+                        .build());
+
+        ConferenceParticipant participant1 = new ConferenceParticipant(
+                Uri.parse("tel:6505551212"),
+                "A",
+                Uri.parse("sip:[email protected]"),
+                Connection.STATE_ACTIVE,
+                Call.Details.DIRECTION_INCOMING);
+        ConferenceParticipant participant2 = new ConferenceParticipant(
+                Uri.parse("tel:6505551213"),
+                "A",
+                Uri.parse("sip:[email protected]"),
+                Connection.STATE_ACTIVE,
+                Call.Details.DIRECTION_INCOMING);
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1, participant2));
+        assertEquals(2, imsConference.getNumberOfParticipants());
+
+        // Drop to 1 participant which enters single party mode.
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1));
+        assertEquals(0, imsConference.getNumberOfParticipants());
+
+        // Get a repeat CEP with the same participant data; we should still be in single party mode
+        // but we should NOT disconnect the conference.
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1));
+        assertEquals(0, imsConference.getNumberOfParticipants());
+        verify(mConferenceHost.mMockCall, never()).hangup();
+
+        // Got another CEP that has no participants at all; we should disconnet in this case
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost, Collections.emptyList());
+        assertEquals(0, imsConference.getNumberOfParticipants());
+        verify(mConferenceHost.mMockCall).hangup();
+    }
+
+    /**
      * Tests a scenario where a handover connection arrives via
      * {@link TelephonyConnection#onOriginalConnectionRedialed(
      * com.android.internal.telephony.Connection)}.  During this process, the conference properties
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index eedeeac..f115a43 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -55,6 +55,7 @@
 import android.os.AsyncResult;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.telecom.Conference;
 import android.telecom.Conferenceable;
 import android.telecom.ConnectionRequest;
@@ -92,6 +93,7 @@
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender;
 
 import org.junit.After;
 import org.junit.Before;
@@ -197,6 +199,8 @@
     @Mock EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
     @Mock NormalCallDomainSelectionConnection mNormalCallDomainSelectionConnection;
     @Mock ImsPhone mImsPhone;
+    @Mock
+    private SatelliteSOSMessageRecommender mSatelliteSOSMessageRecommender;
     private EmergencyStateTracker mEmergencyStateTracker;
     private Phone mPhone0;
     private Phone mPhone1;
@@ -223,6 +227,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
+        doReturn(Looper.getMainLooper()).when(mContext).getMainLooper();
         mTestConnectionService = new TestTelephonyConnectionService(mContext);
         mTestConnectionService.setPhoneFactoryProxy(mPhoneFactoryProxy);
         mTestConnectionService.setSubscriptionManagerProxy(mSubscriptionManagerProxy);
@@ -248,6 +253,11 @@
         mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class);
         replaceInstance(TelephonyConnectionService.class, "mEmergencyStateTracker",
                 mTestConnectionService, mEmergencyStateTracker);
+        replaceInstance(TelephonyConnectionService.class, "mSatelliteSOSMessageRecommender",
+                mTestConnectionService, mSatelliteSOSMessageRecommender);
+        doNothing().when(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
+        doNothing().when(mSatelliteSOSMessageRecommender).onEmergencyCallConnectionStateChanged(
+                anyString(), anyInt());
         doReturn(CompletableFuture.completedFuture(NOT_DISCONNECTED))
                 .when(mEmergencyStateTracker)
                 .startEmergencyCall(any(), anyString(), eq(false));
@@ -1269,6 +1279,7 @@
             // This shouldn't happen
             fail();
         }
+        verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
     }
 
     /**
@@ -1724,6 +1735,7 @@
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
         verify(mEmergencyStateTracker)
                 .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+        verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
         verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
 
         ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -1753,6 +1765,7 @@
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
         verify(mEmergencyStateTracker)
                 .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+        verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
         verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
 
         ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -1783,6 +1796,7 @@
 
         verify(mEmergencyStateTracker, times(1))
                 .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+        verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
         verify(mDomainSelectionResolver, times(0))
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
         verify(mEmergencyCallDomainSelectionConnection, times(0))
@@ -1876,6 +1890,7 @@
         verify(mDomainSelectionResolver)
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
         verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
+        verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
 
         ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
 
@@ -1913,6 +1928,7 @@
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
         verify(mEmergencyStateTracker)
                 .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+        verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
         verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
 
         ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -1953,6 +1969,7 @@
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
         verify(mEmergencyStateTracker)
                 .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+        verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
         verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
 
         ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2216,6 +2233,7 @@
 
         verify(mEmergencyStateTracker)
                 .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+        verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
 
         // dialing is canceled
         mTestConnectionService.onLocalHangup(c);
@@ -2283,6 +2301,7 @@
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
         verify(mEmergencyStateTracker)
                 .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+        verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
         verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
         verify(mPhone0).dial(anyString(), any(), any());
 
@@ -2294,6 +2313,8 @@
 
         TelephonyConnection.TelephonyConnectionListener connectionListener =
                 mTestConnectionService.getEmergencyConnectionListener();
+        TelephonyConnection.TelephonyConnectionListener connectionSatelliteListener =
+                mTestConnectionService.getEmergencyConnectionSatelliteListener();
 
         connectionListener.onOriginalConnectionConfigured(c);
 
@@ -2302,32 +2323,44 @@
 
         verify(mEmergencyStateTracker, times(0)).onEmergencyCallStateChanged(
                 any(), eq(TELECOM_CALL_ID1));
+        verify(mSatelliteSOSMessageRecommender, times(0))
+                .onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
 
         c.setActive();
         doReturn(Call.State.ACTIVE).when(orgConn).getState();
         connectionListener.onStateChanged(c, c.getState());
+        connectionSatelliteListener.onStateChanged(c, c.getState());
 
         // ACTIVE sate is notified
         verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
                 eq(Call.State.ACTIVE), eq(TELECOM_CALL_ID1));
+        verify(mSatelliteSOSMessageRecommender, times(1))
+                .onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1),
+                        eq(android.telecom.Connection.STATE_ACTIVE));
 
         // state change to HOLDING
         c.setOnHold();
         doReturn(Call.State.HOLDING).when(orgConn).getState();
         connectionListener.onStateChanged(c, c.getState());
+        connectionSatelliteListener.onStateChanged(c, c.getState());
 
         // state change not notified any more after CONNECTED once
         verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
                 any(), eq(TELECOM_CALL_ID1));
+        verify(mSatelliteSOSMessageRecommender, times(1))
+                .onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
 
         // state change to ACTIVE again
         c.setActive();
         doReturn(Call.State.ACTIVE).when(orgConn).getState();
         connectionListener.onStateChanged(c, c.getState());
+        connectionSatelliteListener.onStateChanged(c, c.getState());
 
         // state change not notified any more after CONNECTED once
         verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
                 any(), eq(TELECOM_CALL_ID1));
+        verify(mSatelliteSOSMessageRecommender, times(1))
+                .onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
 
         // SRVCC happens
         c.setIsImsConnection(false);
@@ -2343,10 +2376,51 @@
         c.setDisconnected(null);
         doReturn(Call.State.DISCONNECTED).when(orgConn).getState();
         connectionListener.onStateChanged(c, c.getState());
+        connectionSatelliteListener.onStateChanged(c, c.getState());
 
         // state change not notified
         verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
                 any(), eq(TELECOM_CALL_ID1));
+        verify(mSatelliteSOSMessageRecommender, times(1))
+                .onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
+    }
+
+    @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
@@ -2373,6 +2447,7 @@
         verify(mDomainSelectionResolver)
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
         verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
+        verify(mSatelliteSOSMessageRecommender, never()).onEmergencyCallStarted(any(), any());
 
         ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
 
@@ -2397,6 +2472,7 @@
         verify(mDomainSelectionResolver)
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
         verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
+        verify(mSatelliteSOSMessageRecommender, never()).onEmergencyCallStarted(any(), any());
 
         ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
 
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