Have MultiSimSettingController listen to carrier config changes

When SIM is added or swapped, have MultiSimSettingController wait until
carrier config is loaded before evaluating whether to pop up SIM related
dialogs or disable grouped oppotunistic subscriptions. Because carrier
config may update opportunistic bit and groupUuid.

Bug: 136187220
Test: manual and unittest
Change-Id: Ife069855f1366c68e6359f7ecdcd92991546cf99
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index 11ad9a6..4206b5f 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -31,13 +31,16 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Handler;
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -51,6 +54,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -73,6 +77,7 @@
     private static final int EVENT_SUBSCRIPTION_INFO_CHANGED         = 4;
     private static final int EVENT_SUBSCRIPTION_GROUP_CHANGED        = 5;
     private static final int EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED = 6;
+    private static final int EVENT_CARRIER_CONFIG_CHANGED            = 7;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"PRIMARY_SUB_"},
@@ -118,6 +123,32 @@
     // the SIMs are newly inserted instead of being initialized.
     private boolean mSubInfoInitialized = false;
 
+    // mInitialHandling is to make sure we don't always ask user to re-select data SIM after reboot.
+    // After boot-up when things are firstly initialized (mSubInfoInitialized is changed to true
+    // and carrier configs are all loaded), we do a reEvaluateAll(). In the first reEvaluateAll(),
+    // mInitialHandling will be true and we won't pop up SIM select dialog.
+    private boolean mInitialHandling = true;
+
+    // Keep a record of which subIds have carrier config loaded. Length of the array is phone count.
+    // The index is phoneId, and value is subId. For example:
+    // If carrier config of subId 2 is loaded on phone 0,mCarrierConfigLoadedSubIds[0] = 2.
+    // Then if subId 2 is deactivated from phone 0, the value becomes INVALID,
+    // mCarrierConfigLoadedSubIds[0] = INVALID_SUBSCRIPTION_ID.
+    private int[] mCarrierConfigLoadedSubIds;
+
+    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+                int phoneId = intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX,
+                        SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+                int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+                notifyCarrierConfigChanged(phoneId, subId);
+            }
+        }
+    };
+
     /**
      * Return the singleton or create one if not existed.
      */
