/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.network; import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT; import static com.android.internal.util.CollectionUtils.emptyIfNull; import android.content.Context; import android.os.ParcelUuid; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccSlotInfo; import androidx.annotation.VisibleForTesting; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class SubscriptionUtil { private static final String TAG = "SubscriptionUtil"; private static List sAvailableResultsForTesting; private static List sActiveResultsForTesting; @VisibleForTesting public static void setAvailableSubscriptionsForTesting(List results) { sAvailableResultsForTesting = results; } @VisibleForTesting public static void setActiveSubscriptionsForTesting(List results) { sActiveResultsForTesting = results; } public static List getActiveSubscriptions(SubscriptionManager manager) { if (sActiveResultsForTesting != null) { return sActiveResultsForTesting; } final List subscriptions = manager.getActiveSubscriptionInfoList(); if (subscriptions == null) { return new ArrayList<>(); } return subscriptions; } @VisibleForTesting static boolean isInactiveInsertedPSim(UiccSlotInfo slotInfo) { if (slotInfo == null) { return false; } return !slotInfo.getIsEuicc() && !slotInfo.getIsActive() && slotInfo.getCardStateInfo() == CARD_STATE_INFO_PRESENT; } /** * Get all of the subscriptions which is available to display to the user. * * @param context {@code Context} * @return list of {@code SubscriptionInfo} */ public static List getAvailableSubscriptions(Context context) { if (sAvailableResultsForTesting != null) { return sAvailableResultsForTesting; } return new ArrayList<>(emptyIfNull(getSelectableSubscriptionInfoList(context))); } /** * Get subscription which is available to be displayed to the user * per subscription id. * * @param context {@code Context} * @param subscriptionManager The ProxySubscriptionManager for accessing subcription * information * @param subId The id of subscription to be retrieved * @return {@code SubscriptionInfo} based on the given subscription id. Null of subscription * is invalid or not allowed to be displayed to the user. */ public static SubscriptionInfo getAvailableSubscription(Context context, ProxySubscriptionManager subscriptionManager, int subId) { final SubscriptionInfo subInfo = subscriptionManager.getAccessibleSubscriptionInfo(subId); if (subInfo == null) { return null; } final ParcelUuid groupUuid = subInfo.getGroupUuid(); if (groupUuid != null) { if (isPrimarySubscriptionWithinSameUuid(getUiccSlotsInfo(context), groupUuid, subscriptionManager.getAccessibleSubscriptionsInfo(), subId)) { return subInfo; } return null; } return subInfo; } private static UiccSlotInfo [] getUiccSlotsInfo(Context context) { final TelephonyManager telMgr = context.getSystemService(TelephonyManager.class); return telMgr.getUiccSlotsInfo(); } private static boolean isPrimarySubscriptionWithinSameUuid(UiccSlotInfo[] slotsInfo, ParcelUuid groupUuid, List subscriptions, int subId) { // only interested in subscriptions with this group UUID final ArrayList physicalSubInfoList = new ArrayList(); final ArrayList nonOpportunisticSubInfoList = new ArrayList(); final ArrayList activeSlotSubInfoList = new ArrayList(); final ArrayList inactiveSlotSubInfoList = new ArrayList(); for (SubscriptionInfo subInfo : subscriptions) { if (groupUuid.equals(subInfo.getGroupUuid())) { if (!subInfo.isEmbedded()) { physicalSubInfoList.add(subInfo); } else { if (!subInfo.isOpportunistic()) { nonOpportunisticSubInfoList.add(subInfo); } if (subInfo.getSimSlotIndex() != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { activeSlotSubInfoList.add(subInfo); } else { inactiveSlotSubInfoList.add(subInfo); } } } } // find any physical SIM which is currently inserted within logical slot // and which is our target subscription if ((slotsInfo != null) && (physicalSubInfoList.size() > 0)) { final SubscriptionInfo subInfo = searchForSubscriptionId(physicalSubInfoList, subId); if (subInfo == null) { return false; } // verify if subscription is inserted within slot for (UiccSlotInfo slotInfo : slotsInfo) { if ((slotInfo != null) && (!slotInfo.getIsEuicc()) && (slotInfo.getLogicalSlotIdx() == subInfo.getSimSlotIndex())) { return true; } } return false; } // When all of the eSIM profiles are opprtunistic and no physical SIM, // first opportunistic subscriptions with same group UUID can be primary. if (nonOpportunisticSubInfoList.size() <= 0) { if (physicalSubInfoList.size() > 0) { return false; } if (activeSlotSubInfoList.size() > 0) { return (activeSlotSubInfoList.get(0).getSubscriptionId() == subId); } return (inactiveSlotSubInfoList.get(0).getSubscriptionId() == subId); } // Allow non-opportunistic + active eSIM subscription as primary int numberOfActiveNonOpportunisticSubs = 0; boolean isTargetNonOpportunistic = false; for (SubscriptionInfo subInfo : nonOpportunisticSubInfoList) { final boolean isTargetSubInfo = (subInfo.getSubscriptionId() == subId); if (subInfo.getSimSlotIndex() != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { if (isTargetSubInfo) { return true; } numberOfActiveNonOpportunisticSubs++; } else { isTargetNonOpportunistic |= isTargetSubInfo; } } if (numberOfActiveNonOpportunisticSubs > 0) { return false; } return isTargetNonOpportunistic; } private static SubscriptionInfo searchForSubscriptionId(List subInfoList, int subscriptionId) { for (SubscriptionInfo subInfo : subInfoList) { if (subInfo.getSubscriptionId() == subscriptionId) { return subInfo; } } return null; } public static String getDisplayName(SubscriptionInfo info) { final CharSequence name = info.getDisplayName(); if (name != null) { return name.toString(); } return ""; } /** * Whether Settings should show a "Use SIM" toggle in pSIM detailed page. */ public static boolean showToggleForPhysicalSim(SubscriptionManager subMgr) { return subMgr.canDisablePhysicalSubscription(); } /** * Get phoneId or logical slot index for a subId if active, or INVALID_PHONE_INDEX if inactive. */ public static int getPhoneId(Context context, int subId) { final SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class); if (subManager == null) { return INVALID_SIM_SLOT_INDEX; } final SubscriptionInfo info = subManager.getActiveSubscriptionInfo(subId); if (info == null) { return INVALID_SIM_SLOT_INDEX; } return info.getSimSlotIndex(); } /** * Return a list of subscriptions that are available and visible to the user. * * @return list of user selectable subscriptions. */ public static List getSelectableSubscriptionInfoList(Context context) { SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class); List availableList = subManager.getAvailableSubscriptionInfoList(); if (availableList == null) { return null; } else { // Multiple subscriptions in a group should only have one representative. // It should be the current active primary subscription if any, or any // primary subscription. List selectableList = new ArrayList<>(); Map groupMap = new HashMap<>(); for (SubscriptionInfo info : availableList) { // Opportunistic subscriptions are considered invisible // to users so they should never be returned. if (!isSubscriptionVisible(subManager, context, info)) continue; ParcelUuid groupUuid = info.getGroupUuid(); if (groupUuid == null) { // Doesn't belong to any group. Add in the list. selectableList.add(info); } else if (!groupMap.containsKey(groupUuid) || (groupMap.get(groupUuid).getSimSlotIndex() == INVALID_SIM_SLOT_INDEX && info.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX)) { // If it belongs to a group that has never been recorded or it's the current // active subscription, add it in the list. selectableList.remove(groupMap.get(groupUuid)); selectableList.add(info); groupMap.put(groupUuid, info); } } return selectableList; } } /** * Whether a subscription is visible to API caller. If it's a bundled opportunistic * subscription, it should be hidden anywhere in Settings, dialer, status bar etc. * Exception is if caller owns carrier privilege, in which case they will * want to see their own hidden subscriptions. * * @param info the subscriptionInfo to check against. * @return true if this subscription should be visible to the API caller. */ private static boolean isSubscriptionVisible( SubscriptionManager subscriptionManager, Context context, SubscriptionInfo info) { if (info == null) return false; // If subscription is NOT grouped opportunistic subscription, it's visible. if (info.getGroupUuid() == null || !info.isOpportunistic()) return true; // If the caller is the carrier app and owns the subscription, it should be visible // to the caller. TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class) .createForSubscriptionId(info.getSubscriptionId()); boolean hasCarrierPrivilegePermission = telephonyManager.hasCarrierPrivileges() || subscriptionManager.canManageSubscription(info); return hasCarrierPrivilegePermission; } }