Merge "[Settings] Hide subscriptions not existed within device" into sc-dev
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index c2ec13a..2d60778 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -486,7 +486,7 @@
      * @param info the subscriptionInfo to check against.
      * @return true if this subscription should be visible to the API caller.
      */
-    private static boolean isSubscriptionVisible(
+    public static boolean isSubscriptionVisible(
             SubscriptionManager subscriptionManager, Context context, SubscriptionInfo info) {
         if (info == null) return false;
         // If subscription is NOT grouped opportunistic subscription, it's visible.
diff --git a/src/com/android/settings/network/helper/QueryEsimCardId.java b/src/com/android/settings/network/helper/QueryEsimCardId.java
new file mode 100644
index 0000000..dc29c47
--- /dev/null
+++ b/src/com/android/settings/network/helper/QueryEsimCardId.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.network.helper;
+
+import android.telephony.TelephonyManager;
+import android.telephony.UiccCardInfo;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+
+/**
+ * This is a Callable class which queries valid card ID for eSIM
+ */
+public class QueryEsimCardId implements Callable<AtomicIntegerArray> {
+    private static final String TAG = "QueryEsimCardId";
+
+    private TelephonyManager mTelephonyManager;
+
+    /**
+     * Constructor of class
+     * @param TelephonyManager
+     */
+    public QueryEsimCardId(TelephonyManager telephonyManager) {
+        mTelephonyManager = telephonyManager;
+    }
+
+    /**
+     * Implementation of Callable
+     * @return card ID(s) in AtomicIntegerArray
+     */
+    public AtomicIntegerArray call() {
+        List<UiccCardInfo> cardInfos = mTelephonyManager.getUiccCardsInfo();
+        if (cardInfos == null) {
+            return new AtomicIntegerArray(0);
+        }
+        return new AtomicIntegerArray(cardInfos.stream()
+                .filter(Objects::nonNull)
+                .filter(cardInfo -> (!cardInfo.isRemovable()
+                        && (cardInfo.getCardId() != TelephonyManager.UNSUPPORTED_CARD_ID)))
+                .mapToInt(UiccCardInfo::getCardId)
+                .toArray());
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/network/helper/QuerySimSlotIndex.java b/src/com/android/settings/network/helper/QuerySimSlotIndex.java
new file mode 100644
index 0000000..b70a148
--- /dev/null
+++ b/src/com/android/settings/network/helper/QuerySimSlotIndex.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.network.helper;
+
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotInfo;
+
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+
+/**
+ * This is a Callable class which query slot index within device
+ */
+public class QuerySimSlotIndex implements Callable<AtomicIntegerArray> {
+    private static final String TAG = "QuerySimSlotIndex";
+
+    private TelephonyManager mTelephonyManager;
+    private boolean mDisabledSlotsIncluded;
+    private boolean mOnlySlotWithSim;
+
+    /**
+     * Constructor of class
+     * @param TelephonyManager
+     * @param disabledSlotsIncluded query both active and inactive slots when true,
+     *                              only query active slot when false.
+     * @param onlySlotWithSim query slot index with SIM available when true,
+     *                        include absent ones when false.
+     */
+    public QuerySimSlotIndex(TelephonyManager telephonyManager,
+            boolean disabledSlotsIncluded, boolean onlySlotWithSim) {
+        mTelephonyManager = telephonyManager;
+        mDisabledSlotsIncluded = disabledSlotsIncluded;
+        mOnlySlotWithSim = onlySlotWithSim;
+    }
+
+    /**
+     * Implementation of Callable
+     * @return slot index in AtomicIntegerArray
+     */
+    public AtomicIntegerArray call() {
+        UiccSlotInfo [] slotInfo = mTelephonyManager.getUiccSlotsInfo();
+        if (slotInfo == null) {
+            return new AtomicIntegerArray(0);
+        }
+        int slotIndexFilter = mOnlySlotWithSim ? 0 : SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+        return new AtomicIntegerArray(Arrays.stream(slotInfo)
+                .filter(slot -> filterSlot(slot))
+                .mapToInt(slot -> mapToSlotIndex(slot))
+                .filter(slotIndex -> (slotIndex >= slotIndexFilter))
+                .toArray());
+    }
+
+    protected boolean filterSlot(UiccSlotInfo slotInfo) {
+        if (mDisabledSlotsIncluded) {
+            return true;
+        }
+        if (slotInfo == null) {
+            return false;
+        }
+        return slotInfo.getIsActive();
+    }
+
+    protected int mapToSlotIndex(UiccSlotInfo slotInfo) {
+        if (slotInfo == null) {
+            return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+        }
+        if (slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_ABSENT) {
+            return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+        }
+        return slotInfo.getLogicalSlotIdx();
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/network/helper/SelectableSubscriptions.java b/src/com/android/settings/network/helper/SelectableSubscriptions.java
new file mode 100644
index 0000000..436e84c
--- /dev/null
+++ b/src/com/android/settings/network/helper/SelectableSubscriptions.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.network.helper;
+
+import android.content.Context;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.annotation.Keep;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.network.helper.SubscriptionAnnotation;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * This is a Callable class to query user selectable subscription list.
+ *
+ * Here's example of creating a Callable for retrieving a list of SubscriptionAnnotation
+ * for active Subscriptions:
+ *
+ * List<SubscriptionAnnotation> result = (new SelectableSubscriptions(context, false)).call();
+ *
+ * Another example for retrieving a list of SubscriptionAnnotation for all subscriptions
+ * accessible in another thread.
+ *
+ * List<SubscriptionAnnotation> result = ExecutorService.submit(
+ *     new SelectableSubscriptions(context, true)).get()
+ */
+public class SelectableSubscriptions implements Callable<List<SubscriptionAnnotation>> {
+    private static final String TAG = "SelectableSubscriptions";
+
+    private Context mContext;
+    private Supplier<List<SubscriptionInfo>> mSubscriptions;
+    private Predicate<SubscriptionAnnotation> mFilter;
+    private Function<List<SubscriptionAnnotation>, List<SubscriptionAnnotation>> mFinisher;
+
+    /**
+     * Constructor of class
+     * @param context
+     * @param disabledSlotsIncluded query both active and inactive slots when true,
+     *                              only query active slot when false.
+     */
+    public SelectableSubscriptions(Context context, boolean disabledSlotsIncluded) {
+        mContext = context;
+        mSubscriptions = disabledSlotsIncluded ? (() -> getAvailableSubInfoList(context)) :
+                (() -> getActiveSubInfoList(context));
+        mFilter = disabledSlotsIncluded ? (subAnno -> subAnno.isExisted()) :
+                (subAnno -> subAnno.isActive());
+        mFinisher = annoList -> annoList;
+    }
+
+    /**
+     * Add UnaryOperator to be applied to the final result.
+     * @param finisher a function to be applied to the final result.
+     */
+    public SelectableSubscriptions addFinisher(
+            UnaryOperator<List<SubscriptionAnnotation>> finisher) {
+        mFinisher = mFinisher.andThen(finisher);
+        return this;
+    }
+
+    /**
+     * Implementation of Callable
+     * @return a list of SubscriptionAnnotation which is user selectable
+     */
+    public List<SubscriptionAnnotation> call() {
+        TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class);
+
+        try {
+            // query in background thread
+            Future<AtomicIntegerArray> eSimCardId =
+                    ThreadUtils.postOnBackgroundThread(new QueryEsimCardId(telMgr));
+
+            // query in background thread
+            Future<AtomicIntegerArray> simSlotIndex =
+                    ThreadUtils.postOnBackgroundThread(
+                    new QuerySimSlotIndex(telMgr, true, true));
+
+            // query in background thread
+            Future<AtomicIntegerArray> activeSimSlotIndex =
+                    ThreadUtils.postOnBackgroundThread(
+                    new QuerySimSlotIndex(telMgr, false, true));
+
+            List<SubscriptionInfo> subInfoList = mSubscriptions.get();
+
+            // wait for result from background thread
+            List<Integer> eSimCardIdList = atomicToList(eSimCardId.get());
+            List<Integer> simSlotIndexList = atomicToList(simSlotIndex.get());
+            List<Integer> activeSimSlotIndexList = atomicToList(activeSimSlotIndex.get());
+
+            // build a list of SubscriptionAnnotation
+            return IntStream.range(0, subInfoList.size())
+                    .mapToObj(subInfoIndex ->
+                            new SubscriptionAnnotation.Builder(subInfoList, subInfoIndex))
+                    .map(annoBdr -> annoBdr.build(mContext,
+                            eSimCardIdList, simSlotIndexList, activeSimSlotIndexList))
+                    .filter(mFilter)
+                    .collect(Collectors.collectingAndThen(Collectors.toList(), mFinisher));
+        } catch (Exception exception) {
+            Log.w(TAG, "Fail to request subIdList", exception);
+        }
+        return Collections.emptyList();
+    }
+
+    protected List<SubscriptionInfo> getSubInfoList(Context context,
+            Function<SubscriptionManager, List<SubscriptionInfo>> convertor) {
+        SubscriptionManager subManager = getSubscriptionManager(context);
+        return (subManager == null) ? Collections.emptyList() : convertor.apply(subManager);
+    }
+
+    protected SubscriptionManager getSubscriptionManager(Context context) {
+        return context.getSystemService(SubscriptionManager.class);
+    }
+
+    protected List<SubscriptionInfo> getAvailableSubInfoList(Context context) {
+        return getSubInfoList(context, SubscriptionManager::getAvailableSubscriptionInfoList);
+    }
+
+    protected List<SubscriptionInfo> getActiveSubInfoList(Context context) {
+        return getSubInfoList(context, SubscriptionManager::getActiveSubscriptionInfoList);
+    }
+
+    @Keep
+    @VisibleForTesting
+    protected static List<Integer> atomicToList(AtomicIntegerArray atomicIntArray) {
+        if (atomicIntArray == null) {
+            return Collections.emptyList();
+        }
+        return IntStream.range(0, atomicIntArray.length())
+                .map(idx -> atomicIntArray.get(idx)).boxed()
+                .collect(Collectors.toList());
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/network/helper/SubscriptionAnnotation.java b/src/com/android/settings/network/helper/SubscriptionAnnotation.java
new file mode 100644
index 0000000..fae5b9b
--- /dev/null
+++ b/src/com/android/settings/network/helper/SubscriptionAnnotation.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.network.helper;
+
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+
+import androidx.annotation.Keep;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.network.SubscriptionUtil;
+
+import java.util.List;
+
+/**
+ * This is a class helps providing additional info required by UI
+ * based on SubscriptionInfo.
+ */
+public class SubscriptionAnnotation {
+    private static final String TAG = "SubscriptionAnnotation";
+
+    private SubscriptionInfo mSubInfo;
+    private int mOrderWithinList;
+    private int mType = TYPE_UNKNOWN;
+    private boolean mIsExisted;
+    private boolean mIsActive;
+    private boolean mIsAllowToDisplay;
+
+    public static final ParcelUuid EMPTY_UUID = ParcelUuid.fromString("0-0-0-0-0");
+
+    public static final int TYPE_UNKNOWN = 0x0;
+    public static final int TYPE_PSIM = 0x1;
+    public static final int TYPE_ESIM = 0x2;
+
+    /**
+     * Builder class for SubscriptionAnnotation
+     */
+    public static class Builder {
+
+        private List<SubscriptionInfo> mSubInfoList;
+        private int mIndexWithinList;
+
+        /**
+         * Constructor of builder
+         * @param subInfoList list of subscription info
+         * @param indexWithinList target index within list provided
+         */
+        public Builder(List<SubscriptionInfo> subInfoList, int indexWithinList) {
+            mSubInfoList = subInfoList;
+            mIndexWithinList = indexWithinList;
+        }
+
+        public SubscriptionAnnotation build(Context context, List<Integer> eSimCardId,
+                List<Integer> simSlotIndex, List<Integer> activeSimSlotIndex) {
+            return new SubscriptionAnnotation(mSubInfoList, mIndexWithinList, context,
+                    eSimCardId, simSlotIndex, activeSimSlotIndex);
+        }
+    }
+
+    /**
+     * Constructor of class
+     */
+    @Keep
+    @VisibleForTesting
+    protected SubscriptionAnnotation(List<SubscriptionInfo> subInfoList, int subInfoIndex,
+            Context context, List<Integer> eSimCardId,
+            List<Integer> simSlotIndex, List<Integer> activeSimSlotIndexList) {
+        if ((subInfoIndex < 0) || (subInfoIndex >= subInfoList.size())) {
+            return;
+        }
+        mSubInfo = subInfoList.get(subInfoIndex);
+        if (mSubInfo == null) {
+            return;
+        }
+
+        mOrderWithinList = subInfoIndex;
+        mType = mSubInfo.isEmbedded() ? TYPE_ESIM : TYPE_PSIM;
+        if (mType == TYPE_ESIM) {
+            int cardId = mSubInfo.getCardId();
+            mIsExisted = eSimCardId.contains(cardId);
+            if (mIsExisted) {
+                mIsActive = activeSimSlotIndexList.contains(mSubInfo.getSimSlotIndex());
+                mIsAllowToDisplay = isDisplayAllowed(context);
+            }
+            return;
+        }
+
+        mIsExisted = simSlotIndex.contains(mSubInfo.getSimSlotIndex());
+        mIsActive = activeSimSlotIndexList.contains(mSubInfo.getSimSlotIndex());
+        if (mIsExisted) {
+            mIsAllowToDisplay = isDisplayAllowed(context);
+        }
+    }
+
+    // the index provided during construction of Builder
+    @Keep
+    public int getOrderingInList() {
+        return mOrderWithinList;
+    }
+
+    // type of subscription
+    @Keep
+    public int getType() {
+        return mType;
+    }
+
+    // if a subscription is existed within device
+    @Keep
+    public boolean isExisted() {
+        return mIsExisted;
+    }
+
+    // if a subscription is currently ON
+    @Keep
+    public boolean isActive() {
+        return mIsActive;
+    }
+
+    // if display of subscription is allowed
+    @Keep
+    public boolean isDisplayAllowed() {
+        return mIsAllowToDisplay;
+    }
+
+    // the subscription ID
+    @Keep
+    public int getSubscriptionId() {
+        return (mSubInfo == null) ? SubscriptionManager.INVALID_SUBSCRIPTION_ID :
+                mSubInfo.getSubscriptionId();
+    }
+
+    // the grouping UUID
+    @Keep
+    public ParcelUuid getGroupUuid() {
+        return (mSubInfo == null) ? null : mSubInfo.getGroupUuid();
+    }
+
+    // the SubscriptionInfo
+    @Keep
+    public SubscriptionInfo getSubInfo() {
+        return mSubInfo;
+    }
+
+    private boolean isDisplayAllowed(Context context) {
+        return SubscriptionUtil.isSubscriptionVisible(
+                context.getSystemService(SubscriptionManager.class), context, mSubInfo);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/network/helper/SubscriptionGrouping.java b/src/com/android/settings/network/helper/SubscriptionGrouping.java
new file mode 100644
index 0000000..cfb5ea9
--- /dev/null
+++ b/src/com/android/settings/network/helper/SubscriptionGrouping.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.network.helper;
+
+import android.os.ParcelUuid;
+
+import androidx.annotation.Keep;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.network.helper.SubscriptionAnnotation;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+
+/**
+ * A UnaryOperator for converting a list of SubscriptionAnnotation into
+ * another list of SubscriptionAnnotation based on group UUID.
+ * Only one SubscriptionAnnotation with entries with same (valid) group UUID would be kept.
+ *
+ * Here's an example when applying this operation as a finisher of SelectableSubscriptions:
+ *
+ * Callable<SubscriptionAnnotation> callable = (new SelectableSubscriptions(context, true))
+ *         .addFinisher(new SubscriptionGrouping());
+ *
+ * List<SubscriptionAnnotation> result = ExecutorService.submit(callable).get()
+ */
+public class SubscriptionGrouping
+        implements UnaryOperator<List<SubscriptionAnnotation>> {
+
+    // implementation of UnaryOperator
+    public List<SubscriptionAnnotation> apply(List<SubscriptionAnnotation> listOfSubscriptions) {
+        // group by GUID
+        Map<ParcelUuid, List<SubscriptionAnnotation>> groupedSubInfoList =
+                listOfSubscriptions.stream()
+                .filter(Objects::nonNull)
+                .collect(Collectors.groupingBy(subAnno -> getGroupUuid(subAnno)));
+
+        // select best one from subscription(s) within the same group
+        groupedSubInfoList.replaceAll((uuid, annoList) -> {
+            if ((uuid == SubscriptionAnnotation.EMPTY_UUID) || (annoList.size() <= 1)) {
+                return annoList;
+            }
+            return Collections.singletonList(selectBestFromList(annoList));
+        });
+
+        // build a stream of subscriptions
+        return groupedSubInfoList.values()
+                .stream().flatMap(List::stream).collect(Collectors.toList());
+    }
+
+    @Keep
+    @VisibleForTesting
+    protected ParcelUuid getGroupUuid(SubscriptionAnnotation subAnno) {
+        ParcelUuid groupUuid = subAnno.getGroupUuid();
+        return (groupUuid == null) ? SubscriptionAnnotation.EMPTY_UUID : groupUuid;
+    }
+
+    protected SubscriptionAnnotation selectBestFromList(List<SubscriptionAnnotation> annoList) {
+        Comparator<SubscriptionAnnotation> annoSelector = (anno1, anno2) -> {
+            if (anno1.isDisplayAllowed() != anno2.isDisplayAllowed()) {
+                return anno1.isDisplayAllowed() ? -1 : 1;
+            }
+            if (anno1.isActive() != anno2.isActive()) {
+                return anno1.isActive() ? -1 : 1;
+            }
+            if (anno1.isExisted() != anno2.isExisted()) {
+                return anno1.isExisted() ? -1 : 1;
+            }
+            return 0;
+        };
+        annoSelector = annoSelector
+                // eSIM in front of pSIM
+                .thenComparingInt(anno -> -anno.getType())
+                // subscription ID in reverse order
+                .thenComparingInt(anno -> -anno.getSubscriptionId());
+        return annoList.stream().sorted(annoSelector).findFirst().orElse(null);
+    }
+}
diff --git a/src/com/android/settings/network/telephony/MobileNetworkActivity.java b/src/com/android/settings/network/telephony/MobileNetworkActivity.java
index f2be37f..5016460 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkActivity.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkActivity.java
@@ -19,6 +19,7 @@
 import static com.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
 
 import android.app.ActionBar;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.UserManager;
@@ -43,6 +44,8 @@
 import com.android.settings.core.SettingsBaseActivity;
 import com.android.settings.network.ProxySubscriptionManager;
 import com.android.settings.network.SubscriptionUtil;
+import com.android.settings.network.helper.SelectableSubscriptions;
+import com.android.settings.network.helper.SubscriptionAnnotation;
 
 import java.util.List;
 
@@ -244,15 +247,21 @@
      */
     @VisibleForTesting
     SubscriptionInfo getSubscription() {
+        List<SubscriptionAnnotation> subList =
+                (new SelectableSubscriptions(this, true)).call();
+        SubscriptionAnnotation currentSubInfo = null;
         if (mCurSubscriptionId != SUB_ID_NULL) {
-            return getSubscriptionForSubId(mCurSubscriptionId);
+            currentSubInfo = subList.stream()
+                    .filter(SubscriptionAnnotation::isDisplayAllowed)
+                    .filter(subAnno -> (subAnno.getSubscriptionId() == mCurSubscriptionId))
+                    .findFirst().orElse(null);
         }
-        final List<SubscriptionInfo> subInfos = getProxySubscriptionManager()
-                .getActiveSubscriptionsInfo();
-        if (CollectionUtils.isEmpty(subInfos)) {
-            return null;
+        if (currentSubInfo == null) {
+            currentSubInfo = subList.stream()
+                    .filter(SubscriptionAnnotation::isDisplayAllowed)
+                    .findFirst().orElse(null);
         }
-        return subInfos.get(0);
+        return (currentSubInfo == null) ? null : currentSubInfo.getSubInfo();
     }
 
     @VisibleForTesting
diff --git a/tests/unit/src/com/android/settings/network/helper/SelectableSubscriptionsTest.java b/tests/unit/src/com/android/settings/network/helper/SelectableSubscriptionsTest.java
new file mode 100644
index 0000000..04e9122
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/helper/SelectableSubscriptionsTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.network.helper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+
+@RunWith(AndroidJUnit4.class)
+public class SelectableSubscriptionsTest {
+
+    @Before
+    public void setUp() {
+    }
+
+    @Test
+    public void atomicToList_nullInput_getNoneNullEmptyList() {
+        List<Integer> result = SelectableSubscriptions.atomicToList(null);
+
+        assertThat(result.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void atomicToList_zeroLengthInput_getEmptyList() {
+        List<Integer> result = SelectableSubscriptions.atomicToList(new AtomicIntegerArray(0));
+
+        assertThat(result.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void atomicToList_subIdInArray_getList() {
+        AtomicIntegerArray array = new AtomicIntegerArray(3);
+        array.set(0, 3);
+        array.set(1, 7);
+        array.set(2, 4);
+
+        List<Integer> result = SelectableSubscriptions.atomicToList(array);
+
+        assertThat(result.size()).isEqualTo(3);
+        assertThat(result.get(0)).isEqualTo(3);
+        assertThat(result.get(1)).isEqualTo(7);
+        assertThat(result.get(2)).isEqualTo(4);
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/helper/SubscriptionGroupingTest.java b/tests/unit/src/com/android/settings/network/helper/SubscriptionGroupingTest.java
new file mode 100644
index 0000000..97bdb74
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/helper/SubscriptionGroupingTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.network.helper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.network.helper.SubscriptionAnnotation;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SubscriptionGroupingTest {
+
+    private ParcelUuid mMockUuid;
+
+    private Context mContext;
+    private SubscriptionGrouping mTarget;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mMockUuid = ParcelUuid.fromString("1-1-1-1-1");
+        mTarget = spy(new SubscriptionGrouping());
+    }
+
+    @Test
+    public void apply_multipleEntriesWithSameGroupUuid_onlyOneLeft() {
+        SubscriptionAnnotation subAnno1 = new TestSubAnnotation(1,
+                SubscriptionAnnotation.TYPE_ESIM, true, true, mMockUuid);
+        SubscriptionAnnotation subAnno2 = new TestSubAnnotation(2,
+                SubscriptionAnnotation.TYPE_PSIM, true, true, mMockUuid);
+        SubscriptionAnnotation subAnno3 = new TestSubAnnotation(3,
+                SubscriptionAnnotation.TYPE_ESIM, true, true, mMockUuid);
+        doReturn(mMockUuid).when(mTarget).getGroupUuid(any());
+
+        List<SubscriptionAnnotation> result = mTarget
+                .apply(Arrays.asList(subAnno2, subAnno1, subAnno3));
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(0)).isEqualTo(subAnno3);
+    }
+
+    @Test
+    public void apply_multipleEntriesWithSameGroupUuid_disabledOneIsAvoided() {
+        SubscriptionAnnotation subAnno1 = new TestSubAnnotation(1,
+                SubscriptionAnnotation.TYPE_ESIM, true, true, mMockUuid);
+        SubscriptionAnnotation subAnno2 = new TestSubAnnotation(2,
+                SubscriptionAnnotation.TYPE_PSIM, true, true, mMockUuid);
+        SubscriptionAnnotation subAnno3 = new TestSubAnnotation(3,
+                SubscriptionAnnotation.TYPE_ESIM, false, false, mMockUuid);
+        doReturn(mMockUuid).when(mTarget).getGroupUuid(any());
+
+        List<SubscriptionAnnotation> result = mTarget
+                .apply(Arrays.asList(subAnno2, subAnno1, subAnno3));
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(0)).isEqualTo(subAnno1);
+    }
+
+    private class TestSubAnnotation extends SubscriptionAnnotation {
+        private int mSubId;
+        private int mSimType;
+        private boolean mIsActive;
+        private boolean mIsDisplayAllowed;
+        private ParcelUuid mUuid;
+
+        private TestSubAnnotation(int subId, int simType,
+                boolean isActive, boolean isDisplayAllowed, ParcelUuid guuid) {
+            super(null, -1, null, null, null, null);
+            mSubId = subId;
+            mSimType = simType;
+            mIsActive = isActive;
+            mIsDisplayAllowed = isDisplayAllowed;
+            mUuid = guuid;
+        }
+
+        @Override
+        public int getSubscriptionId() {
+            return mSubId;
+        }
+
+        @Override
+        public int getType() {
+            return mSimType;
+        }
+
+        @Override
+        public boolean isExisted() {
+            return true;
+        }
+
+        @Override
+        public boolean isActive() {
+            return mIsActive;
+        }
+
+        @Override
+        public boolean isDisplayAllowed() {
+            return mIsDisplayAllowed;
+        }
+
+        @Override
+        public ParcelUuid getGroupUuid() {
+            return mUuid;
+        }
+    }
+}