Fix bug of disabling grouped CBRS during profile switch on primary SIM

When we update subInfo, we remove the info first and re-fill the fields. In between, we
unexpectedly believed it's deactivated which results in deactivating its grouped oppt sub
and broadcast the decision out. Two actions:
1) Make disableGroupedOppt subscription handled asynchronous in MultiSimSettingController
after all sub info changes are done.
2) Don't broadcast oppt subInfo change whenever the cache is updated, because it may update
several times in one operation. Instead, wait until notifySubscriptionInfoChanged is
triggered, and only broadcast if oppt subInfo is changed.

Bug: 131919246
Bug: 173421434
Test: manual
Change-Id: I64d8166338e0c6550c09e9b88ee99e6c3528528f
Merged-In: Ie0b6d84d12c7fa3ede6e8b24275145849a89f0a3
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index b1c8f78..11ad9a6 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -30,6 +30,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
@@ -40,6 +41,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.euicc.EuiccManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -261,6 +263,7 @@
         mSubInfoInitialized = true;
         updateDefaults(/*init*/ true);
         disableDataForNonDefaultNonOpportunisticSubscriptions();
+        deactivateGroupedOpportunisticSubscriptionIfNeeded();
     }
 
     /**
@@ -273,6 +276,7 @@
         if (!mSubInfoInitialized) return;
         updateDefaults(/*init*/ false);
         disableDataForNonDefaultNonOpportunisticSubscriptions();
+        deactivateGroupedOpportunisticSubscriptionIfNeeded();
     }
 
     /**
@@ -654,6 +658,38 @@
         return SubscriptionManager.isValidSubscriptionId(newValue);
     }
 
+    // When a primary and its grouped opportunistic subscriptions were active, and the primary
+    // subscription gets deactivated or removed, we need to automatically disable the grouped
+    // opportunistic subscription, which will be marked isGroupDisabled as true by SubController.
+    private void deactivateGroupedOpportunisticSubscriptionIfNeeded() {
+        if (!SubscriptionInfoUpdater.isSubInfoInitialized()) return;
+
+        List<SubscriptionInfo> opptSubList = mSubController.getOpportunisticSubscriptions(
+                mContext.getOpPackageName());
+
+        if (ArrayUtils.isEmpty(opptSubList)) return;
+
+        for (SubscriptionInfo info : opptSubList) {
+            if (info.isGroupDisabled() && mSubController.isActiveSubId(info.getSubscriptionId())) {
+                log("[deactivateGroupedOpptSubIfNeeded] "
+                        + "Deactivating grouped opportunistic subscription "
+                        + info.getSubscriptionId());
+                deactivateSubscription(info);
+            }
+        }
+    }
+
+    private void deactivateSubscription(SubscriptionInfo info) {
+        // TODO: b/133379187 have a way to deactivate pSIM.
+        if (info.isEmbedded()) {
+            log("[deactivateSubscription] eSIM profile " + info.getSubscriptionId());
+            EuiccManager euiccManager = (EuiccManager)
+                    mContext.getSystemService(Context.EUICC_SERVICE);
+            euiccManager.switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                    PendingIntent.getService(mContext, 0, new Intent(), 0));
+        }
+    }
+
     private void log(String msg) {
         Log.d(LOG_TAG, msg);
     }
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index 40ebf1b..457c17d 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -77,6 +77,7 @@
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 /**
@@ -114,6 +115,7 @@
 
     /* Similar to mCacheActiveSubInfoList but only caching opportunistic subscriptions. */
     private List<SubscriptionInfo> mCacheOpportunisticSubInfoList = new ArrayList<>();
+    private AtomicBoolean mOpptSubInfoListChangedDirtyBit = new AtomicBoolean();
 
     private static final Comparator<SubscriptionInfo> SUBSCRIPTION_INFO_COMPARATOR =
             (arg0, arg1) -> {
@@ -321,6 +323,10 @@
         synchronized (mSubInfoListLock) {
             subInfos = new ArrayList<>(mCacheActiveSubInfoList);
         }
+
+        if (mOpptSubInfoListChangedDirtyBit.getAndSet(false)) {
+            notifyOpportunisticSubscriptionInfoChanged();
+        }
         metrics.updateActiveSubscriptionInfoList(subInfos);
     }
 
