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