@@ -149,6 +180,15 @@
     public MultiSimSettingController(Context context, SubscriptionController sc) {
         mContext = context;
         mSubController = sc;
+
+        // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change.
+        final int phoneCount = ((TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE)).getPhoneCount();
+        mCarrierConfigLoadedSubIds = new int[phoneCount];
+        Arrays.fill(mCarrierConfigLoadedSubIds, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        context.registerReceiver(mIntentReceiver, new IntentFilter(
+                CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
     }
 
     /**
@@ -222,6 +262,11 @@
             case EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
                 onDefaultDataSettingChanged();
                 break;
+            case EVENT_CARRIER_CONFIG_CHANGED:
+                int phoneId = msg.arg1;
+                int subId = msg.arg2;
+                onCarrierConfigChanged(phoneId, subId);
+                break;
         }
     }
 
@@ -261,9 +306,7 @@
     private void onAllSubscriptionsLoaded() {
         if (DBG) log("onAllSubscriptionsLoaded");
         mSubInfoInitialized = true;
-        updateDefaults(/*init*/ true);
-        disableDataForNonDefaultNonOpportunisticSubscriptions();
-        deactivateGroupedOpportunisticSubscriptionIfNeeded();
+        reEvaluateAll();
     }
 
     /**
@@ -273,8 +316,58 @@
      */
     private void onSubscriptionsChanged() {
         if (DBG) log("onSubscriptionsChanged");
-        if (!mSubInfoInitialized) return;
-        updateDefaults(/*init*/ false);
+        reEvaluateAll();
+    }
+
+    /**
+     * Called when carrier config changes on any phone.
+     */
+    @VisibleForTesting
+    public void notifyCarrierConfigChanged(int phoneId, int subId) {
+        obtainMessage(EVENT_CARRIER_CONFIG_CHANGED, phoneId, subId).sendToTarget();
+    }
+
+    private void onCarrierConfigChanged(int phoneId, int subId) {
+        log("onCarrierConfigChanged phoneId " + phoneId + " subId " + subId);
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
+            loge("Carrier config change with invalid phoneId " + phoneId);
+            return;
+        }
+
+        mCarrierConfigLoadedSubIds[phoneId] = subId;
+        reEvaluateAll();
+    }
+
+    private boolean isCarrierConfigLoadedForAllSub() {
+        int[] activeSubIds = mSubController.getActiveSubIdList(false);
+        for (int activeSubId : activeSubIds) {
+            boolean isLoaded = false;
+            for (int configLoadedSub : mCarrierConfigLoadedSubIds) {
+                if (configLoadedSub == activeSubId) {
+                    isLoaded = true;
+                    break;
+                }
+            }
+            if (!isLoaded) {
+                if (DBG) log("Carrier config subId " + activeSubId + " is not loaded.");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Wait for subInfo initialization (after boot up) and carrier config load for all active
+     * subscriptions before re-evaluate multi SIM settings.
+     */
+    private boolean isReadyToReevaluate() {
+        return mSubInfoInitialized && isCarrierConfigLoadedForAllSub();
+    }
+
+    private void reEvaluateAll() {
+        if (!isReadyToReevaluate()) return;
+        updateDefaults();
         disableDataForNonDefaultNonOpportunisticSubscriptions();
         deactivateGroupedOpportunisticSubscriptionIfNeeded();
     }
@@ -353,12 +446,11 @@
      *    not a user settable value anymore.
      * 4) If non above is met, clear the default value to INVALID.
      *
-     * @param init whether the subscriptions are just initialized.
      */
-    private void updateDefaults(boolean init) {
+    private void updateDefaults() {
         if (DBG) log("updateDefaults");
 
-        if (!mSubInfoInitialized) return;
+        if (!isReadyToReevaluate()) return;
 
         List<SubscriptionInfo> activeSubInfos = mSubController
                 .getActiveSubscriptionInfoList(mContext.getOpPackageName());
@@ -372,7 +464,7 @@
             return;
         }
 
-        int change = updatePrimarySubListAndGetChangeType(activeSubInfos, init);
+        int change = updatePrimarySubListAndGetChangeType(activeSubInfos);
         if (DBG) log("[updateDefaultValues] change: " + change);
         if (change == PRIMARY_SUB_NO_CHANGE) return;
 
@@ -414,8 +506,7 @@
     }
 
     @PrimarySubChangeType
-    private int updatePrimarySubListAndGetChangeType(List<SubscriptionInfo> activeSubList,
-            boolean init) {
+    private int updatePrimarySubListAndGetChangeType(List<SubscriptionInfo> activeSubList) {
         // Update mPrimarySubList. Opportunistic subscriptions can't be default
         // data / voice / sms subscription.
         List<Integer> prevPrimarySubList = mPrimarySubList;
@@ -423,7 +514,10 @@
                 .map(info -> info.getSubscriptionId())
                 .collect(Collectors.toList());
 
-        if (init) return PRIMARY_SUB_INITIALIZED;
+        if (mInitialHandling) {
+            mInitialHandling = false;
+            return PRIMARY_SUB_INITIALIZED;
+        }
         if (mPrimarySubList.equals(prevPrimarySubList)) return PRIMARY_SUB_NO_CHANGE;
         if (mPrimarySubList.size() > prevPrimarySubList.size()) return PRIMARY_SUB_ADDED;
 
@@ -554,7 +648,7 @@
     }
 
     private void disableDataForNonDefaultNonOpportunisticSubscriptions() {
-        if (!mSubInfoInitialized) return;
+        if (!isReadyToReevaluate()) return;
 
         int defaultDataSub = mSubController.getDefaultDataSubId();
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
index 74bbb82..70ebfb9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
@@ -118,6 +118,7 @@
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1, mSubInfo2);
         doReturn(infoList).when(mSubControllerMock)
                 .getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1, 2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mPhones = new Phone[] {mPhoneMock1, mPhoneMock2};
         doReturn(mDataEnabledSettingsMock1).when(mPhoneMock1).getDataEnabledSettings();
@@ -175,6 +176,7 @@
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mPhoneMock2).getSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         // Mark subscription ready as false. The below sub info change should be ignored.
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
@@ -184,6 +186,7 @@
         verify(mSubControllerMock, never()).setDefaultSmsSubId(anyInt());
 
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
         waitABit();
 
         // Sub 1 should be default sub silently.
@@ -210,8 +213,10 @@
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mPhoneMock2).getSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
         waitABit();
 
         // Sub 1 should be default sub silently.
@@ -223,8 +228,10 @@
         doReturn(2).when(mPhoneMock1).getSubId();
         infoList = Arrays.asList(mSubInfo2);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 2);
         waitABit();
 
         // Sub 1 should be default sub silently.
