diff --git a/res/drawable-night/accessibility_captions_banner.xml b/res/drawable-night/accessibility_captions_banner.xml
new file mode 100644
index 00000000000..7ee30f23f48
--- /dev/null
+++ b/res/drawable-night/accessibility_captions_banner.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/accessibility_captions.png b/res/drawable/accessibility_captions.png
deleted file mode 100644
index 718f4ef6dd8..00000000000
Binary files a/res/drawable/accessibility_captions.png and /dev/null differ
diff --git a/res/drawable/accessibility_captions_banner.xml b/res/drawable/accessibility_captions_banner.xml
new file mode 100644
index 00000000000..6597ffb5768
--- /dev/null
+++ b/res/drawable/accessibility_captions_banner.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/accessibility_captions_preview.xml b/res/layout/accessibility_captions_preview.xml
deleted file mode 100644
index 1818e64eec0..00000000000
--- a/res/layout/accessibility_captions_preview.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/res/layout/face_enroll_introduction.xml b/res/layout/face_enroll_introduction.xml
index c9c4d23f578..c1c9ac6ad85 100644
--- a/res/layout/face_enroll_introduction.xml
+++ b/res/layout/face_enroll_introduction.xml
@@ -107,6 +107,28 @@
style="@style/BiometricEnrollIntroMessage" />
+
+
+
+
+
+
+
+
+ true
+
false
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 56500768231..951f2e1016e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -796,6 +796,11 @@
+
+
+
+
+
diff --git a/res/xml/captioning_settings.xml b/res/xml/captioning_settings.xml
index b39714632b3..f5059e595f2 100644
--- a/res/xml/captioning_settings.xml
+++ b/res/xml/captioning_settings.xml
@@ -21,12 +21,11 @@
android:persistent="false"
android:title="@string/accessibility_captioning_title">
-
();
+ mPendingNotifyListeners =
+ new ArrayList();
- mSubscriptionMonitor = new ActiveSubscriptionsListener(looper, context) {
- public void onChanged() {
- notifyAllListeners();
- }
- };
- mAirplaneModeMonitor = new GlobalSettingsChangeListener(looper,
- context, Settings.Global.AIRPLANE_MODE_ON) {
- public void onChanged(String field) {
- mSubscriptionMonitor.clearCache();
- notifyAllListeners();
- }
- };
+ mSubscriptionMonitor = activeSubscriptionsListener;
+ mAirplaneModeMonitor = airplaneModeOnSettingsChangeListener;
mSubscriptionMonitor.start();
}
@@ -98,15 +123,19 @@ public class ProxySubscriptionManager implements LifecycleObserver {
private GlobalSettingsChangeListener mAirplaneModeMonitor;
private List mActiveSubscriptionsListeners;
+ private List mPendingNotifyListeners;
- private void notifyAllListeners() {
- for (OnActiveSubscriptionChangedListener listener : mActiveSubscriptionsListeners) {
- final Lifecycle lifecycle = listener.getLifecycle();
- if ((lifecycle == null)
- || (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED))) {
- listener.onChanged();
- }
- }
+ @Keep
+ @VisibleForTesting
+ protected void notifySubscriptionInfoMightChanged() {
+ // create a merged list for processing all listeners
+ List listeners =
+ new ArrayList(mPendingNotifyListeners);
+ listeners.addAll(mActiveSubscriptionsListeners);
+
+ mActiveSubscriptionsListeners.clear();
+ mPendingNotifyListeners.clear();
+ processStatusChangeOnListeners(listeners);
}
/**
@@ -131,6 +160,11 @@ public class ProxySubscriptionManager implements LifecycleObserver {
@OnLifecycleEvent(ON_START)
void onStart() {
mSubscriptionMonitor.start();
+
+ // callback notify those listener(s) which back to active state
+ List listeners = mPendingNotifyListeners;
+ mPendingNotifyListeners = new ArrayList();
+ processStatusChangeOnListeners(listeners);
}
@OnLifecycleEvent(ON_STOP)
@@ -215,12 +249,17 @@ public class ProxySubscriptionManager implements LifecycleObserver {
}
/**
- * Add listener to active subscriptions monitor list
+ * Add listener to active subscriptions monitor list.
+ * Note: listener only take place when change happens.
+ * No immediate callback performed after the invoke of this method.
*
* @param listener listener to active subscriptions change
*/
+ @Keep
public void addActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener) {
- if (mActiveSubscriptionsListeners.contains(listener)) {
+ removeSpecificListenerAndCleanList(listener, mPendingNotifyListeners);
+ removeSpecificListenerAndCleanList(listener, mActiveSubscriptionsListeners);
+ if ((listener == null) || (getListenerState(listener) == LISTENER_END_OF_LIFE)) {
return;
}
mActiveSubscriptionsListeners.add(listener);
@@ -231,7 +270,51 @@ public class ProxySubscriptionManager implements LifecycleObserver {
*
* @param listener listener to active subscriptions change
*/
+ @Keep
public void removeActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener) {
- mActiveSubscriptionsListeners.remove(listener);
+ removeSpecificListenerAndCleanList(listener, mPendingNotifyListeners);
+ removeSpecificListenerAndCleanList(listener, mActiveSubscriptionsListeners);
+ }
+
+ private int getListenerState(OnActiveSubscriptionChangedListener listener) {
+ Lifecycle lifecycle = listener.getLifecycle();
+ if (lifecycle == null) {
+ return LISTENER_IS_ACTIVE;
+ }
+ Lifecycle.State lifecycleState = lifecycle.getCurrentState();
+ if (lifecycleState == Lifecycle.State.DESTROYED) {
+ Log.d(LOG_TAG, "Listener dead detected - " + listener);
+ return LISTENER_END_OF_LIFE;
+ }
+ return lifecycleState.isAtLeast(Lifecycle.State.STARTED) ?
+ LISTENER_IS_ACTIVE : LISTENER_IS_INACTIVE;
+ }
+
+ private void removeSpecificListenerAndCleanList(OnActiveSubscriptionChangedListener listener,
+ List list) {
+ // also drop listener(s) which is end of life
+ list.removeIf(it -> (it == listener) || (getListenerState(it) == LISTENER_END_OF_LIFE));
+ }
+
+ private void processStatusChangeOnListeners(
+ List listeners) {
+ // categorize listener(s), and end of life listener(s) been ignored
+ Map> categorizedListeners =
+ listeners.stream()
+ .collect(Collectors.groupingBy(it -> getListenerState(it)));
+
+ // have inactive listener(s) in pending list
+ categorizedListeners.computeIfPresent(LISTENER_IS_INACTIVE, (category, list) -> {
+ mPendingNotifyListeners.addAll(list);
+ return list;
+ });
+
+ // get active listener(s)
+ categorizedListeners.computeIfPresent(LISTENER_IS_ACTIVE, (category, list) -> {
+ mActiveSubscriptionsListeners.addAll(list);
+ // notify each one of them
+ list.stream().forEach(it -> it.onChanged());
+ return list;
+ });
}
}
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..b122cdc04b1 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;
@@ -129,15 +132,13 @@ public class MobileNetworkActivity extends SettingsBaseActivity
: ((startIntent != null)
? startIntent.getIntExtra(Settings.EXTRA_SUB_ID, SUB_ID_NULL)
: SUB_ID_NULL);
+ // perform registration after mCurSubscriptionId been configured.
+ registerActiveSubscriptionsListener();
final SubscriptionInfo subscription = getSubscription();
maybeShowContactDiscoveryDialog(subscription);
- // Since onChanged() will take place immediately when addActiveSubscriptionsListener(),
- // perform registration after mCurSubscriptionId been configured.
- registerActiveSubscriptionsListener();
-
- updateSubscriptions(subscription, savedInstanceState);
+ updateSubscriptions(subscription, null);
}
@VisibleForTesting
@@ -244,15 +245,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
@@ -287,7 +294,7 @@ public class MobileNetworkActivity extends SettingsBaseActivity
final Fragment fragment = new MobileNetworkSettings();
fragment.setArguments(bundle);
fragmentTransaction.replace(R.id.content_frame, fragment, fragmentTag);
- fragmentTransaction.commit();
+ fragmentTransaction.commitAllowingStateLoss();
}
private void removeContactDiscoveryDialog(int subId) {
diff --git a/tests/unit/src/com/android/settings/network/ProxySubscriptionManagerTest.java b/tests/unit/src/com/android/settings/network/ProxySubscriptionManagerTest.java
new file mode 100644
index 00000000000..afe9d19c485
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/ProxySubscriptionManagerTest.java
@@ -0,0 +1,228 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class ProxySubscriptionManagerTest {
+
+ private Context mContext;
+ @Mock
+ private ActiveSubscriptionsListener mActiveSubscriptionsListener;
+ @Mock
+ private GlobalSettingsChangeListener mAirplaneModeOnSettingsChangeListener;
+
+ @Mock
+ private Lifecycle mLifecycle_ON_PAUSE;
+ @Mock
+ private Lifecycle mLifecycle_ON_RESUME;
+ @Mock
+ private Lifecycle mLifecycle_ON_DESTROY;
+
+ private Client mClient1;
+ private Client mClient2;
+
+ @Before
+ @UiThreadTest
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = spy(ApplicationProvider.getApplicationContext());
+
+ doReturn(Lifecycle.State.CREATED).when(mLifecycle_ON_PAUSE).getCurrentState();
+ doReturn(Lifecycle.State.STARTED).when(mLifecycle_ON_RESUME).getCurrentState();
+ doReturn(Lifecycle.State.DESTROYED).when(mLifecycle_ON_DESTROY).getCurrentState();
+
+ mClient1 = new Client();
+ mClient1.setLifecycle(mLifecycle_ON_RESUME);
+ mClient2 = new Client();
+ mClient2.setLifecycle(mLifecycle_ON_RESUME);
+ }
+
+ private ProxySubscriptionManager getInstance(Context context) {
+ ProxySubscriptionManager proxy =
+ Mockito.mock(ProxySubscriptionManager.class, Mockito.CALLS_REAL_METHODS);
+ proxy.init(context, mActiveSubscriptionsListener, mAirplaneModeOnSettingsChangeListener);
+ proxy.notifySubscriptionInfoMightChanged();
+ return proxy;
+ }
+
+ public class Client implements ProxySubscriptionManager.OnActiveSubscriptionChangedListener {
+ private Lifecycle lifeCycle;
+ private int numberOfCallback;
+
+ public void onChanged() {
+ numberOfCallback++;
+ }
+
+ public Lifecycle getLifecycle() {
+ return lifeCycle;
+ }
+
+ public int getCallbackCount() {
+ return numberOfCallback;
+ }
+
+ public void setLifecycle(Lifecycle lifecycle) {
+ lifeCycle = lifecycle;
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ public void addActiveSubscriptionsListener_addOneClient_getNoCallback() {
+ ProxySubscriptionManager proxy = getInstance(mContext);
+
+ proxy.addActiveSubscriptionsListener(mClient1);
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+ }
+
+ @Test
+ @UiThreadTest
+ public void addActiveSubscriptionsListener_addOneClient_changeOnSimGetCallback() {
+ ProxySubscriptionManager proxy = getInstance(mContext);
+
+ proxy.addActiveSubscriptionsListener(mClient1);
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+
+ proxy.notifySubscriptionInfoMightChanged();
+ assertThat(mClient1.getCallbackCount()).isEqualTo(1);
+ }
+
+ @Test
+ @UiThreadTest
+ public void addActiveSubscriptionsListener_addOneClient_noCallbackUntilUiResume() {
+ ProxySubscriptionManager proxy = getInstance(mContext);
+
+ mClient1.setLifecycle(mLifecycle_ON_PAUSE);
+
+ proxy.addActiveSubscriptionsListener(mClient1);
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+
+ proxy.notifySubscriptionInfoMightChanged();
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+
+ mClient1.setLifecycle(mLifecycle_ON_RESUME);
+ proxy.onStart();
+ Assert.assertTrue(mClient1.getCallbackCount() > 0);
+
+ mClient1.setLifecycle(mLifecycle_ON_PAUSE);
+ proxy.onStop();
+ int latestCallbackCount = mClient1.getCallbackCount();
+
+ proxy.notifySubscriptionInfoMightChanged();
+ assertThat(mClient1.getCallbackCount()).isEqualTo(latestCallbackCount);
+ }
+
+ @Test
+ @UiThreadTest
+ public void addActiveSubscriptionsListener_addTwoClient_eachClientGetNoCallback() {
+ ProxySubscriptionManager proxy = getInstance(mContext);
+
+ proxy.addActiveSubscriptionsListener(mClient1);
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+
+ proxy.addActiveSubscriptionsListener(mClient2);
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+ assertThat(mClient2.getCallbackCount()).isEqualTo(0);
+ }
+
+ @Test
+ @UiThreadTest
+ public void addActiveSubscriptionsListener_addTwoClient_callbackOnlyWhenResume() {
+ ProxySubscriptionManager proxy = getInstance(mContext);
+
+ proxy.addActiveSubscriptionsListener(mClient1);
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+
+ proxy.addActiveSubscriptionsListener(mClient2);
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+ assertThat(mClient2.getCallbackCount()).isEqualTo(0);
+
+ mClient1.setLifecycle(mLifecycle_ON_PAUSE);
+ proxy.onStop();
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+ assertThat(mClient2.getCallbackCount()).isEqualTo(0);
+
+ proxy.notifySubscriptionInfoMightChanged();
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+ assertThat(mClient2.getCallbackCount()).isEqualTo(1);
+
+ mClient1.setLifecycle(mLifecycle_ON_RESUME);
+ proxy.onStart();
+ Assert.assertTrue(mClient1.getCallbackCount() > 0);
+ assertThat(mClient2.getCallbackCount()).isEqualTo(1);
+ }
+
+ @Test
+ @UiThreadTest
+ public void removeActiveSubscriptionsListener_removedClient_noCallback() {
+ ProxySubscriptionManager proxy = getInstance(mContext);
+
+ proxy.addActiveSubscriptionsListener(mClient1);
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+
+ proxy.notifySubscriptionInfoMightChanged();
+ assertThat(mClient1.getCallbackCount()).isEqualTo(1);
+
+ proxy.removeActiveSubscriptionsListener(mClient1);
+ assertThat(mClient1.getCallbackCount()).isEqualTo(1);
+
+ proxy.notifySubscriptionInfoMightChanged();
+ assertThat(mClient1.getCallbackCount()).isEqualTo(1);
+ }
+
+ @Test
+ @UiThreadTest
+ public void notifySubscriptionInfoMightChanged_destroyedClient_autoRemove() {
+ ProxySubscriptionManager proxy = getInstance(mContext);
+
+ proxy.addActiveSubscriptionsListener(mClient1);
+ assertThat(mClient1.getCallbackCount()).isEqualTo(0);
+
+ proxy.notifySubscriptionInfoMightChanged();
+ assertThat(mClient1.getCallbackCount()).isEqualTo(1);
+
+ mClient1.setLifecycle(mLifecycle_ON_DESTROY);
+ proxy.notifySubscriptionInfoMightChanged();
+ assertThat(mClient1.getCallbackCount()).isEqualTo(1);
+
+ mClient1.setLifecycle(mLifecycle_ON_RESUME);
+ proxy.notifySubscriptionInfoMightChanged();
+ assertThat(mClient1.getCallbackCount()).isEqualTo(1);
+ }
+}
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;
+ }
+ }
+}