In Android R, when users enable, disable, delete, or rename a profile, Settings calls SubscriptionManager APIs which telephony ends up to send actions “TOGGLE_SUBSCRIPTION_PRIVILEGED”, “DELETE_SUBSCRIPTION_PRIVILEGED”, and “RENAME_SUBSCRIPTION_PRIVILEGED” to EuiccManager. After EuiccUiDispatcher dispatches the action, Google LPA receives it and starts the corresponding operations and DSDS dialogs. We can see there some back-and-forth that goes on between LPA and telephony. In order to improve the current structure, we devided to move the dialogs to Settings and make it call EuiccManager APIs directly. Bug: 160819390 Test: Manually tested eSIM profile disabling. Design: https://docs.google.com/document/d/1wb5_hoBkZVbkXGNWHbx4Jf61swjfxsJzkytiTzJosYo/edit?usp=sharing Change-Id: Ib933df42ca3606de2310edc4d64c3e11800a1096
336 lines
14 KiB
Java
336 lines
14 KiB
Java
/*
|
|
* 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.annotation.Nullable;
|
|
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 com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
|
|
|
|
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<SubscriptionInfo> sAvailableResultsForTesting;
|
|
private static List<SubscriptionInfo> sActiveResultsForTesting;
|
|
|
|
@VisibleForTesting
|
|
public static void setAvailableSubscriptionsForTesting(List<SubscriptionInfo> results) {
|
|
sAvailableResultsForTesting = results;
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public static void setActiveSubscriptionsForTesting(List<SubscriptionInfo> results) {
|
|
sActiveResultsForTesting = results;
|
|
}
|
|
|
|
public static List<SubscriptionInfo> getActiveSubscriptions(SubscriptionManager manager) {
|
|
if (sActiveResultsForTesting != null) {
|
|
return sActiveResultsForTesting;
|
|
}
|
|
final List<SubscriptionInfo> 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<SubscriptionInfo> 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<SubscriptionInfo> subscriptions, int subId) {
|
|
// only interested in subscriptions with this group UUID
|
|
final ArrayList<SubscriptionInfo> physicalSubInfoList =
|
|
new ArrayList<SubscriptionInfo>();
|
|
final ArrayList<SubscriptionInfo> nonOpportunisticSubInfoList =
|
|
new ArrayList<SubscriptionInfo>();
|
|
final ArrayList<SubscriptionInfo> activeSlotSubInfoList =
|
|
new ArrayList<SubscriptionInfo>();
|
|
final ArrayList<SubscriptionInfo> inactiveSlotSubInfoList =
|
|
new ArrayList<SubscriptionInfo>();
|
|
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<SubscriptionInfo> 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<SubscriptionInfo> getSelectableSubscriptionInfoList(Context context) {
|
|
SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
|
|
List<SubscriptionInfo> 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<SubscriptionInfo> selectableList = new ArrayList<>();
|
|
Map<ParcelUuid, SubscriptionInfo> 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;
|
|
}
|
|
}
|
|
|
|
/** Starts a dialog activity to handle SIM enabling/disabling. */
|
|
public static void startToggleSubscriptionDialogActivity(
|
|
Context context, int subId, boolean enable) {
|
|
context.startActivity(ToggleSubscriptionDialogActivity.getIntent(context, subId, enable));
|
|
}
|
|
|
|
/**
|
|
* Finds and returns a subscription with a specific subscription ID.
|
|
* @param subscriptionManager The ProxySubscriptionManager for accessing subscription
|
|
* information
|
|
* @param subId The id of subscription to be returned
|
|
* @return the {@code SubscriptionInfo} whose ID is {@code subId}. It returns null if the
|
|
* {@code subId} is {@code SubscriptionManager.INVALID_SUBSCRIPTION_ID} or no such
|
|
* {@code SubscriptionInfo} is found.
|
|
*/
|
|
@Nullable
|
|
public static SubscriptionInfo getSubById(SubscriptionManager subscriptionManager, int subId) {
|
|
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
|
return null;
|
|
}
|
|
return subscriptionManager
|
|
.getAllSubscriptionInfoList()
|
|
.stream()
|
|
.filter(subInfo -> subInfo.getSubscriptionId() == subId)
|
|
.findFirst()
|
|
.get();
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|