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