| /* | |
| * 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); | |
| } | |
| } |