diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index c2ec13aaa3a..2d6077868df 100644 --- a/src/com/android/settings/network/SubscriptionUtil.java +++ b/src/com/android/settings/network/SubscriptionUtil.java @@ -486,7 +486,7 @@ public class SubscriptionUtil { * @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 00000000000..dc29c47f133 --- /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 { + 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 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 00000000000..b70a148d2e4 --- /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 { + 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 00000000000..436e84c08b7 --- /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 result = (new SelectableSubscriptions(context, false)).call(); + * + * Another example for retrieving a list of SubscriptionAnnotation for all subscriptions + * accessible in another thread. + * + * List result = ExecutorService.submit( + * new SelectableSubscriptions(context, true)).get() + */ +public class SelectableSubscriptions implements Callable> { + private static final String TAG = "SelectableSubscriptions"; + + private Context mContext; + private Supplier> mSubscriptions; + private Predicate mFilter; + private Function, List> 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> finisher) { + mFinisher = mFinisher.andThen(finisher); + return this; + } + + /** + * Implementation of Callable + * @return a list of SubscriptionAnnotation which is user selectable + */ + public List call() { + TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class); + + try { + // query in background thread + Future eSimCardId = + ThreadUtils.postOnBackgroundThread(new QueryEsimCardId(telMgr)); + + // query in background thread + Future simSlotIndex = + ThreadUtils.postOnBackgroundThread( + new QuerySimSlotIndex(telMgr, true, true)); + + // query in background thread + Future activeSimSlotIndex = + ThreadUtils.postOnBackgroundThread( + new QuerySimSlotIndex(telMgr, false, true)); + + List subInfoList = mSubscriptions.get(); + + // wait for result from background thread + List eSimCardIdList = atomicToList(eSimCardId.get()); + List simSlotIndexList = atomicToList(simSlotIndex.get()); + List 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 getSubInfoList(Context context, + Function> 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 getAvailableSubInfoList(Context context) { + return getSubInfoList(context, SubscriptionManager::getAvailableSubscriptionInfoList); + } + + protected List getActiveSubInfoList(Context context) { + return getSubInfoList(context, SubscriptionManager::getActiveSubscriptionInfoList); + } + + @Keep + @VisibleForTesting + protected static List 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 00000000000..fae5b9bbb4b --- /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 mSubInfoList; + private int mIndexWithinList; + + /** + * Constructor of builder + * @param subInfoList list of subscription info + * @param indexWithinList target index within list provided + */ + public Builder(List subInfoList, int indexWithinList) { + mSubInfoList = subInfoList; + mIndexWithinList = indexWithinList; + } + + public SubscriptionAnnotation build(Context context, List eSimCardId, + List simSlotIndex, List activeSimSlotIndex) { + return new SubscriptionAnnotation(mSubInfoList, mIndexWithinList, context, + eSimCardId, simSlotIndex, activeSimSlotIndex); + } + } + + /** + * Constructor of class + */ + @Keep + @VisibleForTesting + protected SubscriptionAnnotation(List subInfoList, int subInfoIndex, + Context context, List eSimCardId, + List simSlotIndex, List 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 00000000000..cfb5ea92650 --- /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 callable = (new SelectableSubscriptions(context, true)) + * .addFinisher(new SubscriptionGrouping()); + * + * List result = ExecutorService.submit(callable).get() + */ +public class SubscriptionGrouping + implements UnaryOperator> { + + // implementation of UnaryOperator + public List apply(List listOfSubscriptions) { + // group by GUID + Map> 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 annoList) { + Comparator 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 f2be37fa985..50164609dd0 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkActivity.java +++ b/src/com/android/settings/network/telephony/MobileNetworkActivity.java @@ -19,6 +19,7 @@ package com.android.settings.network.telephony; 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.R; 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 @@ public class MobileNetworkActivity extends SettingsBaseActivity */ @VisibleForTesting SubscriptionInfo getSubscription() { + List 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 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 00000000000..04e91222d60 --- /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 result = SelectableSubscriptions.atomicToList(null); + + assertThat(result.size()).isEqualTo(0); + } + + @Test + public void atomicToList_zeroLengthInput_getEmptyList() { + List 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 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 00000000000..97bdb7433a7 --- /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 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 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; + } + } +}