De-bounce Operator Numeric From ServiceState

ServiceState changes can happen rapidly and cause
transient cases where the operator numeric becomes
invalid, but for brief service interruptions, there
is no need to assume that the locale has changed.

Furthermore, in case the modem is searching for service,
it may provide PLMN info briefly when it camps but then
lose that PLMN as it continues to search. Again, this
causes significant noise in the signal coming from
ServiceStateTracker that doesn't actually imply that
the phone has moved to a new country. To avoid constantly
going in-and-out of service based on SST changes, add
a 10 minute delay to clearing the info from SST.

Bug: 136036974
Test: atest LocaleTrackerTest
Change-Id: Icada0cda8e947460d9a4ca3bc18c6acfd62548d6
diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java
index dc8ad32..13d7dfb 100755
--- a/src/java/com/android/internal/telephony/LocaleTracker.java
+++ b/src/java/com/android/internal/telephony/LocaleTracker.java
@@ -73,6 +73,9 @@
     /** Event for incoming cell info */
     private static final int EVENT_RESPONSE_CELL_INFO = 5;
 
+    /** Event to fire if the operator from ServiceState is considered truly lost */
+    private static final int EVENT_OPERATOR_LOST = 6;
+
     // Todo: Read this from Settings.
     /** The minimum delay to get cell info from the modem */
     private static final long CELL_INFO_MIN_DELAY_MS = 2 * SECOND_IN_MILLIS;
@@ -85,6 +88,13 @@
     /** The delay for periodically getting cell info from the modem */
     private static final long CELL_INFO_PERIODIC_POLLING_DELAY_MS = 10 * MINUTE_IN_MILLIS;
 
+    /**
+     * The delay after the last time the device camped on a cell before declaring that the
+     * ServiceState's MCC information can no longer be used (and thus kicking in the CellInfo
+     * based tracking.
+     */
+    private static final long SERVICE_OPERATOR_LOST_DELAY_MS = 10 * MINUTE_IN_MILLIS;
+
     /** The maximum fail count to prevent delay time overflow */
     private static final int MAX_FAIL_COUNT = 30;
 
@@ -166,6 +176,11 @@
                 onSimCardStateChanged(msg.arg1);
                 break;
 
+            case EVENT_OPERATOR_LOST:
+                updateOperatorNumericImmediate("");
+                updateTrackingStatus();
+                break;
+
             default:
                 throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what);
         }
@@ -247,7 +262,7 @@
      *
      * @param state SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX.
      */
-    private synchronized void onSimCardStateChanged(int state) {
+    private void onSimCardStateChanged(int state) {
         mSimState = state;
         updateLocale();
         updateTrackingStatus();
@@ -270,8 +285,17 @@
      * @param operatorNumeric MCC/MNC of the operator
      */
     public void updateOperatorNumeric(String operatorNumeric) {
+        if (TextUtils.isEmpty(operatorNumeric)) {
+            sendMessageDelayed(obtainMessage(EVENT_OPERATOR_LOST), SERVICE_OPERATOR_LOST_DELAY_MS);
+        } else {
+            removeMessages(EVENT_OPERATOR_LOST);
+            updateOperatorNumericImmediate(operatorNumeric);
+        }
+    }
+
+    private void updateOperatorNumericImmediate(String operatorNumeric) {
         // Check if the operator numeric changes.
-        if (!Objects.equals(mOperatorNumeric, operatorNumeric)) {
+        if (!operatorNumeric.equals(mOperatorNumeric)) {
             String msg = "Operator numeric changes to \"" + operatorNumeric + "\"";
             if (DBG) log(msg);
             mLocalLog.log(msg);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
index a14129d..d161246 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
@@ -44,14 +44,19 @@
 import org.mockito.ArgumentCaptor;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 public class LocaleTrackerTest extends TelephonyTest {
 
     private static final String US_MCC = "310";
+    private static final String LIECHTENSTEIN_MCC = "295";
+
     private static final String FAKE_MNC = "123";
-    private static final String US_COUNTRY_CODE = "us";
+
     private static final String COUNTRY_CODE_UNAVAILABLE = "";
+    private static final String US_COUNTRY_CODE = "us";
+    private static final String LIECHTENSTEIN_COUNTRY_CODE = "li";
 
     private LocaleTracker mLocaleTracker;
 
@@ -156,11 +161,12 @@
     @Test
     @SmallTest
     public void testNoSim() throws Exception {
+        // updateOperatorNumeric("") will not trigger an instantaneous country change
         mLocaleTracker.updateOperatorNumeric("");
         sendGsmCellInfo();
         sendServiceState(ServiceState.STATE_EMERGENCY_ONLY);
         assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
-        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
+        verifyCountryCodeNotified(new String[]{US_COUNTRY_CODE});
         assertTrue(mLocaleTracker.isTracking());
     }
 
@@ -183,11 +189,11 @@
         verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
         assertFalse(mLocaleTracker.isTracking());
 
+        // updateOperatorNumeric("") will not trigger an instantaneous country change
         mLocaleTracker.updateOperatorNumeric("");
         waitForHandlerAction(mLocaleTracker, 100);
-        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
-        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE,
-                COUNTRY_CODE_UNAVAILABLE});
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
         sendServiceState(ServiceState.STATE_POWER_OFF);
         assertFalse(mLocaleTracker.isTracking());
     }
@@ -211,6 +217,70 @@
 
     @Test
     @SmallTest
+    public void testToggleAirplaneModeOosPlmn() throws Exception {
+        sendServiceState(ServiceState.STATE_POWER_OFF);
+        mLocaleTracker.updateOperatorNumeric("");
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE});
+        assertFalse(mLocaleTracker.isTracking());
+
+        // Override the setUp() function and return an empty list for CellInfo
+        doAnswer(invocation -> {
+            Message m = invocation.getArgument(1);
+            AsyncResult.forMessage(m, Collections.emptyList(), null);
+            m.sendToTarget();
+            return null; }).when(mPhone).requestCellInfoUpdate(any(), any());
+
+        sendServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertTrue(mLocaleTracker.isTracking());
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+
+        mLocaleTracker.updateOperatorNumeric(US_MCC + FAKE_MNC);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
+
+        mLocaleTracker.updateOperatorNumeric("");
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
+
+        mLocaleTracker.updateOperatorNumeric(LIECHTENSTEIN_MCC + FAKE_MNC);
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(LIECHTENSTEIN_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{
+                COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE, LIECHTENSTEIN_COUNTRY_CODE});
+    }
+
+    @Test
+    @SmallTest
+    public void testToggleAirplaneModeNoCellInfo() throws Exception {
+        sendServiceState(ServiceState.STATE_POWER_OFF);
+        mLocaleTracker.updateOperatorNumeric("");
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE});
+        assertFalse(mLocaleTracker.isTracking());
+
+        // Override the setUp() function and return an empty list for CellInfo
+        doAnswer(invocation -> {
+            Message m = invocation.getArgument(1);
+            AsyncResult.forMessage(m, Collections.emptyList(), null);
+            m.sendToTarget();
+            return null; }).when(mPhone).requestCellInfoUpdate(any(), any());
+
+        sendServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertTrue(mLocaleTracker.isTracking());
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+    }
+
+
+    @Test
+    @SmallTest
     public void testGetCellInfoDelayTime() throws Exception {
         assertEquals(2000, LocaleTracker.getCellInfoDelayTime(0));
         assertEquals(2000, LocaleTracker.getCellInfoDelayTime(1));