Implement maximum cellular timeout to trigger VoWi-Fi emergency

To support Wifi redial logic change to improve UX.

Bug: 265819822
Test: atest EmergencyCallDomainSelectorTest
Change-Id: Ie29ff9ce265268a92dedbcbcdbd6e0716bd67e2c
diff --git a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
index 6848c9f..3388c97 100644
--- a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
@@ -42,6 +42,7 @@
 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL;
 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_SCAN_TIMER_SEC_INT;
 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT;
 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT;
 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL;
 import static android.telephony.CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE;
@@ -109,6 +110,8 @@
     @VisibleForTesting
     public static final int MSG_NETWORK_SCAN_TIMEOUT = 12;
     private static final int MSG_NETWORK_SCAN_RESULT = 13;
+    @VisibleForTesting
+    public static final int MSG_MAX_CELLULAR_TIMEOUT = 14;
 
     private static final int NOT_SUPPORTED = -1;
 
@@ -180,6 +183,7 @@
     private boolean mIsMonitoringConnectivity;
     private boolean mWiFiAvailable;
     private int mScanTimeout;
+    private int mMaxCellularTimeout;
     private int mMaxNumOfVoWifiTries;
     private boolean mVoWifiOverEmergencyPdn;
     private @CarrierConfigManager.ImsEmergency.EmergencyScanType int mPreferredNetworkScanType;
@@ -199,6 +203,8 @@
     private boolean mDomainSelected = false;
     /** Indicates whether the cross sim redialing timer has expired. */
     private boolean mCrossStackTimerExpired = false;
