Change carrier name shown No Service to ECO

In the case where at least one SIM supports EC (either it has service or
it supports them), we replace "No service" carrier name into "Emergency calls
only". This does not replace the string if the sub is already mark as
ECO.

Test: manual
Test: atest KeyguardUpdateMonitorTest
Test: atest CarrierTextControllerTest
Fixes: 130857483
Fixes: 133201131
Fixes: 132291669
Change-Id: Ia0ba2076c9750a8bb081994291753dbea68a2270
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 2090748..c26fdc2 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -324,13 +324,25 @@
         final CharSequence[] carrierNames = new CharSequence[numSubs];
         if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
 
+        boolean anySimEmergency = mKeyguardUpdateMonitor.isAnySimEmergencyAble();
         for (int i = 0; i < numSubs; i++) {
             int subId = subs.get(i).getSubscriptionId();
             carrierNames[i] = "";
             subsIds[i] = subId;
             subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
             IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
+            ServiceState s = mKeyguardUpdateMonitor.getServiceState(subId);
             CharSequence carrierName = subs.get(i).getCarrierName();
+            // If this sub is showing No service but at least one slot currently supports emergency
+            // calls, it should replace it by Emergency calls only
+            if (s != null && s.getState() != ServiceState.STATE_IN_SERVICE && !s.isEmergencyOnly()
+                    && anySimEmergency) {
+                carrierName = getContext().getText(
+                        com.android.internal.R.string.emergency_calls_only);
+                if (DEBUG) {
+                    Log.d(TAG, "Subscription " + subId + "switched to ECO");
+                }
+            }
             CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
             if (DEBUG) {
                 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
@@ -340,16 +352,15 @@
                 carrierNames[i] = carrierTextForSimState;
             }
             if (simState == IccCardConstants.State.READY) {
-                ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
-                if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
+                if (s != null && s.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
                     // hack for WFC (IWLAN) not turning off immediately once
                     // Wi-Fi is disassociated or disabled
-                    if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+                    if (s.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
                             || (mWifiManager.isWifiEnabled()
                             && mWifiManager.getConnectionInfo() != null
                             && mWifiManager.getConnectionInfo().getBSSID() != null)) {
                         if (DEBUG) {
-                            Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
+                            Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + s);
                         }
                         anySimReadyAndInService = true;
                     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 6a4dbc8d..446366b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -195,6 +195,12 @@
     HashMap<Integer, SimData> mSimDatas = new HashMap<Integer, SimData>();
     HashMap<Integer, ServiceState> mServiceStates = new HashMap<Integer, ServiceState>();
 
+    /**
+     * Support up to 3 slots which is what's supported by {@link TelephonyManager#getPhoneCount}
+     */
+    private static final int SIM_SLOTS = 3;
+    private final ServiceState[] mServiceStatesBySlot = new ServiceState[SIM_SLOTS];
+
     private int mRingMode;
     private int mPhoneState;
     private boolean mKeyguardIsVisible;
@@ -326,7 +332,7 @@
                     handleAirplaneModeChanged();
                     break;
                 case MSG_SERVICE_STATE_CHANGE:
-                    handleServiceStateChange(msg.arg1, (ServiceState) msg.obj);
+                    handleServiceStateChange(msg.arg1, msg.arg2, (ServiceState) msg.obj);
                     break;
                 case MSG_SCREEN_TURNED_ON:
                     handleScreenTurnedOn();
@@ -1038,12 +1044,13 @@
                 ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
                 int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+                int slotId = intent.getIntExtra(PhoneConstants.SLOT_KEY, -1);
                 if (DEBUG) {
                     Log.v(TAG, "action " + action + " serviceState=" + serviceState + " subId="
                             + subId);
                 }
-                mHandler.sendMessage(
-                        mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
+                mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, slotId, serviceState)
+                        .sendToTarget();
             } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(
                     action)) {
                 mHandler.sendEmptyMessage(MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED);
@@ -2042,6 +2049,14 @@
      */
     @VisibleForTesting
     void handleServiceStateChange(int subId, ServiceState serviceState) {
+        handleServiceStateChange(subId, -1, serviceState);
+    }
+
+    /**
+     * Handle {@link #MSG_SERVICE_STATE_CHANGE}
+     */
+    @VisibleForTesting
+    void handleServiceStateChange(int subId, int slotId, ServiceState serviceState) {
         if (DEBUG) {
             Log.d(TAG,
                     "handleServiceStateChange(subId=" + subId + ", serviceState=" + serviceState);
@@ -2055,6 +2070,7 @@
         }
 
         mServiceStates.put(subId, serviceState);
+        if (slotId >= 0 && slotId < SIM_SLOTS) mServiceStatesBySlot[slotId] = serviceState;
 
         for (int j = 0; j < mCallbacks.size(); j++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
@@ -2280,6 +2296,21 @@
         return mServiceStates.get(subId);
     }
 
+    /**
+     * @return true iff at least one slot currently supports emergency calls
+     */
+    public boolean isAnySimEmergencyAble() {
+        for (int i = 0; i < SIM_SLOTS; i++) {
+            ServiceState s = mServiceStatesBySlot[i];
+            if (s != null) {
+                if (s.getState() == ServiceState.STATE_IN_SERVICE || s.isEmergencyOnly()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     public void clearBiometricRecognized() {
         mUserFingerprintAuthenticated.clear();
         mUserFaceAuthenticated.clear();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index db45ad78..fa81e40 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -36,6 +37,7 @@
 import android.net.ConnectivityManager;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
+import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -43,6 +45,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import com.android.internal.R;
 import com.android.internal.telephony.IccCardConstants;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
@@ -56,7 +59,6 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 
 @SmallTest
@@ -68,6 +70,7 @@
     private static final String TEST_CARRIER = "TEST_CARRIER";
     private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
     private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24";
+    private static final String EMERGENCY = "Emergency";
     private static final int TEST_CARRIER_ID = 1;
     private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(0, "", 0,
             TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
@@ -106,6 +109,8 @@
         mContext.addMockSystemService(ConnectivityManager.class, mConnectivityManager);
         mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
         mContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.string.emergency_calls_only, EMERGENCY);
         mDependency.injectMockDependency(WakefulnessLifecycle.class);
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                 new Handler(mTestableLooper.getLooper()));
@@ -190,8 +195,6 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
 
-        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
-
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
                         CarrierTextController.CarrierTextCallbackInfo.class);
@@ -214,8 +217,6 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
 
-        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
-
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
                         CarrierTextController.CarrierTextCallbackInfo.class);
@@ -259,8 +260,6 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
 
-        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
-
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
                         CarrierTextController.CarrierTextCallbackInfo.class);
@@ -284,8 +283,6 @@
                 .thenReturn(IccCardConstants.State.NOT_READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
 
-        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
-
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
                         CarrierTextController.CarrierTextCallbackInfo.class);
@@ -309,8 +306,6 @@
                 .thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
 
-        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
-
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
                         CarrierTextController.CarrierTextCallbackInfo.class);
@@ -335,7 +330,6 @@
                 .thenReturn(IccCardConstants.State.NOT_READY)
                 .thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
-        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
@@ -358,7 +352,6 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt()))
             .thenReturn(IccCardConstants.State.READY);
 
-        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
         mCarrierTextController.updateDisplayOpportunisticSubscriptionCarrierText(true);
         when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
 
@@ -373,6 +366,127 @@
         assertEquals(TEST_CARRIER_2, captor.getValue().carrierText);
     }
 
