Merge "Switch stacks when the current stack has limited service" into main
diff --git a/res/values/config.xml b/res/values/config.xml
index 7cd4e18..cfb472e 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -337,6 +337,14 @@
         <item>de</item>
     </string-array>
 
+    <!-- Array of countries that a normal service capable subscription is preferred
+         for emergency calls. Values should be ISO3166 country codes in lowercase. -->
+    <string-array name="config_countries_prefer_normal_service_capable_subscription"
+            translatable="false">
+        <!-- b/317945295 -->
+        <item>in</item>
+    </string-array>
+
     <!-- The component name(a flattened ComponentName string) for the telephony domain selection
          service. The device should fallback to the modem based domain selection architecture
          if this is not configured. -->
diff --git a/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java b/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java
index 44904f4..d368d46 100644
--- a/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java
+++ b/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java
@@ -29,9 +29,12 @@
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.SystemProperties;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.PreciseDisconnectCauses;
 import android.telephony.CarrierConfigManager;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
@@ -144,7 +147,7 @@
      * @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 inService Indicates 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
      */
@@ -228,12 +231,26 @@
     }
 
     /**
+     * Returns whether there is another slot with which normal service is available.
+     *
+     * @return {@code true} if there is another slot with which normal service is available.
+     *         {@code false} otherwise.
+     */
+    public boolean isThereOtherSlotInService() {
+        return isThereOtherSlot(true);
+    }
+
+    /**
      * Returns whether there is another slot emergency capable.
      *
      * @return {@code true} if there is another slot emergency capable,
      *         {@code false} otherwise.
      */
     public boolean isThereOtherSlot() {
+        return isThereOtherSlot(false);
+    }
+
+    private boolean isThereOtherSlot(boolean networkRegisteredOnly) {
         logi("isThereOtherSlot modemCount=" + mModemCount);
         if (mModemCount < 2) return false;
 
@@ -254,7 +271,13 @@
             int subId = SubscriptionManager.getSubscriptionId(i);
             if (mEmergencyNumberHelper.isEmergencyNumber(subId, mNumber)) {
                 logi("isThereOtherSlot index=" + i + "(" + subId + "), found");
-                return true;
+                if (networkRegisteredOnly) {
+                    if (isNetworkRegistered(subId)) {
+                        return true;
+                    }
+                } else {
+                    return true;
+                }
             } else {
                 logi("isThereOtherSlot index=" + i + "(" + subId + "), not emergency number");
             }
@@ -263,6 +286,31 @@
         return false;
     }
 
+    private boolean isNetworkRegistered(int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) return false;
+
+        TelephonyManager tm = mTelephonyManager.createForSubscriptionId(subId);
+        ServiceState ss = tm.getServiceState();
+        if (ss != null) {
+            NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_PS,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri != null && nri.isNetworkRegistered()) {
+                // PS is IN_SERVICE state.
+                return true;
+            }
+            nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_CS,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri != null && nri.isNetworkRegistered()) {
+                // CS is IN_SERVICE state.
+                return true;
+            }
+        }
+        logi("isNetworkRegistered subId=" + subId + " not network registered");
+        return false;
+    }
+
     /**
      * Caches the configuration.
      */
diff --git a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
index f48ea46..05ae1f0 100644
--- a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
@@ -118,6 +118,7 @@
     private static final LocalLog sLocalLog = new LocalLog(LOG_SIZE);
 
     private static List<String> sSimReadyAllowList;
+    private static List<String> sPreferSlotWithNormalServiceList;
 
     /**
      * Network callback used to determine whether Wi-Fi is connected or not.
@@ -576,26 +577,43 @@
      * Caches the resource configuration.
      */
     private void readResourceConfiguration() {
-        if (sSimReadyAllowList != null) return;
+        if (sSimReadyAllowList == null) {
+            sSimReadyAllowList = readResourceConfiguration(
+                    R.array.config_countries_require_sim_for_emergency);
+        }
+        logi("readResourceConfiguration simReadyCountries=" + sSimReadyAllowList);
+
+        if (sPreferSlotWithNormalServiceList == null) {
+            sPreferSlotWithNormalServiceList = readResourceConfiguration(
+                    R.array.config_countries_prefer_normal_service_capable_subscription);
+        }
+        logi("readResourceConfiguration preferNormalServiceCountries="
+                + sPreferSlotWithNormalServiceList);
+    }
+
+    private List<String> readResourceConfiguration(int id) {
+        logi("readResourceConfiguration id=" + id);
+
+        List<String> resource = null;
         try {
-            sSimReadyAllowList = Arrays.asList(mContext.getResources().getStringArray(
-                    R.array.config_countries_require_sim_for_emergency));
+            resource = Arrays.asList(mContext.getResources().getStringArray(id));
         } catch (Resources.NotFoundException nfe) {
             loge("readResourceConfiguration exception=" + nfe);
         } catch (NullPointerException npe) {
             loge("readResourceConfiguration exception=" + npe);
         } finally {
-            if (sSimReadyAllowList == null) {
-                sSimReadyAllowList = new ArrayList<String>();
+            if (resource == null) {
+                resource = new ArrayList<String>();
             }
         }
-        logi("readResourceConfiguration simReadyCountries=" + sSimReadyAllowList);
+        return resource;
     }
 
     /** For test purpose only */
     @VisibleForTesting
     public void clearResourceConfiguration() {
         sSimReadyAllowList = null;
+        sPreferSlotWithNormalServiceList = null;
     }
 
     private void selectDomain() {
@@ -643,6 +661,9 @@
         boolean psInService = isPsInService();
 
         if (!csInService && !psInService) {
+            if (maybeRedialOnTheOtherSlotInNormalService()) {
+                return;
+            }
             mCsNetworkType = getSelectableCsNetworkType();
             mPsNetworkType = getSelectablePsNetworkType(false);
             logi("selectDomain limited service ps=" + accessNetworkTypeToString(mPsNetworkType)
@@ -1400,6 +1421,20 @@
         return true;
     }
 
+    private boolean maybeRedialOnTheOtherSlotInNormalService() {
+        EmergencyRegistrationResult regResult =
+                mSelectionAttributes.getEmergencyRegistrationResult();
+        if (regResult == null) return false;
+
+        String iso = regResult.getCountryIso();
+        if (sPreferSlotWithNormalServiceList.contains(iso)
+                && mCrossSimRedialingController.isThereOtherSlotInService()) {
+            terminateSelectionForCrossSimRedialing(false);
+            return true;
+        }
+        return false;
+    }
+
     private void terminateSelectionPermanentlyForSlot() {
         logi("terminateSelectionPermanentlyForSlot");
         terminateSelection(true);
diff --git a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
index 10c0776..b8b3359 100644
--- a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
@@ -1759,6 +1759,28 @@
     }
 
     @Test
+    public void testDualSimNormalServiceOnTheOtherSubscription() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        doReturn(true).when(mCsrdCtrl).isThereOtherSlotInService();
+        doReturn(new String[] {"in"}).when(mResources).getStringArray(anyInt());
+
+        EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+                REGISTRATION_STATE_UNKNOWN,
+                0, false, false, 0, 0, "", "", "in");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+        processAllMessages();
+
+        verify(mTransportSelectorCallback, times(1))
+                .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_TEMP_FAILURE));
+    }
+
+    @Test
     public void testEutranWithCsDomainOnly() throws Exception {
         setupForHandleScanResult();