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