+    @Test
+    public void testCarrierText_replaceOutOfServiceWithEmergency() {
+        reset(mCarrierTextCallback);
+
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+
+        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
+                .thenReturn(IccCardConstants.State.READY);
+        ServiceState s = mock(ServiceState.class);
+        when(mKeyguardUpdateMonitor.getServiceState(anyInt())).thenReturn(s);
+        when(s.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+
+        when(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).thenReturn(true);
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(1, captor.getValue().listOfCarriers.length);
+        assertEquals(EMERGENCY, captor.getValue().listOfCarriers[0]);
+    }
+
+    @Test
+    public void testCarrierText_replaceOutOfServiceWithEmergencyOnlyInNoService() {
+        reset(mCarrierTextCallback);
+
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION_2);
+        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+
+        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
+                .thenReturn(IccCardConstants.State.READY);
+        ServiceState sInService = mock(ServiceState.class);
+        ServiceState sOutOfService = mock(ServiceState.class);
+        when(mKeyguardUpdateMonitor.getServiceState(anyInt()))
+                .thenReturn(sInService)
+                .thenReturn(sOutOfService);
+        when(sInService.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        when(sOutOfService.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+
+        when(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).thenReturn(true);
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(2, captor.getValue().listOfCarriers.length);
+        assertEquals(TEST_CARRIER, captor.getValue().listOfCarriers[0]);
+        assertEquals(EMERGENCY, captor.getValue().listOfCarriers[1]);
+    }
+
+    @Test
+    public void testCarrierText_dontReplaceWithEmergencyIfNotAble() {
+        reset(mCarrierTextCallback);
+
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION_2);
+        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+
+        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
+                .thenReturn(IccCardConstants.State.READY);
+        ServiceState sOutOfService = mock(ServiceState.class);
+        when(mKeyguardUpdateMonitor.getServiceState(anyInt())).thenReturn(sOutOfService);
+        when(sOutOfService.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+
+        when(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).thenReturn(false);
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(2, captor.getValue().listOfCarriers.length);
+        assertEquals(TEST_CARRIER, captor.getValue().listOfCarriers[0]);
+        assertEquals(TEST_CARRIER_2, captor.getValue().listOfCarriers[1]);
+    }
+
+    @Test
+    public void testCarrierText_dontReplaceWithEmergencyIfAlreadyEmergency() {
+        reset(mCarrierTextCallback);
+
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+
+        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
+                .thenReturn(IccCardConstants.State.READY);
+        ServiceState sOutOfService = mock(ServiceState.class);
+        when(mKeyguardUpdateMonitor.getServiceState(anyInt())).thenReturn(sOutOfService);
+        when(sOutOfService.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(sOutOfService.isEmergencyOnly()).thenReturn(true);
+
+        when(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).thenReturn(false);
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(1, captor.getValue().listOfCarriers.length);
+        assertEquals(TEST_CARRIER, captor.getValue().listOfCarriers[0]);
+    }
+
     public static class TestCarrierTextController extends CarrierTextController {
         private KeyguardUpdateMonitor mKUM;
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 6208ab8..3a3cbad 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
@@ -361,6 +362,52 @@
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
+    @Test
+    public void testAnySimEmergency_allSimsInService() {
+        ServiceState s0 = mock(ServiceState.class);
+        when(s0.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
+        mKeyguardUpdateMonitor.handleServiceStateChange(0, 0, s0);
+        assertThat(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).isTrue();
+    }
+
+    @Test
+    public void testAnySimEmergency_someSimsInServiceOthersNotECC() {
+        ServiceState s0 = mock(ServiceState.class);
+        when(s0.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        ServiceState s1 = mock(ServiceState.class);
+        when(s1.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
+        mKeyguardUpdateMonitor.handleServiceStateChange(0, 0, s0);
+        mKeyguardUpdateMonitor.handleServiceStateChange(0, 1, s1);
+        assertThat(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).isTrue();
+    }
+
+    @Test
+    public void testAnySimEmergency_someSimsEmergencyCapable() {
+        ServiceState s0 = mock(ServiceState.class);
+        when(s0.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+        ServiceState s1 = mock(ServiceState.class);
+        when(s1.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(s1.isEmergencyOnly()).thenReturn(true);
+
+        mKeyguardUpdateMonitor.handleServiceStateChange(0, 0, s0);
+        mKeyguardUpdateMonitor.handleServiceStateChange(0, 1, s1);
+        assertThat(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).isTrue();
+    }
+
+    @Test
+    public void testAnySimEmergency_noEmergencyCapable() {
+        ServiceState s0 = mock(ServiceState.class);
+        when(s0.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+        ServiceState s1 = mock(ServiceState.class);
+        when(s1.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+
+        mKeyguardUpdateMonitor.handleServiceStateChange(0, 0, s0);
+        mKeyguardUpdateMonitor.handleServiceStateChange(0, 1, s1);
+        assertThat(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).isFalse();
+    }
+
     private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
         int subscription = simInited
                 ? 1/* mock subid=1 */ : SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE;