Merge "Added getMaxCharPerTextMessage() API."
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 09b9b6d..8253f71 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -595,6 +595,30 @@
         public void onSelectionTerminated(@DisconnectCauses int cause) {
             if (mEmergencyCallDomainSelectionConnection != null) {
                 Log.i(this, "onSelectionTerminated cause=" + cause);
+
+                // Cross stack redial
+                if (cause == android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE
+                        || cause == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE) {
+                    if (mEmergencyConnection != null) {
+                        final boolean isPermanentFailure =
+                                cause == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE;
+                        Log.i(this, "onSelectionTerminated trigger cross stack redial"
+                                + " permanent=" + isPermanentFailure);
+                        mDomainSelectionMainExecutor.execute(() -> {
+                            Log.i(this, "onSelectionTerminated execute cross stack redial"
+                                    + " permanent=" + isPermanentFailure);
+                            TelephonyConnection c = mEmergencyConnection;
+                            Phone phone = mEmergencyCallDomainSelectionConnection.getPhone();
+                            mEmergencyConnection.removeTelephonyConnectionListener(
+                                    mEmergencyConnectionListener);
+                            mEmergencyStateTracker.endCall(
+                                    mEmergencyConnection.getTelecomCallId());
+                            releaseEmergencyCallDomainSelection(true);
+                            retryOutgoingOriginalConnection(c, phone, isPermanentFailure);
+                        });
+                        return;
+                    }
+                }
                 mEmergencyCallDomainSelectionConnection = null;
                 if (mEmergencyConnection != null) {
                     mEmergencyConnection.hangup(android.telephony.DisconnectCause.OUT_OF_NETWORK);
@@ -658,7 +682,7 @@
 
         @Override
         public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) {
-            retryOutgoingOriginalConnection(c, isPermanentFailure);
+            retryOutgoingOriginalConnection(c, c.getPhone(), isPermanentFailure);
         }
     };
 
@@ -1809,7 +1833,7 @@
     // Update the mEmergencyRetryCache by removing the Phone used to call the last failed emergency
     // number and then moving it to the back of the queue if it is not a permanent failure cause
     // from the modem.
-    private void updateCachedConnectionPhonePair(TelephonyConnection c,
+    private void updateCachedConnectionPhonePair(TelephonyConnection c, Phone phone,
             boolean isPermanentFailure) {
         // No cache exists, create a new one.
         if (mEmergencyRetryCache == null) {
@@ -1824,7 +1848,7 @@
         Queue<Phone> cachedPhones = mEmergencyRetryCache.second;
         // Need to refer default phone considering ImsPhone because
         // cachedPhones is a list that contains default phones.
-        Phone phoneUsed = c.getPhone().getDefaultPhone();
+        Phone phoneUsed = phone.getDefaultPhone();
         if (phoneUsed == null) {
             return;
         }
@@ -1853,9 +1877,10 @@
      * This will continue until there are no more slots to dial on.
      */
     @VisibleForTesting
-    public void retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure) {
-        int phoneId = (c.getPhone() == null) ? -1 : c.getPhone().getPhoneId();
-        updateCachedConnectionPhonePair(c, isPermanentFailure);
+    public void retryOutgoingOriginalConnection(TelephonyConnection c,
+            Phone phone, boolean isPermanentFailure) {
+        int phoneId = (phone == null) ? -1 : phone.getPhoneId();
+        updateCachedConnectionPhonePair(c, phone, isPermanentFailure);
         // Pull next phone to use from the cache or null if it is empty
         Phone newPhoneToUse = (mEmergencyRetryCache.second != null)
                 ? mEmergencyRetryCache.second.peek() : null;
@@ -2330,10 +2355,12 @@
 
             CompletableFuture<Integer> future =
                     mEmergencyCallDomainSelectionConnection.reselectDomain(attr);
+            // TeleponyConnection will clear original connection. Keep the reference to Phone.
+            final Phone phone = c.getPhone().getDefaultPhone();
             if (future != null) {
                 future.thenAcceptAsync((result) -> {
                     Log.d(this, "reselectDomain-complete");
-                    onEmergencyRedialOnDomain(c, c.getPhone().getDefaultPhone(), result);
+                    onEmergencyRedialOnDomain(c, phone, result);
                 }, mDomainSelectionMainExecutor);
                 return true;
             }
diff --git a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
index fa11a5d..d1eda18 100644
--- a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
@@ -68,6 +68,7 @@
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.BarringInfo;
 import android.telephony.CarrierConfigManager;
+import android.telephony.DisconnectCause;
 import android.telephony.DomainSelectionService;
 import android.telephony.DomainSelectionService.SelectionAttributes;
 import android.telephony.EmergencyRegResult;
@@ -106,6 +107,18 @@
 
     private static final LocalLog sLocalLog = new LocalLog(LOG_SIZE);
 
+    private static final ArrayList<String> sAllowOnlyWithSimReady = new ArrayList<>();
+
+    static {
+        // b/177967010, JP
+        sAllowOnlyWithSimReady.add("jp"); // Japan
+        // b/198393826, DE
+        sAllowOnlyWithSimReady.add("de"); // Germany
+        // b/230443699, IN and SG
+        sAllowOnlyWithSimReady.add("in"); // India
+        sAllowOnlyWithSimReady.add("sg"); // Singapore
+    }
+
     /**
      * Network callback used to determine whether Wi-Fi is connected or not.
      */
@@ -167,6 +180,7 @@
     private boolean mRequiresVoLteEnabled;
     private boolean mLtePreferredAfterNrFailure;
     private boolean mTryCsWhenPsFails;
+    private int mModemCount;
 
     /** Indicates whether this instance is deactivated. */
     private boolean mDestroyed = false;
@@ -228,6 +242,12 @@
     private void handleScanResult(EmergencyRegResult result) {
         logi("handleScanResult result=" + result);
 
+        // Detected the country and found that emergency calls are not allowed with this slot.
+        if (!allowEmergencyCalls(result)) {
+            terminateSelectionPermanentlyForSlot();
+            return;
+        }
+
         if (result.getAccessNetwork() == UNKNOWN) {
             if ((mPreferredNetworkScanType == SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE)
                       || (mScanType == DomainSelectionService.SCAN_TYPE_FULL_SERVICE)) {
@@ -247,7 +267,7 @@
 
         removeMessages(MSG_NETWORK_SCAN_TIMEOUT);
         onWwanNetworkTypeSelected(result.getAccessNetwork());
-        mIsScanRequested = false;
+        mCancelSignal = null;
     }
 
     @Override
@@ -318,11 +338,14 @@
         mTransportSelectorCallback = cb;
         mSelectionAttributes = attr;
 
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        mModemCount = tm.getActiveModemCount();
+
         sendEmptyMessage(MSG_START_DOMAIN_SELECTION);
     }
 
     private void startDomainSelection() {
-        logi("startDomainSelection");
+        logi("startDomainSelection modemCount=" + mModemCount);
         updateCarrierConfiguration();
         mDomainSelectionRequested = true;
         if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
@@ -458,6 +481,12 @@
             return;
         }
 
+        if (!allowEmergencyCalls(mSelectionAttributes.getEmergencyRegResult())) {
+            // Detected the country and found that emergency calls are not allowed with this slot.
+            terminateSelectionPermanentlyForSlot();
+            return;
+        }
+
         if (isWifiPreferred()) {
             onWlanSelected();
             return;
@@ -793,7 +822,7 @@
         List<Integer> ratList = getImsNetworkTypeConfiguration();
         if (ratList.contains(accessNetwork)) {
             if (mIsEmergencyBarred) {
-                logi("sgetSelectablePsNetworkType barred");
+                logi("getSelectablePsNetworkType barred");
                 return UNKNOWN;
             }
             if (accessNetwork == NGRAN) {
@@ -1100,6 +1129,36 @@
         }
     }
 
+    private boolean allowEmergencyCalls(EmergencyRegResult regResult) {
+        if (mModemCount < 2) return true;
+        if (regResult == null) {
+            loge("allowEmergencyCalls null regResult");
+            return true;
+        }
+
+        String iso = regResult.getIso();
+        if (sAllowOnlyWithSimReady.contains(iso)) {
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            int simState = tm.getSimState(getSlotId());
+            if (simState != TelephonyManager.SIM_STATE_READY) {
+                logi("allowEmergencyCalls not ready, simState=" + simState + ", iso=" + iso);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private void terminateSelectionPermanentlyForSlot() {
+        logi("terminateSelectionPermanentlyForSlot");
+        mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.EMERGENCY_PERM_FAILURE);
+
+        if (mIsScanRequested && mCancelSignal != null) {
+            mCancelSignal.cancel();
+            mCancelSignal = null;
+        }
+    }
+
     private static String arrayToString(int[] intArray, IntFunction<String> func) {
         int length = intArray.length;
         StringBuilder sb = new StringBuilder("{");
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 3d6a0aa..ba064e5 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.services.telephony;
 
+import static android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE;
+import static android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE;
 import static android.telephony.DisconnectCause.NOT_DISCONNECTED;
 import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
 import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
@@ -55,6 +57,7 @@
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.telephony.DomainSelectionService;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
@@ -76,6 +79,7 @@
 import com.android.internal.telephony.PhoneInternalInterface.DialArgs;
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.data.PhoneSwitcher;
+import com.android.internal.telephony.domainselection.DomainSelectionConnection;
 import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.domainselection.EmergencyCallDomainSelectionConnection;
 import com.android.internal.telephony.domainselection.NormalCallDomainSelectionConnection;
@@ -864,7 +868,8 @@
         setPhones(phones);
         c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
 
-        mTestConnectionService.retryOutgoingOriginalConnection(c, false /*isPermanentFailure*/);
+        mTestConnectionService.retryOutgoingOriginalConnection(c,
+                c.getPhone(), false /*isPermanentFailure*/);
 
         // We never need to be notified in telecom that the PhoneAccount has changed, because it
         // was redialed on the same slot
@@ -895,7 +900,8 @@
         setPhones(phones);
         c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
 
-        mTestConnectionService.retryOutgoingOriginalConnection(c, true /*isPermanentFailure*/);
+        mTestConnectionService.retryOutgoingOriginalConnection(c,
+                c.getPhone(), true /*isPermanentFailure*/);
 
         // We never need to be notified in telecom that the PhoneAccount has changed, because it
         // was never redialed
@@ -936,7 +942,8 @@
         doReturn(PHONE_ACCOUNT_HANDLE_2).when(mPhoneUtilsProxy).makePstnPhoneAccountHandle(
                 slot1Phone);
 
-        mTestConnectionService.retryOutgoingOriginalConnection(c, false /*isPermanentFailure*/);
+        mTestConnectionService.retryOutgoingOriginalConnection(c,
+                c.getPhone(), false /*isPermanentFailure*/);
 
         // The cache should still contain all of the Phones, since it was a temporary failure.
         assertEquals(2, mTestConnectionService.mEmergencyRetryCache.second.size());
@@ -977,7 +984,8 @@
         doReturn(PHONE_ACCOUNT_HANDLE_2).when(mPhoneUtilsProxy).makePstnPhoneAccountHandle(
                 slot1Phone);
 
-        mTestConnectionService.retryOutgoingOriginalConnection(c, true /*isPermanentFailure*/);
+        mTestConnectionService.retryOutgoingOriginalConnection(c,
+                c.getPhone(), true /*isPermanentFailure*/);
 
         // The cache should only contain the slot1Phone.
         assertEquals(1, mTestConnectionService.mEmergencyRetryCache.second.size());
@@ -1019,7 +1027,8 @@
                 slot1Phone);
 
         // First Temporary failure
-        mTestConnectionService.retryOutgoingOriginalConnection(c, false /*isPermanentFailure*/);
+        mTestConnectionService.retryOutgoingOriginalConnection(c,
+                c.getPhone(), false /*isPermanentFailure*/);
         // Set the Phone to the new phone that was just used to dial.
         c.setMockPhone(slot1Phone);
         // The cache should still contain all of the Phones, since it was a temporary failure.
@@ -1027,7 +1036,8 @@
         // Make sure slot 1 is next in the queue.
         assertEquals(slot1Phone, mTestConnectionService.mEmergencyRetryCache.second.peek());
         // Second Temporary failure
-        mTestConnectionService.retryOutgoingOriginalConnection(c, false /*isPermanentFailure*/);
+        mTestConnectionService.retryOutgoingOriginalConnection(c,
+                c.getPhone(), false /*isPermanentFailure*/);
         // Set the Phone to the new phone that was just used to dial.
         c.setMockPhone(slot0Phone);
         // The cache should still contain all of the Phones, since it was a temporary failure.
@@ -1074,7 +1084,8 @@
                 slot1Phone);
 
         // First Permanent failure
-        mTestConnectionService.retryOutgoingOriginalConnection(c, true /*isPermanentFailure*/);
+        mTestConnectionService.retryOutgoingOriginalConnection(c,
+                c.getPhone(), true /*isPermanentFailure*/);
         // Set the Phone to the new phone that was just used to dial.
         c.setMockPhone(slot1Phone);
         // The cache should only contain one phone
@@ -1082,7 +1093,8 @@
         // Make sure slot 1 is next in the queue.
         assertEquals(slot1Phone, mTestConnectionService.mEmergencyRetryCache.second.peek());
         // Second Permanent failure
-        mTestConnectionService.retryOutgoingOriginalConnection(c, true /*isPermanentFailure*/);
+        mTestConnectionService.retryOutgoingOriginalConnection(c,
+                c.getPhone(), true /*isPermanentFailure*/);
         // The cache should be empty
         assertEquals(true, mTestConnectionService.mEmergencyRetryCache.second.isEmpty());
 
@@ -1631,6 +1643,98 @@
     }
 
     @Test
+    public void testOnSelectionTerminatedPerm() throws Exception {
+        setupForCallTest();
+
+        doReturn(mEmergencyCallDomainSelectionConnection).when(mDomainSelectionResolver)
+                .getDomainSelectionConnection(any(), anyInt(), eq(true));
+        doReturn(mPhone0).when(mEmergencyCallDomainSelectionConnection).getPhone();
+        doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
+
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        doReturn(mImsPhone).when(mPhone0).getImsPhone();
+
+        mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+                createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+                        TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+        ArgumentCaptor<DomainSelectionConnection.DomainSelectionConnectionCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        DomainSelectionConnection.DomainSelectionConnectionCallback.class);
+
+        verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(
+                any(), callbackCaptor.capture());
+
+        DomainSelectionConnection.DomainSelectionConnectionCallback callback =
+                callbackCaptor.getValue();
+
+        assertNotNull(callback);
+
+        EmergencyCallDomainSelectionConnection ecdsc =
+                Mockito.mock(EmergencyCallDomainSelectionConnection.class);
+        doReturn(ecdsc).when(mDomainSelectionResolver)
+                .getDomainSelectionConnection(any(), anyInt(), eq(true));
+
+        callback.onSelectionTerminated(EMERGENCY_PERM_FAILURE);
+
+        ArgumentCaptor<DomainSelectionService.SelectionAttributes> attrCaptor =
+                ArgumentCaptor.forClass(
+                        DomainSelectionService.SelectionAttributes.class);
+
+        verify(ecdsc).createEmergencyConnection(attrCaptor.capture(), any());
+
+        DomainSelectionService.SelectionAttributes attr = attrCaptor.getValue();
+
+        assertEquals(mPhone1.getPhoneId(), attr.getSlotId());
+    }
+
+    @Test
+    public void testOnSelectionTerminatedTemp() throws Exception {
+        setupForCallTest();
+
+        doReturn(mEmergencyCallDomainSelectionConnection).when(mDomainSelectionResolver)
+                .getDomainSelectionConnection(any(), anyInt(), eq(true));
+        doReturn(mPhone0).when(mEmergencyCallDomainSelectionConnection).getPhone();
+        doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
+
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        doReturn(mImsPhone).when(mPhone0).getImsPhone();
+
+        mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+                createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+                        TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+        ArgumentCaptor<DomainSelectionConnection.DomainSelectionConnectionCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        DomainSelectionConnection.DomainSelectionConnectionCallback.class);
+
+        verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(
+                any(), callbackCaptor.capture());
+
+        DomainSelectionConnection.DomainSelectionConnectionCallback callback =
+                callbackCaptor.getValue();
+
+        assertNotNull(callback);
+
+        EmergencyCallDomainSelectionConnection ecdsc =
+                Mockito.mock(EmergencyCallDomainSelectionConnection.class);
+        doReturn(ecdsc).when(mDomainSelectionResolver)
+                .getDomainSelectionConnection(any(), anyInt(), eq(true));
+
+        callback.onSelectionTerminated(EMERGENCY_TEMP_FAILURE);
+
+        ArgumentCaptor<DomainSelectionService.SelectionAttributes> attrCaptor =
+                ArgumentCaptor.forClass(
+                        DomainSelectionService.SelectionAttributes.class);
+
+        verify(ecdsc).createEmergencyConnection(attrCaptor.capture(), any());
+
+        DomainSelectionService.SelectionAttributes attr = attrCaptor.getValue();
+
+        assertEquals(mPhone1.getPhoneId(), attr.getSlotId());
+    }
+
+    @Test
     public void testDomainSelectionWithMmiCode() {
         //UT domain selection should not be handled by new domain selector.
         doNothing().when(mContext).startActivity(any());
diff --git a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
index c50c6b5..27b3f0a 100644
--- a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
@@ -80,6 +80,7 @@
 import android.telephony.BarringInfo;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentityLte;
+import android.telephony.DisconnectCause;
 import android.telephony.DomainSelectionService;
 import android.telephony.DomainSelectionService.SelectionAttributes;
 import android.telephony.EmergencyRegResult;
@@ -1106,6 +1107,27 @@
         verify(mTransportSelectorCallback, times(1)).onWlanSelected();
     }
 
+    @Test
+    public void testDualSimInvalidSubscription() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
+                .when(mTelephonyManager).getSimState(anyInt());
+
+        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(1))
+                .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_PERM_FAILURE));
+    }
+
     private void createSelector(int subId) throws Exception {
         mDomainSelector = new EmergencyCallDomainSelector(
                 mContext, SLOT_0, subId, mHandlerThread.getLooper(),
@@ -1174,9 +1196,19 @@
             @NetworkRegistrationInfo.Domain int domain,
             boolean isVopsSupported, boolean isEmcBearerSupported, int emc, int emf,
             @NonNull String mcc, @NonNull String mnc) {
+        return getEmergencyRegResult(accessNetwork, regState, domain, isVopsSupported,
+                isEmcBearerSupported, emc, emf, mcc, mnc, "");
+    }
+
+    private static EmergencyRegResult getEmergencyRegResult(
+            @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
+            @NetworkRegistrationInfo.RegistrationState int regState,
+            @NetworkRegistrationInfo.Domain int domain,
+            boolean isVopsSupported, boolean isEmcBearerSupported, int emc, int emf,
+            @NonNull String mcc, @NonNull String mnc, @NonNull String iso) {
         return new EmergencyRegResult(accessNetwork, regState,
                 domain, isVopsSupported, isEmcBearerSupported,
-                emc, emf, mcc, mnc, "");
+                emc, emf, mcc, mnc, iso);
     }
 
     private static PersistableBundle getDefaultPersistableBundle() {