@@ -244,8 +251,10 @@
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mPhoneMock2).getSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
         waitABit();
 
         // Sub 1 should be default sub silently.
@@ -262,8 +271,10 @@
         doReturn(2).when(mPhoneMock2).getSubId();
         infoList = Arrays.asList(mSubInfo1, mSubInfo2);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1, 2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
 
         // Intent should be broadcast to ask default data selection.
@@ -282,8 +293,10 @@
         doReturn(3).when(mPhoneMock2).getSubId();
         infoList = Arrays.asList(mSubInfo1, mSubInfo3);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1, 3}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 3);
         waitABit();
 
         // Intent should be broadcast to ask default data selection.
@@ -300,6 +313,8 @@
         doReturn(true).when(mPhoneMock2).isUserDataEnabled();
         // After initialization, sub 2 should have mobile data off.
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
         verify(mDataEnabledSettingsMock2).setUserDataEnabled(false);
 
@@ -323,7 +338,10 @@
         doReturn(false).when(mSubControllerMock).isActiveSubId(1);
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo2);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(
+                1, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         waitABit();
         verify(mSubControllerMock).setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         verify(mSubControllerMock).setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -346,6 +364,8 @@
         GlobalSettingsHelper.setBoolean(mContext, Settings.Global.MOBILE_DATA, 2, true);
         GlobalSettingsHelper.setBoolean(mContext, Settings.Global.DATA_ROAMING, 2, false);
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
 
         // Create subscription grouping.
@@ -391,8 +411,10 @@
         doReturn(1).when(mSubControllerMock).getDefaultVoiceSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1, mSubInfo3);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1, 3}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 3);
         waitABit();
 
         verify(mSubControllerMock).setDefaultDataSubId(3);
@@ -414,6 +436,8 @@
         // Notify subscriptions ready. Sub 2 should become the default. But shouldn't turn off
         // data of oppt sub 1.
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
         verify(mSubControllerMock).setDefaultDataSubId(2);
         verify(mDataEnabledSettingsMock1, never()).setUserDataEnabled(anyBoolean());
@@ -451,6 +475,8 @@
 
         // Notify subscriptions ready. Sub 2 should become the default, as sub 1 is opportunistic.
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
         verify(mSubControllerMock).setDefaultDataSubId(2);
         // No user selection needed, no intent should be sent.
@@ -494,6 +520,8 @@
         GlobalSettingsHelper.setBoolean(mContext, Settings.Global.MOBILE_DATA, 1, true);
         GlobalSettingsHelper.setBoolean(mContext, Settings.Global.DATA_ROAMING, 1, false);
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
 
         // Create subscription grouping.
@@ -514,4 +542,46 @@
         waitABit();
         verify(mDataEnabledSettingsMock2).setUserDataEnabled(false);
     }
+
+    @Test
+    @SmallTest
+    public void testCarrierConfigLoading() throws Exception {
+        doReturn(true).when(mPhoneMock1).isUserDataEnabled();
+        doReturn(true).when(mPhoneMock2).isUserDataEnabled();
+        // Sub 2 should have mobile data off, but it shouldn't happen until carrier configs are
+        // loaded on both subscriptions.
+        mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        waitABit();
+        verify(mDataEnabledSettingsMock2, never()).setUserDataEnabled(false);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        waitABit();
+        verify(mDataEnabledSettingsMock2, never()).setUserDataEnabled(false);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
+        waitABit();
+        verify(mDataEnabledSettingsMock2).setUserDataEnabled(false);
+
+        // Switch from sub 2 to sub 3 in phone[1].
+        clearInvocations(mSubControllerMock);
+        doReturn(false).when(mSubControllerMock).isActiveSubId(2);
+        doReturn(true).when(mSubControllerMock).isActiveSubId(3);
+        doReturn(SubscriptionManager.INVALID_PHONE_INDEX).when(mSubControllerMock).getPhoneId(2);
+        doReturn(1).when(mSubControllerMock).getPhoneId(3);
+        doReturn(3).when(mPhoneMock2).getSubId();
+        List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1, mSubInfo3);
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1, 3}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
+
+        // Nothing should happen until carrier config change is notified on sub 3.
+        mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        waitABit();
+        verify(mContext, never()).sendBroadcast(any());
+
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 3);
+        waitABit();
+        // Intent should be broadcast to ask default data selection.
+        Intent intent = captureBroadcastIntent();
+        assertEquals(ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED, intent.getAction());
+        assertEquals(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA,
+                intent.getIntExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, -1));
+    }
 }