blob: f4d93ea0bba9b78bb969f0d45947b8a9bc8c42d0 [file]
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security;
import android.content.Context;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.telephony.PinResult;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import androidx.annotation.Nullable;
import com.android.settings.network.SubscriptionUtil;
import com.android.settingslib.utils.ThreadUtils;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.IntStream;
/**
* A class for functionality used by various controllers of the automatic SIM card PIN management
* feature.
*
* As the controllers cannot share a base class, this class contains the common functionality.
*/
class AutoManagedSimPinHelper {
private final Context mContext;
private final TelephonyManager mTelephonyManager;
private final SubscriptionManager mSubscriptionManager;
AutoManagedSimPinHelper(Context context) {
mContext = context;
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
}
/**
* Displays an authentication prompt that accepts strong biometric authenticators as well
* as device lock.
*
* @param authenticationCallback The callback for authentication success/failure.
*/
public void showAuthenticationPromptForCallback(
BiometricPrompt.AuthenticationCallback authenticationCallback) {
final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(mContext)
.setUseDefaultTitle();
final BiometricManager bm = mContext.getSystemService(BiometricManager.class);
int authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
| BiometricManager.Authenticators.BIOMETRIC_STRONG;
if (bm.canAuthenticate(authenticators) != BiometricManager.BIOMETRIC_SUCCESS) {
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL;
}
builder.setAllowedAuthenticators(authenticators);
builder.setSubtitle(bm.getStrings(authenticators).getPromptMessage());
final BiometricPrompt bp = builder.build();
final Handler handler = new Handler(Looper.getMainLooper());
bp.authenticate(new CancellationSignal(),
runnable -> handler.post(runnable),
authenticationCallback);
}
/**
* Returns true if the SIM for the provided subscription ID has its PIN managed by the
* platform.
* @param subscriptionId subscription identifying the SIM card.
* @return true if the flag is enabled and the SIM in enrolled into auto PIN management.
*/
public boolean isPinAutoManagedForSubscription(int subscriptionId) {
if (!android.security.Flags.autoSimPinManagement()) {
return false;
}
if (!android.telephony.SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
return false;
}
return mTelephonyManager.createForSubscriptionId(
subscriptionId).getSimAutoPinManagementEnrollmentStatus()
== TelephonyManager.SIM_PIN_ENROLLMENT_STATUS_PLATFORM_MANAGED;
}
public boolean isIccLockEnabled(int subscriptionId) {
return mTelephonyManager.createForSubscriptionId(subscriptionId).isIccLockEnabled();
}
/**
* Returns true if the provided PIN is valid. Assumes the string is numeric only since that's
* the input field type.
* @param pin String to check.
* @return true if it is between 4 and 8 characters, false otherwise.
*/
public static boolean isPinValid(@Nullable String pin) {
if (pin == null) {
return false;
}
if (pin.length() < 4 || pin.length() > 8) {
return false;
}
return true;
}
public int getFirstPhysicalSimSlot() {
final List<SubscriptionInfo> subInfoList =
mSubscriptionManager.getActiveSubscriptionInfoList();
if (subInfoList == null) {
return SubscriptionManager.DEFAULT_SIM_SLOT_INDEX;
}
for (SubscriptionInfo subInfo : subInfoList) {
if (subInfo.isActive() && !subInfo.isEmbedded()) {
return subInfo.getSimSlotIndex();
}
}
return SubscriptionManager.DEFAULT_SIM_SLOT_INDEX;
}
public int getSubIdForFirstPhysicalSimSlot() {
int firstSimSlot = getFirstPhysicalSimSlot();
if (firstSimSlot == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
return mSubscriptionManager.getEnabledSubscriptionId(firstSimSlot);
}
/**
* Returns an array of active slots, without duplicates, sorted from the lowest to the highest
* slot index.
* @return an array of active slots.
*/
public int[] getActiveSlots() {
final List<SubscriptionInfo> subInfoList =
SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager);
if (subInfoList == null || subInfoList.isEmpty()) {
return new int[0];
}
int[] activeSlots = new int[subInfoList.size()];
int i = 0;
for (SubscriptionInfo subInfo : subInfoList) {
activeSlots[i] = subInfo.getSimSlotIndex();
i++;
}
return IntStream.of(activeSlots).sorted().distinct().toArray();
}
public int getSubscriptionIdForSlot(int slotIndex) {
int[] activeSlots = getActiveSlots();
if (activeSlots == null || activeSlots.length < (slotIndex + 1)) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
int activeSlot = activeSlots[slotIndex];
SubscriptionInfo info = mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(
activeSlot);
if (info == null) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
return info.getSubscriptionId();
}
/**
* Callable for setting the ICC Lock state. Needed as this should not be called on the UI
* thread (as opposed to API related to automatic SIM PIN management, which takes an outcome
* receiver and calls it).
*/
private static final class SetIccLockState implements Callable<PinResult> {
private final String mPin;
private final boolean mEnabled;
private final int mSubId;
private final TelephonyManager mTelephonyManager;
SetIccLockState(String pin, boolean enabled, int subId, TelephonyManager telephonyManager) {
mPin = pin;
mEnabled = enabled;
mSubId = subId;
mTelephonyManager = telephonyManager;
}
@Override
public PinResult call() throws Exception {
TelephonyManager tm = mTelephonyManager.createForSubscriptionId(mSubId);
return tm.setIccLockEnabled(mEnabled, mPin);
}
}
ListenableFuture<PinResult> setIccLockState(String pin, boolean enabled, int subId) {
Callable<PinResult> setIccLockState = new SetIccLockState(pin, enabled, subId,
mTelephonyManager);
return ThreadUtils.postOnBackgroundThread(setIccLockState);
}
}