• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.network;
18 
19 import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
20 import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
21 
22 import static com.android.internal.util.CollectionUtils.emptyIfNull;
23 
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.os.ParcelUuid;
27 import android.provider.Settings;
28 import android.telephony.PhoneNumberUtils;
29 import android.telephony.SubscriptionInfo;
30 import android.telephony.SubscriptionManager;
31 import android.telephony.TelephonyManager;
32 import android.telephony.UiccCardInfo;
33 import android.telephony.UiccSlotInfo;
34 import android.text.TextUtils;
35 import android.util.Log;
36 
37 import androidx.annotation.VisibleForTesting;
38 
39 import com.android.internal.telephony.MccTable;
40 import com.android.settings.R;
41 import com.android.settings.network.helper.SelectableSubscriptions;
42 import com.android.settings.network.helper.SubscriptionAnnotation;
43 import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
44 import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
45 import com.android.settingslib.DeviceInfoUtils;
46 
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.HashMap;
50 import java.util.HashSet;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Set;
54 import java.util.function.Function;
55 import java.util.function.Supplier;
56 import java.util.stream.Collectors;
57 import java.util.stream.Stream;
58 
59 public class SubscriptionUtil {
60     private static final String TAG = "SubscriptionUtil";
61     private static final String PROFILE_GENERIC_DISPLAY_NAME = "CARD";
62     private static List<SubscriptionInfo> sAvailableResultsForTesting;
63     private static List<SubscriptionInfo> sActiveResultsForTesting;
64 
65     @VisibleForTesting
setAvailableSubscriptionsForTesting(List<SubscriptionInfo> results)66     public static void setAvailableSubscriptionsForTesting(List<SubscriptionInfo> results) {
67         sAvailableResultsForTesting = results;
68     }
69 
70     @VisibleForTesting
setActiveSubscriptionsForTesting(List<SubscriptionInfo> results)71     public static void setActiveSubscriptionsForTesting(List<SubscriptionInfo> results) {
72         sActiveResultsForTesting = results;
73     }
74 
getActiveSubscriptions(SubscriptionManager manager)75     public static List<SubscriptionInfo> getActiveSubscriptions(SubscriptionManager manager) {
76         if (sActiveResultsForTesting != null) {
77             return sActiveResultsForTesting;
78         }
79         if (manager == null) {
80             return Collections.emptyList();
81         }
82         final List<SubscriptionInfo> subscriptions = manager.getActiveSubscriptionInfoList();
83         if (subscriptions == null) {
84             return new ArrayList<>();
85         }
86         return subscriptions;
87     }
88 
89     /**
90      * Check if SIM hardware is visible to the end user.
91      */
isSimHardwareVisible(Context context)92     public static boolean isSimHardwareVisible(Context context) {
93         return context.getResources()
94             .getBoolean(R.bool.config_show_sim_info);
95     }
96 
97     @VisibleForTesting
isInactiveInsertedPSim(UiccSlotInfo slotInfo)98     static boolean isInactiveInsertedPSim(UiccSlotInfo slotInfo) {
99         if (slotInfo == null)  {
100             return false;
101         }
102         return !slotInfo.getIsEuicc() && !slotInfo.getPorts().stream().findFirst().get()
103                 .isActive() && slotInfo.getCardStateInfo() == CARD_STATE_INFO_PRESENT;
104     }
105 
106     /**
107      * Get all of the subscriptions which is available to display to the user.
108      *
109      * @param context {@code Context}
110      * @return list of {@code SubscriptionInfo}
111      */
getAvailableSubscriptions(Context context)112     public static List<SubscriptionInfo> getAvailableSubscriptions(Context context) {
113         if (sAvailableResultsForTesting != null) {
114             return sAvailableResultsForTesting;
115         }
116         return new ArrayList<>(emptyIfNull(getSelectableSubscriptionInfoList(context)));
117     }
118 
119     /**
120      * Get subscription which is available to be displayed to the user
121      * per subscription id.
122      *
123      * @param context {@code Context}
124      * @param subscriptionManager The ProxySubscriptionManager for accessing subcription
125      *         information
126      * @param subId The id of subscription to be retrieved
127      * @return {@code SubscriptionInfo} based on the given subscription id. Null of subscription
128      *         is invalid or not allowed to be displayed to the user.
129      */
getAvailableSubscription(Context context, ProxySubscriptionManager subscriptionManager, int subId)130     public static SubscriptionInfo getAvailableSubscription(Context context,
131             ProxySubscriptionManager subscriptionManager, int subId) {
132         final SubscriptionInfo subInfo = subscriptionManager.getAccessibleSubscriptionInfo(subId);
133         if (subInfo == null) {
134             return null;
135         }
136 
137         final ParcelUuid groupUuid = subInfo.getGroupUuid();
138 
139         if (groupUuid != null) {
140             if (isPrimarySubscriptionWithinSameUuid(getUiccSlotsInfo(context), groupUuid,
141                     subscriptionManager.getAccessibleSubscriptionsInfo(), subId)) {
142                 return subInfo;
143             }
144             return null;
145         }
146 
147         return subInfo;
148     }
149 
getUiccSlotsInfo(Context context)150     private static UiccSlotInfo [] getUiccSlotsInfo(Context context) {
151         final TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
152         return telMgr.getUiccSlotsInfo();
153     }
154 
isPrimarySubscriptionWithinSameUuid(UiccSlotInfo[] slotsInfo, ParcelUuid groupUuid, List<SubscriptionInfo> subscriptions, int subId)155     private static boolean isPrimarySubscriptionWithinSameUuid(UiccSlotInfo[] slotsInfo,
156             ParcelUuid groupUuid, List<SubscriptionInfo> subscriptions, int subId) {
157         // only interested in subscriptions with this group UUID
158         final ArrayList<SubscriptionInfo> physicalSubInfoList =
159                 new ArrayList<SubscriptionInfo>();
160         final ArrayList<SubscriptionInfo> nonOpportunisticSubInfoList =
161                 new ArrayList<SubscriptionInfo>();
162         final ArrayList<SubscriptionInfo> activeSlotSubInfoList =
163                 new ArrayList<SubscriptionInfo>();
164         final ArrayList<SubscriptionInfo> inactiveSlotSubInfoList =
165                 new ArrayList<SubscriptionInfo>();
166         for (SubscriptionInfo subInfo : subscriptions) {
167             if (groupUuid.equals(subInfo.getGroupUuid())) {
168                 if (!subInfo.isEmbedded()) {
169                     physicalSubInfoList.add(subInfo);
170                 } else  {
171                     if (!subInfo.isOpportunistic()) {
172                         nonOpportunisticSubInfoList.add(subInfo);
173                     }
174                     if (subInfo.getSimSlotIndex()
175                             != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
176                         activeSlotSubInfoList.add(subInfo);
177                     } else {
178                         inactiveSlotSubInfoList.add(subInfo);
179                     }
180                 }
181             }
182         }
183 
184         // find any physical SIM which is currently inserted within logical slot
185         // and which is our target subscription
186         if ((slotsInfo != null) && (physicalSubInfoList.size() > 0)) {
187             final SubscriptionInfo subInfo = searchForSubscriptionId(physicalSubInfoList, subId);
188             if (subInfo == null) {
189                 return false;
190             }
191             // verify if subscription is inserted within slot
192             for (UiccSlotInfo slotInfo : slotsInfo) {
193                 if ((slotInfo != null) && (!slotInfo.getIsEuicc())
194                         && (slotInfo.getPorts().stream().findFirst().get().getLogicalSlotIndex()
195                         == subInfo.getSimSlotIndex())) {
196                     return true;
197                 }
198             }
199             return false;
200         }
201 
202         // When all of the eSIM profiles are opprtunistic and no physical SIM,
203         // first opportunistic subscriptions with same group UUID can be primary.
204         if (nonOpportunisticSubInfoList.size() <= 0) {
205             if (physicalSubInfoList.size() > 0) {
206                 return false;
207             }
208             if (activeSlotSubInfoList.size() > 0) {
209                 return (activeSlotSubInfoList.get(0).getSubscriptionId() == subId);
210             }
211             return (inactiveSlotSubInfoList.get(0).getSubscriptionId() == subId);
212         }
213 
214         // Allow non-opportunistic + active eSIM subscription as primary
215         int numberOfActiveNonOpportunisticSubs = 0;
216         boolean isTargetNonOpportunistic = false;
217         for (SubscriptionInfo subInfo : nonOpportunisticSubInfoList) {
218             final boolean isTargetSubInfo = (subInfo.getSubscriptionId() == subId);
219             if (subInfo.getSimSlotIndex() != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
220                 if (isTargetSubInfo) {
221                     return true;
222                 }
223                 numberOfActiveNonOpportunisticSubs++;
224             } else {
225                 isTargetNonOpportunistic |= isTargetSubInfo;
226             }
227         }
228         if (numberOfActiveNonOpportunisticSubs > 0) {
229             return false;
230         }
231         return isTargetNonOpportunistic;
232     }
233 
searchForSubscriptionId(List<SubscriptionInfo> subInfoList, int subscriptionId)234     private static SubscriptionInfo searchForSubscriptionId(List<SubscriptionInfo> subInfoList,
235             int subscriptionId) {
236         for (SubscriptionInfo subInfo : subInfoList) {
237             if (subInfo.getSubscriptionId() == subscriptionId) {
238                 return subInfo;
239             }
240         }
241         return null;
242     }
243 
244     /**
245      * Return a mapping of active subscription ids to display names. Each display name is
246      * guaranteed to be unique in the following manner:
247      * 1) If the original display name is not unique, the last four digits of the phone number
248      *    will be appended.
249      * 2) If the phone number is not visible or the last four digits are shared with another
250      *    subscription, the subscription id will be appended to the original display name.
251      * More details can be found at go/unique-sub-display-names.
252      *
253      * @return map of active subscription ids to display names.
254      */
255     @VisibleForTesting
getUniqueSubscriptionDisplayNames(Context context)256     public static Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) {
257         class DisplayInfo {
258             public SubscriptionInfo subscriptionInfo;
259             public CharSequence originalName;
260             public CharSequence uniqueName;
261         }
262 
263         // Map of SubscriptionId to DisplayName
264         final Supplier<Stream<DisplayInfo>> originalInfos =
265                 () -> getAvailableSubscriptions(context)
266                 .stream()
267                 .filter(i -> {
268                     // Filter out null values.
269                     return (i != null && i.getDisplayName() != null);
270                 })
271                 .map(i -> {
272                     DisplayInfo info = new DisplayInfo();
273                     info.subscriptionInfo = i;
274                     String displayName = i.getDisplayName().toString();
275                     info.originalName = TextUtils.equals(displayName, PROFILE_GENERIC_DISPLAY_NAME)
276                             ? context.getResources().getString(R.string.sim_card)
277                             : displayName.trim();
278                     return info;
279                 });
280 
281         // TODO(goldmanj) consider using a map of DisplayName to SubscriptionInfos.
282         // A Unique set of display names
283         Set<CharSequence> uniqueNames = new HashSet<>();
284         // Return the set of duplicate names
285         final Set<CharSequence> duplicateOriginalNames = originalInfos.get()
286                 .filter(info -> !uniqueNames.add(info.originalName))
287                 .map(info -> info.originalName)
288                 .collect(Collectors.toSet());
289 
290         // If a display name is duplicate, append the final 4 digits of the phone number.
291         // Creates a mapping of Subscription id to original display name + phone number display name
292         final Supplier<Stream<DisplayInfo>> uniqueInfos = () -> originalInfos.get().map(info -> {
293             if (duplicateOriginalNames.contains(info.originalName)) {
294                 // This may return null, if the user cannot view the phone number itself.
295                 final String phoneNumber = DeviceInfoUtils.getBidiFormattedPhoneNumber(context,
296                         info.subscriptionInfo);
297                 String lastFourDigits = "";
298                 if (phoneNumber != null) {
299                     lastFourDigits = (phoneNumber.length() > 4)
300                         ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber;
301                 }
302 
303                 if (TextUtils.isEmpty(lastFourDigits)) {
304                     info.uniqueName = info.originalName;
305                 } else {
306                     info.uniqueName = info.originalName + " " + lastFourDigits;
307                 }
308 
309             } else {
310                 info.uniqueName = info.originalName;
311             }
312             return info;
313         });
314 
315         // Check uniqueness a second time.
316         // We might not have had permission to view the phone numbers.
317         // There might also be multiple phone numbers whose last 4 digits the same.
318         uniqueNames.clear();
319         final Set<CharSequence> duplicatePhoneNames = uniqueInfos.get()
320                 .filter(info -> !uniqueNames.add(info.uniqueName))
321                 .map(info -> info.uniqueName)
322                 .collect(Collectors.toSet());
323 
324         return uniqueInfos.get().map(info -> {
325             if (duplicatePhoneNames.contains(info.uniqueName)) {
326                 info.uniqueName = info.originalName + " "
327                         + info.subscriptionInfo.getSubscriptionId();
328             }
329             return info;
330         }).collect(Collectors.toMap(
331                 info -> info.subscriptionInfo.getSubscriptionId(),
332                 info -> info.uniqueName));
333     }
334 
335     /**
336      * Return the display name for a subscription id, which is guaranteed to be unique.
337      * The logic to create this name has the following order of operations:
338      * 1) If the original display name is not unique, the last four digits of the phone number
339      *    will be appended.
340      * 2) If the phone number is not visible or the last four digits are shared with another
341      *    subscription, the subscription id will be appended to the original display name.
342      * More details can be found at go/unique-sub-display-names.
343      *
344      * @return map of active subscription ids to display names.
345      */
346     @VisibleForTesting
347     public static CharSequence getUniqueSubscriptionDisplayName(
348             Integer subscriptionId, Context context) {
349         final Map<Integer, CharSequence> displayNames = getUniqueSubscriptionDisplayNames(context);
350         return displayNames.getOrDefault(subscriptionId, "");
351     }
352 
353     /**
354      * Return the display name for a subscription, which is guaranteed to be unique.
355      * The logic to create this name has the following order of operations:
356      * 1) If the original display name is not unique, the last four digits of the phone number
357      *    will be appended.
358      * 2) If the phone number is not visible or the last four digits are shared with another
359      *    subscription, the subscription id will be appended to the original display name.
360      * More details can be found at go/unique-sub-display-names.
361      *
362      * @return map of active subscription ids to display names.
363      */
364     public static CharSequence getUniqueSubscriptionDisplayName(
365             SubscriptionInfo info, Context context) {
366         if (info == null) {
367             return "";
368         }
369         return getUniqueSubscriptionDisplayName(info.getSubscriptionId(), context);
370     }
371 
372     public static String getDisplayName(SubscriptionInfo info) {
373         final CharSequence name = info.getDisplayName();
374         if (name != null) {
375             return name.toString();
376         }
377         return "";
378     }
379 
380     /**
381      * Whether Settings should show a "Use SIM" toggle in pSIM detailed page.
382      */
383     public static boolean showToggleForPhysicalSim(SubscriptionManager subMgr) {
384         return subMgr.canDisablePhysicalSubscription();
385     }
386 
387     /**
388      * Get phoneId or logical slot index for a subId if active, or INVALID_PHONE_INDEX if inactive.
389      */
390     public static int getPhoneId(Context context, int subId) {
391         final SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
392         if (subManager == null) {
393             return INVALID_SIM_SLOT_INDEX;
394         }
395         final SubscriptionInfo info = subManager.getActiveSubscriptionInfo(subId);
396         if (info == null) {
397             return INVALID_SIM_SLOT_INDEX;
398         }
399         return info.getSimSlotIndex();
400     }
401 
402     /**
403      * Return a list of subscriptions that are available and visible to the user.
404      *
405      * @return list of user selectable subscriptions.
406      */
407     public static List<SubscriptionInfo> getSelectableSubscriptionInfoList(Context context) {
408         SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
409         List<SubscriptionInfo> availableList = subManager.getAvailableSubscriptionInfoList();
410         if (availableList == null) {
411             return null;
412         } else {
413             // Multiple subscriptions in a group should only have one representative.
414             // It should be the current active primary subscription if any, or any
415             // primary subscription.
416             List<SubscriptionInfo> selectableList = new ArrayList<>();
417             Map<ParcelUuid, SubscriptionInfo> groupMap = new HashMap<>();
418 
419             for (SubscriptionInfo info : availableList) {
420                 // Opportunistic subscriptions are considered invisible
421                 // to users so they should never be returned.
422                 if (!isSubscriptionVisible(subManager, context, info)) continue;
423 
424                 ParcelUuid groupUuid = info.getGroupUuid();
425                 if (groupUuid == null) {
426                     // Doesn't belong to any group. Add in the list.
427                     selectableList.add(info);
428                 } else if (!groupMap.containsKey(groupUuid)
429                         || (groupMap.get(groupUuid).getSimSlotIndex() == INVALID_SIM_SLOT_INDEX
430                         && info.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX)) {
431                     // If it belongs to a group that has never been recorded or it's the current
432                     // active subscription, add it in the list.
433                     selectableList.remove(groupMap.get(groupUuid));
434                     selectableList.add(info);
435                     groupMap.put(groupUuid, info);
436                 }
437 
438             }
439             return selectableList;
440         }
441     }
442 
443     /**
444      * Starts a dialog activity to handle SIM enabling/disabling.
445      * @param context {@code Context}
446      * @param subId The id of subscription need to be enabled or disabled.
447      * @param enable Whether the subscription with {@code subId} should be enabled or disabled.
448      */
449     public static void startToggleSubscriptionDialogActivity(
450             Context context, int subId, boolean enable) {
451         if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
452             Log.i(TAG, "Unable to toggle subscription due to invalid subscription ID.");
453             return;
454         }
455         context.startActivity(ToggleSubscriptionDialogActivity.getIntent(context, subId, enable));
456     }
457 
458     /**
459      * Starts a dialog activity to handle eSIM deletion.
460      * @param context {@code Context}
461      * @param subId The id of subscription need to be deleted.
462      */
463     public static void startDeleteEuiccSubscriptionDialogActivity(Context context, int subId) {
464         if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
465             Log.i(TAG, "Unable to delete subscription due to invalid subscription ID.");
466             return;
467         }
468         context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId));
469     }
470 
471     /**
472      * Finds and returns a subscription with a specific subscription ID.
473      * @param subscriptionManager The ProxySubscriptionManager for accessing subscription
474      *                            information
475      * @param subId The id of subscription to be returned
476      * @return the {@code SubscriptionInfo} whose ID is {@code subId}. It returns null if the
477      * {@code subId} is {@code SubscriptionManager.INVALID_SUBSCRIPTION_ID} or no such
478      * {@code SubscriptionInfo} is found.
479      */
480     @Nullable
481     public static SubscriptionInfo getSubById(SubscriptionManager subscriptionManager, int subId) {
482         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
483             return null;
484         }
485         return subscriptionManager
486                 .getAllSubscriptionInfoList()
487                 .stream()
488                 .filter(subInfo -> subInfo.getSubscriptionId() == subId)
489                 .findFirst()
490                 .orElse(null);
491     }
492 
493     /**
494      * Whether a subscription is visible to API caller. If it's a bundled opportunistic
495      * subscription, it should be hidden anywhere in Settings, dialer, status bar etc.
496      * Exception is if caller owns carrier privilege, in which case they will
497      * want to see their own hidden subscriptions.
498      *
499      * @param info the subscriptionInfo to check against.
500      * @return true if this subscription should be visible to the API caller.
501      */
502     public static boolean isSubscriptionVisible(
503             SubscriptionManager subscriptionManager, Context context, SubscriptionInfo info) {
504         if (info == null) return false;
505         // If subscription is NOT grouped opportunistic subscription, it's visible.
506         if (info.getGroupUuid() == null || !info.isOpportunistic()) return true;
507 
508         // If the caller is the carrier app and owns the subscription, it should be visible
509         // to the caller.
510         TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
511                 .createForSubscriptionId(info.getSubscriptionId());
512         boolean hasCarrierPrivilegePermission = telephonyManager.hasCarrierPrivileges()
513                 || subscriptionManager.canManageSubscription(info);
514         return hasCarrierPrivilegePermission;
515     }
516 
517     /**
518      * Finds all the available subscriptions having the same group uuid as {@code subscriptionInfo}.
519      * @param subscriptionManager The SubscriptionManager for accessing subscription information
520      * @param subId The id of subscription
521      * @return a list of {@code SubscriptionInfo} which have the same group UUID.
522      */
523     public static List<SubscriptionInfo> findAllSubscriptionsInGroup(
524             SubscriptionManager subscriptionManager, int subId) {
525 
526         SubscriptionInfo subscription = getSubById(subscriptionManager, subId);
527         if (subscription == null) {
528             return Collections.emptyList();
529         }
530         ParcelUuid groupUuid = subscription.getGroupUuid();
531         List<SubscriptionInfo> availableSubscriptions =
532                 subscriptionManager.getAvailableSubscriptionInfoList();
533 
534         if (availableSubscriptions == null
535                 || availableSubscriptions.isEmpty()
536                 || groupUuid == null) {
537             return Collections.singletonList(subscription);
538         }
539 
540         return availableSubscriptions
541                 .stream()
542                 .filter(sub -> sub.isEmbedded() && groupUuid.equals(sub.getGroupUuid()))
543                 .collect(Collectors.toList());
544     }
545 
546     /** Returns the formatted phone number of a subscription. */
547     @Nullable
548     public static String getFormattedPhoneNumber(
549             Context context, SubscriptionInfo subscriptionInfo) {
550         if (subscriptionInfo == null) {
551             Log.e(TAG, "Invalid subscription.");
552             return null;
553         }
554 
555         final SubscriptionManager subscriptionManager = context.getSystemService(
556                 SubscriptionManager.class);
557         String rawPhoneNumber = subscriptionManager.getPhoneNumber(
558                 subscriptionInfo.getSubscriptionId());
559         if (TextUtils.isEmpty(rawPhoneNumber)) {
560             return null;
561         }
562         String countryIso = MccTable.countryCodeForMcc(subscriptionInfo.getMccString());
563         return PhoneNumberUtils.formatNumber(rawPhoneNumber, countryIso);
564     }
565 
566     /**
567      * Returns the subscription on a removable sim card. The device does not need to be on removable
568      * slot.
569      */
570     @Nullable
571     public static SubscriptionInfo getFirstRemovableSubscription(Context context) {
572         TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
573         SubscriptionManager subscriptionManager =
574                 context.getSystemService(SubscriptionManager.class);
575         List<UiccCardInfo> cardInfos = telephonyManager.getUiccCardsInfo();
576         if (cardInfos == null) {
577             Log.w(TAG, "UICC cards info list is empty.");
578             return null;
579         }
580         List<SubscriptionInfo> allSubscriptions = subscriptionManager.getAllSubscriptionInfoList();
581         if (allSubscriptions == null) {
582             Log.w(TAG, "All subscription info list is empty.");
583             return null;
584         }
585         for (UiccCardInfo cardInfo : cardInfos) {
586             if (cardInfo == null) {
587                 Log.w(TAG, "Got null card.");
588                 continue;
589             }
590             if (!cardInfo.isRemovable()
591                     || cardInfo.getCardId() == TelephonyManager.UNSUPPORTED_CARD_ID) {
592                 Log.i(TAG, "Skip embedded card or invalid cardId on slot: "
593                         + cardInfo.getPhysicalSlotIndex());
594                 continue;
595             }
596             Log.i(TAG, "Target removable cardId :" + cardInfo.getCardId());
597             for (SubscriptionInfo subInfo : allSubscriptions) {
598                 // Match the removable card id with subscription card id.
599                 if (cardInfo.getCardId() == subInfo.getCardId()) {
600                     return subInfo;
601                 }
602             }
603         }
604         return null;
605     }
606 
607     public static CharSequence getDefaultSimConfig(Context context, int subId) {
608         boolean isDefaultCall = subId == getDefaultVoiceSubscriptionId();
609         boolean isDefaultSms = subId == getDefaultSmsSubscriptionId();
610         boolean isDefaultData = subId == getDefaultDataSubscriptionId();
611 
612         if (!isDefaultData && !isDefaultCall && !isDefaultSms) {
613             return null;
614         }
615 
616         final StringBuilder defaultConfig = new StringBuilder();
617         if (isDefaultData) {
618             defaultConfig.append(
619                     getResForDefaultConfig(context, R.string.default_active_sim_mobile_data))
620                     .append(", ");
621         }
622 
623         if (isDefaultCall) {
624             defaultConfig.append(getResForDefaultConfig(context, R.string.default_active_sim_calls))
625                     .append(", ");
626         }
627 
628         if (isDefaultSms) {
629             defaultConfig.append(getResForDefaultConfig(context, R.string.default_active_sim_sms))
630                     .append(", ");
631         }
632 
633         // Do not add ", " for the last config.
634         defaultConfig.setLength(defaultConfig.length() - 2);
635 
636         final String summary = context.getResources().getString(
637                 R.string.sim_category_default_active_sim,
638                 defaultConfig);
639 
640         return summary;
641     }
642 
643     private static String getResForDefaultConfig(Context context, int resId) {
644         return context.getResources().getString(resId);
645     }
646 
647     private static int getDefaultVoiceSubscriptionId() {
648         return SubscriptionManager.getDefaultVoiceSubscriptionId();
649     }
650 
651     private static int getDefaultSmsSubscriptionId() {
652         return SubscriptionManager.getDefaultSmsSubscriptionId();
653     }
654 
655     private static int getDefaultDataSubscriptionId() {
656         return SubscriptionManager.getDefaultDataSubscriptionId();
657     }
658 
659 
660     /**
661      * Select one of the subscription as the default subscription.
662      * @param subAnnoList a list of {@link SubscriptionAnnotation}
663      * @return ideally the {@link SubscriptionAnnotation} as expected
664      */
665     private static SubscriptionAnnotation getDefaultSubscriptionSelection(
666             List<SubscriptionAnnotation> subAnnoList) {
667         return (subAnnoList == null) ? null :
668                 subAnnoList.stream()
669                         .filter(SubscriptionAnnotation::isDisplayAllowed)
670                         .filter(SubscriptionAnnotation::isActive)
671                         .findFirst().orElse(null);
672     }
673 
674     public static SubscriptionInfo getSubscriptionOrDefault(Context context, int subscriptionId) {
675         return getSubscription(context, subscriptionId,
676                 (subscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) ? null : (
677                         subAnnoList -> getDefaultSubscriptionSelection(subAnnoList)
678                 ));
679     }
680 
681     /**
682      * Get the current subscription to display. First check whether intent has {@link
683      * Settings#EXTRA_SUB_ID} and if so find the subscription with that id.
684      * If not, select default one based on {@link Function} provided.
685      *
686      * @param preferredSubscriptionId preferred subscription id
687      * @param selectionOfDefault when true current subscription is absent
688      */
689     private static SubscriptionInfo getSubscription(Context context, int preferredSubscriptionId,
690             Function<List<SubscriptionAnnotation>, SubscriptionAnnotation> selectionOfDefault) {
691         List<SubscriptionAnnotation> subList =
692                 (new SelectableSubscriptions(context, true)).call();
693         Log.d(TAG, "get subId=" + preferredSubscriptionId + " from " + subList);
694         SubscriptionAnnotation currentSubInfo = subList.stream()
695                 .filter(SubscriptionAnnotation::isDisplayAllowed)
696                 .filter(subAnno -> (subAnno.getSubscriptionId() == preferredSubscriptionId))
697                 .findFirst().orElse(null);
698         if ((currentSubInfo == null) && (selectionOfDefault != null)) {
699             currentSubInfo = selectionOfDefault.apply(subList);
700         }
701         return (currentSubInfo == null) ? null : currentSubInfo.getSubInfo();
702     }
703 }
704