blob: b4bfd0b334cc900878375915c83718460becd318 [file] [log] [blame]
/*
* Copyright 2022 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.internal.telephony.subscription;
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.TelephonyServiceManager;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Telephony.SimInfo;
import android.service.carrier.CarrierIdentifier;
import android.service.euicc.EuiccProfileInfo;
import android.service.euicc.EuiccService;
import android.service.euicc.GetEuiccProfileInfoListResult;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.DataRoamingMode;
import android.telephony.SubscriptionManager.DeviceToDeviceStatusSharingPreference;
import android.telephony.SubscriptionManager.PhoneNumberSource;
import android.telephony.SubscriptionManager.SimDisplayNameSource;
import android.telephony.SubscriptionManager.SubscriptionType;
import android.telephony.SubscriptionManager.UsageSetting;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyManager.SimState;
import android.telephony.TelephonyRegistryManager;
import android.telephony.UiccAccessRule;
import android.telephony.euicc.EuiccManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Base64;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.CarrierResolver;
import com.android.internal.telephony.ISetOpportunisticDataCallback;
import com.android.internal.telephony.ISub;
import com.android.internal.telephony.IccCard;
import com.android.internal.telephony.MccTable;
import com.android.internal.telephony.MultiSimSettingController;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.telephony.data.PhoneSwitcher;
import com.android.internal.telephony.euicc.EuiccController;
import com.android.internal.telephony.subscription.SubscriptionDatabaseManager.SubscriptionDatabaseManagerCallback;
import com.android.internal.telephony.uicc.IccRecords;
import com.android.internal.telephony.uicc.IccUtils;
import com.android.internal.telephony.uicc.UiccCard;
import com.android.internal.telephony.uicc.UiccController;
import com.android.internal.telephony.uicc.UiccPort;
import com.android.internal.telephony.uicc.UiccSlot;
import com.android.internal.telephony.util.ArrayUtils;
import com.android.telephony.Rlog;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* The subscription manager service is the backend service of {@link SubscriptionManager}.
* The service handles all SIM subscription related requests from clients.
*/
public class SubscriptionManagerService extends ISub.Stub {
private static final String LOG_TAG = "SMSVC";
/** Whether enabling verbose debugging message or not. */
private static final boolean VDBG = false;
/**
* The columns in {@link SimInfo} table that can be directly accessed through
* {@link #getSubscriptionProperty(int, String, String, String)} or
* {@link #setSubscriptionProperty(int, String, String)}. Usually those fields are not
* sensitive. Mostly they are related to user settings, for example, wifi calling
* user settings, cross sim calling user settings, etc...Those fields are protected with
* {@link Manifest.permission#READ_PHONE_STATE} permission only.
*
* For sensitive fields, they usually requires special methods to access. For example,
* {@link #getSubscriptionUserHandle(int)} or {@link #getPhoneNumber(int, int, String, String)}
* that requires higher permission to access.
*/
private static final Set<String> DIRECT_ACCESS_SUBSCRIPTION_COLUMNS = Set.of(
SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
SimInfo.COLUMN_VT_IMS_ENABLED,
SimInfo.COLUMN_WFC_IMS_ENABLED,
SimInfo.COLUMN_WFC_IMS_MODE,
SimInfo.COLUMN_WFC_IMS_ROAMING_MODE,
SimInfo.COLUMN_WFC_IMS_ROAMING_ENABLED,
SimInfo.COLUMN_ENABLED_MOBILE_DATA_POLICIES,
SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
SimInfo.COLUMN_CROSS_SIM_CALLING_ENABLED,
SimInfo.COLUMN_RCS_CONFIG,
SimInfo.COLUMN_ALLOWED_NETWORK_TYPES_FOR_REASONS,
SimInfo.COLUMN_D2D_STATUS_SHARING,
SimInfo.COLUMN_VOIMS_OPT_IN_STATUS,
SimInfo.COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS,
SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED,
SimInfo.COLUMN_SATELLITE_ENABLED
);
/**
* Apps targeting on Android T and beyond will get exception if there is no access to device
* identifiers nor has carrier privileges when calling
* {@link SubscriptionManager#getSubscriptionsInGroup}.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public static final long REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID = 213902861L;
/** Instance of subscription manager service. */
@NonNull
private static SubscriptionManagerService sInstance;
/** The context */
@NonNull
private final Context mContext;
/** App Ops manager instance. */
@NonNull
private final AppOpsManager mAppOpsManager;
/** Telephony manager instance. */
@NonNull
private final TelephonyManager mTelephonyManager;
/** Subscription manager instance. */
@NonNull
private final SubscriptionManager mSubscriptionManager;
/**
* Euicc manager instance. Will be null if the device does not support
* {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@Nullable
private final EuiccManager mEuiccManager;
/** Uicc controller instance. */
@NonNull
private final UiccController mUiccController;
/**
* Euicc controller instance. Will be null if the device does not support
* {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@Nullable
private EuiccController mEuiccController;
/**
* The main handler of subscription manager service. This is running on phone process's main
* thread.
*/
@NonNull
private final Handler mHandler;
/**
* The background handler. This is running on a separate thread.
*/
@NonNull
private final Handler mBackgroundHandler;
/** Local log for most important debug messages. */
@NonNull
private final LocalLog mLocalLog = new LocalLog(128);
/** The subscription database manager. */
@NonNull
private final SubscriptionDatabaseManager mSubscriptionDatabaseManager;
/** The slot index subscription id map. Key is the slot index, and the value is sub id. */
@NonNull
private final WatchedMap<Integer, Integer> mSlotIndexToSubId = new WatchedMap<>();
/** Subscription manager service callbacks. */
@NonNull
private final Set<SubscriptionManagerServiceCallback> mSubscriptionManagerServiceCallbacks =
new ArraySet<>();
/**
* Default sub id. Derived from {@link #mDefaultVoiceSubId} and {@link #mDefaultDataSubId},
* depending on device capability.
*/
@NonNull
private final WatchedInt mDefaultSubId;
/** Default voice subscription id. */
@NonNull
private final WatchedInt mDefaultVoiceSubId;
/** Default data subscription id. */
@NonNull
private final WatchedInt mDefaultDataSubId;
/** Default sms subscription id. */
@NonNull
private final WatchedInt mDefaultSmsSubId;
/** Sim state per logical SIM slot index. */
@NonNull
private final int[] mSimState;
/**
* Watched map that automatically invalidate cache in {@link SubscriptionManager}.
*/
private static class WatchedMap<K, V> extends ConcurrentHashMap<K, V> {
@Override
public void clear() {
super.clear();
SubscriptionManager.invalidateSubscriptionManagerServiceCaches();
}
@Override
public V put(K key, V value) {
V oldValue = super.put(key, value);
if (!Objects.equals(oldValue, value)) {
SubscriptionManager.invalidateSubscriptionManagerServiceCaches();
}
return oldValue;
}
@Override
public V remove(Object key) {
V oldValue = super.remove(key);
if (oldValue != null) {
SubscriptionManager.invalidateSubscriptionManagerServiceCaches();
}
return oldValue;
}
}
/**
* Watched integer.
*/
public static class WatchedInt {
protected int mValue;
/**
* Constructor.
*
* @param initialValue The initial value.
*/
public WatchedInt(int initialValue) {
mValue = initialValue;
}
/**
* @return The value.
*/
public int get() {
return mValue;
}
/**
* Set the value.
*
* @param newValue The new value.
*
* @return {@code true} if {@code newValue} is different from the existing value.
*/
public boolean set(int newValue) {
if (mValue != newValue) {
mValue = newValue;
SubscriptionManager.invalidateSubscriptionManagerServiceCaches();
return true;
}
return false;
}
}
/**
* This is the callback used for listening events from {@link SubscriptionManagerService}.
*/
public static class SubscriptionManagerServiceCallback {
/** The executor of the callback. */
@NonNull
private final Executor mExecutor;
/**
* Constructor
*
* @param executor The executor of the callback.
*/
public SubscriptionManagerServiceCallback(@NonNull @CallbackExecutor Executor executor) {
mExecutor = executor;
}
/**
* @return The executor of the callback.
*/
@NonNull
@VisibleForTesting
public Executor getExecutor() {
return mExecutor;
}
/**
* Invoke the callback from executor.
*
* @param runnable The callback method to invoke.
*/
public void invokeFromExecutor(@NonNull Runnable runnable) {
mExecutor.execute(runnable);
}
/**
* Called when subscription changed.
*
* @param subId The subscription id.
*/
public void onSubscriptionChanged(int subId) {}
/**
* Called when {@link SubscriptionInfoInternal#areUiccApplicationsEnabled()} changed.
*
* @param subId The subscription id.
*/
public void onUiccApplicationsEnabled(int subId) {}
}
/** DeviceConfig key for whether work profile telephony feature is enabled. */
private static final String KEY_ENABLE_WORK_PROFILE_TELEPHONY = "enable_work_profile_telephony";
/** {@code true} if the work profile telephony feature is enabled otherwise {@code false}. */
private boolean mIsWorkProfileTelephonyEnabled = false;
/**
* The constructor
*
* @param context The context
* @param looper The looper for the handler.
*/
public SubscriptionManagerService(@NonNull Context context, @NonNull Looper looper) {
sInstance = this;
mContext = context;
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
mEuiccManager = context.getSystemService(EuiccManager.class);
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mUiccController = UiccController.getInstance();
mHandler = new Handler(looper);
HandlerThread backgroundThread = new HandlerThread(LOG_TAG);
backgroundThread.start();
mBackgroundHandler = new Handler(backgroundThread.getLooper());
TelephonyServiceManager.ServiceRegisterer subscriptionServiceRegisterer =
TelephonyFrameworkInitializer
.getTelephonyServiceManager()
.getSubscriptionServiceRegisterer();
if (subscriptionServiceRegisterer.get() == null) {
subscriptionServiceRegisterer.register(this);
}
mDefaultVoiceSubId = new WatchedInt(Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
SubscriptionManager.INVALID_SUBSCRIPTION_ID)) {
@Override
public boolean set(int newValue) {
int oldValue = mValue;
if (super.set(newValue)) {
logl("Default voice subId changed from " + oldValue + " to " + newValue);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, newValue);
return true;
}
return false;
}
};
mDefaultDataSubId = new WatchedInt(Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
SubscriptionManager.INVALID_SUBSCRIPTION_ID)) {
@Override
public boolean set(int newValue) {
int oldValue = mValue;
if (super.set(newValue)) {
logl("Default data subId changed from " + oldValue + " to " + newValue);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, newValue);
return true;
}
return false;
}
};
mDefaultSmsSubId = new WatchedInt(Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
SubscriptionManager.INVALID_SUBSCRIPTION_ID)) {
@Override
public boolean set(int newValue) {
int oldValue = mValue;
if (super.set(newValue)) {
logl("Default SMS subId changed from " + oldValue + " to " + newValue);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, newValue);
return true;
}
return false;
}
};
mDefaultSubId = new WatchedInt(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mSimState = new int[mTelephonyManager.getSupportedModemCount()];
Arrays.fill(mSimState, TelephonyManager.SIM_STATE_UNKNOWN);
mIsWorkProfileTelephonyEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY,
KEY_ENABLE_WORK_PROFILE_TELEPHONY, false);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_TELEPHONY,
mHandler::post, properties -> {
if (TextUtils.equals(DeviceConfig.NAMESPACE_TELEPHONY, properties.getNamespace())) {
onDeviceConfigChanged();
}
});
// Create a separate thread for subscription database manager. The database will be updated
// from a different thread.
HandlerThread handlerThread = new HandlerThread(LOG_TAG);
handlerThread.start();
mSubscriptionDatabaseManager = new SubscriptionDatabaseManager(context,
handlerThread.getLooper(), new SubscriptionDatabaseManagerCallback(mHandler::post) {
/**
* Called when database has been loaded into the cache.
*/
@Override
public void onDatabaseLoaded() {
log("Subscription database has been loaded.");
for (int phoneId = 0; phoneId < mTelephonyManager.getActiveModemCount()
; phoneId++) {
markSubscriptionsInactive(phoneId);
}
}
/**
* Called when subscription changed.
*
* @param subId The subscription id.
*/
@Override
public void onSubscriptionChanged(int subId) {
mSubscriptionManagerServiceCallbacks.forEach(
callback -> callback.invokeFromExecutor(
() -> callback.onSubscriptionChanged(subId)));
MultiSimSettingController.getInstance().notifySubscriptionInfoChanged();
TelephonyRegistryManager telephonyRegistryManager =
mContext.getSystemService(TelephonyRegistryManager.class);
if (telephonyRegistryManager != null) {
telephonyRegistryManager.notifySubscriptionInfoChanged();
}
SubscriptionInfoInternal subInfo =
mSubscriptionDatabaseManager.getSubscriptionInfoInternal(subId);
if (subInfo != null && subInfo.isOpportunistic()
&& telephonyRegistryManager != null) {
telephonyRegistryManager.notifyOpportunisticSubscriptionInfoChanged();
}
// TODO: Call TelephonyMetrics.updateActiveSubscriptionInfoList when active
// subscription changes.
}
/**
* Called when {@link SubscriptionInfoInternal#areUiccApplicationsEnabled()}
* changed.
*
* @param subId The subscription id.
*/
@Override
public void onUiccApplicationsEnabled(int subId) {
log("onUiccApplicationsEnabled: subId=" + subId);
mSubscriptionManagerServiceCallbacks.forEach(
callback -> callback.invokeFromExecutor(
() -> callback.onUiccApplicationsEnabled(subId)));
}
});
updateDefaultSubId();
mHandler.post(() -> {
// EuiccController is created after SubscriptionManagerService. So we need to get
// the instance later in the handler.
if (mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TELEPHONY_EUICC)) {
mEuiccController = EuiccController.get();
}
});
SubscriptionManager.invalidateSubscriptionManagerServiceCaches();
SubscriptionManager.invalidateSubscriptionManagerServiceEnabledCaches();
}
/**
* @return The singleton instance of {@link SubscriptionManagerService}.
*/
@NonNull
public static SubscriptionManagerService getInstance() {
return sInstance;
}
/**
* Check if the calling package can manage the subscription group.
*
* @param groupUuid a UUID assigned to the subscription group.
* @param callingPackage the package making the IPC.
*
* @return {@code true} if calling package is the owner of or has carrier privileges for all
* subscriptions in the group.
*/
private boolean canPackageManageGroup(@NonNull ParcelUuid groupUuid,
@NonNull String callingPackage) {
if (groupUuid == null) {
throw new IllegalArgumentException("Invalid groupUuid");
}
if (TextUtils.isEmpty(callingPackage)) {
throw new IllegalArgumentException("Empty callingPackage");
}
List<SubscriptionInfo> infoList;
// Getting all subscriptions in the group.
infoList = mSubscriptionDatabaseManager.getAllSubscriptions().stream()
.filter(subInfo -> subInfo.getGroupUuid().equals(groupUuid.toString()))
.map(SubscriptionInfoInternal::toSubscriptionInfo)
.collect(Collectors.toList());
// If the group does not exist, then by default the UUID is up for grabs so no need to
// restrict management of a group (that someone may be attempting to create).
if (ArrayUtils.isEmpty(infoList)) {
return true;
}
// If the calling package is the group owner, skip carrier permission check and return
// true as it was done before.
if (callingPackage.equals(infoList.get(0).getGroupOwner())) return true;
// Check carrier privilege for all subscriptions in the group.
return (checkCarrierPrivilegeOnSubList(infoList.stream()
.mapToInt(SubscriptionInfo::getSubscriptionId).toArray(), callingPackage));
}
/**
* Helper function to check if the caller has carrier privilege permissions on a list of subId.
* The check can either be processed against access rules on currently active SIM cards, or
* the access rules we keep in our database for currently inactive SIMs.
*
* @param subIdList List of subscription ids.
* @param callingPackage The package making the call.
*
* @throws IllegalArgumentException if the some subId is invalid or doesn't exist.
*
* @return {@code true} if checking passes on all subId, {@code false} otherwise.
*/
private boolean checkCarrierPrivilegeOnSubList(@NonNull int[] subIdList,
@NonNull String callingPackage) {
for (int subId : subIdList) {
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
if (subInfo == null) {
loge("checkCarrierPrivilegeOnSubList: subId " + subId + " does not exist.");
return false;
}
if (subInfo.isActive()) {
if (!mTelephonyManager.hasCarrierPrivileges(subId)) {
loge("checkCarrierPrivilegeOnSubList: Does not have carrier privilege on sub "
+ subId);
return false;
}
} else {
if (!mSubscriptionManager.canManageSubscription(subInfo.toSubscriptionInfo(),
callingPackage)) {
loge("checkCarrierPrivilegeOnSubList: cannot manage sub " + subId);
return false;
}
}
}
return true;
}
/**
* Sync the settings from specified subscription to all grouped subscriptions.
*
* @param subId The subscription id of the referenced subscription.
*/
public void syncGroupedSetting(int subId) {
mHandler.post(() -> {
SubscriptionInfoInternal reference = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
if (reference == null) {
loge("syncSettings: Can't find subscription info for sub " + subId);
return;
}
mSubscriptionDatabaseManager.syncToGroup(subId);
});
}
/**
* Check whether the {@code callingPackage} has access to the phone number on the specified
* {@code subId} or not.
*
* @param subId The subscription id.
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
* @param message Message to include in the exception or NoteOp.
*
* @return {@code true} if the caller has phone number access.
*/
private boolean hasPhoneNumberAccess(int subId, @NonNull String callingPackage,
@Nullable String callingFeatureId, @Nullable String message) {
try {
return TelephonyPermissions.checkCallingOrSelfReadPhoneNumber(mContext, subId,
callingPackage, callingFeatureId, message);
} catch (SecurityException e) {
return false;
}
}
/**
* Check whether the {@code callingPackage} has access to subscriber identifiers on the
* specified {@code subId} or not.
*
* @param subId The subscription id.
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
* @param message Message to include in the exception or NoteOp.
* @param reportFailure Indicates if failure should be reported.
*
* @return {@code true} if the caller has identifier access.
*/
private boolean hasSubscriberIdentifierAccess(int subId, @NonNull String callingPackage,
@Nullable String callingFeatureId, @Nullable String message, boolean reportFailure) {
try {
return TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mContext, subId,
callingPackage, callingFeatureId, message, reportFailure);
} catch (SecurityException e) {
// A SecurityException indicates that the calling package is targeting at least the
// minimum level that enforces identifier access restrictions and the new access
// requirements are not met.
return false;
}
}
/**
* Conditionally removes identifiers from the provided {@link SubscriptionInfo} if the {@code
* callingPackage} does not meet the access requirements for identifiers and returns the
* potentially modified object.
*
* <p>
* If the caller does not have {@link Manifest.permission#READ_PHONE_NUMBERS} permission,
* {@link SubscriptionInfo#getNumber()} will return empty string.
* If the caller does not have {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER},
* {@link SubscriptionInfo#getIccId()} and {@link SubscriptionInfo#getCardString()} will return
* empty string, and {@link SubscriptionInfo#getGroupUuid()} will return {@code null}.
*
* @param subInfo The subscription info.
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
* @param message Message to include in the exception or NoteOp.
*
* @return The modified {@link SubscriptionInfo} depending on caller's permission.
*/
@NonNull
private SubscriptionInfo conditionallyRemoveIdentifiers(@NonNull SubscriptionInfo subInfo,
@NonNull String callingPackage, @Nullable String callingFeatureId,
@Nullable String message) {
int subId = subInfo.getSubscriptionId();
boolean hasIdentifierAccess = hasSubscriberIdentifierAccess(subId, callingPackage,
callingFeatureId, message, true);
boolean hasPhoneNumberAccess = hasPhoneNumberAccess(subId, callingPackage,
callingFeatureId, message);
if (hasIdentifierAccess && hasPhoneNumberAccess) {
return subInfo;
}
SubscriptionInfo.Builder result = new SubscriptionInfo.Builder(subInfo);
if (!hasIdentifierAccess) {
result.setIccId(null);
result.setCardString(null);
result.setGroupUuid(null);
}
if (!hasPhoneNumberAccess) {
result.setNumber(null);
}
return result.build();
}
/**
* @return The list of ICCIDs from the inserted physical SIMs.
*/
@NonNull
private List<String> getIccIdsOfInsertedPhysicalSims() {
List<String> iccidList = new ArrayList<>();
UiccSlot[] uiccSlots = mUiccController.getUiccSlots();
if (uiccSlots == null) return iccidList;
for (UiccSlot uiccSlot : uiccSlots) {
if (uiccSlot != null && uiccSlot.getCardState() != null
&& uiccSlot.getCardState().isCardPresent() && !uiccSlot.isEuicc()) {
// Non euicc slots will have single port, so use default port index.
String iccId = uiccSlot.getIccId(TelephonyManager.DEFAULT_PORT_INDEX);
if (!TextUtils.isEmpty(iccId)) {
iccidList.add(IccUtils.stripTrailingFs(iccId));
}
}
}
return iccidList;
}
/**
* Enable or disable work profile telephony feature.
* @param isWorkProfileTelephonyEnabled - {@code true} if the work profile telephony feature
* is enabled otherwise {@code false}.
*/
@VisibleForTesting
public void setWorkProfileTelephonyEnabled(boolean isWorkProfileTelephonyEnabled) {
mIsWorkProfileTelephonyEnabled = isWorkProfileTelephonyEnabled;
}
/**
* Set the subscription carrier id.
*
* @param subId Subscription id.
* @param carrierId The carrier id.
*
* @throws IllegalArgumentException if {@code subId} is invalid or the subscription does not
* exist.
*
* @see TelephonyManager#getSimCarrierId()
*/
public void setCarrierId(int subId, int carrierId) {
// This can throw IllegalArgumentException if the subscription does not exist.
try {
mSubscriptionDatabaseManager.setCarrierId(subId, carrierId);
} catch (IllegalArgumentException e) {
loge("setCarrierId: invalid subId=" + subId);
}
}
/**
* Set MCC/MNC by subscription id.
*
* @param mccMnc MCC/MNC associated with the subscription.
* @param subId The subscription id.
*/
public void setMccMnc(int subId, @NonNull String mccMnc) {
// This can throw IllegalArgumentException if the subscription does not exist.
try {
mSubscriptionDatabaseManager.setMcc(subId, mccMnc.substring(0, 3));
mSubscriptionDatabaseManager.setMnc(subId, mccMnc.substring(3));
} catch (IllegalArgumentException e) {
loge("setMccMnc: invalid subId=" + subId);
}
}
/**
* Set ISO country code by subscription id.
*
* @param iso ISO country code associated with the subscription.
* @param subId The subscription id.
*/
public void setCountryIso(int subId, @NonNull String iso) {
// This can throw IllegalArgumentException if the subscription does not exist.
try {
mSubscriptionDatabaseManager.setCountryIso(subId, iso);
} catch (IllegalArgumentException e) {
loge("setCountryIso: invalid subId=" + subId);
}
}
/**
* Set the name displayed to the user that identifies subscription provider name. This name
* is the SPN displayed in status bar and many other places. Can't be renamed by the user.
*
* @param subId Subscription id.
* @param carrierName The carrier name.
*/
public void setCarrierName(int subId, @NonNull String carrierName) {
// This can throw IllegalArgumentException if the subscription does not exist.
try {
mSubscriptionDatabaseManager.setCarrierName(subId, carrierName);
} catch (IllegalArgumentException e) {
loge("setCarrierName: invalid subId=" + subId);
}
}
/**
* Set last used TP message reference.
*
* @param subId Subscription id.
* @param lastUsedTPMessageReference Last used TP message reference.
*/
public void setLastUsedTPMessageReference(int subId, int lastUsedTPMessageReference) {
// This can throw IllegalArgumentException if the subscription does not exist.
try {
mSubscriptionDatabaseManager.setLastUsedTPMessageReference(
subId, lastUsedTPMessageReference);
} catch (IllegalArgumentException e) {
loge("setLastUsedTPMessageReference: invalid subId=" + subId);
}
}
/**
* Set the enabled mobile data policies.
*
* @param subId Subscription id.
* @param enabledMobileDataPolicies The enabled mobile data policies.
*/
public void setEnabledMobileDataPolicies(int subId, @NonNull String enabledMobileDataPolicies) {
// This can throw IllegalArgumentException if the subscription does not exist.
try {
mSubscriptionDatabaseManager.setEnabledMobileDataPolicies(
subId, enabledMobileDataPolicies);
} catch (IllegalArgumentException e) {
loge("setEnabledMobileDataPolicies: invalid subId=" + subId);
}
}
/**
* Set the phone number retrieved from IMS.
*
* @param subId Subscription id.
* @param numberFromIms The phone number retrieved from IMS.
*/
public void setNumberFromIms(int subId, @NonNull String numberFromIms) {
// This can throw IllegalArgumentException if the subscription does not exist.
try {
mSubscriptionDatabaseManager.setNumberFromIms(subId, numberFromIms);
} catch (IllegalArgumentException e) {
loge("setNumberFromIms: invalid subId=" + subId);
}
}
/**
* Mark all subscriptions on this SIM slot index inactive.
*
* @param simSlotIndex The logical SIM slot index (i.e. phone id).
*/
public void markSubscriptionsInactive(int simSlotIndex) {
logl("markSubscriptionsInactive: slot " + simSlotIndex);
mSlotIndexToSubId.remove(simSlotIndex);
mSubscriptionDatabaseManager.getAllSubscriptions().stream()
.filter(subInfo -> subInfo.getSimSlotIndex() == simSlotIndex)
.forEach(subInfo -> {
mSubscriptionDatabaseManager.setSimSlotIndex(subInfo.getSubscriptionId(),
SubscriptionManager.INVALID_SIM_SLOT_INDEX);
mSlotIndexToSubId.remove(simSlotIndex);
});
}
/**
* This is only for internal use and the returned priority is arbitrary. The idea is to give a
* higher value to name source that has higher priority to override other name sources.
*
* @param nameSource Source of display name.
*
* @return The priority. Higher value means higher priority.
*/
private static int getNameSourcePriority(@SimDisplayNameSource int nameSource) {
int index = Arrays.asList(
SubscriptionManager.NAME_SOURCE_UNKNOWN,
SubscriptionManager.NAME_SOURCE_CARRIER_ID,
SubscriptionManager.NAME_SOURCE_SIM_PNN,
SubscriptionManager.NAME_SOURCE_SIM_SPN,
SubscriptionManager.NAME_SOURCE_CARRIER,
SubscriptionManager.NAME_SOURCE_USER_INPUT // user has highest priority.
).indexOf(nameSource);
return Math.max(0, index);
}
/**
* Randomly pick a color from {@link R.array#sim_colors}.
*
* @return The selected color for the subscription.
*/
private int getColor() {
int[] colors = mContext.getResources().getIntArray(com.android.internal.R.array.sim_colors);
if (colors.length == 0) return 0xFFFFFFFF; // white
Random rand = new Random();
return colors[rand.nextInt(colors.length)];
}
/**
* Get the embedded profile port index by ICCID.
*
* @param iccId The ICCID.
* @return The port index.
*/
private int getEmbeddedProfilePortIndex(String iccId) {
UiccSlot[] slots = UiccController.getInstance().getUiccSlots();
for (UiccSlot slot : slots) {
if (slot != null && slot.isEuicc()
&& slot.getPortIndexFromIccId(iccId) != TelephonyManager.INVALID_PORT_INDEX) {
return slot.getPortIndexFromIccId(iccId);
}
}
return TelephonyManager.INVALID_PORT_INDEX;
}
/**
* Pull the embedded subscription from {@link EuiccController} for the eUICC with the given list
* of card IDs {@code cardIds}.
*
* @param cardIds The card ids of the embedded subscriptions.
* @param callback Callback to be called upon completion.
*/
public void updateEmbeddedSubscriptions(@NonNull List<Integer> cardIds,
@Nullable Runnable callback) {
// Run this on a background thread.
mBackgroundHandler.post(() -> {
// Do nothing if eUICCs are disabled. (Previous entries may remain in the cache, but
// they are filtered out of list calls as long as EuiccManager.isEnabled returns false).
if (mEuiccManager == null || !mEuiccManager.isEnabled()) {
loge("updateEmbeddedSubscriptions: eUICC not enabled");
if (callback != null) {
callback.run();
}
return;
}
log("updateEmbeddedSubscriptions: start to get euicc profiles.");
for (int cardId : cardIds) {
GetEuiccProfileInfoListResult result = mEuiccController
.blockingGetEuiccProfileInfoList(cardId);
log("updateEmbeddedSubscriptions: cardId=" + cardId + ", result=" + result);
if (result.getResult() != EuiccService.RESULT_OK) {
loge("Failed to get euicc profile info. result="
+ EuiccService.resultToString(result.getResult()));
continue;
}
if (result.getProfiles() == null || result.getProfiles().isEmpty()) {
loge("No profiles returned.");
continue;
}
final boolean isRemovable = result.getIsRemovable();
for (EuiccProfileInfo embeddedProfile : result.getProfiles()) {
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternalByIccId(embeddedProfile.getIccid());
// The subscription does not exist in the database. Insert a new one here.
if (subInfo == null) {
subInfo = new SubscriptionInfoInternal.Builder()
.setIccId(embeddedProfile.getIccid())
.setIconTint(getColor())
.build();
int subId = mSubscriptionDatabaseManager.insertSubscriptionInfo(subInfo);
subInfo = new SubscriptionInfoInternal.Builder(subInfo)
.setId(subId).build();
}
int nameSource = subInfo.getDisplayNameSource();
int carrierId = subInfo.getCarrierId();
SubscriptionInfoInternal.Builder builder = new SubscriptionInfoInternal
.Builder(subInfo);
builder.setEmbedded(1);
List<UiccAccessRule> ruleList = embeddedProfile.getUiccAccessRules();
if (ruleList != null && !ruleList.isEmpty()) {
builder.setNativeAccessRules(embeddedProfile.getUiccAccessRules());
}
builder.setRemovableEmbedded(isRemovable);
// override DISPLAY_NAME if the priority of existing nameSource is <= carrier
if (getNameSourcePriority(nameSource) <= getNameSourcePriority(
SubscriptionManager.NAME_SOURCE_CARRIER)) {
builder.setDisplayName(embeddedProfile.getNickname());
builder.setDisplayNameSource(SubscriptionManager.NAME_SOURCE_CARRIER);
}
builder.setProfileClass(embeddedProfile.getProfileClass());
builder.setPortIndex(getEmbeddedProfilePortIndex(embeddedProfile.getIccid()));
CarrierIdentifier cid = embeddedProfile.getCarrierIdentifier();
if (cid != null) {
// Due to the limited subscription information, carrier id identified here
// might not be accurate compared with CarrierResolver. Only update carrier
// id if there is no valid carrier id present.
if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
builder.setCarrierId(CarrierResolver
.getCarrierIdFromIdentifier(mContext, cid));
}
String mcc = cid.getMcc();
String mnc = cid.getMnc();
builder.setMcc(mcc);
builder.setMnc(mnc);
}
// If cardId = unsupported or un-initialized, we have no reason to update DB.
// Additionally, if the device does not support cardId for default eUICC, the
// CARD_ID field should not contain the EID
if (cardId >= 0 && mUiccController.getCardIdForDefaultEuicc()
!= TelephonyManager.UNSUPPORTED_CARD_ID) {
builder.setCardString(mUiccController.convertToCardString(cardId));
}
subInfo = builder.build();
log("updateEmbeddedSubscriptions: update subscription " + subInfo);
mSubscriptionDatabaseManager.updateSubscription(subInfo);
}
}
});
log("updateEmbeddedSubscriptions: Finished embedded subscription update.");
if (callback != null) {
callback.run();
}
}
/**
* Check if the SIM application is enabled on the card or not.
*
* @param phoneId The phone id.
*
* @return {@code true} if the application is enabled.
*/
public boolean areUiccAppsEnabledOnCard(int phoneId) {
// When uicc apps are disabled(supported in IRadio 1.5), we will still get IccId from
// cardStatus (since IRadio 1.2). And upon cardStatus change we'll receive another
// handleSimNotReady so this will be evaluated again.
UiccSlot slot = mUiccController.getUiccSlotForPhone(phoneId);
if (slot == null) return false;
UiccPort port = mUiccController.getUiccPort(phoneId);
String iccId = (port == null) ? null : port.getIccId();
if (iccId == null) {
return false;
}
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternalByIccId(IccUtils.stripTrailingFs(iccId));
return subInfo != null && subInfo.areUiccApplicationsEnabled();
}
/**
* Get ICCID by phone id.
*
* @param phoneId The phone id (i.e. Logical SIM slot index.)
*
* @return The ICCID. Empty string if not available.
*/
@NonNull
private String getIccId(int phoneId) {
UiccPort port = mUiccController.getUiccPort(phoneId);
return (port == null) ? "" : TextUtils.emptyIfNull(
IccUtils.stripTrailingFs(port.getIccId()));
}
/**
* @return {@code true} if all the need-to-be-loaded subscriptions from SIM slots are already
* loaded. {@code false} if more than one are still being loaded.
*/
private boolean areAllSubscriptionsLoaded() {
for (int phoneId = 0; phoneId < mTelephonyManager.getActiveModemCount(); phoneId++) {
UiccSlot slot = mUiccController.getUiccSlotForPhone(phoneId);
if (slot == null) {
log("areAllSubscriptionsLoaded: slot is null. phoneId=" + phoneId);
return false;
}
if (!slot.isActive()) {
log("areAllSubscriptionsLoaded: slot is inactive. phoneId=" + phoneId);
return false;
}
if (slot.isEuicc() && mUiccController.getUiccPort(phoneId) == null) {
log("Wait for port corresponding to phone " + phoneId + " to be active, portIndex "
+ "is " + slot.getPortIndexFromPhoneId(phoneId));
return false;
}
if (mSimState[phoneId] == TelephonyManager.SIM_STATE_NOT_READY) {
// Check if this is the final state.
IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard();
if (!iccCard.isEmptyProfile() && areUiccAppsEnabledOnCard(phoneId)) {
log("areAllSubscriptionsLoaded: NOT_READY is not a final state.");
return false;
}
}
if (mSimState[phoneId] == TelephonyManager.SIM_STATE_UNKNOWN) {
log("areAllSubscriptionsLoaded: SIM state is still unknown.");
return false;
}
}
return true;
}
/**
* Update the subscriptions on the logical SIM slot index (i.e. phone id).
*
* @param phoneId The phone id (i.e. Logical SIM slot index)
*/
private void updateSubscriptions(int phoneId) {
int simState = mSimState[phoneId];
log("updateSubscriptions: phoneId=" + phoneId + ", simState="
+ TelephonyManager.simStateToString(simState));
if (simState == TelephonyManager.SIM_STATE_ABSENT) {
if (mSlotIndexToSubId.containsKey(phoneId)) {
int subId = mSlotIndexToSubId.get(phoneId);
// Re-enable the SIM when it's removed, so it will be in enabled state when it gets
// re-inserted again. (pre-U behavior)
log("updateSubscriptions: Re-enable Uicc application on sub " + subId);
mSubscriptionDatabaseManager.setUiccApplicationsEnabled(subId, true);
// When sim is absent, set the port index to invalid port index. (pre-U behavior)
mSubscriptionDatabaseManager.setPortIndex(subId,
TelephonyManager.INVALID_PORT_INDEX);
markSubscriptionsInactive(phoneId);
}
} else if (simState == TelephonyManager.SIM_STATE_NOT_READY) {
// Check if this is the final state. Only update the subscription if NOT_READY is a
// final state.
IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard();
if (!iccCard.isEmptyProfile() && areUiccAppsEnabledOnCard(phoneId)) {
log("updateSubscriptions: SIM_STATE_NOT_READY is not a final state. Will update "
+ "subscription later.");
return;
}
if (!areUiccAppsEnabledOnCard(phoneId)) {
logl("updateSubscriptions: UICC app disabled on slot " + phoneId);
markSubscriptionsInactive(phoneId);
}
}
String iccId = getIccId(phoneId);
if (!TextUtils.isEmpty(iccId)) {
// Check if the subscription already existed.
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternalByIccId(iccId);
int subId;
if (subInfo == null) {
// This is a new SIM card. Insert a new record.
subId = mSubscriptionDatabaseManager.insertSubscriptionInfo(
new SubscriptionInfoInternal.Builder()
.setIccId(iccId)
.setSimSlotIndex(phoneId)
.setIconTint(getColor())
.build());
logl("updateSubscriptions: Inserted a new subscription. subId=" + subId
+ ", phoneId=" + phoneId);
} else {
subId = subInfo.getSubscriptionId();
log("updateSubscriptions: Found existing subscription. subId= " + subId
+ ", phoneId=" + phoneId);
}
subInfo = mSubscriptionDatabaseManager.getSubscriptionInfoInternal(subId);
if (subInfo != null && subInfo.areUiccApplicationsEnabled()) {
mSlotIndexToSubId.put(phoneId, subId);
// Update the SIM slot index. This will make the subscription active.
mSubscriptionDatabaseManager.setSimSlotIndex(subId, phoneId);
}
// Update the card id.
UiccCard card = mUiccController.getUiccCardForPhone(phoneId);
if (card != null) {
String cardId = card.getCardId();
if (cardId != null) {
mSubscriptionDatabaseManager.setCardString(subId, cardId);
}
}
// Update the port index.
UiccSlot slot = mUiccController.getUiccSlotForPhone(phoneId);
if (slot != null && !slot.isEuicc()) {
int portIndex = slot.getPortIndexFromIccId(iccId);
mSubscriptionDatabaseManager.setPortIndex(subId, portIndex);
}
if (simState == TelephonyManager.SIM_STATE_LOADED) {
String mccMnc = mTelephonyManager.getSimOperatorNumeric(subId);
if (!TextUtils.isEmpty(mccMnc)) {
if (subId == getDefaultSubId()) {
MccTable.updateMccMncConfiguration(mContext, mccMnc);
}
setMccMnc(subId, mccMnc);
} else {
loge("updateSubscriptions: mcc/mnc is empty");
}
String iso = TelephonyManager.getSimCountryIsoForPhone(phoneId);
if (!TextUtils.isEmpty(iso)) {
setCountryIso(subId, iso);
} else {
loge("updateSubscriptions: sim country iso is null");
}
String msisdn = mTelephonyManager.getLine1Number(subId);
if (!TextUtils.isEmpty(msisdn)) {
setDisplayNumber(msisdn, subId);
}
String imsi = mTelephonyManager.createForSubscriptionId(subId).getSubscriberId();
if (imsi != null) {
mSubscriptionDatabaseManager.setImsi(subId, imsi);
}
IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard();
if (iccCard != null) {
IccRecords records = iccCard.getIccRecords();
if (records != null) {
String[] ehplmns = records.getEhplmns();
if (ehplmns != null) {
mSubscriptionDatabaseManager.setEhplmns(subId, ehplmns);
}
String[] hplmns = records.getPlmnsFromHplmnActRecord();
if (hplmns != null) {
mSubscriptionDatabaseManager.setHplmns(subId, hplmns);
}
} else {
loge("updateSubscriptions: ICC records are not available.");
}
} else {
loge("updateSubscriptions: ICC card is not available.");
}
}
} else {
log("updateSubscriptions: No ICCID available for phone " + phoneId);
mSlotIndexToSubId.remove(phoneId);
}
if (areAllSubscriptionsLoaded()) {
log("Notify all subscriptions loaded.");
MultiSimSettingController.getInstance().notifyAllSubscriptionLoaded();
}
updateDefaultSubId();
}
/**
* Calculate the usage setting based on the carrier request.
*
* @param currentUsageSetting the current setting in the subscription DB.
* @param preferredUsageSetting provided by the carrier config.
*
* @return the calculated usage setting.
*/
@VisibleForTesting
@UsageSetting public int calculateUsageSetting(@UsageSetting int currentUsageSetting,
@UsageSetting int preferredUsageSetting) {
int[] supportedUsageSettings;
// Load the resources to provide the device capability
try {
supportedUsageSettings = mContext.getResources().getIntArray(
com.android.internal.R.array.config_supported_cellular_usage_settings);
// If usage settings are not supported, return the default setting, which is UNKNOWN.
if (supportedUsageSettings == null
|| supportedUsageSettings.length < 1) return currentUsageSetting;
} catch (Resources.NotFoundException nfe) {
loge("calculateUsageSetting: Failed to load usage setting resources!");
return currentUsageSetting;
}
// If the current setting is invalid, including the first time the value is set,
// update it to default (this will trigger a change in the DB).
if (currentUsageSetting < SubscriptionManager.USAGE_SETTING_DEFAULT
|| currentUsageSetting > SubscriptionManager.USAGE_SETTING_DATA_CENTRIC) {
log("calculateUsageSetting: Updating usage setting for current subscription");
currentUsageSetting = SubscriptionManager.USAGE_SETTING_DEFAULT;
}
// Range check the inputs, and on failure, make no changes
if (preferredUsageSetting < SubscriptionManager.USAGE_SETTING_DEFAULT
|| preferredUsageSetting > SubscriptionManager.USAGE_SETTING_DATA_CENTRIC) {
loge("calculateUsageSetting: Invalid usage setting!" + preferredUsageSetting);
return currentUsageSetting;
}
// Default is always allowed
if (preferredUsageSetting == SubscriptionManager.USAGE_SETTING_DEFAULT) {
return preferredUsageSetting;
}
// Forced setting must be explicitly supported
for (int supportedUsageSetting : supportedUsageSettings) {
if (preferredUsageSetting == supportedUsageSetting) return preferredUsageSetting;
}
// If the preferred setting is not possible, just keep the current setting.
return currentUsageSetting;
}
/**
* Called by CarrierConfigLoader to update the subscription before sending a broadcast.
*/
public void updateSubscriptionByCarrierConfig(int phoneId, @NonNull String configPackageName,
@NonNull PersistableBundle config, @NonNull Runnable callback) {
mHandler.post(() -> {
updateSubscriptionByCarrierConfigInternal(phoneId, configPackageName, config);
callback.run();
});
}
private void updateSubscriptionByCarrierConfigInternal(int phoneId,
@NonNull String configPackageName, @NonNull PersistableBundle config) {
log("updateSubscriptionByCarrierConfig: phoneId=" + phoneId + ", configPackageName="
+ configPackageName);
if (!SubscriptionManager.isValidPhoneId(phoneId)
|| TextUtils.isEmpty(configPackageName) || config == null) {
loge("updateSubscriptionByCarrierConfig: Failed to update the subscription. phoneId="
+ phoneId + " configPackageName=" + configPackageName + " config="
+ ((config == null) ? "null" : config.hashCode()));
return;
}
if (!mSlotIndexToSubId.containsKey(phoneId)) {
log("updateSubscriptionByCarrierConfig: No subscription is active for phone being "
+ "updated.");
return;
}
int subId = mSlotIndexToSubId.get(phoneId);
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
if (subInfo == null) {
loge("updateSubscriptionByCarrierConfig: Couldn't retrieve subscription info for "
+ "current subscription. subId=" + subId);
return;
}
ParcelUuid groupUuid;
// carrier certificates are not subscription-specific, so we want to load them even if
// this current package is not a CarrierServicePackage
String[] certs = config.getStringArray(
CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
UiccAccessRule[] carrierConfigAccessRules = UiccAccessRule.decodeRulesFromCarrierConfig(
certs);
if (carrierConfigAccessRules != null) {
mSubscriptionDatabaseManager.setCarrierConfigAccessRules(
subId, carrierConfigAccessRules);
}
boolean isOpportunistic = config.getBoolean(
CarrierConfigManager.KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL,
subInfo.isOpportunistic());
mSubscriptionDatabaseManager.setOpportunistic(subId, isOpportunistic);
String groupUuidString = config.getString(
CarrierConfigManager.KEY_SUBSCRIPTION_GROUP_UUID_STRING, "");
String oldGroupUuidString = subInfo.getGroupUuid();
if (!TextUtils.isEmpty(groupUuidString)) {
try {
// Update via a UUID Structure to ensure consistent formatting
groupUuid = ParcelUuid.fromString(groupUuidString);
if (groupUuidString.equals(CarrierConfigManager.REMOVE_GROUP_UUID_STRING)) {
// Remove the group UUID.
mSubscriptionDatabaseManager.setGroupUuid(subId, "");
} else if (canPackageManageGroup(groupUuid, configPackageName)) {
mSubscriptionDatabaseManager.setGroupUuid(subId, groupUuidString);
mSubscriptionDatabaseManager.setGroupOwner(subId, configPackageName);
log("updateSubscriptionByCarrierConfig: Group added for sub " + subId);
} else {
loge("updateSubscriptionByCarrierConfig: configPackageName "
+ configPackageName + " doesn't own groupUuid " + groupUuid);
}
if (!groupUuidString.equals(oldGroupUuidString)) {
MultiSimSettingController.getInstance()
.notifySubscriptionGroupChanged(groupUuid);
}
} catch (IllegalArgumentException e) {
loge("updateSubscriptionByCarrierConfig: Invalid Group UUID="
+ groupUuidString);
}
}
final int preferredUsageSetting = config.getInt(
CarrierConfigManager.KEY_CELLULAR_USAGE_SETTING_INT,
SubscriptionManager.USAGE_SETTING_UNKNOWN);
int newUsageSetting = calculateUsageSetting(
subInfo.getUsageSetting(), preferredUsageSetting);
if (newUsageSetting != subInfo.getUsageSetting()) {
mSubscriptionDatabaseManager.setUsageSetting(subId, newUsageSetting);
log("updateSubscriptionByCarrierConfig: UsageSetting changed,"
+ " oldSetting=" + SubscriptionManager.usageSettingToString(
subInfo.getUsageSetting())
+ " preferredSetting=" + SubscriptionManager.usageSettingToString(
preferredUsageSetting)
+ " newSetting=" + SubscriptionManager.usageSettingToString(newUsageSetting));
}
}
/**
* Get all subscription info records from SIMs that are inserted now or previously inserted.
*
* <p>
* If the caller does not have {@link Manifest.permission#READ_PHONE_NUMBERS} permission,
* {@link SubscriptionInfo#getNumber()} will return empty string.
* If the caller does not have {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER},
* {@link SubscriptionInfo#getIccId()} and {@link SubscriptionInfo#getCardString()} will return
* empty string, and {@link SubscriptionInfo#getGroupUuid()} will return {@code null}.
*
* <p>
* The carrier app will only get the list of subscriptions that it has carrier privilege on,
* but will have non-stripped {@link SubscriptionInfo} in the list.
*
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return List of all {@link SubscriptionInfo} records from SIMs that are inserted or
* previously inserted. Sorted by {@link SubscriptionInfo#getSimSlotIndex()}, then
* {@link SubscriptionInfo#getSubscriptionId()}.
*
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@NonNull
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
public List<SubscriptionInfo> getAllSubInfoList(@NonNull String callingPackage,
@Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
// Check if the caller has READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or carrier
// privilege on any active subscription. The carrier app will get full subscription infos
// on the subs it has carrier privilege.
if (!TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(mContext,
Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, callingFeatureId,
"getAllSubInfoList")) {
throw new SecurityException("Need READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or "
+ "carrier privilege");
}
return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
// callers have READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE can get a full
// list. Carrier apps can only get the subscriptions they have privileged.
.filter(subInfo -> TelephonyPermissions.checkCallingOrSelfReadPhoneStateNoThrow(
mContext, subInfo.getSubscriptionId(), callingPackage, callingFeatureId,
"getAllSubInfoList"))
// Remove the identifier if the caller does not have sufficient permission.
// carrier apps will get full subscription info on the subscriptions associated
// to them.
.map(subInfo -> conditionallyRemoveIdentifiers(subInfo.toSubscriptionInfo(),
callingPackage, callingFeatureId, "getAllSubInfoList"))
.sorted(Comparator.comparing(SubscriptionInfo::getSimSlotIndex)
.thenComparing(SubscriptionInfo::getSubscriptionId))
.collect(Collectors.toList());
}
/**
* Get the active {@link SubscriptionInfo} with the subscription id key.
*
* @param subId The unique {@link SubscriptionInfo} key in database
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return The subscription info.
*
* @throws SecurityException if the caller does not have required permissions.
*/
@Override
@Nullable
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
public SubscriptionInfo getActiveSubscriptionInfo(int subId, @NonNull String callingPackage,
@Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
callingFeatureId, "getActiveSubscriptionInfo")) {
throw new SecurityException("Need READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or "
+ "carrier privilege");
}
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
if (subInfo != null && subInfo.isActive()) {
return conditionallyRemoveIdentifiers(subInfo.toSubscriptionInfo(), callingPackage,
callingFeatureId, "getActiveSubscriptionInfo");
}
return null;
}
/**
* Get the active {@link SubscriptionInfo} associated with the iccId.
*
* @param iccId the IccId of SIM card
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return The subscription info.
*
* @throws SecurityException if the caller does not have required permissions.
*/
@Override
@Nullable
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public SubscriptionInfo getActiveSubscriptionInfoForIccId(@NonNull String iccId,
@NonNull String callingPackage, @Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
enforcePermissions("getActiveSubscriptionInfoForIccId",
Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
final long identity = Binder.clearCallingIdentity();
try {
iccId = IccUtils.stripTrailingFs(iccId);
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternalByIccId(iccId);
return (subInfo != null && subInfo.isActive()) ? subInfo.toSubscriptionInfo() : null;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Get the active {@link SubscriptionInfo} associated with the logical SIM slot index.
*
* @param slotIndex the logical SIM slot index which the subscription is inserted.
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return {@link SubscriptionInfo}, null for Remote-SIMs or non-active logical SIM slot index.
*
* @throws SecurityException if the caller does not have required permissions.
*/
@Override
@Nullable
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex,
@NonNull String callingPackage, @Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
int subId = mSlotIndexToSubId.getOrDefault(slotIndex,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId,
callingPackage, callingFeatureId,
"getActiveSubscriptionInfoForSimSlotIndex")) {
throw new SecurityException("Need READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or "
+ "carrier privilege");
}
if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
throw new IllegalArgumentException("Invalid slot index " + slotIndex);
}
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
if (subInfo != null && subInfo.isActive()) {
return conditionallyRemoveIdentifiers(subInfo.toSubscriptionInfo(), callingPackage,
callingFeatureId, "getActiveSubscriptionInfoForSimSlotIndex");
}
return null;
}
/**
* Get the SubscriptionInfo(s) of the active subscriptions. The records will be sorted
* by {@link SubscriptionInfo#getSimSlotIndex} then by
* {@link SubscriptionInfo#getSubscriptionId}.
*
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return Sorted list of the currently {@link SubscriptionInfo} records available on the
* device.
*/
@Override
@NonNull
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
public List<SubscriptionInfo> getActiveSubscriptionInfoList(@NonNull String callingPackage,
@Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
// Check if the caller has READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or carrier
// privilege on any active subscription. The carrier app will get full subscription infos
// on the subs it has carrier privilege.
if (!TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(mContext,
Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, callingFeatureId,
"getAllSubInfoList")) {
// Ideally we should avoid silent failure, but since this API has already been used by
// many apps and they do not expect the security exception, we return an empty list
// here so it's consistent with pre-U behavior.
loge("getActiveSubscriptionInfoList: " + callingPackage + " does not have enough "
+ "permission. Returning empty list here.");
return Collections.emptyList();
}
return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
.filter(SubscriptionInfoInternal::isActive)
// Remove the identifier if the caller does not have sufficient permission.
// carrier apps will get full subscription info on the subscriptions associated
// to them.
.map(subInfo -> conditionallyRemoveIdentifiers(subInfo.toSubscriptionInfo(),
callingPackage, callingFeatureId, "getAllSubInfoList"))
.sorted(Comparator.comparing(SubscriptionInfo::getSimSlotIndex)
.thenComparing(SubscriptionInfo::getSubscriptionId))
.collect(Collectors.toList());
}
/**
* Get the number of active {@link SubscriptionInfo}.
*
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return the number of active subscriptions.
*
* @throws SecurityException if the caller does not have required permissions.
*/
@Override
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
public int getActiveSubInfoCount(@NonNull String callingPackage,
@Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
if (!TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(mContext,
Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, callingFeatureId,
"getAllSubInfoList")) {
throw new SecurityException("Need READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or "
+ "carrier privilege");
}
final long token = Binder.clearCallingIdentity();
try {
return getActiveSubIdList(false).length;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* @return the maximum number of subscriptions this device will support at any one time.
*/
@Override
public int getActiveSubInfoCountMax() {
return mTelephonyManager.getActiveModemCount();
}
/**
* Gets the SubscriptionInfo(s) of all available subscriptions, if any.
*
* Available subscriptions include active ones (those with a non-negative
* {@link SubscriptionInfo#getSimSlotIndex()}) as well as inactive but installed embedded
* subscriptions.
*
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return The available subscription info.
*
* @throws SecurityException if the caller does not have required permissions.
*/
@Override
@NonNull
public List<SubscriptionInfo> getAvailableSubscriptionInfoList(@NonNull String callingPackage,
@Nullable String callingFeatureId) {
enforcePermissions("getAvailableSubscriptionInfoList",
Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
// Now that all security checks pass, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
// Available eSIM profiles are reported by EuiccManager. However for physical SIMs if
// they are in inactive slot or programmatically disabled, they are still considered
// available. In this case we get their iccid from slot info and include their
// subscriptionInfos.
List<String> iccIds = getIccIdsOfInsertedPhysicalSims();
return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
.filter(subInfo -> subInfo.isActive() || iccIds.contains(subInfo.getIccId())
|| (mEuiccManager != null && mEuiccManager.isEnabled()
&& subInfo.isEmbedded()))
.map(SubscriptionInfoInternal::toSubscriptionInfo)
.sorted(Comparator.comparing(SubscriptionInfo::getSimSlotIndex)
.thenComparing(SubscriptionInfo::getSubscriptionId))
.collect(Collectors.toList());
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Gets the SubscriptionInfo(s) of all embedded subscriptions accessible to the calling app, if
* any.
*
* <p>Only those subscriptions for which the calling app has carrier privileges per the
* subscription metadata, if any, will be included in the returned list.
*
* <p>The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
* {@link SubscriptionInfo#getSubscriptionId}.
*
* @return Sorted list of the current embedded {@link SubscriptionInfo} records available on the
* device which are accessible to the caller.
* <ul>
* <li>
*
* if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex}
* then by {@link SubscriptionInfo#getSubscriptionId}.
* </ul>
*
* @param callingPackage The package making the call.
*
* @throws SecurityException if the caller does not have required permissions.
*/
@Override
public List<SubscriptionInfo> getAccessibleSubscriptionInfoList(
@NonNull String callingPackage) {
if (!mEuiccManager.isEnabled()) {
return null;
}
return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
.map(SubscriptionInfoInternal::toSubscriptionInfo)
.filter(subInfo -> mSubscriptionManager
.canManageSubscription(subInfo, callingPackage))
.sorted(Comparator.comparing(SubscriptionInfo::getSimSlotIndex)
.thenComparing(SubscriptionInfo::getSubscriptionId))
.collect(Collectors.toList());
}
/**
* @see SubscriptionManager#requestEmbeddedSubscriptionInfoListRefresh
*/
@Override
// TODO: Remove this after SubscriptionController is removed.
public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) {
updateEmbeddedSubscriptions(List.of(cardId), null);
}
/**
* Add a new subscription info record, if needed. This should be only used for remote SIM.
*
* @param iccId ICCID of the SIM card.
* @param displayName human-readable name of the device the subscription corresponds to.
* @param slotIndex the logical SIM slot index assigned to this device.
* @param subscriptionType the type of subscription to be added
*
* @return 0 if success, < 0 on error
*
* @throws SecurityException if the caller does not have required permissions.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public int addSubInfo(@NonNull String iccId, @NonNull String displayName, int slotIndex,
@SubscriptionType int subscriptionType) {
log("addSubInfo: iccId=" + SubscriptionInfo.givePrintableIccid(iccId) + ", slotIndex="
+ slotIndex + ", displayName=" + displayName + ", type="
+ SubscriptionManager.subscriptionTypeToString(subscriptionType));
enforcePermissions("addSubInfo", Manifest.permission.MODIFY_PHONE_STATE);
// Now that all security checks passes, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
if (TextUtils.isEmpty(iccId)) {
loge("addSubInfo: null or empty iccId");
return -1;
}
iccId = IccUtils.stripTrailingFs(iccId);
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternalByIccId(iccId);
// Check if the record exists or not.
if (subInfo == null) {
// Record does not exist.
if (mSlotIndexToSubId.containsKey(slotIndex)) {
loge("Already a subscription on slot " + slotIndex);
return -1;
}
int subId = mSubscriptionDatabaseManager.insertSubscriptionInfo(
new SubscriptionInfoInternal.Builder()
.setIccId(iccId)
.setSimSlotIndex(slotIndex)
.setDisplayName(displayName)
.setIconTint(getColor())
.setType(subscriptionType)
.build()
);
mSlotIndexToSubId.put(slotIndex, subId);
} else {
// Record already exists.
loge("Subscription record already existed.");
return -1;
}
} finally {
Binder.restoreCallingIdentity(identity);
}
return 0;
}
/**
* Remove subscription info record from the subscription database.
*
* @param uniqueId This is the unique identifier for the subscription within the specific
* subscription type.
* @param subscriptionType the type of subscription to be removed.
*
* // TODO: Remove this terrible return value once SubscriptionController is removed.
* @return 0 if success, < 0 on error.
*
* @throws NullPointerException if {@code uniqueId} is {@code null}.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public int removeSubInfo(@NonNull String uniqueId, int subscriptionType) {
enforcePermissions("removeSubInfo", Manifest.permission.MODIFY_PHONE_STATE);
final long identity = Binder.clearCallingIdentity();
try {
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternalByIccId(uniqueId);
if (subInfo == null) {
loge("Cannot find subscription with uniqueId " + uniqueId);
return -1;
}
if (subInfo.getSubscriptionType() != subscriptionType) {
loge("The subscription type does not match.");
return -1;
}
mSubscriptionDatabaseManager.removeSubscriptionInfo(subInfo.getSubscriptionId());
return 0;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Set SIM icon tint color by simInfo index.
*
* @param subId the unique subscription index in database
* @param tint the icon tint color of the SIM
*
* @return the number of records updated
*
* @throws IllegalArgumentException if {@code subId} is invalid or the subscription does not
* exist.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public int setIconTint(int subId, @ColorInt int tint) {
enforcePermissions("setIconTint", Manifest.permission.MODIFY_PHONE_STATE);
final long identity = Binder.clearCallingIdentity();
try {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid sub id passed as parameter");
}
mSubscriptionDatabaseManager.setIconTint(subId, tint);
return 1;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Set display name of a subscription.
*
* @param displayName The display name of SIM card.
* @param subId The subscription id.
* @param nameSource The display name source.
*
* @return the number of records updated
*
* @throws IllegalArgumentException if {@code nameSource} is invalid, or {@code subId} is
* invalid.
* @throws NullPointerException if {@code displayName} is {@code null}.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public int setDisplayNameUsingSrc(@NonNull String displayName, int subId,
@SimDisplayNameSource int nameSource) {
enforcePermissions("setDisplayNameUsingSrc", Manifest.permission.MODIFY_PHONE_STATE);
String callingPackage = getCallingPackage();
final long identity = Binder.clearCallingIdentity();
try {
Objects.requireNonNull(displayName, "setDisplayNameUsingSrc");
if (nameSource < SubscriptionManager.NAME_SOURCE_CARRIER_ID
|| nameSource > SubscriptionManager.NAME_SOURCE_SIM_PNN) {
throw new IllegalArgumentException("illegal name source " + nameSource);
}
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
if (subInfo == null) {
throw new IllegalArgumentException("Cannot find subscription info with sub id "
+ subId);
}
if (getNameSourcePriority(subInfo.getDisplayNameSource())
> getNameSourcePriority(nameSource)
|| (getNameSourcePriority(subInfo.getDisplayNameSource())
== getNameSourcePriority(nameSource))
&& (TextUtils.equals(displayName, subInfo.getDisplayName()))) {
log("No need to update the display name. nameSource="
+ SubscriptionManager.displayNameSourceToString(nameSource)
+ ", existing name=" + subInfo.getDisplayName() + ", source="
+ SubscriptionManager.displayNameSourceToString(
subInfo.getDisplayNameSource()));
return 0;
}
String nameToSet;
if (TextUtils.isEmpty(displayName) || displayName.trim().length() == 0) {
nameToSet = mTelephonyManager.getSimOperatorName(subId);
if (TextUtils.isEmpty(nameToSet)) {
if (nameSource == SubscriptionManager.NAME_SOURCE_USER_INPUT
&& SubscriptionManager.isValidSlotIndex(getSlotIndex(subId))) {
Resources r = Resources.getSystem();
nameToSet = r.getString(R.string.default_card_name,
(getSlotIndex(subId) + 1));
} else {
nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES);
}
}
} else {
nameToSet = displayName;
}
logl("setDisplayNameUsingSrc: subId=" + subId + ", name=" + nameToSet
+ ", nameSource=" + SubscriptionManager.displayNameSourceToString(nameSource)
+ ", calling package=" + callingPackage);
mSubscriptionDatabaseManager.setDisplayName(subId, nameToSet);
mSubscriptionDatabaseManager.setDisplayNameSource(subId, nameSource);
// Update the nickname on the eUICC chip if it's an embedded subscription.
SubscriptionInfo sub = getSubscriptionInfo(subId);
if (sub != null && sub.isEmbedded()) {
int cardId = sub.getCardId();
log("Updating embedded sub nickname on cardId: " + cardId);
mEuiccManager.updateSubscriptionNickname(subId, nameToSet,
// This PendingIntent simply fulfills the requirement to pass in a callback;
// we don't care about the result (hence 0 requestCode and no action
// specified on the intent).
PendingIntent.getService(mContext, 0 /* requestCode */, new Intent(),
PendingIntent.FLAG_IMMUTABLE /* flags */));
}
return 1;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Set phone number by subscription id.
*
* @param number the phone number of the SIM.
* @param subId the unique SubscriptionInfo index in database.
*
* @return the number of records updated.
*
* @throws SecurityException if callers do not hold the required permission.
* @throws NullPointerException if {@code number} is {@code null}.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public int setDisplayNumber(@NonNull String number, int subId) {
enforcePermissions("setDisplayNumber", Manifest.permission.MODIFY_PHONE_STATE);
logl("setDisplayNumber: subId=" + subId + ", number=" + number
+ ", calling package=" + getCallingPackage());
// Now that all security checks passes, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
mSubscriptionDatabaseManager.setNumber(subId, number);
return 1;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Set data roaming by simInfo index
*
* @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming
* @param subId the unique SubscriptionInfo index in database
*
* @return the number of records updated
*
* @throws IllegalArgumentException if {@code subId} or {@code roaming} is not valid.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public int setDataRoaming(@DataRoamingMode int roaming, int subId) {
enforcePermissions("setDataRoaming", Manifest.permission.MODIFY_PHONE_STATE);
// Now that all security checks passes, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
if (roaming < 0) {
throw new IllegalArgumentException("Invalid roaming value " + roaming);
}
mSubscriptionDatabaseManager.setDataRoaming(subId, roaming);
return 1;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Switch to a certain subscription.
*
* @param opportunistic whether it’s opportunistic subscription
* @param subId the unique SubscriptionInfo index in database
* @param callingPackage The package making the call
*
* @return the number of records updated
*
* @throws IllegalArgumentException if {@code subId} is invalid.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(anyOf = {
Manifest.permission.MODIFY_PHONE_STATE,
"carrier privileges",
})
public int setOpportunistic(boolean opportunistic, int subId, @NonNull String callingPackage) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(
mContext, Binder.getCallingUid(), subId, true, "setOpportunistic",
Manifest.permission.MODIFY_PHONE_STATE);
long token = Binder.clearCallingIdentity();
try {
mSubscriptionDatabaseManager.setOpportunistic(subId, opportunistic);
return 1;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Inform SubscriptionManager that subscriptions in the list are bundled as a group. Typically
* it's a primary subscription and an opportunistic subscription. It should only affect
* multi-SIM scenarios where primary and opportunistic subscriptions can be activated together.
*
* Being in the same group means they might be activated or deactivated together, some of them
* may be invisible to the users, etc.
*
* Caller will either have {@link Manifest.permission#MODIFY_PHONE_STATE} permission or
* can manage all subscriptions in the list, according to their access rules.
*
* @param subIdList list of subId that will be in the same group.
* @param callingPackage The package making the call.
*
* @return groupUUID a UUID assigned to the subscription group. It returns null if fails.
*
* @throws IllegalArgumentException if {@code subId} is invalid.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(anyOf = {
Manifest.permission.MODIFY_PHONE_STATE,
"carrier privileges",
})
public ParcelUuid createSubscriptionGroup(@NonNull int[] subIdList,
@NonNull String callingPackage) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
Objects.requireNonNull(subIdList, "createSubscriptionGroup");
if (subIdList.length == 0) {
throw new IllegalArgumentException("Invalid subIdList " + Arrays.toString(subIdList));
}
// If it doesn't have modify phone state permission, or carrier privilege permission,
// a SecurityException will be thrown.
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED && !checkCarrierPrivilegeOnSubList(
subIdList, callingPackage)) {
throw new SecurityException("CreateSubscriptionGroup needs MODIFY_PHONE_STATE or"
+ " carrier privilege permission on all specified subscriptions");
}
long identity = Binder.clearCallingIdentity();
try {
// Generate a UUID.
ParcelUuid groupUUID = new ParcelUuid(UUID.randomUUID());
String uuidString = groupUUID.toString();
for (int subId : subIdList) {
mSubscriptionDatabaseManager.setGroupUuid(subId, uuidString);
mSubscriptionDatabaseManager.setGroupOwner(subId, callingPackage);
}
MultiSimSettingController.getInstance().notifySubscriptionGroupChanged(groupUUID);
return groupUUID;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Set which subscription is preferred for cellular data. It's designed to overwrite default
* data subscription temporarily.
*
* @param subId which subscription is preferred to for cellular data
* @param needValidation whether validation is needed before switching
* @param callback callback upon request completion
*
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void setPreferredDataSubscriptionId(int subId, boolean needValidation,
@Nullable ISetOpportunisticDataCallback callback) {
enforcePermissions("setPreferredDataSubscriptionId",
Manifest.permission.MODIFY_PHONE_STATE);
final long token = Binder.clearCallingIdentity();
try {
PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
if (phoneSwitcher == null) {
loge("Set preferred data sub: phoneSwitcher is null.");
if (callback != null) {
try {
callback.onComplete(
TelephonyManager.SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
} catch (RemoteException exception) {
loge("RemoteException " + exception);
}
}
return;
}
phoneSwitcher.trySetOpportunisticDataSubscription(subId, needValidation, callback);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* @return The subscription id of preferred subscription for cellular data. This reflects
* the active modem which can serve large amount of cellular data.
*
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public int getPreferredDataSubscriptionId() {
enforcePermissions("getPreferredDataSubscriptionId",
Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
final long token = Binder.clearCallingIdentity();
try {
PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
if (phoneSwitcher == null) {
loge("getPreferredDataSubscriptionId: PhoneSwitcher not available. Return the "
+ "default data sub " + getDefaultDataSubId());
return getDefaultDataSubId();
}
return phoneSwitcher.getAutoSelectedDataSubId();
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Get the opportunistic subscriptions.
*
* Callers with {@link Manifest.permission#READ_PHONE_STATE} or
* {@link Manifest.permission#READ_PRIVILEGED_PHONE_STATE} will have a full list of
* opportunistic subscriptions. Subscriptions that the carrier app has no privilege will be
* excluded from the list.
*
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return The list of opportunistic subscription info that can be accessed by the callers.
*/
@Override
@NonNull
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
public List<SubscriptionInfo> getOpportunisticSubscriptions(@NonNull String callingPackage,
@Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
// Check if the caller has READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or carrier
// privilege on any active subscription. The carrier app will get full subscription infos
// on the subs it has carrier privilege.
if (!TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(mContext,
Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, callingFeatureId,
"getOpportunisticSubscriptions")) {
// Ideally we should avoid silent failure, but since this API has already been used by
// many apps and they do not expect the security exception, we return an empty list
// here so it's consistent with pre-U behavior.
loge("getOpportunisticSubscriptions: " + callingPackage + " does not have enough "
+ "permission. Returning empty list here.");
return Collections.emptyList();
}
return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
// callers have READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE can get a full
// list. Carrier apps can only get the subscriptions they have privileged.
.filter(subInfo -> subInfo.isOpportunistic()
&& TelephonyPermissions.checkCallingOrSelfReadPhoneStateNoThrow(
mContext, subInfo.getSubscriptionId(), callingPackage,
callingFeatureId, "getOpportunisticSubscriptions"))
// Remove the identifier if the caller does not have sufficient permission.
// carrier apps will get full subscription info on the subscriptions associated
// to them.
.map(subInfo -> conditionallyRemoveIdentifiers(subInfo.toSubscriptionInfo(),
callingPackage, callingFeatureId, "getOpportunisticSubscriptions"))
.sorted(Comparator.comparing(SubscriptionInfo::getSimSlotIndex)
.thenComparing(SubscriptionInfo::getSubscriptionId))
.collect(Collectors.toList());
}
/**
* Remove a list of subscriptions from their subscription group.
*
* @param subIdList list of subId that need removing from their groups.
* @param groupUuid The UUID of the subscription group.
* @param callingPackage The package making the call.
*
* @throws SecurityException if the caller doesn't meet the requirements outlined above.
* @throws IllegalArgumentException if the some subscriptions in the list doesn't belong the
* specified group.
*
* @see SubscriptionManager#createSubscriptionGroup(List)
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void removeSubscriptionsFromGroup(@NonNull int[] subIdList,
@NonNull ParcelUuid groupUuid, @NonNull String callingPackage) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
// If it doesn't have modify phone state permission, or carrier privilege permission,
// a SecurityException will be thrown. If it's due to invalid parameter or internal state,
// it will return null.
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED
&& !(checkCarrierPrivilegeOnSubList(subIdList, callingPackage)
&& canPackageManageGroup(groupUuid, callingPackage))) {
throw new SecurityException("removeSubscriptionsFromGroup needs MODIFY_PHONE_STATE or"
+ " carrier privilege permission on all specified subscriptions.");
}
Objects.requireNonNull(subIdList);
Objects.requireNonNull(groupUuid);
if (subIdList.length == 0) {
throw new IllegalArgumentException("subIdList is empty.");
}
long identity = Binder.clearCallingIdentity();
try {
for (int subId : subIdList) {
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
if (subInfo == null) {
throw new IllegalArgumentException("The provided sub id " + subId
+ " is not valid.");
}
if (!groupUuid.toString().equals(subInfo.getGroupUuid())) {
throw new IllegalArgumentException("Subscription " + subInfo.getSubscriptionId()
+ " doesn't belong to group " + groupUuid);
}
}
for (SubscriptionInfoInternal subInfo :
mSubscriptionDatabaseManager.getAllSubscriptions()) {
if (IntStream.of(subIdList).anyMatch(
subId -> subId == subInfo.getSubscriptionId())) {
mSubscriptionDatabaseManager.setGroupUuid(subInfo.getSubscriptionId(), "");
mSubscriptionDatabaseManager.setGroupOwner(subInfo.getSubscriptionId(), "");
} else if (subInfo.getGroupUuid().equals(groupUuid.toString())) {
// Pre-T behavior. If there are still subscriptions having the same UUID, update
// to the new owner.
mSubscriptionDatabaseManager.setGroupOwner(
subInfo.getSubscriptionId(), callingPackage);
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Add a list of subscriptions into a group.
*
* Caller should either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
* permission or had carrier privilege permission on the subscriptions.
*
* @param subIdList list of subId that need adding into the group
* @param groupUuid the groupUuid the subscriptions are being added to.
* @param callingPackage The package making the call.
*
* @throws SecurityException if the caller doesn't meet the requirements outlined above.
* @throws IllegalArgumentException if the some subscriptions in the list doesn't exist.
*
* @see SubscriptionManager#createSubscriptionGroup(List)
*/
@Override
@RequiresPermission(anyOf = {
Manifest.permission.MODIFY_PHONE_STATE,
"carrier privileges",
})
public void addSubscriptionsIntoGroup(@NonNull int[] subIdList, @NonNull ParcelUuid groupUuid,
@NonNull String callingPackage) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
Objects.requireNonNull(subIdList, "subIdList");
if (subIdList.length == 0) {
throw new IllegalArgumentException("Invalid subId list");
}
Objects.requireNonNull(groupUuid, "groupUuid");
String groupUuidString = groupUuid.toString();
if (groupUuidString.equals(CarrierConfigManager.REMOVE_GROUP_UUID_STRING)) {
throw new IllegalArgumentException("Invalid groupUuid");
}
// If it doesn't have modify phone state permission, or carrier privilege permission,
// a SecurityException will be thrown.
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED
&& !(checkCarrierPrivilegeOnSubList(subIdList, callingPackage)
&& canPackageManageGroup(groupUuid, callingPackage))) {
throw new SecurityException("Requires MODIFY_PHONE_STATE or carrier privilege"
+ " permissions on subscriptions and the group.");
}
long identity = Binder.clearCallingIdentity();
try {
for (int subId : subIdList) {
mSubscriptionDatabaseManager.setGroupUuid(subId, groupUuidString);
mSubscriptionDatabaseManager.setGroupOwner(subId, callingPackage);
}
MultiSimSettingController.getInstance().notifySubscriptionGroupChanged(groupUuid);
logl("addSubscriptionsIntoGroup: add subs " + Arrays.toString(subIdList)
+ " to the group.");
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Get subscriptionInfo list of subscriptions that are in the same group of given subId.
* See {@link #createSubscriptionGroup(int[], String)} for more details.
*
* Caller must have {@link android.Manifest.permission#READ_PHONE_STATE}
* or carrier privilege permission on the subscription.
*
* <p>Starting with API level 33, the caller also needs permission to access device identifiers
* to get the list of subscriptions associated with a group UUID.
* This method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the app has carrier privilege permission.
* {@link TelephonyManager#hasCarrierPrivileges()}
* <li>If the app has {@link android.Manifest.permission#READ_PHONE_STATE} permission and
* access to device identifiers.
* </ul>
*
* @param groupUuid of which list of subInfo will be returned.
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return List of {@link SubscriptionInfo} that belong to the same group, including the given
* subscription itself. It will return an empty list if no subscription belongs to the group.
*
* @throws SecurityException if the caller doesn't meet the requirements outlined above.
*/
@Override
@NonNull
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
public List<SubscriptionInfo> getSubscriptionsInGroup(@NonNull ParcelUuid groupUuid,
@NonNull String callingPackage, @Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
// If the calling app neither has carrier privileges nor READ_PHONE_STATE and access to
// device identifiers, it will throw a SecurityException.
if (CompatChanges.isChangeEnabled(REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID,
Binder.getCallingUid())) {
try {
if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mContext,
callingPackage, callingFeatureId, "getSubscriptionsInGroup")) {
EventLog.writeEvent(0x534e4554, "213902861", Binder.getCallingUid());
throw new SecurityException("Need to have carrier privileges or access to "
+ "device identifiers to call getSubscriptionsInGroup");
}
} catch (SecurityException e) {
EventLog.writeEvent(0x534e4554, "213902861", Binder.getCallingUid());
throw e;
}
}
return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
.map(SubscriptionInfoInternal::toSubscriptionInfo)
.filter(info -> groupUuid.equals(info.getGroupUuid())
&& (mSubscriptionManager.canManageSubscription(info, callingPackage)
|| TelephonyPermissions.checkCallingOrSelfReadPhoneStateNoThrow(
mContext, info.getSubscriptionId(), callingPackage,
callingFeatureId, "getSubscriptionsInGroup")))
.map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo,
callingPackage, callingFeatureId, "getSubscriptionsInGroup"))
.collect(Collectors.toList());
}
/**
* Get slot index associated with the subscription.
*
* @param subId The subscription id.
*
* @return Logical slot index (i.e. phone id) as a positive integer or
* {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if the supplied {@code subId} doesn't have
* an associated slot index.
*/
@Override
public int getSlotIndex(int subId) {
if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
subId = getDefaultSubId();
}
for (Map.Entry<Integer, Integer> entry : mSlotIndexToSubId.entrySet()) {
if (entry.getValue() == subId) return entry.getKey();
}
return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
}
/**
* Get the subscription id for specified slot index.
*
* @param slotIndex Logical SIM slot index.
* @return The subscription id. {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if SIM is
* absent.
*/
@Override
public int getSubId(int slotIndex) {
if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
slotIndex = getSlotIndex(getDefaultSubId());
}
// Check that we have a valid slotIndex or the slotIndex is for a remote SIM (remote SIM
// uses special slot index that may be invalid otherwise)
if (!SubscriptionManager.isValidSlotIndex(slotIndex)
&& slotIndex != SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
return mSlotIndexToSubId.getOrDefault(slotIndex,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
@Override
public int[] getSubIds(int slotIndex) {
return new int[]{getSubId(slotIndex)};
}
/**
* Update default sub id.
*/
private void updateDefaultSubId() {
int subId;
boolean isVoiceCapable = mTelephonyManager.isVoiceCapable();
if (isVoiceCapable) {
subId = getDefaultVoiceSubId();
} else {
subId = getDefaultDataSubId();
}
// If the subId is not active, use the fist active subscription's subId.
if (!mSlotIndexToSubId.containsValue(subId)) {
int[] activeSubIds = getActiveSubIdList(true);
if (activeSubIds.length > 0) {
subId = activeSubIds[0];
log("updateDefaultSubId: First available active sub = " + subId);
} else {
subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
}
if (mDefaultSubId.get() != subId) {
int phoneId = getPhoneId(subId);
logl("updateDefaultSubId: Default sub id updated from " + mDefaultSubId.get() + " to "
+ subId + ", phoneId=" + phoneId);
mDefaultSubId.set(subId);
Intent intent = new Intent(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId, subId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
}
/**
* @return The default subscription id.
*/
@Override
public int getDefaultSubId() {
return mDefaultSubId.get();
}
/**
* Get phone id from the subscription id. In the implementation, the logical SIM slot index
* is equivalent to phone id. So this method is same as {@link #getSlotIndex(int)}.
*
* @param subId The subscription id.
*
* @return The phone id.
*/
@Override
public int getPhoneId(int subId) {
if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
subId = getDefaultSubId();
}
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
return SubscriptionManager.INVALID_PHONE_INDEX;
}
// slot index and phone id are equivalent in the current implementation.
int slotIndex = getSlotIndex(subId);
if (SubscriptionManager.isValidSlotIndex(slotIndex)) {
return slotIndex;
}
return SubscriptionManager.DEFAULT_PHONE_INDEX;
}
/**
* @return Subscription id of the default cellular data. This reflects the user's default data
* choice, which might be a little bit different than the active one returned by
* {@link #getPreferredDataSubscriptionId()}.
*/
@Override
public int getDefaultDataSubId() {
return mDefaultDataSubId.get();
}
/**
* Set the default data subscription id.
*
* @param subId The default data subscription id.
*
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void setDefaultDataSubId(int subId) {
enforcePermissions("setDefaultDataSubId", Manifest.permission.MODIFY_PHONE_STATE);
if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
throw new RuntimeException("setDefaultDataSubId called with DEFAULT_SUBSCRIPTION_ID");
}
final long token = Binder.clearCallingIdentity();
try {
if (mDefaultDataSubId.set(subId)) {
MultiSimSettingController.getInstance().notifyDefaultDataSubChanged();
Intent intent = new Intent(
TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
SubscriptionManager.putSubscriptionIdExtra(intent, subId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
updateDefaultSubId();
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* @return The default subscription id for voice.
*/
@Override
public int getDefaultVoiceSubId() {
return mDefaultVoiceSubId.get();
}
/**
* Set the default voice subscription id.
*
* @param subId The default SMS subscription id.
*
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void setDefaultVoiceSubId(int subId) {
enforcePermissions("setDefaultVoiceSubId", Manifest.permission.MODIFY_PHONE_STATE);
if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
throw new RuntimeException("setDefaultVoiceSubId called with DEFAULT_SUB_ID");
}
final long token = Binder.clearCallingIdentity();
try {
if (mDefaultVoiceSubId.set(subId)) {
Intent intent = new Intent(
TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
SubscriptionManager.putSubscriptionIdExtra(intent, subId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
PhoneAccountHandle newHandle = subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
? null : mTelephonyManager.getPhoneAccountHandleForSubscriptionId(subId);
TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
if (telecomManager != null) {
telecomManager.setUserSelectedOutgoingPhoneAccount(newHandle);
}
updateDefaultSubId();
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* @return The default subscription id for SMS.
*/
@Override
public int getDefaultSmsSubId() {
return mDefaultSmsSubId.get();
}
/**
* Set the default SMS subscription id.
*
* @param subId The default SMS subscription id.
*
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void setDefaultSmsSubId(int subId) {
enforcePermissions("setDefaultSmsSubId", Manifest.permission.MODIFY_PHONE_STATE);
if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
throw new RuntimeException("setDefaultSmsSubId called with DEFAULT_SUB_ID");
}
final long token = Binder.clearCallingIdentity();
try {
if (mDefaultSmsSubId.set(subId)) {
Intent intent = new Intent(
SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
SubscriptionManager.putSubscriptionIdExtra(intent, subId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Get the active subscription id list.
*
* @param visibleOnly {@code true} if only includes user visible subscription's sub id.
*
* @return List of the active subscription id.
*
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public int[] getActiveSubIdList(boolean visibleOnly) {
enforcePermissions("getActiveSubIdList", Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
final long token = Binder.clearCallingIdentity();
try {
return mSlotIndexToSubId.values().stream()
.filter(subId -> {
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
return subInfo != null && (!visibleOnly || subInfo.isVisible()); })
.mapToInt(x -> x)
.toArray();
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Set a field in the subscription database. Note not all fields are supported.
*
* @param subId Subscription Id of Subscription.
* @param columnName Column name in the database. Note not all fields are supported.
* @param value Value to store in the database.
*
* // TODO: Remove return value after SubscriptionController is deleted.
* @return always 1
*
* @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
* exposed.
* @throws SecurityException if callers do not hold the required permission.
*
* @see #getSubscriptionProperty(int, String, String, String)
* @see SimInfo for all the columns.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public int setSubscriptionProperty(int subId, @NonNull String columnName,
@NonNull String value) {
enforcePermissions("setSubscriptionProperty", Manifest.permission.MODIFY_PHONE_STATE);
final long token = Binder.clearCallingIdentity();
try {
logl("setSubscriptionProperty: subId=" + subId + ", columnName=" + columnName
+ ", value=" + value);
if (!SimInfo.getAllColumns().contains(columnName)) {
throw new IllegalArgumentException("Invalid column name " + columnName);
}
// Check if the columns are allowed to be accessed through the generic
// getSubscriptionProperty method.
if (!DIRECT_ACCESS_SUBSCRIPTION_COLUMNS.contains(columnName)) {
throw new SecurityException("Column " + columnName + " is not allowed be directly "
+ "accessed through setSubscriptionProperty.");
}
mSubscriptionDatabaseManager.setSubscriptionProperty(subId, columnName, value);
return 1;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Get specific field in string format from the subscription info database.
*
* @param subId Subscription id of the subscription.
* @param columnName Column name in subscription database.
*
* @return Value in string format associated with {@code subscriptionId} and {@code columnName}
* from the database. {@code null} if the {@code subscriptionId} is invalid (for backward
* compatible).
*
* @throws IllegalArgumentException if the field is not exposed.
* @throws SecurityException if callers do not hold the required permission.
*
* @see SimInfo for all the columns.
*/
@Override
@Nullable
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
public String getSubscriptionProperty(int subId, @NonNull String columnName,
@NonNull String callingPackage, @Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
Objects.requireNonNull(columnName);
if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId,
callingPackage, callingFeatureId,
"getSubscriptionProperty")) {
throw new SecurityException("Need READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or "
+ "carrier privilege");
}
if (!SimInfo.getAllColumns().contains(columnName)) {
throw new IllegalArgumentException("Invalid column name " + columnName);
}
// Check if the columns are allowed to be accessed through the generic
// getSubscriptionProperty method.
if (!DIRECT_ACCESS_SUBSCRIPTION_COLUMNS.contains(columnName)) {
throw new SecurityException("Column " + columnName + " is not allowed be directly "
+ "accessed through getSubscriptionProperty.");
}
final long token = Binder.clearCallingIdentity();
try {
Object value = mSubscriptionDatabaseManager.getSubscriptionProperty(subId, columnName);
// The raw types of subscription database should only have 3 different types.
if (value instanceof Integer) {
return String.valueOf(value);
} else if (value instanceof String) {
return (String) value;
} else if (value instanceof byte[]) {
return Base64.encodeToString((byte[]) value, Base64.DEFAULT);
} else {
// This should not happen unless SubscriptionDatabaseManager.getSubscriptionProperty
// did not implement correctly.
throw new RuntimeException("Unexpected type " + value.getClass().getTypeName()
+ " was returned from SubscriptionDatabaseManager for column "
+ columnName);
}
} catch (IllegalArgumentException e) {
logv("getSubscriptionProperty: Invalid subId " + subId + ", columnName=" + columnName);
return null;
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean setSubscriptionEnabled(boolean enable, int subId) {
enforcePermissions("setSubscriptionEnabled", Manifest.permission.MODIFY_PHONE_STATE);
return true;
}
/**
* Check if a subscription is active.
*
* @param subId The subscription id to check.
*
* @return {@code true} if the subscription is active.
*
* @throws IllegalArgumentException if the provided slot index is invalid.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public boolean isSubscriptionEnabled(int subId) {
enforcePermissions("isSubscriptionEnabled",
Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid subscription id " + subId);
}
final long identity = Binder.clearCallingIdentity();
try {
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
return subInfo != null && subInfo.isActive();
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Get the active subscription id by logical SIM slot index.
*
* @param slotIndex The logical SIM slot index.
* @return The active subscription id.
*
* @throws IllegalArgumentException if the provided slot index is invalid.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public int getEnabledSubscriptionId(int slotIndex) {
enforcePermissions("getEnabledSubscriptionId",
Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
throw new IllegalArgumentException("Invalid slot index " + slotIndex);
}
final long identity = Binder.clearCallingIdentity();
try {
return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
.filter(subInfo -> subInfo.isActive() && subInfo.getSimSlotIndex() == slotIndex)
.mapToInt(SubscriptionInfoInternal::getSubscriptionId)
.findFirst()
.orElse(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Check if a subscription is active.
*
* @param subId The subscription id.
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return {@code true} if the subscription is active.
*
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
public boolean isActiveSubId(int subId, @NonNull String callingPackage,
@Nullable String callingFeatureId) {
if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
callingFeatureId, "isActiveSubId")) {
throw new SecurityException("Need READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or "
+ "carrier privilege");
}
final long identity = Binder.clearCallingIdentity();
try {
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
return subInfo != null && subInfo.isActive();
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Get active data subscription id. Active data subscription refers to the subscription
* currently chosen to provide cellular internet connection to the user. This may be
* different from getDefaultDataSubscriptionId().
*
* @return Active data subscription id if any is chosen, or
* SubscriptionManager.INVALID_SUBSCRIPTION_ID if not.
*
* @see android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
*/
@Override
public int getActiveDataSubscriptionId() {
final long token = Binder.clearCallingIdentity();
try {
PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
if (phoneSwitcher != null) {
int activeDataSubId = phoneSwitcher.getActiveDataSubId();
if (SubscriptionManager.isUsableSubscriptionId(activeDataSubId)) {
return activeDataSubId;
}
}
// If phone switcher isn't ready, or active data sub id is not available, use default
// sub id from settings.
return getDefaultDataSubId();
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Whether it's supported to disable / re-enable a subscription on a physical (non-euicc) SIM.
*
* Physical SIM refers non-euicc, or aka non-programmable SIM.
*
* It provides whether a physical SIM card can be disabled without taking it out, which is done
* via {@link SubscriptionManager#setSubscriptionEnabled(int, boolean)} API.
*
* @return whether can disable subscriptions on physical SIMs.
*
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public boolean canDisablePhysicalSubscription() {
enforcePermissions("canDisablePhysicalSubscription",
Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = PhoneFactory.getDefaultPhone();
return phone != null && phone.canDisablePhysicalSubscription();
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Set uicc applications being enabled or disabled.
*
* The value will be remembered on the subscription and will be applied whenever it's present.
* If the subscription in currently present, it will also apply the setting to modem
* immediately (the setting in the modem will not change until the modem receives and responds
* to the request, but typically this should only take a few seconds. The user visible setting
* available from {@link SubscriptionInfo#areUiccApplicationsEnabled()} will be updated
* immediately.)
*
* @param enabled whether uicc applications are enabled or disabled.
* @param subId which subscription to operate on.
*
* @return the number of records updated.
*
* @throws IllegalArgumentException if the subscription does not exist.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public int setUiccApplicationsEnabled(boolean enabled, int subId) {
enforcePermissions("setUiccApplicationsEnabled",
Manifest.permission.MODIFY_PHONE_STATE);
logl("setUiccApplicationsEnabled: subId=" + subId + ", enabled=" + enabled
+ ", calling package=" + getCallingPackage());
final long identity = Binder.clearCallingIdentity();
try {
mSubscriptionDatabaseManager.setUiccApplicationsEnabled(subId, enabled);
} finally {
Binder.restoreCallingIdentity(identity);
}
return 1;
}
/**
* Set the device to device status sharing user preference for a subscription ID. The setting
* app uses this method to indicate with whom they wish to share device to device status
* information.
*
* @param sharing the status sharing preference.
* @param subId the unique Subscription ID in database.
*
* @return the number of records updated.
*
* @throws IllegalArgumentException if the subscription does not exist, or the sharing
* preference is invalid.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public int setDeviceToDeviceStatusSharing(@DeviceToDeviceStatusSharingPreference int sharing,
int subId) {
enforcePermissions("setDeviceToDeviceStatusSharing",
Manifest.permission.MODIFY_PHONE_STATE);
final long identity = Binder.clearCallingIdentity();
try {
if (sharing < SubscriptionManager.D2D_SHARING_DISABLED
|| sharing > SubscriptionManager.D2D_SHARING_ALL) {
throw new IllegalArgumentException("invalid sharing " + sharing);
}
mSubscriptionDatabaseManager.setDeviceToDeviceStatusSharingPreference(subId, sharing);
return 1;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Set the list of contacts that allow device to device status sharing for a subscription ID.
* The setting app uses this method to indicate with whom they wish to share device to device
* status information.
*
* @param contacts The list of contacts that allow device to device status sharing
* @param subId The unique Subscription ID in database.
*
* @throws IllegalArgumentException if {@code subId} is invalid.
* @throws NullPointerException if {@code contacts} is {@code null}.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public int setDeviceToDeviceStatusSharingContacts(@NonNull String contacts, int subId) {
enforcePermissions("setDeviceToDeviceStatusSharingContacts",
Manifest.permission.MODIFY_PHONE_STATE);
final long identity = Binder.clearCallingIdentity();
try {
Objects.requireNonNull(contacts, "contacts");
mSubscriptionDatabaseManager.setDeviceToDeviceStatusSharingContacts(subId, contacts);
return 1;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Returns the phone number for the given {@code subscriptionId} and {@code source},
* or an empty string if not available.
*
* <p>General apps that need to know the phone number should use
* {@link SubscriptionManager#getPhoneNumber(int)} instead. This API may be suitable specific
* apps that needs to know the phone number from a specific source. For example, a carrier app
* needs to know exactly what's on {@link SubscriptionManager#PHONE_NUMBER_SOURCE_UICC UICC} and
* decide if the previously set phone number of source
* {@link SubscriptionManager#PHONE_NUMBER_SOURCE_CARRIER carrier} should be updated.
*
* <p>The API provides no guarantees of what format the number is in: the format can vary
* depending on the {@code source} and the network etc. Programmatic parsing should be done
* cautiously, for example, after formatting the number to a consistent format with
* {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}.
*
* <p>Note the assumption is that one subscription (which usually means one SIM) has
* only one phone number. The multiple sources backup each other so hopefully at least one
* is available. For example, for a carrier that doesn't typically set phone numbers
* on {@link SubscriptionManager#PHONE_NUMBER_SOURCE_UICC UICC}, the source
* {@link SubscriptionManager#PHONE_NUMBER_SOURCE_IMS IMS} may provide one. Or, a carrier may
* decide to provide the phone number via source
* {@link SubscriptionManager#PHONE_NUMBER_SOURCE_CARRIER carrier} if neither source UICC nor
* IMS is available.
*
* <p>The availability and correctness of the phone number depends on the underlying source
* and the network etc. Additional verification is needed to use this number for
* security-related or other sensitive scenarios.
*
* @param subId The subscription ID.
* @param source The source of the phone number.
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return The phone number, or an empty string if not available.
*
* @throws IllegalArgumentException if {@code source} or {@code subId} is invalid.
* @throws SecurityException if the caller doesn't have permissions required.
*
* @see SubscriptionManager#PHONE_NUMBER_SOURCE_UICC
* @see SubscriptionManager#PHONE_NUMBER_SOURCE_CARRIER
* @see SubscriptionManager#PHONE_NUMBER_SOURCE_IMS
*
* @throws IllegalArgumentException if {@code subId} is invalid.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@NonNull
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_NUMBERS,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
public String getPhoneNumber(int subId, @PhoneNumberSource int source,
@NonNull String callingPackage, @Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(
mContext, subId, Binder.getCallingUid(), "getPhoneNumber",
Manifest.permission.READ_PHONE_NUMBERS,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
final long identity = Binder.clearCallingIdentity();
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
if (subInfo == null) {
throw new IllegalArgumentException("Invalid sub id " + subId);
}
try {
switch(source) {
case SubscriptionManager.PHONE_NUMBER_SOURCE_UICC:
Phone phone = PhoneFactory.getPhone(getPhoneId(subId));
if (phone != null) {
return TextUtils.emptyIfNull(phone.getLine1Number());
} else {
return subInfo.getNumber();
}
case SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER:
return subInfo.getNumberFromCarrier();
case SubscriptionManager.PHONE_NUMBER_SOURCE_IMS:
return subInfo.getNumberFromIms();
default:
throw new IllegalArgumentException("Invalid number source " + source);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Get phone number from first available source. The order would be
* {@link SubscriptionManager#PHONE_NUMBER_SOURCE_CARRIER},
* {@link SubscriptionManager#PHONE_NUMBER_SOURCE_UICC}, then
* {@link SubscriptionManager#PHONE_NUMBER_SOURCE_IMS}.
*
* @param subId The subscription ID.
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @return The phone number from the first available source.
*
* @throws IllegalArgumentException if {@code subId} is invalid.
* @throws SecurityException if callers do not hold the required permission.
*/
@Override
@NonNull
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_NUMBERS,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
public String getPhoneNumberFromFirstAvailableSource(int subId,
@NonNull String callingPackage, @Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(
mContext, subId, Binder.getCallingUid(), "getPhoneNumberFromFirstAvailableSource",
Manifest.permission.READ_PHONE_NUMBERS,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
String numberFromCarrier = getPhoneNumber(subId,
SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, callingPackage,
callingFeatureId);
if (!TextUtils.isEmpty(numberFromCarrier)) {
return numberFromCarrier;
}
String numberFromUicc = getPhoneNumber(
subId, SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, callingPackage,
callingFeatureId);
if (!TextUtils.isEmpty(numberFromUicc)) {
return numberFromUicc;
}
String numberFromIms = getPhoneNumber(
subId, SubscriptionManager.PHONE_NUMBER_SOURCE_IMS, callingPackage,
callingFeatureId);
if (!TextUtils.isEmpty(numberFromIms)) {
return numberFromIms;
}
return "";
}
/**
* Set the phone number of the subscription.
*
* @param subId The subscription id.
* @param source The phone number source.
* @param number The phone number.
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
*
* @throws IllegalArgumentException {@code subId} is invalid, or {@code source} is not
* {@link SubscriptionManager#PHONE_NUMBER_SOURCE_CARRIER}.
* @throws NullPointerException if {@code number} is {@code null}.
*/
@Override
@RequiresPermission("carrier privileges")
public void setPhoneNumber(int subId, @PhoneNumberSource int source, @NonNull String number,
@NonNull String callingPackage, @Nullable String callingFeatureId) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
if (!TelephonyPermissions.checkCarrierPrivilegeForSubId(mContext, subId)) {
throw new SecurityException("setPhoneNumber for CARRIER needs carrier privilege.");
}
if (source != SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER) {
throw new IllegalArgumentException("setPhoneNumber doesn't accept source "
+ SubscriptionManager.phoneNumberSourceToString(source));
}
Objects.requireNonNull(number, "number");
final long identity = Binder.clearCallingIdentity();
try {
mSubscriptionDatabaseManager.setNumberFromCarrier(subId, number);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Set the Usage Setting for this subscription.
*
* @param usageSetting the usage setting for this subscription
* @param subId the unique SubscriptionInfo index in database
* @param callingPackage The package making the IPC.
*
* @throws IllegalArgumentException if the subscription does not exist, or {@code usageSetting}
* is invalid.
* @throws SecurityException if doesn't have MODIFY_PHONE_STATE or Carrier Privileges
*/
@Override
@RequiresPermission(anyOf = {
Manifest.permission.MODIFY_PHONE_STATE,
"carrier privileges",
})
public int setUsageSetting(@UsageSetting int usageSetting, int subId,
@NonNull String callingPackage) {
// Verify that the callingPackage belongs to the calling UID
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(
mContext, Binder.getCallingUid(), subId, true, "setUsageSetting",
Manifest.permission.MODIFY_PHONE_STATE);
if (usageSetting < SubscriptionManager.USAGE_SETTING_DEFAULT
|| usageSetting > SubscriptionManager.USAGE_SETTING_DATA_CENTRIC) {
throw new IllegalArgumentException("setUsageSetting: Invalid usage setting: "
+ usageSetting);
}
final long token = Binder.clearCallingIdentity();
try {
mSubscriptionDatabaseManager.setUsageSetting(subId, usageSetting);
return 1;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Set UserHandle for this subscription.
*
* @param userHandle the userHandle associated with the subscription
* Pass {@code null} user handle to clear the association
* @param subId the unique SubscriptionInfo index in database
* @return the number of records updated.
*
* @throws SecurityException if callers do not hold the required permission.
* @throws IllegalArgumentException if {@code subId} is invalid.
*/
@Override
@RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
public int setSubscriptionUserHandle(@Nullable UserHandle userHandle, int subId) {
enforcePermissions("setSubscriptionUserHandle",
Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
if (userHandle == null) {
userHandle = UserHandle.of(UserHandle.USER_NULL);
}
long token = Binder.clearCallingIdentity();
try {
// This can throw IllegalArgumentException if the subscription does not exist.
mSubscriptionDatabaseManager.setUserId(subId, userHandle.getIdentifier());
return 1;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Get UserHandle of this subscription.
*
* @param subId the unique SubscriptionInfo index in database
* @return userHandle associated with this subscription
* or {@code null} if subscription is not associated with any user.
*
* @throws SecurityException if doesn't have required permission.
* @throws IllegalArgumentException if {@code subId} is invalid.
*/
@Override
@Nullable
@RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
public UserHandle getSubscriptionUserHandle(int subId) {
enforcePermissions("getSubscriptionUserHandle",
Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
if (!mIsWorkProfileTelephonyEnabled) {
return null;
}
long token = Binder.clearCallingIdentity();
try {
SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subId);
if (subInfo == null) {
throw new IllegalArgumentException("getSubscriptionUserHandle: Invalid subId: "
+ subId);
}
UserHandle userHandle = UserHandle.of(subInfo.getUserId());
if (userHandle.getIdentifier() == UserHandle.USER_NULL) {
return null;
}
return userHandle;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Check if subscription and user are associated with each other.
*
* @param subscriptionId the subId of the subscription
* @param userHandle user handle of the user
* @return {@code true} if subscription is associated with user
* {code true} if there are no subscriptions on device
* else {@code false} if subscription is not associated with user.
*
* @throws SecurityException if the caller doesn't have permissions required.
* @throws IllegalStateException if subscription service is not available.
*
*/
@Override
public boolean isSubscriptionAssociatedWithUser(int subscriptionId,
@NonNull UserHandle userHandle) {
enforcePermissions("isSubscriptionAssociatedWithUser",
Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
if (!mIsWorkProfileTelephonyEnabled) {
return true;
}
long token = Binder.clearCallingIdentity();
try {
// Return true if there are no subscriptions on the device.
List<SubscriptionInfo> subInfoList = getAllSubInfoList(
mContext.getOpPackageName(), mContext.getAttributionTag());
if (subInfoList == null || subInfoList.isEmpty()) {
return true;
}
// Get list of subscriptions associated with this user.
List<SubscriptionInfo> associatedSubscriptionsList =
getSubscriptionInfoListAssociatedWithUser(userHandle);
if (associatedSubscriptionsList.isEmpty()) {
return false;
}
// Return true if required subscription is present in associated subscriptions list.
for (SubscriptionInfo subInfo: associatedSubscriptionsList) {
if (subInfo.getSubscriptionId() == subscriptionId){
return true;
}
}
return false;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Get list of subscriptions associated with user.
*
* If user handle is associated with some subscriptions, return subscriptionsAssociatedWithUser
* else return all the subscriptions which are not associated with any user.
*
* @param userHandle user handle of the user
* @return list of subscriptionInfo associated with the user.
*
* @throws SecurityException if the caller doesn't have permissions required.
* @throws IllegalStateException if subscription service is not available.
*
*/
@Override
public @NonNull List<SubscriptionInfo> getSubscriptionInfoListAssociatedWithUser(
@NonNull UserHandle userHandle) {
enforcePermissions("getSubscriptionInfoListAssociatedWithUser",
Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
long token = Binder.clearCallingIdentity();
try {
List<SubscriptionInfo> subInfoList = getAllSubInfoList(
mContext.getOpPackageName(), mContext.getAttributionTag());
if (subInfoList == null || subInfoList.isEmpty()) {
return new ArrayList<>();
}
if (!mIsWorkProfileTelephonyEnabled) {
return subInfoList;
}
List<SubscriptionInfo> subscriptionsAssociatedWithUser = new ArrayList<>();
List<SubscriptionInfo> subscriptionsWithNoAssociation = new ArrayList<>();
for (SubscriptionInfo subInfo : subInfoList) {
int subId = subInfo.getSubscriptionId();
UserHandle subIdUserHandle = getSubscriptionUserHandle(subId);
if (userHandle.equals(subIdUserHandle)) {
// Store subscriptions whose user handle matches with required user handle.
subscriptionsAssociatedWithUser.add(subInfo);
} else if (subIdUserHandle == null) {
// Store subscriptions whose user handle is set to null.
subscriptionsWithNoAssociation.add(subInfo);
}
}
return subscriptionsAssociatedWithUser.isEmpty() ?
subscriptionsWithNoAssociation : subscriptionsAssociatedWithUser;
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* @return {@code true} if using {@link SubscriptionManagerService} instead of
* {@link SubscriptionController}.
*/
//TODO: Removed before U AOSP public release.
@Override
public boolean isSubscriptionManagerServiceEnabled() {
return true;
}
/**
* Register the callback for receiving information from {@link SubscriptionManagerService}.
*
* @param callback The callback.
*/
public void registerCallback(@NonNull SubscriptionManagerServiceCallback callback) {
mSubscriptionManagerServiceCallbacks.add(callback);
}
/**
* Unregister the previously registered {@link SubscriptionManagerServiceCallback}.
*
* @param callback The callback to unregister.
*/
public void unregisterCallback(@NonNull SubscriptionManagerServiceCallback callback) {
mSubscriptionManagerServiceCallbacks.remove(callback);
}
/**
* Enforce callers have any of the provided permissions.
*
* @param message Message to include in the exception.
* @param permissions The permissions to enforce.
*
* @throws SecurityException if the caller does not have any permissions.
*/
private void enforcePermissions(@Nullable String message, @NonNull String ...permissions) {
for (String permission : permissions) {
if (mContext.checkCallingOrSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED) {
return;
}
}
throw new SecurityException(message + ". Does not have any of the following permissions. "
+ Arrays.toString(permissions));
}
/**
* Get the {@link SubscriptionInfoInternal} by subscription id.
*
* @param subId The subscription id.
*
* @return The subscription info. {@code null} if not found.
*/
@Nullable
public SubscriptionInfoInternal getSubscriptionInfoInternal(int subId) {
return mSubscriptionDatabaseManager.getSubscriptionInfoInternal(subId);
}
/**
* Get the {@link SubscriptionInfo} by subscription id.
*
* @param subId The subscription id.
*
* @return The subscription info. {@code null} if not found.
*/
@Nullable
public SubscriptionInfo getSubscriptionInfo(int subId) {
SubscriptionInfoInternal subscriptionInfoInternal = getSubscriptionInfoInternal(subId);
return subscriptionInfoInternal != null
? subscriptionInfoInternal.toSubscriptionInfo() : null;
}
/**
* Called when eSIM becomes inactive.
*
* @param slotIndex The logical SIM slot index.
*/
public void updateSimStateForInactivePort(int slotIndex) {
mHandler.post(() -> {
logl("updateSimStateForInactivePort: slotIndex=" + slotIndex);
if (mSlotIndexToSubId.containsKey(slotIndex)) {
// Re-enable the UICC application , so it will be in enabled state when it becomes
// active again. (pre-U behavior)
mSubscriptionDatabaseManager.setUiccApplicationsEnabled(
mSlotIndexToSubId.get(slotIndex), true);
updateSubscriptions(slotIndex);
}
});
}
/**
* Update SIM state. This method is supposed to be called by {@link UiccController} only.
*
* @param slotIndex The logical SIM slot index.
* @param simState SIM state.
* @param executor The executor to execute the callback.
* @param updateCompleteCallback The callback to call when subscription manager service
* completes subscription update. SIM state changed event will be broadcasted by
* {@link UiccController} upon receiving callback.
*/
public void updateSimState(int slotIndex, @SimState int simState,
@Nullable @CallbackExecutor Executor executor,
@Nullable Runnable updateCompleteCallback) {
mHandler.post(() -> {
mSimState[slotIndex] = simState;
logl("updateSimState: slot " + slotIndex + " "
+ TelephonyManager.simStateToString(simState));
switch (simState) {
case TelephonyManager.SIM_STATE_ABSENT:
case TelephonyManager.SIM_STATE_PIN_REQUIRED:
case TelephonyManager.SIM_STATE_PUK_REQUIRED:
case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
case TelephonyManager.SIM_STATE_PERM_DISABLED:
case TelephonyManager.SIM_STATE_READY:
case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
case TelephonyManager.SIM_STATE_LOADED:
case TelephonyManager.SIM_STATE_NOT_READY:
updateSubscriptions(slotIndex);
break;
case TelephonyManager.SIM_STATE_CARD_RESTRICTED:
default:
// No specific things needed to be done. Just return and broadcast the SIM
// states.
break;
}
if (executor != null && updateCompleteCallback != null) {
executor.execute(updateCompleteCallback);
}
});
}
/**
* Listener to update cached flag values from DeviceConfig.
*/
private void onDeviceConfigChanged() {
boolean isWorkProfileTelephonyEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_WORK_PROFILE_TELEPHONY,
false);
if (isWorkProfileTelephonyEnabled != mIsWorkProfileTelephonyEnabled) {
log("onDeviceConfigChanged: isWorkProfileTelephonyEnabled "
+ "changed from " + mIsWorkProfileTelephonyEnabled + " to "
+ isWorkProfileTelephonyEnabled);
mIsWorkProfileTelephonyEnabled = isWorkProfileTelephonyEnabled;
}
}
/**
* Get the calling package(s).
*
* @return The calling package(s).
*/
@NonNull
private String getCallingPackage() {
return Arrays.toString(mContext.getPackageManager().getPackagesForUid(
Binder.getCallingUid()));
}
/**
* Log debug messages.
*
* @param s debug messages
*/
private void log(@NonNull String s) {
Rlog.d(LOG_TAG, s);
}
/**
* Log error messages.
*
* @param s error messages
*/
private void loge(@NonNull String s) {
Rlog.e(LOG_TAG, s);
}
/**
* Log verbose messages.
*
* @param s debug messages.
*/
private void logv(@NonNull String s) {
if (VDBG) Rlog.v(LOG_TAG, s);
}
/**
* Log debug messages and also log into the local log.
*
* @param s debug messages
*/
private void logl(@NonNull String s) {
log(s);
mLocalLog.log(s);
}
/**
* Dump the state of {@link SubscriptionManagerService}.
*
* @param fd File descriptor
* @param printWriter Print writer
* @param args Arguments
*/
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter printWriter,
@NonNull String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.println(SubscriptionManagerService.class.getSimpleName() + ":");
pw.println("Logical SIM slot sub id mapping:");
pw.increaseIndent();
mSlotIndexToSubId.forEach((slotIndex, subId)
-> pw.println("Logical SIM slot " + slotIndex + ": subId=" + subId));
pw.decreaseIndent();
pw.println();
pw.println("defaultSubId=" + getDefaultSubId());
pw.println("defaultVoiceSubId=" + getDefaultVoiceSubId());
pw.println("defaultDataSubId=" + getDefaultDataSubId());
pw.println("activeDataSubId=" + getActiveDataSubscriptionId());
pw.println("defaultSmsSubId=" + getDefaultSmsSubId());
pw.println("areAllSubscriptionsLoaded=" + areAllSubscriptionsLoaded());
pw.println();
for (int i = 0; i < mSimState.length; i++) {
pw.println("mSimState[" + i + "]=" + TelephonyManager.simStateToString(mSimState[i]));
}
pw.println();
pw.println("Active subscriptions:");
pw.increaseIndent();
mSubscriptionDatabaseManager.getAllSubscriptions().stream()
.filter(SubscriptionInfoInternal::isActive).forEach(pw::println);
pw.decreaseIndent();
pw.println();
pw.println("All subscriptions:");
pw.increaseIndent();
mSubscriptionDatabaseManager.getAllSubscriptions().forEach(pw::println);
pw.decreaseIndent();
pw.println();
pw.print("Embedded subscriptions: [");
pw.println(mSubscriptionDatabaseManager.getAllSubscriptions().stream()
.filter(SubscriptionInfoInternal::isEmbedded)
.map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
.collect(Collectors.joining(", ")) + "]");
pw.print("Opportunistic subscriptions: [");
pw.println(mSubscriptionDatabaseManager.getAllSubscriptions().stream()
.filter(SubscriptionInfoInternal::isOpportunistic)
.map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
.collect(Collectors.joining(", ")) + "]");
pw.print("getAvailableSubscriptionInfoList: [");
pw.println(getAvailableSubscriptionInfoList(
mContext.getOpPackageName(), mContext.getFeatureId()).stream()
.map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
.collect(Collectors.joining(", ")) + "]");
pw.print("getSelectableSubscriptionInfoList: [");
pw.println(mSubscriptionManager.getSelectableSubscriptionInfoList().stream()
.map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
.collect(Collectors.joining(", ")) + "]");
if (mEuiccManager != null) {
pw.println("Euicc enabled=" + mEuiccManager.isEnabled());
}
pw.println();
pw.println("Local log:");
pw.increaseIndent();
mLocalLog.dump(fd, pw, args);
pw.decreaseIndent();
pw.decreaseIndent();
}
}