@@ -737,7 +743,7 @@
             }
 
             // Refresh cached opportunistic sub list and detect whether it's changed.
-            opptSubListChanged = refreshCachedOpportunisticSubscriptionInfoList();
+            refreshCachedOpportunisticSubscriptionInfoList();
 
             if (DBG_CACHE) {
                 if (!mCacheActiveSubInfoList.isEmpty()) {
@@ -750,11 +756,6 @@
                 }
             }
         }
-
-        // Send notification outside synchronization.
-        if (opptSubListChanged) {
-            notifyOpportunisticSubscriptionInfoChanged();
-        }
     }
 
     /**
@@ -3695,7 +3696,7 @@
         }
     }
 
-    private boolean refreshCachedOpportunisticSubscriptionInfoList() {
+    private void refreshCachedOpportunisticSubscriptionInfoList() {
         synchronized (mSubInfoListLock) {
             List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList;
 
@@ -3715,13 +3716,6 @@
             for (SubscriptionInfo info : mCacheOpportunisticSubInfoList) {
                 if (shouldDisableSubGroup(info.getGroupUuid())) {
                     info.setGroupDisabled(true);
-                    // TODO: move it to ONS.
-                    if (isActiveSubId(info.getSubscriptionId()) && isSubInfoReady()) {
-                        logd("[refreshCachedOpportunisticSubscriptionInfoList] "
-                                + "Deactivating grouped opportunistic subscription "
-                                + info.getSubscriptionId());
-                        deactivateSubscription(info);
-                    }
                 }
             }
 
@@ -3736,7 +3730,9 @@
                 }
             }
 
-            return !oldOpptCachedList.equals(mCacheOpportunisticSubInfoList);
+            if (!oldOpptCachedList.equals(mCacheOpportunisticSubInfoList)) {
+                mOpptSubInfoListChangedDirtyBit.set(true);
+            }
         }
     }
 
@@ -3752,17 +3748,6 @@
         return true;
     }
 
-    private void deactivateSubscription(SubscriptionInfo info) {
-        // TODO: b/120439488 deactivate pSIM.
-        if (info.isEmbedded()) {
-            logd("[deactivateSubscription] eSIM profile " + info.getSubscriptionId());
-            EuiccManager euiccManager = (EuiccManager)
-                    mContext.getSystemService(Context.EUICC_SERVICE);
-            euiccManager.switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                    PendingIntent.getService(mContext, 0, new Intent(), 0));
-        }
-    }
-
     // TODO: This method should belong to Telephony manager like other data enabled settings and
     // override APIs. Remove this once TelephonyManager API is added.
     @Override
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
index 9b3de18..52e6a2f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
@@ -816,6 +816,7 @@
         mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null);
         mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
+        mSubscriptionControllerUT.notifySubscriptionInfoChanged();
 
         verify(mTelephonyRegisteryMock, times(1))
                 .notifyOpportunisticSubscriptionInfoChanged();
@@ -826,6 +827,7 @@
                 subIdList, mContext.getOpPackageName());
         assertNotEquals(null, groupId);
 
+        mSubscriptionControllerUT.notifySubscriptionInfoChanged();
         verify(mTelephonyRegisteryMock, times(2))
                 .notifyOpportunisticSubscriptionInfoChanged();
         List<SubscriptionInfo> opptSubList = mSubscriptionControllerUT
@@ -840,6 +842,7 @@
         mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 1, null);
         mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
+        mSubscriptionControllerUT.notifySubscriptionInfoChanged();
 
         verify(mTelephonyRegisteryMock, times(3))
                 .notifyOpportunisticSubscriptionInfoChanged();