Add cross stack redialing during emergency call

If an emergency call is initiated during a non-emergency NTN session,
the subscription currently in progress with the emergency call becomes
inactive and the subscription in other stack becomes active. Due to this
situation, an emergency call is connected through other stack about 3
minutes later.
This change is to ensure that when the above situation occurs, the
emergency call can be connected quickly by determining whether an
emergency call is possible to other stack at an early stage of the
domain selection procedure.

Bug: 389190613
Flag: com.android.internal.telephony.flags.perform_cross_stack_redial_check_for_emergency_call
Test: atest EmergencyStateTrackerTest
Test: manual (verified quick cross stack redialing during emergency call
while non-emergency NTN session is in progress)
Test: manual (verified emergency call using test emergency number)

Change-Id: I86f59b4718e93f041b22621e45e22b60152b7636
diff --git a/flags/domainselection.aconfig b/flags/domainselection.aconfig
index 29b278f..88a6e9e 100644
--- a/flags/domainselection.aconfig
+++ b/flags/domainselection.aconfig
@@ -71,3 +71,14 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+# OWNER=hwangoo TARGET=25Q2
+flag {
+    name: "perform_cross_stack_redial_check_for_emergency_call"
+    namespace: "telephony"
+    description: "This flag performs a quick cross stack redial if the subscription is being invalid and an exception occurs, while an emergency call is in progress."
+    bug:"389190613"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
index 3c95d77..ee00e45 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
@@ -312,8 +312,24 @@
                     maybeNotifyTransportChangeCompleted(emergencyType, false);
 
                     if (emergencyType == EMERGENCY_TYPE_CALL) {
-                        setIsInEmergencyCall(true);
-                        completeEmergencyMode(emergencyType);
+                        // If the emergency registration result(mLastEmergencyRegistrationResult) is
+                        // null, it means that the emergency mode is not set properly on the modem.
+                        // Therefore, based on the emergency registration result and current
+                        // subscription status, the current phone is not available for an emergency
+                        // call, so we check if an emergency call is possible through cross stack.
+                        if (mFeatureFlags.performCrossStackRedialCheckForEmergencyCall()
+                                && mLastEmergencyRegistrationResult == null
+                                && mPhone != null
+                                && !SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())
+                                && needToSwitchPhone(mPhone)) {
+                            Rlog.i(TAG, "setEmergencyMode failed: need to switch stacks.");
+                            mEmergencyMode = MODE_EMERGENCY_NONE;
+                            completeEmergencyMode(emergencyType,
+                                    DisconnectCause.EMERGENCY_PERM_FAILURE);
+                        } else {
+                            setIsInEmergencyCall(true);
+                            completeEmergencyMode(emergencyType);
+                        }
 
                         // Case 1) When the emergency call is setting the emergency mode and
                         // the emergency SMS is being sent, completes the SMS future also.
@@ -861,23 +877,25 @@
 
     private void completeEmergencyMode(@EmergencyType int emergencyType,
             @DisconnectCauses int result) {
+        CompletableFuture<Integer> emergencyModeFuture = null;
+
         if (emergencyType == EMERGENCY_TYPE_CALL) {
-            if (mCallEmergencyModeFuture != null && !mCallEmergencyModeFuture.isDone()) {
-                mCallEmergencyModeFuture.complete(result);
-            }
+            emergencyModeFuture = mCallEmergencyModeFuture;
 
             if (result != DisconnectCause.NOT_DISCONNECTED) {
                 clearEmergencyCallInfo();
             }
         } else if (emergencyType == EMERGENCY_TYPE_SMS) {
-            if (mSmsEmergencyModeFuture != null && !mSmsEmergencyModeFuture.isDone()) {
-                mSmsEmergencyModeFuture.complete(result);
-            }
+            emergencyModeFuture = mSmsEmergencyModeFuture;
 
             if (result != DisconnectCause.NOT_DISCONNECTED) {
                 clearEmergencySmsInfo();
             }
         }
+
+        if (emergencyModeFuture != null && !emergencyModeFuture.isDone()) {
+            emergencyModeFuture.complete(result);
+        }
     }
 
     /**
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
index 8fc0684..2909966 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
@@ -21,6 +21,7 @@
 import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS_PS;
 import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
 import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
 import static android.telephony.TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL;
 import static android.telephony.TelephonyManager.EMERGENCY_CALLBACK_MODE_SMS;
 
@@ -74,10 +75,12 @@
 
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.GsmCdmaPhone;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.data.PhoneSwitcher;
@@ -107,6 +110,12 @@
     private static final int TEST_WAIT_FOR_IN_SERVICE_TIMEOUT_MS = 3000;
     private static final EmergencyRegistrationResult E_REG_RESULT = new EmergencyRegistrationResult(
             EUTRAN, REGISTRATION_STATE_HOME, DOMAIN_CS_PS, true, true, 0, 1, "001", "01", "US");
+    private static final EmergencyRegistrationResult UNKNOWN_E_REG_RESULT =
+            new EmergencyRegistrationResult(
+                    AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+                    REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING,
+                    NetworkRegistrationInfo.DOMAIN_UNKNOWN,
+                    false, false, 0, 0, "", "", "");
 
     @Mock EmergencyStateTracker.PhoneFactoryProxy mPhoneFactoryProxy;
     @Mock EmergencyStateTracker.PhoneSwitcherProxy mPhoneSwitcherProxy;
@@ -125,6 +134,7 @@
                 .when(mTelephonyManagerProxy).getSimState(anyInt());
         doReturn(true).when(mFeatureFlags).emergencyCallbackModeNotification();
         doReturn(true).when(mFeatureFlags).disableEcbmBasedOnRat();
+        doReturn(true).when(mFeatureFlags).performCrossStackRedialCheckForEmergencyCall();
     }
 
     @After
@@ -3491,6 +3501,74 @@
         verify(phone1, never()).setEmergencyMode(anyInt(), any(Message.class));
     }
 
+    @Test
+    @SmallTest
+    public void testSwitchPhoneWhenNonEmergencyNtnSessionInProgress() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(
+                phone0, UNKNOWN_E_REG_RESULT, RILConstants.INTERNAL_ERR);
+        // Start an emergency call over Phone0
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+
+        Phone phone1 = getPhone(1);
+        // Phone0: Disable NTN
+        doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+                .when(phone0).getSubId();
+        doReturn(TelephonyManager.SIM_STATE_ABSENT)
+                .when(mTelephonyManagerProxy).getSimState(eq(0));
+        // Phone1: Enable TN
+        doReturn(2).when(phone1).getSubId();
+        doReturn(TelephonyManager.SIM_STATE_READY)
+                .when(mTelephonyManagerProxy).getSimState(eq(1));
+
+        processAllMessages();
+
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(future.isDone());
+        // Expect: DisconnectCause#EMERGENCY_PERM_FAILURE
+        assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
+                Integer.valueOf(DisconnectCause.EMERGENCY_PERM_FAILURE));
+    }
+
+    @Test
+    @SmallTest
+    public void testSwitchPhoneWhenNonEmergencyNtnSessionInProgressAndFlagDisabled() {
+        doReturn(false).when(mFeatureFlags).performCrossStackRedialCheckForEmergencyCall();
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(
+                phone0, UNKNOWN_E_REG_RESULT, RILConstants.INTERNAL_ERR);
+        // Start an emergency call over Phone0
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+
+        Phone phone1 = getPhone(1);
+        // Phone0: Disable NTN
+        doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+                .when(phone0).getSubId();
+        doReturn(TelephonyManager.SIM_STATE_ABSENT)
+                .when(mTelephonyManagerProxy).getSimState(eq(0));
+        // Phone1: Enable TN
+        doReturn(2).when(phone1).getSubId();
+        doReturn(TelephonyManager.SIM_STATE_READY)
+                .when(mTelephonyManagerProxy).getSimState(eq(1));
+
+        processAllMessages();
+
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertTrue(future.isDone());
+        // Expect: DisconnectCause#NOT_DISCONNECTED
+        assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+    }
+
     private EmergencyStateTracker setupEmergencyStateTracker(
             boolean isSuplDdsSwitchRequiredForEmergencyCall) {
         doReturn(mPhoneSwitcher).when(mPhoneSwitcherProxy).getPhoneSwitcher();
@@ -3571,6 +3649,17 @@
         }).when(phone).setEmergencyMode(anyInt(), any(Message.class));
     }
 
+    private void setUpAsyncResultForSetEmergencyMode(Phone phone,
+            EmergencyRegistrationResult regResult, int rilError) {
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            final Message msg = (Message) args[1];
+            AsyncResult.forMessage(msg, regResult, CommandException.fromRilErrno(rilError));
+            msg.sendToTarget();
+            return null;
+        }).when(phone).setEmergencyMode(anyInt(), any(Message.class));
+    }
+
     private void setUpAsyncResultForExitEmergencyMode(Phone phone) {
         doAnswer((invocation) -> {
             Object[] args = invocation.getArguments();