+    /** Indicates whether max cellular timer expired. */
+    private boolean mMaxCellularTimerExpired = false;
 
     /**
      * Indicates whether {@link #selectDomain(SelectionAttributes, TransportSelectionCallback)}
@@ -243,6 +249,10 @@
                 handleScanResult((EmergencyRegResult) msg.obj);
                 break;
 
+            case MSG_MAX_CELLULAR_TIMEOUT:
+                handleMaxCellularTimeout();
+                break;
+
             default:
                 super.handleMessage(msg);
                 break;
@@ -362,6 +372,18 @@
             }
         }
 
+        if (mMaxCellularTimerExpired) {
+            if (mLastTransportType == TRANSPORT_TYPE_WWAN
+                    && maybeDialOverWlan()) {
+                // Cellular call failed and max cellular search timer expired, so redial on Wi-Fi.
+                // If this VoWi-Fi fails, the timer shall be restarted on next reselectDomain().
+                return;
+            } else if (mLastTransportType == TRANSPORT_TYPE_WLAN) {
+                // Since VoWi-Fi failed, allow for requestScan to restart max cellular timer.
+                mMaxCellularTimerExpired = false;
+            }
+        }
+
         if (mLastTransportType == TRANSPORT_TYPE_WLAN) {
             // Dialing over Wi-Fi failed. Try scanning cellular networks.
             onWwanSelected(this::reselectDomainInternal);
@@ -477,6 +499,7 @@
                 KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL);
         mVoWifiRequiresCondition = b.getInt(KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT);
         mScanTimeout = b.getInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT) * 1000;
+        mMaxCellularTimeout = b.getInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT) * 1000;
         mMaxNumOfVoWifiTries = b.getInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT);
         mVoWifiOverEmergencyPdn = b.getBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL);
         mPreferredNetworkScanType = b.getInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT);
@@ -512,6 +535,7 @@
                 + ", preferImsOnCs=" + mPreferImsWhenCallsOnCs
                 + ", voWifiRequiresCondition=" + mVoWifiRequiresCondition
                 + ", scanTimeout=" + mScanTimeout
+                + ", maxCellularTimeout=" + mMaxCellularTimeout
                 + ", maxNumOfVoWifiTries=" + mMaxNumOfVoWifiTries
                 + ", voWifiOverEmergencyPdn=" + mVoWifiOverEmergencyPdn
                 + ", preferredScanType=" + carrierConfigNetworkScanTypeToString(
@@ -699,6 +723,9 @@
                 registerForConnectivityChanges();
             }
         }
+        if (!mMaxCellularTimerExpired && !hasMessages(MSG_MAX_CELLULAR_TIMEOUT)) {
+            startMaxCellularTimer();
+        }
     }
 
     /**
@@ -811,8 +838,33 @@
         return preferredNetworks;
     }
 
+    private void handleMaxCellularTimeout() {
+        logi("handleMaxCellularTimeout");
+        if (mVoWifiTrialCount >= mMaxNumOfVoWifiTries) {
+            logi("handleMaxCellularTimeout already tried maximum");
+            return;
+        }
+
+        mMaxCellularTimerExpired = true;
+
+        if (mDomainSelected) {
+            // Dialing is already requested.
+            logi("handleMaxCellularTimeout wait for reselectDomain");
+            return;
+        }
+
+        if (!maybeDialOverWlan()) {
+            logd("handleMaxCellularTimeout VoWi-Fi is not available");
+        }
+    }
+
     private void handleNetworkScanTimeout() {
-        logi("handleNetworkScanTimeout overEmergencyPdn=" + mVoWifiOverEmergencyPdn
+        logi("handleNetworkScanTimeout");
+        maybeDialOverWlan();
+    }
+
+    private boolean maybeDialOverWlan() {
+        logi("maybeDialOverWlan overEmergencyPdn=" + mVoWifiOverEmergencyPdn
                 + ", wifiAvailable=" + mWiFiAvailable);
         boolean available = mWiFiAvailable;
         if (mVoWifiOverEmergencyPdn) {
@@ -837,7 +889,7 @@
             available = isImsRegisteredWithVoiceCapability() && isImsRegisteredOverWifi();
         }
 
-        logi("handleNetworkScanTimeout VoWi-Fi available=" + available);
+        logi("maybeDialOverWlan VoWi-Fi available=" + available);
         if (available) {
             if (mCancelSignal != null) {
                 mCancelSignal.cancel();
@@ -845,6 +897,8 @@
             }
             onWlanSelected();
         }
+
+        return available;
     }
 
     /**
@@ -983,7 +1037,11 @@
     private boolean isEmcOverWifiSupported() {
         if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
             List<Integer> domains = getDomainPreference();
-            return domains.contains(DOMAIN_PS_NON_3GPP);
+            boolean ret = domains.contains(DOMAIN_PS_NON_3GPP);
+            logi("isEmcOverWifiSupported " + ret);
+            return ret;
+        } else {
+            logi("isEmcOverWifiSupported invalid subId");
         }
         return false;
     }
@@ -1184,6 +1242,8 @@
         mVoWifiTrialCount++;
         mTransportSelectorCallback.onWlanSelected(mVoWifiOverEmergencyPdn);
         mWwanSelectorCallback = null;
+        removeMessages(MSG_NETWORK_SCAN_TIMEOUT);
+        removeMessages(MSG_MAX_CELLULAR_TIMEOUT);
     }
 
     private void onWwanSelected(Runnable runnable) {
@@ -1251,6 +1311,19 @@
         }
     }
 
+    /** Starts the max cellular timer. */
+    private void startMaxCellularTimer() {
+        logd("startMaxCellularTimer tried=" + mVoWifiTrialCount
+                + ", max=" + mMaxNumOfVoWifiTries);
+        if (isEmcOverWifiSupported()
+                && (mMaxCellularTimeout > 0)
+                && (mVoWifiTrialCount < mMaxNumOfVoWifiTries)) {
+            logi("startMaxCellularTimer start timer");
+            sendEmptyMessageDelayed(MSG_MAX_CELLULAR_TIMEOUT, mMaxCellularTimeout);
+            registerForConnectivityChanges();
+        }
+    }
+
     private boolean allowEmergencyCalls(EmergencyRegResult regResult) {
         if (mModemCount < 2) return true;
         if (regResult == null) {
diff --git a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
index 72162b8..9be85ed 100644
--- a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
@@ -37,6 +37,7 @@
 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL;
 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_SCAN_TIMER_SEC_INT;
 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT;
 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT;
 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL;
 import static android.telephony.CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE;
@@ -52,6 +53,7 @@
 import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
 import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
 
+import static com.android.services.telephony.domainselection.EmergencyCallDomainSelector.MSG_MAX_CELLULAR_TIMEOUT;
 import static com.android.services.telephony.domainselection.EmergencyCallDomainSelector.MSG_NETWORK_SCAN_TIMEOUT;
 
 import static junit.framework.Assert.assertEquals;
@@ -1679,6 +1681,172 @@
         assertFalse(mAccessNetwork.contains(EUTRAN));
     }
 
+    @Test
+    public void testMaxCellularTimeout() throws Exception {
+        PersistableBundle bundle = getDefaultPersistableBundle();
+        bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
+        bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 20);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+        setupForHandleScanResult();
+
+        assertTrue(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+        assertTrue(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+        verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+
+        // Wi-Fi is connected.
+        mNetworkCallback.onAvailable(null);
+
+        // Max cellular timer expired
+        mDomainSelector.removeMessages(MSG_MAX_CELLULAR_TIMEOUT);
+        mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_MAX_CELLULAR_TIMEOUT));
+
+        assertFalse(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+        verify(mTransportSelectorCallback, times(1)).onWlanSelected(anyBoolean());
+    }
+
+
+    @Test
+    public void testMaxCellularTimeoutScanTimeout() throws Exception {
+        PersistableBundle bundle = getDefaultPersistableBundle();
+        bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
+        bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 20);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+        setupForHandleScanResult();
+
+        assertTrue(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+        assertTrue(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+        verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+
+        // Wi-Fi is connected.
+        mNetworkCallback.onAvailable(null);
+
+        // Scan timer expired
+        mDomainSelector.removeMessages(MSG_NETWORK_SCAN_TIMEOUT);
+        mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_NETWORK_SCAN_TIMEOUT));
+
+        assertFalse(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+        verify(mTransportSelectorCallback, times(1)).onWlanSelected(anyBoolean());
+    }
+
+    @Test
+    public void testMaxCellularTimeoutWhileDialingOnCellular() throws Exception {
+        PersistableBundle bundle = getDefaultPersistableBundle();
+        bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
+        bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 5);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+        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();
+
+        assertFalse(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+        assertFalse(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+        mDomainSelector.reselectDomain(attr);
+        processAllMessages();
+
+        assertTrue(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+        assertTrue(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+        // Wi-Fi is connected.
+        mNetworkCallback.onAvailable(null);
+        processAllMessages();
+
+        verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+
+        // Max cellular timer expired
+        mDomainSelector.removeMessages(MSG_MAX_CELLULAR_TIMEOUT);
+        mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_MAX_CELLULAR_TIMEOUT));
+        processAllMessages();
+
+        mDomainSelector.reselectDomain(attr);
+        processAllMessages();
+
+        assertFalse(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+        verify(mTransportSelectorCallback, times(1)).onWlanSelected(anyBoolean());
+    }
+
+    @Test
+    public void testMaxCellularTimeoutWileDialingOnWlan() throws Exception {
+        PersistableBundle bundle = getDefaultPersistableBundle();
+        bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
+        bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 20);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+        setupForHandleScanResult();
+
+        assertTrue(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+        assertTrue(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+        verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+
+        // Wi-Fi is connected.
+        mNetworkCallback.onAvailable(null);
+
+        // Network scan timer expired
+        mDomainSelector.removeMessages(MSG_NETWORK_SCAN_TIMEOUT);
+        mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_NETWORK_SCAN_TIMEOUT));
+
+        verify(mTransportSelectorCallback, times(1)).onWlanSelected(anyBoolean());
+        assertFalse(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+    }
+
+    @Test
+    public void testMaxCellularTimeoutWileDialingOnWlanAllowMultipleTries() throws Exception {
+        PersistableBundle bundle = getDefaultPersistableBundle();
+        bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
+        bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 20);
+        bundle.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, 2);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+        setupForHandleScanResult();
+
+        assertTrue(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+        assertTrue(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+        verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+
+        // Wi-Fi is connected.
+        mNetworkCallback.onAvailable(null);
+
+        // Network scan timer expired
+        mDomainSelector.removeMessages(MSG_NETWORK_SCAN_TIMEOUT);
+        mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_NETWORK_SCAN_TIMEOUT));
+
+        verify(mTransportSelectorCallback, times(1)).onWlanSelected(anyBoolean());
+        assertFalse(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+        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.reselectDomain(attr);
+        processAllMessages();
+
+        assertTrue(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+        // Max cellular timer expired
+        mDomainSelector.removeMessages(MSG_MAX_CELLULAR_TIMEOUT);
+        mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_MAX_CELLULAR_TIMEOUT));
+        processAllMessages();
+
+        verify(mTransportSelectorCallback, times(2)).onWlanSelected(anyBoolean());
+    }
+
     private void setupForScanListTest(PersistableBundle bundle) throws Exception {
         setupForScanListTest(bundle, false);
     }
@@ -1851,6 +2019,7 @@
         int voWifiRequiresCondition = VOWIFI_REQUIRES_NONE;
         int maxRetriesOverWiFi = 1;
         int cellularScanTimerSec = 10;
+        int maxCellularTimerSec = 0;
         boolean voWifiOverEmergencyPdn = false;
         int scanType = SCAN_TYPE_NO_PREFERENCE;
         boolean requiresImsRegistration = false;
@@ -1861,7 +2030,7 @@
         return getPersistableBundle(imsRats, csRats, imsRoamRats, csRoamRats,
                 domainPreference, roamDomainPreference, imsWhenVoiceOnCs,
                 voWifiRequiresCondition, maxRetriesOverWiFi, cellularScanTimerSec,
-                scanType, voWifiOverEmergencyPdn, requiresImsRegistration,
+                maxCellularTimerSec, scanType, voWifiOverEmergencyPdn, requiresImsRegistration,
                 requiresVoLteEnabled, ltePreferredAfterNrFailed, cdmaPreferredNumbers);
     }
 
@@ -1870,7 +2039,8 @@
             @Nullable int[] imsRoamRats, @Nullable int[] csRoamRats,
             @Nullable int[] domainPreference, @Nullable int[] roamDomainPreference,
             boolean imsWhenVoiceOnCs, int voWifiRequiresCondition,
-            int maxRetriesOverWiFi, int cellularScanTimerSec, int scanType,
+            int maxRetriesOverWiFi, int cellularScanTimerSec,
+            int maxCellularTimerSec, int scanType,
             boolean voWifiOverEmergencyPdn, boolean requiresImsRegistration,
             boolean requiresVoLteEnabled, boolean ltePreferredAfterNrFailed,
             @Nullable String[] cdmaPreferredNumbers) {
@@ -1905,6 +2075,7 @@
         bundle.putInt(KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT, voWifiRequiresCondition);
         bundle.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, maxRetriesOverWiFi);
         bundle.putInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT, cellularScanTimerSec);
+        bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, maxCellularTimerSec);
         bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, voWifiOverEmergencyPdn);
         bundle.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, scanType);
         bundle.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, requiresImsRegistration);