/*
 * Copyright (C) 2012 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.server.locksettings;

import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
import static android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
import static android.Manifest.permission.SET_INITIAL_LOCK;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Intent.ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.security.Flags.secureLockdown;
import static com.android.internal.widget.LockDomain.Primary;
import static com.android.internal.widget.LockDomain.Secondary;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY_BASE;
import static com.android.internal.widget.LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE;
import static com.android.internal.widget.LockPatternUtils.ThrowIfUserNotExist.DoNotThrow;
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
import static com.android.internal.widget.LockPatternUtils.isSpecialUserId;
import static com.android.internal.widget.LockPatternUtils.pinOrPasswordQualityToCredentialType;
import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG;
import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK;
import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.decryptProfilePassword;
import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.encryptProfilePassword;
import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.profilePasswordDecryptAlias;
import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.profilePasswordEncryptAlias;
import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.removeKeystoreProfileKey;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteLockscreenValidationResult;
import android.app.RemoteLockscreenValidationSession;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DeviceStateCache;
import android.app.admin.PasswordMetrics;
import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
import android.database.ContentObserver;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.authsecret.IAuthSecret;
import android.hardware.biometrics.BiometricManager;
import android.hardware.face.Face;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IProgressListener;

import com.android.secureline.ISecureLineManager;
import com.android.server.locksettings.SecureLineUnlockBridge;

import android.os.ParcelableException;
import android.os.ParcelDuration;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.security.AndroidKeyStoreMaintenance;
import android.security.KeyStoreAuthorization;
import android.security.keystore.KeyProperties;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.RecoveryCertPath;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.security.keystore2.AndroidKeyStoreProvider;
import android.service.gatekeeper.IGateKeeperService;
import android.service.notification.StatusBarNotification;
import android.system.keystore2.Domain;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockDomain;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.StorageManagerInternal;
import com.android.server.SystemService;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
import com.android.server.locksettings.SyntheticPasswordManager.SyntheticPassword;
import com.android.server.locksettings.SyntheticPasswordManager.TokenType;
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.security.authenticationpolicy.SecureLockDeviceServiceInternal;
import com.android.server.utils.Slogf;
import com.android.server.wm.WindowManagerInternal;

import libcore.util.HexEncoding;

import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

/**
 * LockSettingsService (LSS) mainly has the following responsibilities:
 * <p>
 * <ul>
 *   <li>Provide APIs to verify and change the Lock Screen Knowledge Factor (LSKF) ("lockscreen
 *   credential") of each user.  Unlock users when their correct LSKF is given.</li>
 *
 *   <li>Store other lockscreen related settings, such as some Keyguard (UI) settings.</li>
 *
 *   <li>Manage each user's synthetic password (SP), which is their main cryptographic secret.
 *   See {@link SyntheticPasswordManager}.</li>
 *
 *   <li>Protect each user's SP using their LSKF.  Use the Gatekeeper or Weaver HAL to ensure that
 *   guesses of the LSKF are ratelimited by the TEE or secure element.</li>
 *
 *   <li>Protect each user's data using their SP.  For example, use the SP to encrypt/decrypt the
 *   user's credential-encrypted (CE) key for file-based encryption (FBE).</li>
 *
 *   <li>Manage the tying and untying of profile credentials. Tied profiles are unlocked with their
 *   parent. Tied profile credentials can be either NONE or PASSWORD. Tied profile passwords are
 *   also called "unified profile passwords". Untied profile credentials can be any credential.</li>
 *
 *   <li>Generate, protect, and use unified profile passwords.</li>
 *
 *   <li>Support unlocking the SP by alternative means: resume-on-reboot (reboot escrow) for easier
 *   OTA updates, and escrow tokens when set up by the Device Policy Controller (DPC).</li>
 *
 *   <li>Implement part of the Factory Reset Protection (FRP) and Repair Mode features by storing
 *   the information needed to verify a user's LSKF on the persist or metadata partition.</li>
 *
 *   <li>Support insider attack resistance using the AuthSecret HAL.</li>
 *
 *   <li>Implement "recoverable keystore", a feature that enables end-to-end encrypted backups.
 *   See {@link android.security.keystore.recovery.RecoveryController}.</li>
 * </ul>
 * <p>
 * The main clients of LockSettingsService are Keyguard (i.e. the lockscreen UI, which is part of
 * System UI), the Settings app (com.android.settings), and other parts of system_server.  Most
 * methods are protected by ACCESS_KEYGUARD_SECURE_STORAGE which only system processes can have.
 *
 * @hide
 */
public class LockSettingsService extends ILockSettings.Stub {

    private static final String TAG = "SecurelineBF";

    private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
    private static final String PREV_LSKF_BASED_PROTECTOR_ID_KEY_BASE = "prev-sp-handle";
    private static final String LSKF_LAST_CHANGED_TIME_KEY_BASE = "sp-handle-ts";
    private static final String USER_SERIAL_NUMBER_KEY = "serial-number";

    private static final String MIGRATED_FRP2 = "migrated_frp2";
    private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
    private static final String MIGRATED_SP_FULL = "migrated_all_users_to_sp_and_bound_keys";
    private static final String MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS =
            "migrated_weaver_disabled_on_unsecured_users";
    private static final String MIGRATED_SECONDARY_SP = "migrated_all_users_to_secondary_sp";
    // Note: some other migrated_* strings used to be used and may exist in the database already.

    // Duration that LockSettingsService will store the gatekeeper password for. This allows
    // multiple biometric enrollments without prompting the user to enter their password via
    // ConfirmLockPassword/ConfirmLockPattern multiple times. This needs to be at least the duration
    // from the start of the first biometric sensor's enrollment to the start of the last biometric
    // sensor's enrollment. If biometric enrollment requests a password handle that has expired, the
    // user's credential must be presented again, e.g. via ConfirmLockPattern/ConfirmLockPassword.
    private static final int GK_PW_HANDLE_STORE_DURATION_MS = 10 * 60 * 1000; // 10 minutes

    private static final int HEADLESS_VENDOR_AUTH_SECRET_LENGTH = 32;

    // ================================
    // SecureLine Brute Force Tracking
    // ================================

    private static final String SECURELINE_TAG = "SecurelineBF";

    /*
     * persisted attempt counter
     */

    private static final String SECURELINE_FAILED_ATTEMPTS_KEY =
            "secureline.failed_attempts";

    /*
     * timestamp for last failed attempt
     */

    private static final String SECURELINE_LAST_ATTEMPT_KEY =
            "secureline.last_attempt_timestamp";

    /*
     * reset counter if no attempts for 24h
     */

    private static final long SECURELINE_RESET_WINDOW_MS =
            24 * 60 * 60 * 1000;

    /*
     * read persisted attempts
     */

    private int getSecureLineFailedAttempts(int userId) {

        long lastAttempt =
                getLong(
                        SECURELINE_LAST_ATTEMPT_KEY,
                        0,
                        userId
                );

        if (

                lastAttempt > 0

                &&

                System.currentTimeMillis() - lastAttempt
                        > SECURELINE_RESET_WINDOW_MS

        ) {

            return 0;
        }

        return (int) getLong(

                SECURELINE_FAILED_ATTEMPTS_KEY,

                0,

                userId

        );
    }

    /*
     * persist attempts
     */

    private void setSecureLineFailedAttempts(

            int attempts,

            int userId

    ) {

        setLong(

                SECURELINE_FAILED_ATTEMPTS_KEY,

                attempts,

                userId

        );

        setLong(

                SECURELINE_LAST_ATTEMPT_KEY,

                System.currentTimeMillis(),

                userId

        );
    }

    // Order of holding lock:
    //          mSeparateChallengeLock -> mSpManager
    //          mSpManager -> this
    //          mSpManager -> mSoftwareRateLimiter
    // Do not call into ActivityManager while holding mSpManager lock.
    private final Object mSeparateChallengeLock = new Object();

    private final DeviceProvisionedObserver mDeviceProvisionedObserver =
            new DeviceProvisionedObserver();

    private final Injector mInjector;
    private final Context mContext;
    @VisibleForTesting
    protected final Handler mHandler;
    @VisibleForTesting
    protected final LockSettingsStorage mStorage;
    private final LockSettingsStrongAuth mStrongAuth;
    private final SynchronizedStrongAuthTracker mStrongAuthTracker;
    private final BiometricDeferredQueue mBiometricDeferredQueue;
    private final LongSparseArray<byte[]> mGatekeeperPasswords;
    private final SoftwareRateLimiter mSoftwareRateLimiter;

    private final NotificationManager mNotificationManager;
    protected final UserManager mUserManager;
    private final IStorageManager mStorageManager;
    private final StorageManagerInternal mStorageManagerInternal;
    private final IActivityManager mActivityManager;
    private final SyntheticPasswordManager mSpManager;

    private final KeyStore mKeyStore;
    private final KeyStoreAuthorization mKeyStoreAuthorization;
    private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
    private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache;

    private final RebootEscrowManager mRebootEscrowManager;

    private final LockPatternUtils mLockPatternUtils;

    // Locking order is mUserCreationAndRemovalLock -> mSpManager.
    private final Object mUserCreationAndRemovalLock = new Object();
    // These two arrays are only used at boot time.  To save memory, they are set to null near the
    // end of the boot, when onThirdPartyAppsStarted() is called.
    @GuardedBy("mUserCreationAndRemovalLock")
    private SparseIntArray mEarlyCreatedUsers = new SparseIntArray();
    @GuardedBy("mUserCreationAndRemovalLock")
    private SparseIntArray mEarlyRemovedUsers = new SparseIntArray();
    @GuardedBy("mUserCreationAndRemovalLock")
    private boolean mThirdPartyAppsStarted;

    // This list contains the (protectorId, userId) of any protectors that were by replaced by a
    // migration and should be destroyed once rollback to the old build is no longer possible.
    private ArrayList<Pair<Long, Integer>> mProtectorsToDestroyOnBootCompleted = new ArrayList<>();

    // Current password metrics for all secured users on the device. Updated when user unlocks the
    // device or changes password. Removed when user is locked.
    @GuardedBy("this")
    private final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
    @GuardedBy("this")
    private final SparseArray<PasswordMetrics> mUserBiometricSecondFactorMetrics =
            new SparseArray<>();

    @VisibleForTesting
    protected boolean mHasSecureLockScreen;

    @VisibleForTesting
    protected final Object mHeadlessAuthSecretLock = new Object();

    @VisibleForTesting
    @GuardedBy("mHeadlessAuthSecretLock")
    protected byte[] mAuthSecret;

    protected IGateKeeperService mGateKeeperService;
    protected IAuthSecret mAuthSecretService;

    /**
     * The UIDs that are used for system credential storage in keystore.
     */
    private static final int[] SYSTEM_CREDENTIAL_UIDS = {
            Process.VPN_UID, Process.ROOT_UID, Process.SYSTEM_UID};

    private final CopyOnWriteArrayList<LockSettingsStateListener> mLockSettingsStateListeners =
            new CopyOnWriteArrayList<>();

    // This class manages life cycle events for encrypted users on File Based Encryption (FBE)
    // devices. The most basic of these is to show/hide notifications about missing features until
    // the user unlocks the account and credential-encrypted storage is available.
    public static final class Lifecycle extends SystemService {
        private LockSettingsService mLockSettingsService;

        public Lifecycle(Context context) {
            super(context);
        }

        @Override
        public void onStart() {
            AndroidKeyStoreProvider.install();
            mLockSettingsService = new LockSettingsService(getContext());
            publishBinderService("lock_settings", mLockSettingsService);
        }

        @Override
        public void onBootPhase(int phase) {
            super.onBootPhase(phase);
            mLockSettingsService.onBootPhase(phase);
        }

        @Override
        public void onUserStarting(@NonNull TargetUser user) {
            mLockSettingsService.onUserStarting(user.getUserIdentifier());
        }

        @Override
        public void onUserUnlocking(@NonNull TargetUser user) {
            mLockSettingsService.onUserUnlocking(user.getUserIdentifier());
        }

        @Override
        public void onUserStopped(@NonNull TargetUser user) {
            mLockSettingsService.onUserStopped(user.getUserIdentifier());
        }
    }

    private void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
            migrateOldDataAfterSystemReady();
            deleteRepairModePersistentDataIfNeeded();
        } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
            mHandler.post(() -> {
                // In the case of an upgrade, PHASE_BOOT_COMPLETED means that a rollback to the old
                // build can no longer occur.  This is the time to destroy any migrated protectors.
                destroyMigratedProtectors();

                loadEscrowData();
            });
        }
    }

    @VisibleForTesting
    protected static class SynchronizedStrongAuthTracker
            extends LockPatternUtils.StrongAuthTracker {
        public SynchronizedStrongAuthTracker(Context context) {
            super(context);
        }

        @Override
        protected void handleStrongAuthRequiredChanged(int strongAuthFlags, int userId) {
            synchronized (this) {
                super.handleStrongAuthRequiredChanged(strongAuthFlags, userId);
            }
        }

        @Override
        public int getStrongAuthForUser(int userId) {
            synchronized (this) {
                return super.getStrongAuthForUser(userId);
            }
        }

        void register(LockSettingsStrongAuth strongAuth) {
            strongAuth.registerStrongAuthTracker(getStub());
        }
    }

    private LockscreenCredential generateRandomProfilePassword() {
        byte[] randomLockSeed = SecureRandomUtils.randomBytes(40);
        char[] newPasswordChars = HexEncoding.encode(randomLockSeed);
        byte[] newPassword = new byte[newPasswordChars.length];
        for (int i = 0; i < newPasswordChars.length; i++) {
            newPassword[i] = (byte) newPasswordChars[i];
        }
        LockscreenCredential credential =
                LockscreenCredential.createUnifiedProfilePassword(newPassword);
        ArrayUtils.zeroize(newPasswordChars);
        ArrayUtils.zeroize(newPassword);
        ArrayUtils.zeroize(randomLockSeed);
        return credential;
    }

    /**
     * Tie profile to primary profile if it is in unified mode and not tied before.
     * Only for profiles which share credential with parent. (e.g. managed and clone profiles)
     *
     * @param profileUserId  profile user Id
     * @param profileUserPassword  profile original password (when it has separated lock).
     */
    @GuardedBy("mSpManager")
    private void tieProfileLockIfNecessary(int profileUserId,
            LockscreenCredential profileUserPassword) {
        // Only for profiles that shares credential with parent
        if (!isCredentialShareableWithParent(profileUserId)) {
            return;
        }
        // Do not tie profile when separate challenge is enabled
        if (getSeparateProfileChallengeEnabledInternal(profileUserId)) {
            return;
        }
        // Do not tie profile to parent when it's done already
        if (mStorage.hasChildProfileLock(profileUserId)) {
            return;
        }
        final UserInfo parent = mUserManager.getProfileParent(profileUserId);
        if (parent == null) {
            return;
        }
        // If parent does not have a screen lock, simply clear credential from the profile,
        // to maintain the invariant that unified profile should always have the same secure state
        // as its parent.
        if (!isUserSecure(parent.id) && !profileUserPassword.isNone()) {
            Slogf.i(TAG, "Clearing password for profile user %d to match parent", profileUserId);
            setLockCredentialInternal(LockscreenCredential.createNone(), profileUserPassword,
                    Primary, profileUserId, /* isLockTiedToParent= */ true);
            return;
        }
        final long parentSid;
        // Do not tie when the parent has no SID (but does have a screen lock).
        // This can only happen during an upgrade path where SID is yet to be
        // generated when the user unlocks for the first time.
        try {
            parentSid = getGateKeeperService().getSecureUserId(parent.id);
            if (parentSid == 0) {
                return;
            }
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to talk to GateKeeper service", e);
            return;
        }
        try (LockscreenCredential unifiedProfilePassword = generateRandomProfilePassword()) {
            setLockCredentialInternal(unifiedProfilePassword, profileUserPassword, Primary,
                    profileUserId, /* isLockTiedToParent= */ true);
            tieProfileLockToParent(profileUserId, parent.id, unifiedProfilePassword);
            mUnifiedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword,
                    parentSid);
        }
    }

    static class Injector {

        protected Context mContext;
        private ServiceThread mHandlerThread;
        private Handler mHandler;

        public Injector(Context context) {
            mContext = context;
        }

        public Context getContext() {
            return mContext;
        }

        public ServiceThread getServiceThread() {
            if (mHandlerThread == null) {
                mHandlerThread = new ServiceThread(TAG,
                        Process.THREAD_PRIORITY_BACKGROUND,
                        true /*allowIo*/);
                mHandlerThread.start();
            }
            return mHandlerThread;
        }

        public Handler getHandler(ServiceThread handlerThread) {
            if (mHandler == null) {
                mHandler = new Handler(handlerThread.getLooper());
            }
            return mHandler;
        }

        public LockSettingsStorage getStorage() {
            final LockSettingsStorage storage = new LockSettingsStorage(mContext);
            storage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
                @Override
                public void initialize(SQLiteDatabase db) {
                    // Get the lockscreen default from a system property, if available
                    boolean lockScreenDisable = SystemProperties.getBoolean(
                            "ro.lockscreen.disable.default", false);
                    if (lockScreenDisable) {
                        storage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
                    }
                }
            });
            return storage;
        }

        public LockSettingsStrongAuth getStrongAuth() {
            return new LockSettingsStrongAuth(mContext);
        }

        public SynchronizedStrongAuthTracker getStrongAuthTracker() {
            return new SynchronizedStrongAuthTracker(mContext);
        }

        public IActivityManager getActivityManager() {
            return ActivityManager.getService();
        }

        public NotificationManager getNotificationManager() {
            return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        }

        @Nullable
        public SecureLockDeviceServiceInternal getSecureLockDeviceServiceInternal() {
            if (secureLockdown()) {
                return LocalServices.getService(SecureLockDeviceServiceInternal.class);
            } else {
                return null;
            }
        }

        public UserManager getUserManager() {
            return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
        }

        public UserManagerInternal getUserManagerInternal() {
            return LocalServices.getService(UserManagerInternal.class);
        }

        /**
         * Return the {@link DevicePolicyManager} object.
         *
         * Since LockSettingsService is considered a lower-level component than DevicePolicyManager,
         * do NOT hold any lock in this class while calling into DevicePolicyManager to prevent
         * the risk of deadlock.
         */
        public DevicePolicyManager getDevicePolicyManager() {
            return (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
        }

        public DeviceStateCache getDeviceStateCache() {
            return DeviceStateCache.getInstance();
        }

        public RecoverableKeyStoreManager getRecoverableKeyStoreManager() {
            return RecoverableKeyStoreManager.getInstance(mContext);
        }

        public IStorageManager getStorageManager() {
            final IBinder service = ServiceManager.getService("mount");
            if (service != null) {
                return IStorageManager.Stub.asInterface(service);
            }
            return null;
        }

        public StorageManagerInternal getStorageManagerInternal() {
            return LocalServices.getService(StorageManagerInternal.class);
        }

        public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
            return new SyntheticPasswordManager(getContext(), storage, getUserManager(),
                    new PasswordSlotManager(), false);
        }

        public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks,
                LockSettingsStorage storage) {
            return new RebootEscrowManager(mContext, callbacks, storage,
                    getHandler(getServiceThread()), getUserManagerInternal());
        }

        public LockPatternUtils getLockPatternUtils() {
            return new LockPatternUtils(mContext);
        }

        public DuressPasswordHelper getDuressPasswordHelper(LockSettingsService lockSettingsService,
                LockSettingsStorage lockSettingsStorage,
                SyntheticPasswordManager syntheticPasswordManager) {
            return new DuressPasswordHelper(lockSettingsService, lockSettingsStorage,
                    syntheticPasswordManager);
        }

        public int binderGetCallingUid() {
            return Binder.getCallingUid();
        }

        public boolean isGsiRunning() {
            return LockPatternUtils.isGsiRunning();
        }

        public FingerprintManager getFingerprintManager() {
            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
                return (FingerprintManager) mContext.getSystemService(Context.FINGERPRINT_SERVICE);
            } else {
                return null;
            }
        }

        public FaceManager getFaceManager() {
            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
                return (FaceManager) mContext.getSystemService(Context.FACE_SERVICE);
            } else {
                return null;
            }
        }

        public BiometricManager getBiometricManager() {
            return (BiometricManager) mContext.getSystemService(Context.BIOMETRIC_SERVICE);
        }

        public KeyStore getKeyStore() {
            try {
                return SyntheticPasswordCrypto.getKeyStore();
            } catch (Exception e) {
                throw new IllegalStateException("Cannot load keystore", e);
            }
        }

        public KeyStoreAuthorization getKeyStoreAuthorization() {
            return KeyStoreAuthorization.getInstance();
        }

        public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(KeyStore ks) {
            return new UnifiedProfilePasswordCache(ks);
        }

        public boolean isHeadlessSystemUserMode() {
            return UserManager.isHeadlessSystemUserMode();
        }

        public Duration getTimeSinceBoot() {
            return Duration.ofMillis(SystemClock.elapsedRealtime());
        }

        public void invalidateLockoutEndTimeCache() {
            LockPatternUtils.invalidateLockoutEndTimeCache();
        }
    }

    private class SoftwareRateLimiterInjector implements SoftwareRateLimiter.Injector {

        @Override
        public int readFailureCounter(LskfIdentifier id) {
            return mSpManager.readFailureCounter(id);
        }

        @Override
        public void writeFailureCounter(LskfIdentifier id, int count) {
            mSpManager.writeFailureCounter(id, count);
        }

        @Override
        public Duration getTimeSinceBoot() {
            return mInjector.getTimeSinceBoot();
        }

        @Override
        public void removeCallbacksAndMessages(Object token) {
            Handler.getMain().removeCallbacksAndMessages(token);
        }

        @Override
        public void postDelayed(Runnable runnable, Object token, long delayMillis) {
            Handler.getMain().postDelayed(runnable, token, delayMillis);
        }

        @Override
        public int getHardwareRateLimiter(LskfIdentifier id) {
            return mSpManager.getHardwareRateLimiter(id);
        }

        @Override
        public void invalidateLockoutEndTimeCache() {
            mInjector.invalidateLockoutEndTimeCache();
        }
    }

    public LockSettingsService(Context context) {
        this(new Injector(context));
    }

    @VisibleForTesting
    protected LockSettingsService(Injector injector) {
        mInjector = injector;
        mContext = injector.getContext();
        mKeyStore = injector.getKeyStore();
        mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
        mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
        mHandler = injector.getHandler(injector.getServiceThread());
        mStrongAuth = injector.getStrongAuth();
        mActivityManager = injector.getActivityManager();

        boolean enforcing =
                mContext.getResources()
                        .getBoolean(
                                com.android.internal.R.bool
                                        .config_softwareLskfRateLimiterEnforcing);
        mSoftwareRateLimiter =
                new SoftwareRateLimiter(new SoftwareRateLimiterInjector(), enforcing);

        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_USER_STARTING);
        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
        injector.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
                null, null);

        mStorage = injector.getStorage();
        mNotificationManager = injector.getNotificationManager();
        mUserManager = injector.getUserManager();
        mStorageManager = injector.getStorageManager();
        mStorageManagerInternal = mInjector.getStorageManagerInternal();
        mStrongAuthTracker = injector.getStrongAuthTracker();
        mStrongAuthTracker.register(mStrongAuth);
        mGatekeeperPasswords = new LongSparseArray<>();

        mSpManager = injector.getSyntheticPasswordManager(mStorage);
        mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mKeyStore);
        mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager);

        mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
                mStorage);

        mLockPatternUtils = injector.getLockPatternUtils();

        LocalServices.addService(LockSettingsInternal.class, new LocalService());

        duressPasswordHelper = injector.getDuressPasswordHelper(this, mStorage, mSpManager);
    }

    private void updateActivatedEncryptionNotifications(String reason) {
        for (UserInfo userInfo : mUserManager.getUsers()) {
            Context userContext = mContext.createContextAsUser(UserHandle.of(userInfo.id), 0);
            NotificationManager nm = (NotificationManager)
                    userContext.getSystemService(Context.NOTIFICATION_SERVICE);
            for (StatusBarNotification notification : nm.getActiveNotifications()) {
                if (notification.getId() == SystemMessage.NOTE_FBE_ENCRYPTED_NOTIFICATION) {
                    maybeShowEncryptionNotificationForUser(userInfo.id, reason);
                    break;
                }
            }
        }
    }

    /**
     * If the user is a managed profile whose credential-encrypted storage is locked, show a
     * notification requesting the user to unlock the device.
     */
    private void maybeShowEncryptionNotificationForUser(@UserIdInt int userId, String reason) {
        final UserInfo user = mUserManager.getUserInfo(userId);
        if (!user.isManagedProfile()) {
            // When the user is locked, we communicate it loud-and-clear
            // on the lockscreen; we only show a notification below for
            // locked managed profiles.
            return;
        }

        if (isCeStorageUnlocked(userId)) {
            // If the user's CE storage is already unlocked, then the user will be automatically
            // unlocked, so there is no need to show the notification.
            return;
        }

        final UserHandle userHandle = user.getUserHandle();
        final boolean isSecure = isUserSecure(userId);
        if (isSecure && !mUserManager.isUserUnlockingOrUnlocked(userHandle)) {
            UserInfo parent = mUserManager.getProfileParent(userId);
            if (parent != null &&
                    mUserManager.isUserUnlockingOrUnlocked(parent.getUserHandle()) &&
                    !mUserManager.isQuietModeEnabled(userHandle)) {
                // Only show notifications for managed profiles once their parent
                // user is unlocked.
                showEncryptionNotificationForProfile(userHandle, parent.getUserHandle(), reason);
            }
        }
    }

    private void showEncryptionNotificationForProfile(UserHandle user, UserHandle parent,
            String reason) {
        CharSequence title = getEncryptionNotificationTitle();
        CharSequence message = getEncryptionNotificationMessage();
        CharSequence detail = getEncryptionNotificationDetail();

        final KeyguardManager km = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE);
        final Intent unlockIntent =
                km.createConfirmDeviceCredentialIntent(null, null, user.getIdentifier());
        if (unlockIntent == null) {
            return;
        }

        // Suppress all notifications on non-FBE devices for now
        if (!StorageManager.isFileEncrypted()) return;

        unlockIntent.setFlags(
                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
        PendingIntent intent = PendingIntent.getActivityAsUser(mContext, 0, unlockIntent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED,
                null, parent);

        Slogf.d(TAG, "Showing encryption notification for user %d; reason: %s",
                user.getIdentifier(), reason);

        showEncryptionNotification(user, title, message, detail, intent);
    }

    private String getEncryptionNotificationTitle() {
        return mInjector.getDevicePolicyManager().getResources().getString(
                PROFILE_ENCRYPTED_TITLE,
                () -> mContext.getString(R.string.profile_encrypted_title));
    }

    private String getEncryptionNotificationDetail() {
        return mInjector.getDevicePolicyManager().getResources().getString(
                PROFILE_ENCRYPTED_DETAIL,
                () -> mContext.getString(R.string.profile_encrypted_detail));
    }

    private String getEncryptionNotificationMessage() {
        return mInjector.getDevicePolicyManager().getResources().getString(
                PROFILE_ENCRYPTED_MESSAGE,
                () -> mContext.getString(R.string.profile_encrypted_message));
    }

    private void showEncryptionNotification(UserHandle user, CharSequence title,
            CharSequence message, CharSequence detail, PendingIntent intent) {
        Notification notification =
                new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                        .setSmallIcon(com.android.internal.R.drawable.ic_user_secure)
                        .setWhen(0)
                        .setOngoing(true)
                        .setTicker(title)
                        .setColor(mContext.getColor(
                                com.android.internal.R.color.system_notification_accent_color))
                        .setContentTitle(title)
                        .setContentText(message)
                        .setSubText(detail)
                        .setVisibility(Notification.VISIBILITY_PUBLIC)
                        .setContentIntent(intent)
                        .build();
        mNotificationManager.notifyAsUser(null, SystemMessage.NOTE_FBE_ENCRYPTED_NOTIFICATION,
            notification, user);
    }

    private void hideEncryptionNotification(UserHandle userHandle) {
        Slogf.d(TAG, "Hiding encryption notification for user %d", userHandle.getIdentifier());
        mNotificationManager.cancelAsUser(null, SystemMessage.NOTE_FBE_ENCRYPTED_NOTIFICATION,
            userHandle);
    }

    @VisibleForTesting
    void onUserStopped(int userId) {
        hideEncryptionNotification(new UserHandle(userId));

        if (android.security.Flags.resetAuthFlagsAndMetricsInLockUser()) {
            // Don't perform any locking actions (e.g. locking CE storage) here, since the user is
            // not necessarily being locked. These actions happen in lockUser() instead.
            return;
        }

        // Normally, CE storage is locked when a user is stopped, and restarting the user requires
        // strong auth.  Therefore, reset the user's strong auth flags.  The exception is users that
        // allow delayed locking; under some circumstances, biometric authentication is allowed to
        // restart such users.  Don't reset the strong auth flags for such users.
        //
        // TODO(b/319142556): It might make more sense to reset the strong auth flags when CE
        // storage is locked, instead of when the user is stopped.  This would ensure the flags get
        // reset if CE storage is locked later for a user that allows delayed locking.
        UserProperties userProperties = getUserProperties(userId);
        if (userProperties != null && userProperties.getAllowStoppingUserWithDelayedLocking()) {
            return;
        }
        int strongAuthRequired = LockPatternUtils.StrongAuthTracker.getDefaultFlags(mContext);
        requireStrongAuth(strongAuthRequired, userId);

        // Don't keep the password metrics in memory for a stopped user that will require strong
        // auth to start again, since strong auth will make the password metrics available again.
        synchronized (this) {
            mUserPasswordMetrics.remove(userId);
            mUserBiometricSecondFactorMetrics.remove(userId);
        }
    }

    private void onUserStarting(final int userId) {
        maybeShowEncryptionNotificationForUser(userId, "user started");
    }

    /**
     * Removes the LSS state for the given userId if the userId was reused without its LSS state
     * being fully removed.
     * <p>
     * This is primarily needed for users that were removed by Android 13 or earlier, which didn't
     * guarantee removal of LSS state as it relied on the {@code ACTION_USER_REMOVED} intent.  It is
     * also needed because {@link #removeUser()} delays requests to remove LSS state until Weaver is
     * guaranteed to be available, so they can be lost.
     * <p>
     * Stale state is detected by checking whether the user serial number changed.  This works
     * because user serial numbers are never reused.
     */
    private void removeStateForReusedUserIdIfNecessary(@UserIdInt int userId, int serialNumber) {
        if (userId == UserHandle.USER_SYSTEM) {
            // Short circuit as we never clean up user 0.
            return;
        }
        int storedSerialNumber = mStorage.getInt(USER_SERIAL_NUMBER_KEY, -1, userId);
        if (storedSerialNumber != serialNumber) {
            // If LockSettingsStorage does not have a copy of the serial number, it could be either
            // this is a user created before the serial number recording logic is introduced, or
            // the user does not exist or was removed and cleaned up properly. In either case, don't
            // invoke removeUserState().
            if (storedSerialNumber != -1) {
                Slogf.i(TAG, "Removing stale state for reused userId %d (serial %d => %d)", userId,
                        storedSerialNumber, serialNumber);
                removeUserState(userId);
            }
            mStorage.setInt(USER_SERIAL_NUMBER_KEY, serialNumber, userId);
        }
    }

    private void onUserUnlocking(final int userId) {
        // Perform tasks which require locks in LSS on a handler, as we are callbacks from
        // ActivityManager.unlockUser()
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                // Hide notification first, as tie profile lock takes time
                hideEncryptionNotification(new UserHandle(userId));

                synchronized (mSpManager) {
                    tieProfileLockIfNecessary(userId, LockscreenCredential.createNone());
                }
            }
        });
    }

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
                final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                mStorage.prefetchUser(userHandle);
            } else if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
                updateActivatedEncryptionNotifications("locale changed");
            }
        }
    };

    @Override // binder interface
    public void systemReady() {
        checkWritePermission();

        mHasSecureLockScreen = mContext.getPackageManager()
                .hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN);
        migrateOldData();
        getAuthSecretHal();
        mDeviceProvisionedObserver.onSystemReady();

        // Work around an issue in PropertyInvalidatedCache where the cache doesn't work until the
        // first invalidation.  This can be removed if PropertyInvalidatedCache is fixed.
        LockPatternUtils.invalidateCredentialTypeCache();

        // TODO: maybe skip this for split system user mode.
        mStorage.prefetchUser(UserHandle.USER_SYSTEM);
        mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
                mInjector.getFaceManager(), mInjector.getBiometricManager());

        // When secure lock device is enabled, require two-factor authentication for
        // device entry: primary authentication as the first factor, followed by strong
        // biometric authentication as the second factor. The flags are set on all users, because
        // secure lock device is a global state applied on the whole device. Keyguard should only
        // allow authentication requests by the user that initiated Secure Lock Device, but flags
        // are set on all users to be safe.
        SecureLockDeviceServiceInternal secureLockDeviceServiceInternal =
                mInjector.getSecureLockDeviceServiceInternal();
        if (secureLockDeviceServiceInternal != null
                && secureLockDeviceServiceInternal.isSecureLockDeviceEnabled()) {
            for (UserInfo userInfo : mUserManager.getUsers()) {
                int userId = userInfo.id;
                Slog.d(TAG, "Applying Secure Lock Device strong auth flags to user: "
                        + userId);
                requireStrongAuth(PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE
                        | STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE, userId);
            }
        }
    }

    private void loadEscrowData() {
        mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler);
    }

    private void getAuthSecretHal() {
        mAuthSecretService =
                IAuthSecret.Stub.asInterface(
                        ServiceManager.waitForDeclaredService(IAuthSecret.DESCRIPTOR + "/default"));
        if (mAuthSecretService != null) {
            Slog.i(TAG, "Device implements AIDL AuthSecret HAL");
        } else {
            try {
                android.hardware.authsecret.V1_0.IAuthSecret authSecretServiceHidl =
                        android.hardware.authsecret.V1_0.IAuthSecret.getService(/* retry */ true);
                mAuthSecretService = new AuthSecretHidlAdapter(authSecretServiceHidl);
                Slog.i(TAG, "Device implements HIDL AuthSecret HAL");
            } catch (NoSuchElementException e) {
                Slog.i(TAG, "Device doesn't implement AuthSecret HAL");
            } catch (RemoteException e) {
                Slog.w(TAG, "Failed to get AuthSecret HAL(hidl)", e);
            }
        }
    }

    private void migrateOldData() {
        if (getString(MIGRATED_KEYSTORE_NS, null, 0) == null) {
            boolean success = true;
            synchronized (mSpManager) {
                success &= mSpManager.migrateKeyNamespace();
            }
            success &= migrateProfileLockKeys();
            if (success) {
                setString(MIGRATED_KEYSTORE_NS, "true", 0);
                Slog.i(TAG, "Migrated keys to LSS namespace");
            } else {
                Slog.w(TAG, "Failed to migrate keys to LSS namespace");
            }
        }

    }

    @VisibleForTesting
    void migrateOldDataAfterSystemReady() {
        // Write the FRP persistent data block if needed.
        //
        // The original purpose of this code was to write the FRP block for the first time, when
        // upgrading from Android 8.1 or earlier which didn't use the FRP block.  This code has
        // since been repurposed to also fix the "bad" (non-forwards-compatible) FRP block written
        // by Android 14 Beta 2.  For this reason, the database key used here has been renamed from
        // "migrated_frp" to "migrated_frp2" to cause migrateFrpCredential() to run again on devices
        // where it had run before.
        if (LockPatternUtils.frpCredentialEnabled(mContext)
                && !getBoolean(MIGRATED_FRP2, false, 0)) {
            migrateFrpCredential();
            setBoolean(MIGRATED_FRP2, true, 0);
        }
    }

    /**
     * Write the FRP persistent data block if the following are satisfied:
     * - the user who owns the FRP credential has a nonempty credential
     * - the FRP persistent data block doesn't exist or uses the "bad" format from Android 14 Beta 2
     */
    private void migrateFrpCredential() {
        PersistentData data = mStorage.readPersistentDataBlock();
        if (data != PersistentData.NONE && !data.isBadFormatFromAndroid14Beta()) {
            return;
        }
        for (UserInfo userInfo : mUserManager.getUsers()) {
            if (userOwnsFrpCredential(mContext, userInfo) && isUserSecure(userInfo.id)) {
                synchronized (mSpManager) {
                    int actualQuality = (int) getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);

                    mSpManager.migrateFrpPasswordLocked(
                            getCurrentLskfBasedProtectorId(userInfo.id),
                            userInfo,
                            redactActualQualityToMostLenientEquivalentQuality(actualQuality));
                }
                return;
            }
        }
    }

    private boolean migrateProfileLockKeys() {
        boolean success = true;
        final List<UserInfo> users = mUserManager.getUsers();
        final int userCount = users.size();
        for (int i = 0; i < userCount; i++) {
            UserInfo user = users.get(i);
            if (isCredentialShareableWithParent(user.id)
                    && !getSeparateProfileChallengeEnabledInternal(user.id)) {
                success &= SyntheticPasswordCrypto.migrateLockSettingsKey(
                        profilePasswordEncryptAlias(user.id));
                success &= SyntheticPasswordCrypto.migrateLockSettingsKey(
                        profilePasswordDecryptAlias(user.id));
            }
        }
        return success;
    }

    @VisibleForTesting
    void deleteRepairModePersistentDataIfNeeded() {
        if (!LockPatternUtils.isRepairModeSupported(mContext)
                || LockPatternUtils.isRepairModeActive(mContext)
                || mInjector.isGsiRunning()) {
            return;
        }
        mStorage.deleteRepairModePersistentData();
    }

    private boolean isWeaverDisabledOnUnsecuredUsers() {
        return mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers);
    }

    // This is called when Weaver is guaranteed to be available (if the device supports Weaver).
    // It does any synthetic password related work that was delayed from earlier in the boot.
    private void onThirdPartyAppsStarted() {
        synchronized (mUserCreationAndRemovalLock) {
            // Handle delayed calls to LSS.removeUser() and LSS.createNewUser().
            for (int i = 0; i < mEarlyRemovedUsers.size(); i++) {
                int userId = mEarlyRemovedUsers.keyAt(i);
                Slogf.i(TAG, "Removing locksettings state for removed user %d now that boot "
                        + "is complete", userId);
                removeUserState(userId);
            }
            mEarlyRemovedUsers = null; // no longer needed
            for (int i = 0; i < mEarlyCreatedUsers.size(); i++) {
                int userId = mEarlyCreatedUsers.keyAt(i);
                int serialNumber = mEarlyCreatedUsers.valueAt(i);

                removeStateForReusedUserIdIfNecessary(userId, serialNumber);
                Slogf.i(TAG, "Creating locksettings state for user %d now that boot is complete",
                        userId);
                initializeSyntheticPassword(userId);
            }
            mEarlyCreatedUsers = null; // no longer needed

            // Do a one-time migration for any unsecured users: create the user's synthetic password
            // if not already done, encrypt the user's CE key with the synthetic password if not
            // already done, and create the user's Keystore super keys if not already done.
            //
            // This is needed for the following cases:
            //
            // - Finalizing the creation of the system user on the first boot of a device, as the
            //   system user is special and doesn't go through the normal user creation flow.
            //
            // - Upgrading from Android 13 or earlier, where unsecured users didn't necessarily have
            //   a synthetic password, and if they did have a synthetic password their CE key wasn't
            //   encrypted by it.  Also, unsecured users didn't have Keystore super keys.
            //
            // - Upgrading from Android 14, where unsecured users didn't have Keystore super keys.
            //
            // - Upgrading from a build with config_disableWeaverOnUnsecuredUsers=false to one with
            //   config_disableWeaverOnUnsecuredUsers=true.  (We don't bother to proactively add
            //   Weaver for the reverse update to false, as it's too late to help in that case.)
            //
            // The end result is that all users, regardless of whether they are secured or not, have
            // a synthetic password with all keys initialized and protected by it, and honoring
            // config_disableWeaverOnUnsecuredUsers=true when applicable.
            //
            // Note: if this migration gets interrupted (e.g. by the device powering off), there
            // shouldn't be a problem since this will run again on the next boot, and
            // setCeStorageProtection() and initKeystoreSuperKeys(..., true) are idempotent.
            if (!getBoolean(MIGRATED_SP_FULL, false, 0)
                    || (isWeaverDisabledOnUnsecuredUsers()
                        && !getBoolean(MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS, false, 0))) {
                for (UserInfo user : mUserManager.getAliveUsers()) {
                    removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
                    synchronized (mSpManager) {
                        migrateUserToSpWithBoundKeysLocked(user.id);
                    }
                }
                setBoolean(MIGRATED_SP_FULL, true, 0);
                if (isWeaverDisabledOnUnsecuredUsers()) {
                    setBoolean(MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS, true, 0);
                }
            }

            // Do an early migration to remain consistent with upstream's invariant that, after this
            // method is run, #getCurrentLskfBasedProtectorId never returns NULL_PROTECTOR_ID.
            if (!getBoolean(MIGRATED_SECONDARY_SP, false, 0)) {
                for (UserInfo user : mUserManager.getAliveUsers()) {
                    synchronized (mSpManager) {
                        migrateUserToSecondarySpLocked(user.id);
                    }
                }
                setBoolean(MIGRATED_SECONDARY_SP, true, 0);
            }

            mThirdPartyAppsStarted = true;
        }
    }

    @GuardedBy("mSpManager")
    private void migrateUserToSpWithBoundKeysLocked(@UserIdInt int userId) {
        if (isUserSecure(userId)) {
            Slogf.d(TAG, "User %d is secured; no migration needed", userId);
            return;
        }
        long protectorId = getCurrentLskfBasedProtectorId(userId);
        if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
            Slogf.i(TAG, "Migrating unsecured user %d to SP-based credential", userId);
            initializeSyntheticPassword(userId);
            return;
        }
        Slogf.i(TAG, "Existing unsecured user %d has a synthetic password", userId);
        AuthenticationResult result = mSpManager.unlockLskfBasedProtector(
                getGateKeeperService(), protectorId, LockscreenCredential.createNone(), userId,
                null);
        SyntheticPassword sp = result.syntheticPassword;
        if (isWeaverDisabledOnUnsecuredUsers()) {
            Slog.i(TAG, "config_disableWeaverOnUnsecuredUsers=true");

            // If config_disableWeaverOnUnsecuredUsers=true, then the Weaver HAL may be buggy and
            // need multiple retries before it works here to unwrap the SP, if the SP was already
            // protected by Weaver.
            for (int i = 0; i < 12 && sp == null; i++) {
                Slog.e(TAG, "Failed to unwrap synthetic password. Waiting 5 seconds to retry.");
                SystemClock.sleep(5000);
                result = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
                            LockscreenCredential.createNone(), userId, null);
                sp = result.syntheticPassword;
            }
            if (sp == null) {
                throw new IllegalStateException(
                        "Failed to unwrap synthetic password for unsecured user");
            }
            // If the SP is protected by Weaver, then remove the Weaver protection in order to make
            // config_disableWeaverOnUnsecuredUsers=true take effect.
            if (result.usedWeaver) {
                Slog.i(TAG, "Removing Weaver protection from the synthetic password");
                // Create a new protector, which will not use Weaver.
                long newProtectorId = mSpManager.createLskfBasedProtector(
                        getGateKeeperService(), LockscreenCredential.createNone(), Primary, sp, userId);

                // Out of paranoia, make sure the new protector really works.
                result = mSpManager.unlockLskfBasedProtector(getGateKeeperService(),
                            newProtectorId, LockscreenCredential.createNone(), userId, null);
                sp = result.syntheticPassword;
                if (sp == null) {
                    throw new IllegalStateException("New SP protector does not work");
                }

                // Replace the protector.  Wait until PHASE_BOOT_COMPLETED to destroy the old
                // protector, since the Weaver slot erasure and freeing cannot be rolled back.
                setCurrentLskfBasedProtectorId(newProtectorId, userId, Primary);
                mProtectorsToDestroyOnBootCompleted.add(new Pair(protectorId, userId));
            } else {
                Slog.i(TAG, "Synthetic password is already not protected by Weaver");
            }
        } else if (sp == null) {
            throw new IllegalStateException(
                    "Failed to unwrap synthetic password for unsecured user " + userId);
        }

        // Call setCeStorageProtection(), to re-encrypt the CE key with the SP if it's currently
        // encrypted by an empty secret.  If the CE key is already encrypted by the SP, then this is
        // a no-op except for some log messages.
        Slogf.i(TAG, "Encrypting CE key of user %d with synthetic password", userId);
        setCeStorageProtection(userId, sp);

        Slogf.i(TAG, "Initializing Keystore super keys for user %d", userId);
        initKeystoreSuperKeys(userId, sp, /* allowExisting= */ true);
    }

    private void destroyMigratedProtectors() {
        if (!mProtectorsToDestroyOnBootCompleted.isEmpty()) {
            synchronized (mSpManager) {
                for (Pair<Long, Integer> pair : mProtectorsToDestroyOnBootCompleted) {
                    mSpManager.destroyLskfBasedProtector(pair.first, pair.second);
                }
            }
        }
        mProtectorsToDestroyOnBootCompleted = null; // The list is no longer needed.
    }

    @GuardedBy("mSpManager")
    private void migrateUserToSecondarySpLocked(@UserIdInt int userId) {
        long protectorId = getCurrentLskfBasedProtectorId(userId, Secondary);
        if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
            createNoneProtectorWithNewSpAndSetCurrent(userId, Secondary);
        }
    }

    /**
     * Returns the lowest password quality that still presents the same UI for entering it.
     *
     * For the FRP credential, we do not want to leak the actual quality of the password, only what
     * kind of UI it requires. However, when migrating, we only know the actual quality, not the
     * originally requested quality; since this is only used to determine what input variant to
     * present to the user, we just assume the lowest possible quality was requested.
     */
    private int redactActualQualityToMostLenientEquivalentQuality(int quality) {
        switch (quality) {
            case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
            case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
            case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
                return DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
                return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
            case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
            case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
            case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
            case DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK:
            default:
                return quality;
        }
    }

    private void enforceFrpNotActive() {
        final int mainUserId = mInjector.getUserManagerInternal().getMainUserId();
        if (mainUserId < 0) {
            Slog.d(TAG, "No Main user on device; skipping enforceFrpNotActive");
            return;
        }

        if (mStorage.isFactoryResetProtectionActive()) {
            throw new SecurityException("Cannot change credential while factory reset protection"
                    + " is active");
        }
    }

    private boolean checkUserSupportsBiometricSecondFactorIfSecondary(int userId,
            LockDomain lockDomain) {
        if (lockDomain == Primary) {
            return true;
        }
        return mLockPatternUtils.checkUserSupportsBiometricSecondFactor(userId);
    }

    private final void checkWritePermission() {
        mContext.enforceCallingOrSelfPermission(ACCESS_KEYGUARD_SECURE_STORAGE,
                "LockSettingsWrite");
    }

    private final void checkPasswordReadPermission() {
        mContext.enforceCallingOrSelfPermission(ACCESS_KEYGUARD_SECURE_STORAGE, "LockSettingsRead");
    }

    private final void checkPasswordHavePermission() {
        mContext.enforceCallingOrSelfPermission(ACCESS_KEYGUARD_SECURE_STORAGE, "LockSettingsHave");
    }

    private final void checkDatabaseReadPermission(String requestedKey, int userId) {
        if (!hasPermission(ACCESS_KEYGUARD_SECURE_STORAGE)) {
            throw new SecurityException("uid=" + getCallingUid() + " needs permission "
                    + ACCESS_KEYGUARD_SECURE_STORAGE + " to read " + requestedKey
                    + " for user " + userId);
        }
    }

    private final void checkBiometricPermission() {
        mContext.enforceCallingOrSelfPermission(MANAGE_BIOMETRIC, "LockSettingsBiometric");
    }

    private boolean hasPermission(String permission) {
        return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
    }

    private void checkManageWeakEscrowTokenMethodUsage() {
        mContext.enforceCallingOrSelfPermission(
                Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN,
                "Requires MANAGE_WEAK_ESCROW_TOKEN permission.");
        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
            throw new IllegalArgumentException(
                    "Weak escrow token are only for automotive devices.");
        }
    }

    @Override
    public boolean hasSecureLockScreen() {
        return mHasSecureLockScreen;
    }

    @Override
    public boolean getSeparateProfileChallengeEnabled(int userId) {
        checkDatabaseReadPermission(SEPARATE_PROFILE_CHALLENGE_KEY, userId);
        return getSeparateProfileChallengeEnabledInternal(userId);
    }

    private boolean getSeparateProfileChallengeEnabledInternal(int userId) {
        synchronized (mSeparateChallengeLock) {
            return mStorage.getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
        }
    }

    @Override
    public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
            LockscreenCredential profileUserPassword) {
        checkWritePermission();
        try {
            if (!mHasSecureLockScreen
                    && profileUserPassword != null
                    && profileUserPassword.getType() != CREDENTIAL_TYPE_NONE) {
                throw new UnsupportedOperationException(
                        "This operation requires secure lock screen feature.");
            }
            synchronized (mSeparateChallengeLock) {
                setSeparateProfileChallengeEnabledLocked(
                        userId,
                        enabled,
                        profileUserPassword != null
                                ? profileUserPassword
                                : LockscreenCredential.createNone());
            }
            notifySeparateProfileChallengeChanged(userId);
        } finally {
            LockscreenCredential.zeroizeIfFromParcel(profileUserPassword);
        }
    }

    @GuardedBy("mSeparateChallengeLock")
    private void setSeparateProfileChallengeEnabledLocked(@UserIdInt int userId,
            boolean enabled, LockscreenCredential profileUserPassword) {
        final boolean old = getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
        setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
        try {
            if (enabled) {
                mStorage.removeChildProfileLock(userId);
                removeKeystoreProfileKey(mKeyStore, userId);
            } else {
                synchronized (mSpManager) {
                    tieProfileLockIfNecessary(userId, profileUserPassword);
                }
            }
        } catch (IllegalStateException e) {
            setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, old, userId);
            throw e;
        }
    }

    private void notifySeparateProfileChallengeChanged(int userId) {
        // LSS cannot call into DPM directly, otherwise it will cause deadlock.
        // In this case, calling DPM on a handler thread is OK since DPM doesn't
        // expect reportSeparateProfileChallengeChanged() to happen synchronously.
        mHandler.post(() -> {
            final DevicePolicyManagerInternal dpmi = LocalServices.getService(
                    DevicePolicyManagerInternal.class);
            if (dpmi != null) {
                dpmi.reportSeparateProfileChallengeChanged(userId);
            }
        });
    }

    @Override
    public void setBoolean(String key, boolean value, int userId) {
        checkWritePermission();
        Objects.requireNonNull(key);
        mStorage.setBoolean(key, value, userId);
    }

    @Override
    public void setLong(String key, long value, int userId) {
        checkWritePermission();
        Objects.requireNonNull(key);
        mStorage.setLong(key, value, userId);
    }

    @Override
    public void setString(String key, String value, int userId) {
        checkWritePermission();
        Objects.requireNonNull(key);
        mStorage.setString(key, value, userId);
    }

    @Override
    public boolean getBoolean(String key, boolean defaultValue, int userId) {
        checkDatabaseReadPermission(key, userId);
        return mStorage.getBoolean(key, defaultValue, userId);
    }

    @Override
    public long getLong(String key, long defaultValue, int userId) {
        checkDatabaseReadPermission(key, userId);
        return mStorage.getLong(key, defaultValue, userId);
    }

    @Override
    public String getString(String key, String defaultValue, int userId) {
        checkDatabaseReadPermission(key, userId);
        return mStorage.getString(key, defaultValue, userId);
    }

    // Not relevant for new devices, but some legacy devices still have PASSWORD_TYPE_KEY around to
    // distinguish between credential types.
    private int getKeyguardStoredQuality(int userId) {
        return (int) mStorage.getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
                DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
    }

    /*
     * Gets the PIN length for the given user if it is currently available.
     * Can only be invoked by process/activity that have the right permission.
     * Returns:
     *      A. Actual PIN length if credential type PIN and auto confirm feature is enabled
     *         for the user or user's PIN has been successfully verified since the device booted
     *      B. PIN_LENGTH_UNAVAILABLE if pin length is not stored/available
     */
    @Override
    public int getPinLength(int userId, LockDomain lockDomain) {
        checkPasswordHavePermission();
        if (!checkUserSupportsBiometricSecondFactorIfSecondary(userId, lockDomain)) {
            return PIN_LENGTH_UNAVAILABLE;
        }

        PasswordMetrics passwordMetrics = getUserPasswordMetrics(userId, lockDomain);
        if (passwordMetrics != null && passwordMetrics.credType == CREDENTIAL_TYPE_PIN) {
            return passwordMetrics.length;
        }
        synchronized (mSpManager) {
            final long protectorId = getCurrentLskfBasedProtectorId(userId, lockDomain);
            if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
                // Only possible for new users during early boot (before onThirdPartyAppsStarted())
                return PIN_LENGTH_UNAVAILABLE;
            }
            return mSpManager.getPinLength(protectorId, userId);
        }
    }

    /**
     * {@link LockPatternUtils#refreshStoredPinLength(int, LockDomain)}
     * @param userId user id of the user whose pin length we want to save
     * @param lockDomain whether to refresh the primary or biometric second factor PIN
     * @return true/false depending on whether PIN length has been saved or not
     */
    @Override
    public boolean refreshStoredPinLength(int userId, LockDomain lockDomain) {
        checkPasswordHavePermission();
        if (!checkUserSupportsBiometricSecondFactorIfSecondary(userId, lockDomain)) {
            return false;
        }

        synchronized (mSpManager) {
            PasswordMetrics passwordMetrics = getUserPasswordMetrics(userId, lockDomain);
            if (passwordMetrics != null) {
                final long protectorId = getCurrentLskfBasedProtectorId(userId, lockDomain);
                return mSpManager.refreshPinLengthOnDisk(passwordMetrics, protectorId, userId,
                        lockDomain);
            } else {
                Log.w(TAG, "PasswordMetrics is not available");
                return false;
            }
        }
    }

    public int getCredentialType(int userId) {
        return getCredentialType(userId, Primary);
    }

    /**
     * This API is cached; whenever the result would change,
     * {@link com.android.internal.widget.LockPatternUtils#invalidateCredentialTypeCache}
     * must be called.
     */
    @Override
    public int getCredentialType(int userId, LockDomain lockDomain) {
        checkPasswordHavePermission();
        return getCredentialTypeInternal(userId, lockDomain);
    }

    /**
     * Returns the credential type of the user, can be one of {@link #CREDENTIAL_TYPE_NONE},
     * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} and
     * {@link #CREDENTIAL_TYPE_PASSWORD}
     */
    private int getCredentialTypeInternal(int userId, LockDomain lockDomain) {
        if (isSpecialUserId(userId) && lockDomain == Primary) {
                return mSpManager.getSpecialUserCredentialType(userId);
        }
        if (!checkUserSupportsBiometricSecondFactorIfSecondary(userId, lockDomain)) {
            return CREDENTIAL_TYPE_NONE;
        }
        synchronized (mSpManager) {
            final long protectorId = getCurrentLskfBasedProtectorId(userId, lockDomain);
            if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
                // Only possible for new users during early boot (before onThirdPartyAppsStarted())
                return CREDENTIAL_TYPE_NONE;
            }
            int rawType = mSpManager.getCredentialType(protectorId, userId);
            if (rawType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
                return rawType;
            }
            return pinOrPasswordQualityToCredentialType(getKeyguardStoredQuality(userId));
        }
    }

    private boolean isUserSecure(int userId) {
        return isUserSecure(userId, Primary);
    }

    private boolean isUserSecure(int userId, LockDomain lockDomain) {
        return getCredentialTypeInternal(userId, lockDomain) != CREDENTIAL_TYPE_NONE;
    }

    @VisibleForTesting /** Note: this method is overridden in unit tests */
    void initKeystoreSuperKeys(@UserIdInt int userId, SyntheticPassword sp, boolean allowExisting) {
        final byte[] password = sp.deriveKeyStorePassword();
        try {
            int res = AndroidKeyStoreMaintenance.initUserSuperKeys(userId, password, allowExisting);
            if (res != 0) {
                throw new IllegalStateException("Failed to initialize Keystore super keys for user "
                        + userId);
            }
        } finally {
            ArrayUtils.zeroize(password);
        }
    }

    private void unlockKeystore(int userId, SyntheticPassword sp) {
        final byte[] password = sp.deriveKeyStorePassword();
        try {
            mKeyStoreAuthorization.onDeviceUnlocked(userId, password);
        } finally {
            ArrayUtils.zeroize(password);
        }
    }

    private void lockKeystore(int userId) {
        mKeyStoreAuthorization.onUserStorageLocked(userId);
    }

    private LockscreenCredential getDecryptedPasswordForUnifiedProfile(int userId)
            throws KeyStoreException,
                    UnrecoverableKeyException,
                    NoSuchAlgorithmException,
                    NoSuchPaddingException,
                    InvalidKeyException,
                    InvalidAlgorithmParameterException,
                    IllegalBlockSizeException,
                    BadPaddingException,
                    CertificateException,
                    IOException {
        Slogf.d(TAG, "Decrypting password for unified profile %d", userId);
        byte[] storedData = mStorage.readChildProfileLock(userId);
        if (storedData == null) {
            throw new FileNotFoundException("Child profile lock file not found");
        }
        LockscreenCredential credential = decryptProfilePassword(mKeyStore, userId, storedData);
        try {
            long parentSid = getGateKeeperService().getSecureUserId(
                    mUserManager.getProfileParent(userId).id);
            mUnifiedProfilePasswordCache.storePassword(userId, credential, parentSid);
        } catch (RemoteException e) {
            Slogf.w(TAG, "Failed to talk to GateKeeper service", e);
        }
        return credential;
    }

    private void unlockChildProfile(int profileHandle) {
        try (LockscreenCredential credential =
                     getDecryptedPasswordForUnifiedProfile(profileHandle)) {
            doVerifyCredential(
                    credential, Primary, profileHandle, /* progressCallback= */ null, /* flags= */ 0);
        } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
                | NoSuchAlgorithmException | NoSuchPaddingException
                | InvalidAlgorithmParameterException | IllegalBlockSizeException
                | BadPaddingException | CertificateException | IOException e) {
            if (e instanceof FileNotFoundException) {
                Slog.i(TAG, "Child profile key not found");
            } else {
                Slog.e(TAG, "Failed to decrypt child profile key", e);
            }
        }
    }

    /**
     * Unlock the user (both storage and user state) and its associated profiles
     * that share lock credential (e.g. managed and clone profiles) synchronously.
     *
     * <em>Be very careful about the risk of deadlock here: ActivityManager.unlockUser()
     * can end up calling into other system services to process user unlock request (via
     * {@link com.android.server.SystemServiceManager#unlockUser} </em>
     */
    private void unlockUser(@UserIdInt int userId) {
        // TODO: make this method fully async so we can update UI with progress strings
        final boolean alreadyUnlocked = mUserManager.isUserUnlockingOrUnlocked(userId);
        final CountDownLatch latch = new CountDownLatch(1);
        final IProgressListener listener = new IProgressListener.Stub() {
            @Override
            public void onStarted(int id, Bundle extras) throws RemoteException {
                Slog.d(TAG, "unlockUser started");
            }

            @Override
            public void onProgress(int id, int progress, Bundle extras) throws RemoteException {
                Slog.d(TAG, "unlockUser progress " + progress);
            }

            @Override
            public void onFinished(int id, Bundle extras) throws RemoteException {
                Slog.d(TAG, "unlockUser finished");
                latch.countDown();
            }
        };

        try {
            mActivityManager.unlockUser2(userId, listener);
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }

        try {
            latch.await(15, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        if (isCredentialShareableWithParent(userId)) {
            if (!hasUnifiedProfilePassword(userId)) {
                mBiometricDeferredQueue.processPendingLockoutResets();
            }
            return;
        }

        for (UserInfo profile : mUserManager.getProfiles(userId)) {
            if (profile.id == userId) continue;
            if (!isCredentialShareableWithParent(profile.id)) continue;

            if (hasUnifiedProfilePassword(profile.id)) {
                if (mUserManager.isUserRunning(profile.id)) {
                    // Unlock profile with unified lock
                    unlockChildProfile(profile.id);
                } else {
                    try (LockscreenCredential credential =
                            getDecryptedPasswordForUnifiedProfile(profile.id)) {
                        // Profile not ready for unlock yet, but decrypt the unified challenge now
                        // so it goes into the cache
                    } catch (GeneralSecurityException | IOException e) {
                        Slog.d(TAG, "Cache unified profile password failed", e);
                    }
                }
            }
            // Now we have unlocked the parent user and attempted to unlock the profile we should
            // show notifications if the profile is still locked.
            if (!alreadyUnlocked) {
                final long ident = clearCallingIdentity();
                try {
                    maybeShowEncryptionNotificationForUser(profile.id, "parent unlocked");
                } finally {
                    restoreCallingIdentity(ident);
                }
            }
        }

        mBiometricDeferredQueue.processPendingLockoutResets();
    }

    /**
     * Determine if the given user is a profile with a unified password tied to the parent user's
     * password. This will return false in the "tied none" credential case.
     */
    private boolean hasUnifiedProfilePassword(int userId) {
        return !getSeparateProfileChallengeEnabledInternal(userId)
                && mStorage.hasChildProfileLock(userId);
    }

    private Map<Integer, LockscreenCredential> getDecryptedPasswordsForAllUnifiedProfiles(
            int userId) {
        if (isCredentialShareableWithParent(userId)) {
            return null;
        }
        Map<Integer, LockscreenCredential> result = new ArrayMap<>();
        final List<UserInfo> profiles = mUserManager.getProfiles(userId);
        final int size = profiles.size();
        for (int i = 0; i < size; i++) {
            final UserInfo profile = profiles.get(i);
            if (!isCredentialShareableWithParent(profile.id)) {
                continue;
            }
            final int profileUserId = profile.id;
            if (getSeparateProfileChallengeEnabledInternal(profileUserId)) {
                continue;
            }
            try {
                result.put(profileUserId, getDecryptedPasswordForUnifiedProfile(profileUserId));
            } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException
                    | NoSuchPaddingException | InvalidKeyException
                    | InvalidAlgorithmParameterException | IllegalBlockSizeException
                    | BadPaddingException | CertificateException | IOException e) {
                Slog.e(TAG, "getDecryptedPasswordsForAllUnifiedProfiles failed for user "
                        + profileUserId, e);
            }
        }
        return result;
    }

    /**
     * Synchronize all profile's challenge of the given user if it's unified: tie or clear them
     * depending on the parent user's secure state.
     *
     * When clearing unified challenges, a pre-computed password table for profiles are required,
     * since changing password for unified profiles requires the existing password, and existing
     * passwords can only be computed before the parent user's password is cleared.
     *
     * Strictly this is a recursive function, since setLockCredentialInternal ends up calling this
     * method again on profiles. However the recursion is guaranteed to terminate as this method
     * terminates when the user is a profile that shares lock credentials with parent.
     * (e.g. managed and clone profile).
     */
    private void synchronizeTiedChallengeForProfiles(int userId,
            Map<Integer, LockscreenCredential> profilePasswordMap) {
        if (isCredentialShareableWithParent(userId)) {
            return;
        }
        final boolean isSecure = isUserSecure(userId);
        final List<UserInfo> profiles = mUserManager.getProfiles(userId);
        final int size = profiles.size();
        for (int i = 0; i < size; i++) {
            final UserInfo profile = profiles.get(i);
            final int profileUserId = profile.id;
            if (isCredentialShareableWithParent(profileUserId)) {
                if (getSeparateProfileChallengeEnabledInternal(profileUserId)) {
                    continue;
                }
                if (isSecure) {
                    tieProfileLockIfNecessary(profileUserId,
                            LockscreenCredential.createNone());
                } else {
                    // We use cached profile password computed before clearing the parent's
                    // credential, otherwise they get lost
                    if (profilePasswordMap != null
                            && profilePasswordMap.containsKey(profileUserId)) {
                        setLockCredentialInternal(LockscreenCredential.createNone(),
                                profilePasswordMap.get(profileUserId),
                                Primary,
                                profileUserId,
                                /* isLockTiedToParent= */ true);
                        mStorage.removeChildProfileLock(profileUserId);
                        removeKeystoreProfileKey(mKeyStore, profileUserId);
                    } else {
                        Slog.wtf(TAG, "Attempt to clear tied challenge, but no password supplied.");
                    }
                }
            }
        }
    }

    /**
     * Determines whether the given user is a profile with a tied none credential or a unified
     * password.
     */
    @VisibleForTesting
    boolean isProfileWithTiedLock(int userId) {
        return isCredentialShareableWithParent(userId)
                && !getSeparateProfileChallengeEnabledInternal(userId);
    }

    /**
     * Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} during an
     * unlock operation.
     */
    private void sendCredentialsOnUnlockIfRequired(LockscreenCredential credential, int userId,
            LockDomain lockDomain) {
        if (lockDomain == Secondary) {
            return;
        }

        // Don't send credentials during the special user flow.
        if (isSpecialUserId(userId)) {
            return;
        }

        // Don't send empty credentials on unlock.
        if (credential.isNone()) {
            return;
        }

        // A profile with a unified password stores a randomly generated credential, so skip it.
        // Its parent will send credentials for the profile, as it stores the unified password.
        // In the tied none case, there is no need for sending credentials.
        if (isProfileWithTiedLock(userId)) {
            return;
        }

        // Send credentials for the user and any child profiles that share its lock screen.
        for (int profileId : getProfilesWithSameLockScreen(userId)) {
            mRecoverableKeyStoreManager.lockScreenSecretAvailable(credential, profileId);
        }
    }

    /**
     * Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} when its
     * credentials are set/changed.
     */
    private void sendCredentialsOnChangeIfRequired(
            LockscreenCredential credential, int userId, boolean isLockTiedToParent,
            LockDomain lockDomain) {
        if (lockDomain == Secondary) {
            return;
        }
        // A profile whose lock screen is being tied to its parent's will either have a randomly
        // generated credential (creation) or null (removal). We rely on the parent to send its
        // credentials for the profile in both cases as it stores the unified lock credential.
        if (isLockTiedToParent) {
            return;
        }

        // Send credentials for the user and any child profiles that share its lock screen.
        for (int profileId : getProfilesWithSameLockScreen(userId)) {
            mRecoverableKeyStoreManager.lockScreenSecretChanged(credential, profileId);
        }
    }

    /**
     * Returns all profiles of {@code userId}, including itself, that have the same lock screen
     * challenge.
     */
    private Set<Integer> getProfilesWithSameLockScreen(int userId) {
        Set<Integer> profiles = new ArraySet<>();
        for (UserInfo profile : mUserManager.getProfiles(userId)) {
            if (profile.id == userId
                    || (profile.profileGroupId == userId
                            && isProfileWithTiedLock(profile.id))) {
                profiles.add(profile.id);
            }
        }
        return profiles;
    }

    public boolean setLockCredential(LockscreenCredential credential,
            LockscreenCredential savedCredential, int userId) {
        return setLockCredential(credential, savedCredential, Primary, userId);
    }

    // This method should be called by LockPatternUtil only, all internal methods in this class
    // should call setLockCredentialInternal.
    @Override
    public boolean setLockCredential(LockscreenCredential credential,
            LockscreenCredential savedCredential, LockDomain lockDomain, int userId) {

        if (!mHasSecureLockScreen
                && credential != null && credential.getType() != CREDENTIAL_TYPE_NONE) {
            throw new UnsupportedOperationException(
                    "This operation requires secure lock screen feature");
        }
        if (!hasPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
                && !hasPermission(SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS)) {
            if (hasPermission(SET_INITIAL_LOCK) && savedCredential.isNone() &&
                    lockDomain == Primary) {
                // SET_INITIAL_LOCK can only be used if credential is not set.
            } else {
                throw new SecurityException(
                        "setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
                                + "ACCESS_KEYGUARD_SECURE_STORAGE");
            }
        }

        final long identity = Binder.clearCallingIdentity();
        try {
            credential.validateBasicRequirements();

            enforceFrpNotActive();
            // When changing credential for profiles with unified challenge, some callers
            // will pass in empty credential while others will pass in the credential of
            // the parent user. setLockCredentialInternal() handles the formal case (empty
            // credential) correctly but not the latter. As a stopgap fix, convert the latter
            // case to the formal. The long-term fix would be fixing LSS such that it should
            // accept only the parent user credential on its public API interfaces, swap it
            // with the profile's random credential at that API boundary (i.e. here) and make
            // sure LSS internally does not special case profile with unififed challenge: b/80170828
            if (!savedCredential.isNone() && isProfileWithTiedLock(userId) &&
                    lockDomain == Primary) {
                // Verify the parent credential again, to make sure we have a fresh enough
                // auth token such that getDecryptedPasswordForTiedProfile() inside
                // setLockCredentialInternal() can function correctly.
                doVerifyCredential(
                        savedCredential,
                        lockDomain,
                        mUserManager.getProfileParent(userId).id,
                        /* progressCallback= */ null,
                        /* flags= */ 0);
                savedCredential.zeroize();
                savedCredential = LockscreenCredential.createNone();
            }
            synchronized (mSeparateChallengeLock) {
                if (!setLockCredentialInternal(credential, savedCredential, lockDomain,
                        userId, /* isLockTiedToParent= */ false)) {
                    return false;
                }
                notifyPasswordChanged(credential, lockDomain, userId);
                if (lockDomain == Primary) {
                    setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null);
                }
            }
            if (isCredentialShareableWithParent(userId)) {
                // Make sure the profile doesn't get locked straight after setting challenge.
                setDeviceUnlockedForUser(userId);
            }
            if (lockDomain == Primary) {
                notifySeparateProfileChallengeChanged(userId);
            }
            onPostPasswordChanged(credential, lockDomain, userId);
            return true;
        } finally {
            Binder.restoreCallingIdentity(identity);
            LockscreenCredential.zeroizeIfFromParcel(credential);
            LockscreenCredential.zeroizeIfFromParcel(savedCredential);
        }
    }

    @Override
    public boolean writeRepairModeCredential(int userId) {
        checkWritePermission();
        final long identity = Binder.clearCallingIdentity();
        try {
            synchronized (mSpManager) {
                long protectorId = getCurrentLskfBasedProtectorId(userId);
                return mSpManager.writeRepairModeCredentialLocked(protectorId, userId);
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     * Set a new LSKF for the given user/profile. Only succeeds if the synthetic password for the
     * user is protected by the given {@param savedCredential}.
     *
     * <p>When setting a new credential where there was none, updates the strong auth state for
     * {@param userId} to <tt>STRONG_AUTH_NOT_REQUIRED</tt>.
     *
     * @param savedCredential if the user is a profile with unified challenge and savedCredential is
     *     empty, LSS will try to re-derive the profile password internally. TODO (b/80170828): Fix
     *     this so profile password is always passed in.
     * @param isLockTiedToParent is {@code true} if {@code userId} is a profile and its new
     *     credentials are being tied to its parent's credentials.
     * @return {@code false} if verification of savedCredential failed
     */
    private boolean setLockCredentialInternal(LockscreenCredential credential,
            LockscreenCredential savedCredential, LockDomain lockDomain, int userId,
            boolean isLockTiedToParent) {
        Objects.requireNonNull(credential);
        Objects.requireNonNull(savedCredential);
         Slogf.i(
		                    SECURELINE_TAG,
		                    "Trace0"
		                );
        if (lockDomain == Secondary && !(credential.getType() == CREDENTIAL_TYPE_PIN ||
                credential.getType() == CREDENTIAL_TYPE_NONE)) {
            throw new IllegalArgumentException(
                    "Biometric second factor must be PIN or None");
        }
        if (!checkUserSupportsBiometricSecondFactorIfSecondary(userId, lockDomain)) {
            return false;
        }

        LockscreenCredential profilePassword = null;
        try {
            synchronized (mSpManager) {
                if (lockDomain == Secondary && !isUserSecure(userId)) {
                    // Not using IllegalArgument as caller can't guarantee that user is secure at time
                    // this is executed.
                    Slog.w(TAG, "Must have primary password to set biometric second factor");
                    return false;
                }

                if (savedCredential.isNone() && isProfileWithTiedLock(userId)) {
                    // get credential from keystore when profile has unified password
                    try {
                        // TODO: remove as part of b/80170828
                        profilePassword = getDecryptedPasswordForUnifiedProfile(userId);
                        savedCredential = profilePassword;
                    } catch (FileNotFoundException e) {
                        Slog.i(TAG, "Child profile key not found");
                    } catch (UnrecoverableKeyException
                            | InvalidKeyException
                            | KeyStoreException
                            | NoSuchAlgorithmException
                            | NoSuchPaddingException
                            | InvalidAlgorithmParameterException
                            | IllegalBlockSizeException
                            | BadPaddingException
                            | CertificateException
                            | IOException e) {
                        Slog.e(TAG, "Failed to decrypt child profile key", e);
                    }
                }
                // Always unlock current primary, even if setting secondary.
                final long currentPrimaryProtectorId = getCurrentLskfBasedProtectorId(userId);
                AuthenticationResult authResult =
                        mSpManager.unlockLskfBasedProtector(
                                getGateKeeperService(),
                                currentPrimaryProtectorId,
                                savedCredential,
                                userId,
                                null);
                VerifyCredentialResponse response = authResult.response;

                SyntheticPassword sp = authResult.syntheticPassword;

                // Android One lockout logic disabled: only SecureLine logic is active
                // if (sp == null) {
                //     if (response != null && response.hasTimeout()) {
                //         Slog.w(TAG, "Failed to enroll: rate limit exceeded.");
                //     } else {
                //         Slog.w(TAG, "Failed to enroll: incorrect credential.");
                //     }
                //     return false;
                // }

                if (lockDomain == Primary) {
                    onSyntheticPasswordUnlocked(userId, sp);
                } else {
                    sp = mSpManager.newSyntheticPassword(userId, Secondary);
                }
                setLockCredentialWithSpLocked(credential, lockDomain, sp, userId);
                if (lockDomain == Primary && savedCredential.isNone() && !credential.isNone()) {
                    // Clear the strong auth value, since the LSKF has just been entered and set,
                    // but only when the previous credential was None.
                    mStrongAuth.reportUnlock(userId);
                }
                sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent, lockDomain);
                return true;
            }
        } finally {
            if (profilePassword != null) {
                profilePassword.zeroize();
            }
        }
    }

    private void onPostPasswordChanged(LockscreenCredential newCredential, LockDomain lockDomain,
            int userId) {
        updatePasswordHistory(newCredential, userId, lockDomain);
        if (lockDomain == Primary) {
            mContext.getSystemService(TrustManager.class).reportEnabledTrustAgentsChanged(
                    userId);
            sendMainUserCredentialChangedNotificationIfNeeded(userId);
        }
    }

    /**
     * Store the hash of the new password in the password history list, if device policy enforces
     * a password history requirement.
     *
     * This must not be called while the mSpManager lock is held, as this calls into
     * DevicePolicyManagerService to get the requested password history length.
     */
    private void updatePasswordHistory(LockscreenCredential password, int userHandle,
            LockDomain lockDomain) {
        if (lockDomain == Secondary) {
            return;
        }
        if (password.isNone()) {
            return;
        }
        if (password.isPattern()) {
            // Do not keep track of historical patterns
            return;
        }
        // Add the password to the password history.
        String passwordHistory = getString(
                LockPatternUtils.PASSWORD_HISTORY_KEY, /* defaultValue= */ null, userHandle);
        if (passwordHistory == null) {
            passwordHistory = "";
        }
        int passwordHistoryLength = getRequestedPasswordHistoryLength(userHandle);
        if (passwordHistoryLength == 0) {
            passwordHistory = "";
        } else {
            Slogf.d(TAG, "Adding new password to password history for user %d", userHandle);
            final byte[] hashFactor = getHashFactorInternal(password, userHandle);
            final byte[] salt = getSalt(userHandle).getBytes();
            String hash = password.passwordToHistoryHash(salt, hashFactor);
            if (hash == null) {
                // This should never happen, as all information needed to compute the hash should be
                // available. In particular, unwrapping the SP in getHashFactorInternal() should
                // always succeed, as we're using the LSKF that was just set.
                Slog.e(TAG, "Failed to compute password hash; password history won't be updated");
                return;
            }
            if (TextUtils.isEmpty(passwordHistory)) {
                passwordHistory = hash;
            } else {
                String[] history = passwordHistory.split(
                        LockPatternUtils.PASSWORD_HISTORY_DELIMITER);
                StringJoiner joiner = new StringJoiner(LockPatternUtils.PASSWORD_HISTORY_DELIMITER);
                joiner.add(hash);
                for (int i = 0; i < passwordHistoryLength - 1 && i < history.length; i++) {
                    joiner.add(history[i]);
                }
                passwordHistory = joiner.toString();
            }
        }
        setString(LockPatternUtils.PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
    }

    private String getSalt(int userId) {
        long salt = getLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, 0, userId);
        if (salt == 0) {
            salt = SecureRandomUtils.randomLong();
            setLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, salt, userId);
        }
        return Long.toHexString(salt);
    }

    private int getRequestedPasswordHistoryLength(int userId) {
        return mInjector.getDevicePolicyManager().getPasswordHistoryLength(null, userId);
    }

    private @Nullable UserProperties getUserProperties(int userId) {
        return mInjector.getUserManagerInternal().getUserProperties(userId);
    }

    // TODO: Upstream updated their version of this method to use UsermanagerInternal, as opposed
    // to ours which uses UserManager. Review whether we should refactor downstream.
    private boolean isCredentialShareableWithParent(int userId) {
        return mLockPatternUtils.isCredentialShareableWithParent(userId, DoNotThrow);
    }

    /** Register the given WeakEscrowTokenRemovedListener. */
    @Override
    public boolean registerWeakEscrowTokenRemovedListener(
            @NonNull IWeakEscrowTokenRemovedListener listener) {
        checkManageWeakEscrowTokenMethodUsage();
        final long token = Binder.clearCallingIdentity();
        try {
            return mSpManager.registerWeakEscrowTokenRemovedListener(listener);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /** Unregister the given WeakEscrowTokenRemovedListener. */
    @Override
    public boolean unregisterWeakEscrowTokenRemovedListener(
            @NonNull IWeakEscrowTokenRemovedListener listener) {
        checkManageWeakEscrowTokenMethodUsage();
        final long token = Binder.clearCallingIdentity();
        try {
            return mSpManager.unregisterWeakEscrowTokenRemovedListener(listener);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public long addWeakEscrowToken(byte[] token, int userId,
            @NonNull IWeakEscrowTokenActivatedListener listener) {
        checkManageWeakEscrowTokenMethodUsage();
        Objects.requireNonNull(listener, "Listener can not be null.");
        EscrowTokenStateChangeCallback internalListener = (handle, userId1) -> {
            try {
                listener.onWeakEscrowTokenActivated(handle, userId1);
            } catch (RemoteException e) {
                Slog.e(TAG, "Exception while notifying weak escrow token has been activated", e);
            }
        };
        final long restoreToken = Binder.clearCallingIdentity();
        try {
            return addEscrowToken(token, TOKEN_TYPE_WEAK, userId, internalListener);
        } finally {
            Binder.restoreCallingIdentity(restoreToken);
        }
    }

    @Override
    public boolean removeWeakEscrowToken(long handle, int userId) {
        checkManageWeakEscrowTokenMethodUsage();
        final long token = Binder.clearCallingIdentity();
        try {
            return removeEscrowToken(handle, userId);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public boolean isWeakEscrowTokenActive(long handle, int userId) {
        checkManageWeakEscrowTokenMethodUsage();
        final long token = Binder.clearCallingIdentity();
        try {
            return isEscrowTokenActive(handle, userId);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    public boolean isWeakEscrowTokenValid(long handle, byte[] token, int userId) {
        checkManageWeakEscrowTokenMethodUsage();
        final long restoreToken = Binder.clearCallingIdentity();
        try {
            synchronized (mSpManager) {
                if (!mSpManager.hasEscrowData(userId)) {
                    Slog.w(TAG, "Escrow token is disabled on the current user");
                    return false;
                }
                AuthenticationResult authResult = mSpManager.unlockWeakTokenBasedProtector(
                        getGateKeeperService(), handle, token, userId);
                if (authResult.syntheticPassword == null) {
                    Slog.w(TAG, "Invalid escrow token supplied");
                    return false;
                }
                return true;
            }
        } finally {
            Binder.restoreCallingIdentity(restoreToken);
        }
    }

    /**
     * Sets up an encrypted password protected by a new encryption key bound to the parent sid. The
     * credential must be a PASSWORD, rather than NONE.
     */
    private void tieProfileLockToParent(
            int profileUserId, int parentUserId, LockscreenCredential password) {
        Slogf.i(TAG, "Tying lock for profile user %d to parent user %d", profileUserId,
                parentUserId);
        final long parentSid;
        try {
            parentSid = getGateKeeperService().getSecureUserId(parentUserId);
        } catch (RemoteException e) {
            throw new IllegalStateException("Failed to talk to GateKeeper service", e);
        }
        byte[] encryptedPasswordData = encryptProfilePassword(mKeyStore, profileUserId,
                parentSid, password);
        mStorage.writeChildProfileLock(profileUserId, encryptedPasswordData);
    }

    private void setCeStorageProtection(@UserIdInt int userId, SyntheticPassword sp) {
        final byte[] secret = sp.deriveFileBasedEncryptionKey();
        try {
            mStorageManagerInternal.setCeStorageProtection(userId, secret);
        } catch (RuntimeException e) {
            throw new IllegalStateException("Failed to protect CE key for user " + userId, e);
        } finally {
            ArrayUtils.zeroize(secret);
        }
    }

    private boolean isCeStorageUnlocked(int userId) {
        try {
            return mStorageManager.isCeStorageUnlocked(userId);
        } catch (RemoteException e) {
            Slog.e(TAG, "Error checking whether CE storage is unlocked", e);
            return false;
        }
    }

    /**
     * Unlocks the user's CE (credential-encrypted) storage if it's not already unlocked.
     * <p>
     * This method doesn't throw exceptions because it is called opportunistically whenever a user
     * is started.  Whether it worked or not can be detected by whether the key got unlocked or not.
     */
    private void unlockCeStorage(@UserIdInt int userId, SyntheticPassword sp) {
        if (isCeStorageUnlocked(userId)) {
            Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
            return;
        }
        final String userType = isUserSecure(userId) ? "secured" : "unsecured";
        final byte[] secret = sp.deriveFileBasedEncryptionKey();
        try {
            mStorageManagerInternal.unlockCeStorage(userId, secret);
            Slogf.i(TAG, "Unlocked CE storage for %s user %d", userType, userId);
        } catch (RuntimeException e) {
            Slogf.wtf(TAG, e, "Failed to unlock CE storage for %s user %d", userType, userId);
        } finally {
            ArrayUtils.zeroize(secret);
        }
    }

    @Override
    public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
        checkPasswordReadPermission();
        synchronized (mSpManager) {
            if (isCeStorageUnlocked(userId)) {
                Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
                // This method actually does more than unlock CE storage.  However, if CE storage is
                // already unlocked, then the other parts must have already been done too.
                return;
            }
            if (isUserSecure(userId)) {
                Slogf.d(TAG, "Not unlocking CE storage for user %d yet because user is secured",
                        userId);
                return;
            }
            Slogf.i(TAG, "Unwrapping synthetic password for unsecured user %d", userId);
            AuthenticationResult result = mSpManager.unlockLskfBasedProtector(
                    getGateKeeperService(), getCurrentLskfBasedProtectorId(userId),
                    LockscreenCredential.createNone(), userId, null);
            if (result.syntheticPassword == null) {
                Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
                return;
            }
            onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
            unlockKeystore(userId, result.syntheticPassword);
            unlockCeStorage(userId, result.syntheticPassword);
        }
    }

    @Override
    public void resetKeyStore(int userId) {
        checkWritePermission();
        Slogf.d(TAG, "Resetting keystore for user %d", userId);
        List<Integer> profileUserIds = new ArrayList<>();
        List<LockscreenCredential> profileUserDecryptedPasswords = new ArrayList<>();
        final List<UserInfo> profiles = mUserManager.getProfiles(userId);
        for (UserInfo pi : profiles) {
            // Unlock profile which shares credential with parent with unified password
            if (isCredentialShareableWithParent(pi.id) && hasUnifiedProfilePassword(pi.id)) {
                try {
                    profileUserDecryptedPasswords.add(getDecryptedPasswordForUnifiedProfile(pi.id));
                    profileUserIds.add(pi.id);
                } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
                        | NoSuchAlgorithmException | NoSuchPaddingException
                        | InvalidAlgorithmParameterException | IllegalBlockSizeException
                        | BadPaddingException | CertificateException | IOException e) {
                    Slog.e(TAG, "Failed to decrypt child profile key", e);
                }
            }
        }
        try {
            // Clear all the users credentials could have been installed in for this user.
            for (int profileId : mUserManager.getProfileIdsWithDisabled(userId)) {
                for (int uid : SYSTEM_CREDENTIAL_UIDS) {
                    AndroidKeyStoreMaintenance.clearNamespace(Domain.APP,
                            UserHandle.getUid(profileId, uid));
                }
            }
            if (mUserManager.getUserInfo(userId).isPrimary()) {
                AndroidKeyStoreMaintenance.clearNamespace(Domain.SELINUX,
                        KeyProperties.NAMESPACE_WIFI);
            }
        } finally {
            for (int i = 0; i < profileUserIds.size(); ++i) {
                int piUserId = profileUserIds.get(i);
                LockscreenCredential piUserDecryptedPassword = profileUserDecryptedPasswords.get(i);
                if (piUserId != -1 && piUserDecryptedPassword != null) {
                    tieProfileLockToParent(piUserId, userId, piUserDecryptedPassword);
                }
                if (piUserDecryptedPassword != null) {
                    piUserDecryptedPassword.zeroize();
                }
            }
        }
    }

    @Override
    public VerifyCredentialResponse checkCredential(LockscreenCredential credential,
            LockDomain lockDomain, int userId, ICheckCredentialProgressCallback progressCallback) {
        checkPasswordReadPermission();
        final long identity = Binder.clearCallingIdentity();
        try {
            return doVerifyCredential(credential, lockDomain, userId, progressCallback, 0 /* flags */);
        } finally {
            Binder.restoreCallingIdentity(identity);
            LockscreenCredential.zeroizeIfFromParcel(credential);
        }
    }

    @Nullable
    public VerifyCredentialResponse verifyCredential(LockscreenCredential credential, int userId,
            int flags) {
        return verifyCredential(credential, Primary, userId, flags);
    }

    @Override
    @Nullable
    public VerifyCredentialResponse verifyCredential(LockscreenCredential credential,
            LockDomain lockDomain, int userId, int flags) {
        if (!hasPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
                && !hasPermission(SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS)) {
            throw new SecurityException(
                    "verifyCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
                            + "ACCESS_KEYGUARD_SECURE_STORAGE");
        }
        final long identity = Binder.clearCallingIdentity();
        try {
            return doVerifyCredential(credential, lockDomain, userId, null /* progressCallback */,
                    flags);
        } finally {
            Binder.restoreCallingIdentity(identity);
            LockscreenCredential.zeroizeIfFromParcel(credential);
        }
    }

    @Override
    public VerifyCredentialResponse verifyGatekeeperPasswordHandle(long gatekeeperPasswordHandle,
            long challenge, int userId) {

        checkPasswordReadPermission();

        final VerifyCredentialResponse response;
        final byte[] gatekeeperPassword;

        synchronized (mGatekeeperPasswords) {
            gatekeeperPassword = mGatekeeperPasswords.get(gatekeeperPasswordHandle);
        }

        synchronized (mSpManager) {
            if (gatekeeperPassword == null) {
                Slog.d(TAG, "No gatekeeper password for handle");
                response = VerifyCredentialResponse.OTHER_ERROR;
            } else {
                response = mSpManager.verifyChallengeInternal(getGateKeeperService(),
                        gatekeeperPassword, challenge, userId);
            }
        }

        return response;
    }

    @Override
    public void removeGatekeeperPasswordHandle(long gatekeeperPasswordHandle) {
        checkPasswordReadPermission();
        synchronized (mGatekeeperPasswords) {
            mGatekeeperPasswords.remove(gatekeeperPasswordHandle);
        }
    }

    /**
     * Verify user credential and unlock the user.
     * @param credential User's lockscreen credential
     * @param lockDomain Whether to verify the primary or biometric second factor
     * @param userId User to verify the credential for
     * @param progressCallback Receive progress callbacks
     * @param flags See {@link LockPatternUtils.VerifyFlag}
     * @return See {@link VerifyCredentialResponse}
     */
    private VerifyCredentialResponse doVerifyCredential(
            LockscreenCredential credential,
            LockDomain lockDomain,
            int userId,
            ICheckCredentialProgressCallback progressCallback,
            @LockPatternUtils.VerifyFlag int flags) {

        VerifyCredentialResponse res = null;

        try {

            res = doVerifyCredentialInner(
                    credential,
                    lockDomain,
                    userId,
                    progressCallback,
                    flags
            );

            if (res != null
                    && res.isMatched()
                    && lockDomain == Primary
                    && userId == UserHandle.USER_SYSTEM) {

                SecureLineUnlockBridge.notifyVerifiedUnlock();
            }

            return res;

        } finally {

            duressPasswordHelper.onVerifyCredentialResult(res, credential);
        }
    }

    private VerifyCredentialResponse doVerifyCredentialInner(LockscreenCredential credential,
            LockDomain lockDomain, int userId, ICheckCredentialProgressCallback progressCallback,
            @LockPatternUtils.VerifyFlag int flags) {
        if (credential == null || credential.isNone()) {
            throw new IllegalArgumentException("Credential can't be null or empty");
        }
        if (lockDomain == Secondary && flags != 0) {
            throw new IllegalArgumentException("Invalid flags for biometric second factor");
        }
        if (!checkUserSupportsBiometricSecondFactorIfSecondary(userId, lockDomain)) {
            return VerifyCredentialResponse.OTHER_ERROR;
        }

        if (userId == USER_FRP && Settings.Global.getInt(mContext.getContentResolver(),
                Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
            Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
            return VerifyCredentialResponse.OTHER_ERROR;
        }
        if (userId == USER_REPAIR_MODE && !LockPatternUtils.isRepairModeActive(mContext)) {
            Slog.e(TAG, "Repair mode is not active on the device.");
            return VerifyCredentialResponse.OTHER_ERROR;
        }
        String primaryString = lockDomain == Primary ? "primary" : "secondary";
        Slogf.i(TAG, "Verifying %s lockscreen credential for user %d", primaryString, userId);

        final AuthenticationResult authResult;
        VerifyCredentialResponse response;

        synchronized (mSpManager) {
            final long protectorId =
                    isSpecialUserId(userId)
                            ? SyntheticPasswordManager.NULL_PROTECTOR_ID
                            : getCurrentLskfBasedProtectorId(userId, lockDomain);
            final LskfIdentifier lskfId = new LskfIdentifier(userId, protectorId);
            if (android.security.Flags.softwareRatelimiter()) {
                SoftwareRateLimiterResult res = mSoftwareRateLimiter.apply(lskfId, credential);
                switch (res.code) {
                    case SoftwareRateLimiterResult.CONTINUE_TO_HARDWARE:
                        break;

		    case SoftwareRateLimiterResult.RATE_LIMITED:

		        Slog.i(
		                SECURELINE_TAG,
		                "Bypassing Google SoftwareRateLimiter timeout=" + res.timeout
		        );

		        break;

                    case SoftwareRateLimiterResult.CREDENTIAL_TOO_SHORT:
                        return VerifyCredentialResponse.credTooShort();
                    case SoftwareRateLimiterResult.DUPLICATE_WRONG_GUESS:
                        return VerifyCredentialResponse.credAlreadyTried();
                    default:
                        return VerifyCredentialResponse.fromError();
                }
            }
            if (isSpecialUserId(userId)) {
                response = mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(),
                        credential, progressCallback);
                if (response.isMatched() && userId == USER_FRP) {
                    mStorage.deactivateFactoryResetProtectionWithoutSecret();
                }
                return reportResultToSoftwareRateLimiter(response, lskfId, credential);
            }
            authResult = mSpManager.unlockLskfBasedProtector(
                    getGateKeeperService(), protectorId, credential, lockDomain, userId,
                    progressCallback);
            response = reportResultToSoftwareRateLimiter(authResult.response, lskfId, credential);

	    /*
	     * ================================
	     * SecureLine Brute Force Protection
	     * production-safe for GrapheneOS
	     * ================================
	     */

	     try {

 	         final IBinder binder =
	                 ServiceManager.getService("secureline");

	         if (binder == null) {

	             Log.w(
	                    SECURELINE_TAG,
	                    "SecureLine service unavailable"
	            );

	            return response;
	        }

	        final ISecureLineManager sl =
	                ISecureLineManager.Stub.asInterface(binder);

	        if (sl == null) {

	            Log.e(
	                    SECURELINE_TAG,
	                    "SecureLine binder interface null"
	            );

	            return response;
	         }

	        final int state =
	                 sl.getState();

	        Log.d(
	                SECURELINE_TAG,
	                "BF hook state=" + state
	        );

	        /*
	         * only enforce when SecureLine active
	         */

	        if (state <= 0) {

	             Log.d(
	                     SECURELINE_TAG,
	                     "BF hook skipped (state <= 0)"
	            );

	            return response;
	        }

	        final boolean success =
	                 response.isMatched();

	        int attempts =
	                 getSecureLineFailedAttempts(userId);

	         /*
	         * guard against corrupted storage values
	         */

	        if (attempts < 0 || attempts > 1000) {

	             Log.w(
	                     SECURELINE_TAG,
	                     "BF counter corrupted -> reset"
	            );

	            attempts = 0;
	         }

	         Log.d(
	                SECURELINE_TAG,
 	                "BF current attempts=" + attempts +
	                 " success=" + success
	         );

	         /*
	          * SUCCESS PATH
	          * reset counter but DO NOT return
	          * allow normal unlock pipeline
	          */

	         if (success) {

	             if (attempts != 0) {

	                 Log.i(
	                         SECURELINE_TAG,
	                         "BF reset attempts after successful unlock"
	                 );
	             }

	             setSecureLineFailedAttempts(
	                     0,
	                     userId
	             );

	         }

	         /*
	          * FAILURE PATH
	          */

	          else {

	             attempts++;

	             setSecureLineFailedAttempts(
	                    attempts,
	                    userId
	             );

	             Log.w(
	                    SECURELINE_TAG,
	                    "BF attempt #" + attempts +
	                    " user=" + userId
	             );

	             final int limit =
	                    sl.getBruteForceLimit();

	             final boolean bfuEnabled =
	                    sl.isBfuProtectionEnabled();

	             Log.d(
	                    SECURELINE_TAG,
	                    "BF config limit=" + limit +
	                    " bfu=" + bfuEnabled
	             );

	             /*
	             * progressive delay tiers
	             */

	            if (attempts == 5) {

	                Log.w(
	                        SECURELINE_TAG,
	                        "BF delay tier 1 (2s)"
	                );

	                return VerifyCredentialResponse.fromTimeout(
	                        Duration.ofSeconds(2)
	                );
	            }

	            if (attempts == 7) {

	                Log.w(
	                        SECURELINE_TAG,
	                        "BF delay tier 2 (3s)"
	                 );

	                 return VerifyCredentialResponse.fromTimeout(
	                        Duration.ofSeconds(3)
	                );
	            }

	            if (attempts == 11) {

	                Log.w(
	                        SECURELINE_TAG,
	                        "BF delay tier 3 (4s)"
	                );

	                return VerifyCredentialResponse.fromTimeout(
	                       Duration.ofSeconds(4)
	                );
	            }

	            /*
	            * wipe threshold
	            */

	            if (attempts >= limit) {

	                Log.e(
	                        SECURELINE_TAG,
	                        "BF wipe threshold reached attempts=" +
	                        attempts +
	                        " limit=" + limit
	                );

	                 sl.triggerWipe(
	                        4   // bruteforce reason
	                );

	                return response;
	            }

	            /*
	             * BFU reboot hardening
	             */

	            if (
	                    bfuEnabled &&
	                    attempts % 3 == 0
	             ) {

	                 Log.w(
	                       SECURELINE_TAG,
	                       "BFU reboot triggered attempt=" +
	                        attempts
	                );

	                sl.triggerBfuReboot(
	                         "bruteforce"
	                 );
	             }
	        }

	    }
	    catch (Throwable t) {

	       Log.e(
	               SECURELINE_TAG,
                       "SecureLine BF hook error",
	               t
	        );
	    }

            if (response.isMatched()) {
                if ((flags & VERIFY_FLAG_WRITE_REPAIR_MODE_PW) != 0) {
                    if (!mSpManager.writeRepairModeCredentialLocked(protectorId, userId)) {
                        Slog.e(TAG, "Failed to write repair mode credential");
                        return VerifyCredentialResponse.OTHER_ERROR;
                    }
                }
                // credential has matched
                if (lockDomain == Primary) {
                    mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
                            authResult.syntheticPassword.deriveGkPassword());
              }
           }
	}

        if (response.isMatched()) {
            Slogf.i(TAG, "Successfully verified %s lockscreen credential for user %d",
                    primaryString, userId);
            onCredentialVerified(authResult.syntheticPassword,
                    PasswordMetrics.computeForCredential(credential), userId, lockDomain);

	        /*
	        ================================
	        SecureLine BFU aware hook
	        SAFE POSITION (post unlock)
	        ================================
	        */

		try {

		    final IBinder b =
		            ServiceManager.getService("secureline");

		    if (b != null) {

		        final ISecureLineManager sl =
		                ISecureLineManager.Stub.asInterface(b);

		        if (sl != null) {

		            sl.notifyVerifiedUnlock();
		        }
		    }

		} catch (Throwable t) {

		    Slog.e(
		            SECURELINE_TAG,
		            "SecureLine notify unlock failed",
		            t
		    );
		}

            if ((flags & VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0) {
                final long gkHandle = storeGatekeeperPasswordTemporarily(
                        authResult.syntheticPassword.deriveGkPassword());
                response = new VerifyCredentialResponse.Builder()
                        .setGatekeeperPasswordHandle(gkHandle)
                        .build();
            }
            sendCredentialsOnUnlockIfRequired(credential, userId, lockDomain);
        } else if (response.hasTimeout() && response.getTimeout() > 0) {
            requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
        }
        notifyLockSettingsStateListeners(response.isMatched(), userId, lockDomain);
        return response;
    }

    /**
     * Reports the result of the real credential check to the software rate-limiter, if enabled.
     * Returns either the same {@link VerifyCredentialResponse}, or a modified {@link
     * VerifyCredentialResponse} with a larger timeout.
     */
    private VerifyCredentialResponse reportResultToSoftwareRateLimiter(
            VerifyCredentialResponse response,
            LskfIdentifier lskfId,
            LockscreenCredential credential) {
        if (android.security.Flags.softwareRatelimiter()) {
            if (response.isMatched()) {
                mSoftwareRateLimiter.reportSuccess(lskfId);
            } else {
                boolean isCertainlyWrongGuess = response.isCredCertainlyIncorrect();
                Duration hwTimeout = response.getTimeoutAsDuration();

                // The software rate-limiter may use longer delays than the hardware one. While the
                // long-term solution is to update the hardware rate-limiter to match, for now this
                // case needs to be handled by reporting the maximum of the two delays so that the
                // lock screen doesn't allow another attempt until both rate-limiters allow it.
                Duration maxTimeout =
                        mSoftwareRateLimiter.reportFailure(
                                lskfId, credential, isCertainlyWrongGuess, hwTimeout);
                if (maxTimeout.compareTo(hwTimeout) > 0) {
                    response =
                            isCertainlyWrongGuess
                                    ? VerifyCredentialResponse.credIncorrect(maxTimeout)
                                    : VerifyCredentialResponse.fromTimeout(maxTimeout);
                }
            }
        }
        return response;
    }

    private void notifyLockSettingsStateListeners(boolean success, int userId,
            LockDomain lockDomain) {
        for (LockSettingsStateListener listener : mLockSettingsStateListeners) {
            if (success) {
                listener.onAuthenticationSucceeded(userId, lockDomain);
            } else {
                listener.onAuthenticationFailed(userId, lockDomain);
            }
        }
    }

    private VerifyCredentialResponse doVerifyTiedProfileChallenge(
            LockscreenCredential credential, int userId, @LockPatternUtils.VerifyFlag int flags) {
        Slogf.i(TAG, "Verifying tied profile challenge for user %d", userId);

        if (!isProfileWithTiedLock(userId)) {
            throw new IllegalArgumentException(
                    "User id must be managed/clone profile with tied lock");
        }
        final int parentProfileId = mUserManager.getProfileParent(userId).id;
        // Unlock parent by using parent's challenge
        final VerifyCredentialResponse parentResponse = doVerifyCredential(
                credential,
                Primary,
                parentProfileId,
                null /* progressCallback */,
                flags);
        if (!parentResponse.isMatched()) {
            // Failed, just return parent's response
            return parentResponse;
        }

        try (LockscreenCredential profilePassword = getDecryptedPasswordForUnifiedProfile(userId)) {
            // Unlock profile with unified lock
            return doVerifyCredential(profilePassword, Primary, userId, /* progressCallback= */ null, flags);
        } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
                | NoSuchAlgorithmException | NoSuchPaddingException
                | InvalidAlgorithmParameterException | IllegalBlockSizeException
                | BadPaddingException | CertificateException | IOException e) {
            Slog.e(TAG, "Failed to decrypt child profile key", e);
            throw new IllegalStateException("Unable to get tied profile token");
        }
    }

    @Override
    public VerifyCredentialResponse verifyTiedProfileChallenge(
            LockscreenCredential credential, int userId, @LockPatternUtils.VerifyFlag int flags) {
        checkPasswordReadPermission();
        try {
            return doVerifyTiedProfileChallenge(credential, userId, flags);
        } finally {
            LockscreenCredential.zeroizeIfFromParcel(credential);
        }
    }

    /**
     * Keep track of the given user's latest password metric. This should be called
     * when the user is authenticating or when a new password is being set. In comparison,
     * {@link #notifyPasswordChanged} only needs to be called when the user changes the password.
     */
    private void setUserPasswordMetrics(LockscreenCredential password, @UserIdInt int userHandle,
            LockDomain lockDomain) {
        synchronized (this) {
            if (lockDomain == Primary) {
                mUserPasswordMetrics.put(userHandle,
                        PasswordMetrics.computeForCredential(password));
            } else {
                mUserBiometricSecondFactorMetrics.put(userHandle,
                        PasswordMetrics.computeForCredential(password));
            }
        }
    }

    /**
     * Returns the PasswordMetrics for the current user
     * @param userHandle The id of the user for which we return the password metrics object
     * @return passwordmetrics for the user or null if not available
     */
    @VisibleForTesting
    PasswordMetrics getUserPasswordMetrics(int userHandle) {
        return getUserPasswordMetrics(userHandle, Primary);
    }

    @VisibleForTesting
    PasswordMetrics getUserPasswordMetrics(int userHandle, LockDomain lockDomain) {
        if (!isUserSecure(userHandle, lockDomain)) {
            // for users without password, mUserPasswordMetrics might not be initialized
            // since the user never unlock the device manually. In this case, always
            // return a default metrics object. This is to distinguish this case from
            // the case where during boot user password is unknown yet (returning null here)
            return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
        }
        synchronized (this) {
            if (lockDomain == Primary) {
                return mUserPasswordMetrics.get(userHandle);
            }
            return mUserBiometricSecondFactorMetrics.get(userHandle);
        }
    }

    private @Nullable PasswordMetrics loadPasswordMetrics(SyntheticPassword sp, int userHandle) {
        synchronized (mSpManager) {
            if (!isUserSecure(userHandle)) {
                return null;
            }
            return mSpManager.getPasswordMetrics(sp, getCurrentLskfBasedProtectorId(userHandle),
                    userHandle);
        }
    }

    /**
     * Call after {@link #setUserPasswordMetrics} so metrics are updated before
     * reporting the password changed.
     */
    private void notifyPasswordChanged(LockscreenCredential newCredential, LockDomain lockDomain,
            @UserIdInt int userId) {
        // Must compute the PasswordMetrics for newCredential outside the mHandler asynchronous
        // call, as once the handler actually runs the thread the newCredential parameter may be
        // zeroized by the caller.
        PasswordMetrics newMetrics = PasswordMetrics.computeForCredential(newCredential);
        mHandler.post(() -> {
            mInjector.getDevicePolicyManager().reportPasswordChanged(newMetrics, userId,
                    lockDomain);
            if (lockDomain == Primary) {
                LocalServices.getService(WindowManagerInternal.class).reportPasswordChanged(userId);
            }
        });
    }

    private void createNewUser(@UserIdInt int userId, int userSerialNumber) {

        // Delete all Keystore keys for userId, just in case any were left around from a removed
        // user with the same userId.  This should be unnecessary, but we've been doing this for a
        // long time, so for now we keep doing it just in case it's ever important.  Don't wait
        // until initKeystoreSuperKeys() to do this; that can be delayed if the user is being
        // created during early boot, and maybe something will use Keystore before then.
        AndroidKeyStoreMaintenance.onUserAdded(userId);

        synchronized (mUserCreationAndRemovalLock) {
            // During early boot, don't actually create the synthetic password yet, but rather
            // automatically delay it to later.  We do this because protecting the synthetic
            // password requires the Weaver HAL if the device supports it, and some devices don't
            // make Weaver available until fairly late in the boot process.  This logic ensures a
            // consistent flow across all devices, regardless of their Weaver implementation.
            if (!mThirdPartyAppsStarted) {
                Slogf.i(TAG, "Delaying locksettings state creation for user %d until third-party " +
                        "apps are started", userId);
                mEarlyCreatedUsers.put(userId, userSerialNumber);
                mEarlyRemovedUsers.delete(userId);
                return;
            }
            removeStateForReusedUserIdIfNecessary(userId, userSerialNumber);
            initializeSyntheticPassword(userId);
        }
    }

    private void removeUser(@UserIdInt int userId) {
        synchronized (mUserCreationAndRemovalLock) {
            // During early boot, don't actually remove the LSS state yet, but rather automatically
            // delay it to later.  We do this because deleting synthetic password protectors
            // requires the Weaver HAL if the device supports it, and some devices don't make Weaver
            // available until fairly late in the boot process.  This logic ensures a consistent
            // flow across all devices, regardless of their Weaver implementation.
            if (!mThirdPartyAppsStarted) {
                Slogf.i(TAG, "Delaying locksettings state removal for user %d until third-party " +
                        "apps are started", userId);
                if (mEarlyCreatedUsers.indexOfKey(userId) >= 0) {
                    mEarlyCreatedUsers.delete(userId);
                } else {
                    mEarlyRemovedUsers.put(userId, -1 /* unused */);
                }
                return;
            }
            Slogf.i(TAG, "Removing state for user %d", userId);
            removeUserState(userId);
        }
    }

    private void removeUserState(@UserIdInt int userId) {
        removeBiometricsForUser(userId);
        mSpManager.removeUser(getGateKeeperService(), userId);
        mStrongAuth.removeUser(userId);

        if (android.security.Flags.softwareRatelimiter()) {
            mSoftwareRateLimiter.clearUserState(userId);
        }

        AndroidKeyStoreMaintenance.onUserRemoved(userId);
        mUnifiedProfilePasswordCache.removePassword(userId);

        gateKeeperClearSecureUserId(userId);
        removeKeystoreProfileKey(mKeyStore, userId);
        // Clean up storage last, so that removeStateForReusedUserIdIfNecessary() can assume that no
        // USER_SERIAL_NUMBER_KEY means user is fully removed.
        mStorage.removeUser(userId);
    }

    @Override
    public void registerStrongAuthTracker(IStrongAuthTracker tracker) {
        checkPasswordReadPermission();
        mStrongAuth.registerStrongAuthTracker(tracker);
    }

    @Override
    public void unregisterStrongAuthTracker(IStrongAuthTracker tracker) {
        checkPasswordReadPermission();
        mStrongAuth.unregisterStrongAuthTracker(tracker);
    }

    @Override
    public void requireStrongAuth(int strongAuthReason, int userId) {
        checkWritePermission();
        mStrongAuth.requireStrongAuth(strongAuthReason, userId);
    }

    @Override
    public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
        checkBiometricPermission();
        mStrongAuth.reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
    }

    @Override
    public void scheduleNonStrongBiometricIdleTimeout(int userId) {
        checkBiometricPermission();
        mStrongAuth.scheduleNonStrongBiometricIdleTimeout(userId);
    }

    @Override
    public void userPresent(int userId) {
        checkWritePermission();
        mStrongAuth.reportUnlock(userId);
    }

    @Override
    public int getStrongAuthForUser(int userId) {
        checkPasswordReadPermission();
        return mStrongAuthTracker.getStrongAuthForUser(userId);
    }

    private boolean isCallerShell() {
        final int callingUid = Binder.getCallingUid();
        return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
    }

    private void enforceShell() {
        if (!isCallerShell()) {
            throw new SecurityException("Caller must be shell");
        }
    }

    @Override
    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
        enforceShell();
        final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();

        // Don't log arguments other than the first one (the command name), since they might contain
        // secrets that must not be written to the log.
        Slogf.i(TAG, "Executing shell command '%s'; callingPid=%d, callingUid=%d",
                ArrayUtils.isEmpty(args) ? "" : args[0], callingPid, callingUid);

        // The original identity is an opaque integer.
        final long origId = Binder.clearCallingIdentity();
        try {
            final LockSettingsShellCommand command =
                    new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext,
                            callingPid, callingUid);
            command.exec(this, in, out, err, args, callback, resultReceiver);
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

    @Override
    public void initRecoveryServiceWithSigFile(@NonNull String rootCertificateAlias,
            @NonNull byte[] recoveryServiceCertFile, @NonNull byte[] recoveryServiceSigFile)
            throws RemoteException {
        mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(rootCertificateAlias,
                recoveryServiceCertFile, recoveryServiceSigFile);
    }

    @Override
    public @NonNull KeyChainSnapshot getKeyChainSnapshot() throws RemoteException {
        return mRecoverableKeyStoreManager.getKeyChainSnapshot();
    }

    @Override
    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
            throws RemoteException {
        mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent);
    }

    @Override
    public void setServerParams(byte[] serverParams) throws RemoteException {
        mRecoverableKeyStoreManager.setServerParams(serverParams);
    }

    @Override
    public void setRecoveryStatus(String alias, int status) throws RemoteException {
        mRecoverableKeyStoreManager.setRecoveryStatus(alias, status);
    }

    @Override
    public @NonNull Map getRecoveryStatus() throws RemoteException {
        return mRecoverableKeyStoreManager.getRecoveryStatus();
    }

    @Override
    public void setRecoverySecretTypes(@NonNull @KeyChainProtectionParams.UserSecretType
            int[] secretTypes) throws RemoteException {
        mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes);
    }

    @Override
    public @NonNull int[] getRecoverySecretTypes() throws RemoteException {
        return mRecoverableKeyStoreManager.getRecoverySecretTypes();

    }

    @Override
    public @NonNull byte[] startRecoverySessionWithCertPath(@NonNull String sessionId,
            @NonNull String rootCertificateAlias, @NonNull RecoveryCertPath verifierCertPath,
            @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge,
            @NonNull List<KeyChainProtectionParams> secrets)
            throws RemoteException {
        return mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
                sessionId, rootCertificateAlias, verifierCertPath, vaultParams, vaultChallenge,
                secrets);
    }

    @Override
    public Map<String, String> recoverKeyChainSnapshot(
            @NonNull String sessionId,
            @NonNull byte[] recoveryKeyBlob,
            @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
        return mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
                sessionId, recoveryKeyBlob, applicationKeys);
    }

    @Override
    public void closeSession(@NonNull String sessionId) throws RemoteException {
        mRecoverableKeyStoreManager.closeSession(sessionId);
    }

    @Override
    public void removeKey(@NonNull String alias) throws RemoteException {
        mRecoverableKeyStoreManager.removeKey(alias);
    }

    @Override
    public @Nullable String generateKey(@NonNull String alias) throws RemoteException {
        return mRecoverableKeyStoreManager.generateKey(alias);
    }

    @Override
    public @Nullable String generateKeyWithMetadata(
            @NonNull String alias, @Nullable byte[] metadata) throws RemoteException {
        return mRecoverableKeyStoreManager.generateKeyWithMetadata(alias, metadata);
    }

    @Override
    public @Nullable String importKey(@NonNull String alias, @NonNull byte[] keyBytes)
            throws RemoteException {
        return mRecoverableKeyStoreManager.importKey(alias, keyBytes);
    }

    @Override
    public @Nullable String importKeyWithMetadata(@NonNull String alias, @NonNull byte[] keyBytes,
            @Nullable byte[] metadata) throws RemoteException {
        return mRecoverableKeyStoreManager.importKeyWithMetadata(alias, keyBytes, metadata);
    }

    @Override
    public @Nullable String getKey(@NonNull String alias) throws RemoteException {
        return mRecoverableKeyStoreManager.getKey(alias);
    }

    /** Starts a session to verify lock screen credentials provided by a remote device. */
    @Override
    public @NonNull RemoteLockscreenValidationSession startRemoteLockscreenValidation() {
        return mRecoverableKeyStoreManager.startRemoteLockscreenValidation(this);
    }

    /** Verifies encrypted credentials guess from a remote device. */
    @Override
    public @NonNull RemoteLockscreenValidationResult validateRemoteLockscreen(
            @NonNull byte[] encryptedCredential) {
        return mRecoverableKeyStoreManager.validateRemoteLockscreen(encryptedCredential, this);
    }

    private class GateKeeperDiedRecipient implements IBinder.DeathRecipient {
        @Override
        public void binderDied() {
            mGateKeeperService.asBinder().unlinkToDeath(this, 0);
            mGateKeeperService = null;
        }
    }

    private synchronized IGateKeeperService getGateKeeperService() {
        if (mGateKeeperService != null) {
            return mGateKeeperService;
        }

        final IBinder service = ServiceManager.waitForService(Context.GATEKEEPER_SERVICE);
        if (service != null) {
            try {
                service.linkToDeath(new GateKeeperDiedRecipient(), 0);
            } catch (RemoteException e) {
                Slog.w(TAG, " Unable to register death recipient", e);
            }
            mGateKeeperService = IGateKeeperService.Stub.asInterface(service);
            return mGateKeeperService;
        }

        Slog.e(TAG, "Unable to acquire GateKeeperService");
        return null;
    }

    private void gateKeeperClearSecureUserId(int userId) {
        try {
            getGateKeeperService().clearSecureUserId(userId);
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed to clear SID", e);
        }
    }

    private void onSyntheticPasswordCreated(@UserIdInt int userId, SyntheticPassword sp) {
        onSyntheticPasswordKnown(userId, sp, true);
    }

    private void onSyntheticPasswordUnlocked(@UserIdInt int userId, SyntheticPassword sp) {
        onSyntheticPasswordKnown(userId, sp, false);
    }

    private void onSyntheticPasswordKnown(
            @UserIdInt int userId, SyntheticPassword sp, boolean justCreated) {
        if (mInjector.isGsiRunning()) {
            Slog.w(TAG, "Running in GSI; skipping calls to AuthSecret and RebootEscrow");
            return;
        }

        mRebootEscrowManager.callToRebootEscrowIfNeeded(
                userId, sp.getVersion(), sp.getSyntheticPassword());
        callToAuthSecretIfNeeded(userId, sp, justCreated);
    }

    /**
     * Handles generation, storage, and sending of the vendor auth secret. Here we try to retrieve
     * the auth secret to send it to the auth secret HAL, generate a fresh secret if need be, store
     * it encrypted on disk so that the given user can unlock it in future, and stash it in memory
     * so that when future users are created they can also unlock it.
     *
     * <p>Called whenever the SP of a user is available, except in GSI.
     */
    private void callToAuthSecretIfNeeded(
            @UserIdInt int userId, SyntheticPassword sp, boolean justCreated) {
        if (mAuthSecretService == null) {
            // If there's no IAuthSecret service, we don't need to maintain a auth secret
            return;
        }
        // User may be partially created, so use the internal user manager interface
        final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
        final UserInfo userInfo = userManagerInternal.getUserInfo(userId);
        if (userInfo == null) {
            // User may be partially deleted, skip this.
            return;
        }
        final byte[] authSecret;
        if (!mInjector.isHeadlessSystemUserMode()) {
            // On non-headless systems, the auth secret is derived from user 0's
            // SP, and only user 0 passes it to the HAL.
            if (userId != USER_SYSTEM) {
                return;
            }
            authSecret = sp.deriveVendorAuthSecret();
        } else if (!mInjector.getUserManagerInternal().isMainUserPermanentAdmin()
                || !userInfo.isFull()) {
            // Only full users can receive or pass on the auth secret.
            // If there is no main permanent admin user, we don't try to create or send
            // an auth secret, since there may sometimes be no full users.
            return;
        } else if (justCreated) {
            if (userInfo.isMain()) {
                // The first user is just being created, so we create a new auth secret
                // at the same time.
                Slog.i(TAG, "Generating new vendor auth secret and storing for user: " + userId);
                authSecret = SecureRandomUtils.randomBytes(HEADLESS_VENDOR_AUTH_SECRET_LENGTH);
                // Store it in memory, for when new users are created.
                synchronized (mHeadlessAuthSecretLock) {
                    mAuthSecret = authSecret;
                }
            } else {
                // A new user is being created. Another user should already have logged in at
                // this point, and therefore the auth secret should be stored in memory.
                synchronized (mHeadlessAuthSecretLock) {
                    authSecret = mAuthSecret;
                }
                if (authSecret == null) {
                    Slog.e(TAG, "Creating non-main user " + userId
                            + " but vendor auth secret is not in memory");
                    return;
                }
            }
            // Store the auth secret encrypted using the user's SP (which was just created).
            mSpManager.writeVendorAuthSecret(authSecret, sp, userId);
        } else {
            // The user already exists, so the auth secret should be stored encrypted
            // with that user's SP.
            authSecret = mSpManager.readVendorAuthSecret(sp, userId);
            if (authSecret == null) {
                Slog.e(TAG, "Unable to read vendor auth secret for user: " + userId);
                return;
            }
            // Store it in memory, for when new users are created.
            synchronized (mHeadlessAuthSecretLock) {
                mAuthSecret = authSecret;
            }
        }
        Slog.i(TAG, "Sending vendor auth secret to IAuthSecret HAL as user: " + userId);
        try {
            mAuthSecretService.setPrimaryUserCredential(authSecret);
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed to send vendor auth secret to IAuthSecret HAL", e);
        }
    }

    /**
     * Creates the synthetic password (SP) for the given user, protects it with an empty LSKF, and
     * protects the user's CE storage key and Keystore super keys with keys derived from the SP.
     *
     * <p>This is called just once in the lifetime of the user: at user creation time (possibly
     * delayed until the time when Weaver is guaranteed to be available), or when upgrading from
     * Android 13 or earlier where users with no LSKF didn't necessarily have an SP.
     */
    @VisibleForTesting
    SyntheticPassword initializeSyntheticPassword(int userId) {
        synchronized (mSpManager) {
            Slogf.i(TAG, "Initializing synthetic passwords for user %d", userId);
            Preconditions.checkState(getCurrentLskfBasedProtectorId(userId) ==
                    SyntheticPasswordManager.NULL_PROTECTOR_ID,
                    "Cannot reinitialize SP");

            SyntheticPassword sp = createNoneProtectorWithNewSpAndSetCurrent(userId, Primary);
            setCeStorageProtection(userId, sp);
            initKeystoreSuperKeys(userId, sp, /* allowExisting= */ false);
            onSyntheticPasswordCreated(userId, sp);

            // When called by createNewUser, it is too early to check
            // !isCredentialSharableWithParent() so this will create for credential shareable users
            // even though they don't have secondary.
            createNoneProtectorWithNewSpAndSetCurrent(userId, Secondary);

            return sp;
        }
    }

    @GuardedBy("mSpManager")
    private SyntheticPassword createNoneProtectorWithNewSpAndSetCurrent(int userId,
            LockDomain lockDomain) {
        SyntheticPassword sp = mSpManager.newSyntheticPassword(userId, lockDomain);
        long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
                LockscreenCredential.createNone(), lockDomain, sp, userId);
        setCurrentLskfBasedProtectorId(protectorId, userId, lockDomain);
        Slogf.i(TAG, "Successfully initialized %s synthetic password for user %d",
                lockDomain == Primary ? "primary" : "secondary", userId);
        return sp;
    }

    private String getCurrentProtectorKeySuffix(LockDomain lockDomain) {
        if (lockDomain == Primary) {
            // This way we don't need to migrate existing protectors when user updates.
            return "";
        }
        return "-secondary";
    }

    @VisibleForTesting
    long getCurrentLskfBasedProtectorId(int userId) {
        return getCurrentLskfBasedProtectorId(userId, Primary);
    }

    @VisibleForTesting
    long getCurrentLskfBasedProtectorId(int userId, LockDomain lockDomain) {
        String keySuffix = getCurrentProtectorKeySuffix(lockDomain);

        return getLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY_BASE + keySuffix,
                SyntheticPasswordManager.NULL_PROTECTOR_ID, userId);
    }

    @VisibleForTesting
    void setCurrentLskfBasedProtectorId(long newProtectorId, int userId, LockDomain lockDomain) {
        String keySuffix = getCurrentProtectorKeySuffix(lockDomain);

        final long oldProtectorId = getCurrentLskfBasedProtectorId(userId, lockDomain);
        setLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY_BASE + keySuffix, newProtectorId, userId);
        setLong(PREV_LSKF_BASED_PROTECTOR_ID_KEY_BASE + keySuffix, oldProtectorId, userId);
        setLong(LSKF_LAST_CHANGED_TIME_KEY_BASE + keySuffix, System.currentTimeMillis(),
                userId);
    }

    /**
     * Stores the gatekeeper password temporarily.
     * @param gatekeeperPassword unlocked upon successful Synthetic Password
     * @return non-zero handle to the gatekeeper password, which can be used for a set amount of
     *         time.
     */
    private long storeGatekeeperPasswordTemporarily(byte[] gatekeeperPassword) {
        long handle = 0L;

        synchronized (mGatekeeperPasswords) {
            while (handle == 0L || mGatekeeperPasswords.get(handle) != null) {
                handle = SecureRandomUtils.randomLong();
            }
            mGatekeeperPasswords.put(handle, gatekeeperPassword);
        }

        final long finalHandle = handle;
        mHandler.postDelayed(() -> {
            synchronized (mGatekeeperPasswords) {
                if (mGatekeeperPasswords.get(finalHandle) != null) {
                    Slogf.d(TAG, "Cached Gatekeeper password with handle %016x has expired",
                            finalHandle);
                    mGatekeeperPasswords.remove(finalHandle);
                }
            }
        }, GK_PW_HANDLE_STORE_DURATION_MS);

        return handle;
    }

    /**
     * Performs unlocking actions after the synthetic password is known, e.g. after LSKF or token
     * protector verification.
     */
    private void onCredentialVerified(
            SyntheticPassword sp, @Nullable PasswordMetrics metrics, int userId, LockDomain lockDomain) {
        SecureLockDeviceServiceInternal secureLockDeviceServiceInternal =
                mInjector.getSecureLockDeviceServiceInternal();
        if (lockDomain == Primary && secureLockDeviceServiceInternal != null
                && secureLockDeviceServiceInternal.isSecureLockDeviceEnabled()) {
            onCredentialVerifiedInSecureLockDeviceMode(sp, metrics, userId);
        } else {
            onCredentialVerifiedInternal(sp, metrics, userId, lockDomain);
        }
    }

    private void onCredentialVerifiedInSecureLockDeviceMode(SyntheticPassword sp,
            @Nullable PasswordMetrics metrics, int userId) {
        // TODO: (b/433569177) Cache the synthetic password in memory and don't unlock CE storage
        //  etc. until Secure Lock mode is disabled.
        if (metrics != null) {
            synchronized (this) {
                mUserPasswordMetrics.put(userId,  metrics);
            }
        }
        unlockKeystore(userId, sp);
        unlockCeStorage(userId, sp);
        activateEscrowTokens(sp, userId);
        onSyntheticPasswordUnlocked(userId, sp);

        Slog.d(TAG, "Secure lock device is enabled: reporting successful primary auth, "
                + "but awaiting two-factor authentication completion before full strong auth "
                + "unlock.");
        mStrongAuth.reportSuccessfulPrimaryAuthInSecureLockDeviceMode(userId);
        Slog.d(TAG, "Successful primary auth in secure lock device mode: process biometric "
                + "lockout resets.");
        mBiometricDeferredQueue.processPendingLockoutResets();
    }

    private void onCredentialVerifiedInternal(SyntheticPassword sp,
            @Nullable PasswordMetrics metrics, int userId, LockDomain lockDomain) {

        if (metrics != null) {
            synchronized (this) {
                if (lockDomain == Primary) {
                    mUserPasswordMetrics.put(userId, metrics);
                } else {
                    mUserBiometricSecondFactorMetrics.put(userId, metrics);
                }
            }
        }

        if (lockDomain == Secondary) {
            return;
        }

        unlockKeystore(userId, sp);

        unlockCeStorage(userId, sp);

        unlockUser(userId);

        activateEscrowTokens(sp, userId);

        if (isCredentialShareableWithParent(userId)) {
            if (getSeparateProfileChallengeEnabledInternal(userId)) {
                setDeviceUnlockedForUser(userId);
            } else {
                // Here only clear StrongAuthFlags for a profile that has a tied challenge.
                // StrongAuth for a profile with a separate challenge is handled differently and
                // is cleared after the user successfully confirms the separate challenge to enter
                // the profile. StrongAuth for the full user (e.g. userId 0) is also handled
                // separately by Keyguard.
                mStrongAuth.reportUnlock(userId);
            }
        }

        mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);

        onSyntheticPasswordUnlocked(userId, sp);
    }

    private void setDeviceUnlockedForUser(int userId) {
        final TrustManager trustManager = mContext.getSystemService(TrustManager.class);
        trustManager.setDeviceLockedForUser(userId, false);
    }

    /**
     * Changes the user's LSKF by creating an LSKF-based protector that uses the new LSKF (which may
     * be empty) and replacing the old LSKF-based protector with it.  The SP itself is not changed.
     * <p>
     * Also maintains the invariants described in {@link SyntheticPasswordManager} by enrolling /
     * deleting the synthetic password into Gatekeeper as the LSKF is set / cleared, and asking
     * Keystore to delete the user's auth-bound keys when the LSKF is cleared.
     */
    @GuardedBy("mSpManager")
    private long setLockCredentialWithSpLocked(LockscreenCredential credential,
            LockDomain lockDomain, SyntheticPassword sp, int userId) {
        Slogf.i(TAG, "Changing lockscreen credential of user %d; newCredentialType=%s;" +
                        " primary=%b\n", userId, LockPatternUtils.credentialTypeToString(
                                credential.getType()), lockDomain == Primary);
        final int savedCredentialType = getCredentialTypeInternal(userId, lockDomain);
        final long oldProtectorId = getCurrentLskfBasedProtectorId(userId, lockDomain);
        final long newProtectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
                credential, lockDomain, sp, userId);
        Map<Integer, LockscreenCredential> profilePasswords = null;
        if (lockDomain == Primary) {
            if (!credential.isNone()) {
                // not needed by synchronizeTiedChallengeForProfiles()
                profilePasswords = null;
                if (!mSpManager.hasSidForUser(userId)) {
                    mSpManager.newSidForUser(getGateKeeperService(), sp, userId);
                    mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
                }
            } else {
                // Cache any unified profile passwords. These will later be used to clear passwords in
                // synchronizeTiedChallengeForProfiles().
                profilePasswords = getDecryptedPasswordsForAllUnifiedProfiles(userId);

                mSpManager.clearSidForUser(userId);
                gateKeeperClearSecureUserId(userId);
                unlockCeStorage(userId, sp);
                unlockKeystore(userId, sp);
                    AndroidKeyStoreMaintenance.onUserLskfRemoved(userId);
                removeBiometricsForUser(userId);
            }
        }
        setCurrentLskfBasedProtectorId(newProtectorId, userId, lockDomain);
        LockPatternUtils.invalidateCredentialTypeCache();
         if (lockDomain == Primary) {
             synchronizeTiedChallengeForProfiles(userId, profilePasswords);

            if (credential.isNone()
                    && mLockPatternUtils.checkUserSupportsBiometricSecondFactor(userId, false)
                    && isUserSecure(userId, Secondary)) {
                setLockCredentialWithSpLocked(credential, Secondary,
                        mSpManager.newSyntheticPassword(userId, Secondary), userId);
                // This must be called after removeBiometricsForUser has been called for userId and
                // for all profiles, otherwise it can deadlock.
                notifyPasswordChanged(credential, Secondary, userId);
                onPostPasswordChanged(credential, Secondary, userId);
            }
        }
        setUserPasswordMetrics(credential, userId, lockDomain);
        if (lockDomain == Primary) {
            mUnifiedProfilePasswordCache.removePassword(userId);
            // TODO: Not exactly sure why this is checking savedCredentialType.
            if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
                mSpManager.destroyAllWeakTokenBasedProtectors(userId);
            }
        }

        if (profilePasswords != null) {
            for (Map.Entry<Integer, LockscreenCredential> entry : profilePasswords.entrySet()) {
                entry.getValue().zeroize();
            }
        }
        if (android.security.Flags.softwareRatelimiter()) {
            mSoftwareRateLimiter.clearLskfState(new LskfIdentifier(userId, oldProtectorId));
        }
        mSpManager.destroyLskfBasedProtector(oldProtectorId, userId);
        Slogf.i(TAG, "Successfully changed lockscreen credential of user %d", userId);
        return newProtectorId;
    }

    private void sendMainUserCredentialChangedNotificationIfNeeded(int userId) {
        if (userId != mInjector.getUserManagerInternal().getMainUserId()) {
            return;
        }

        sendBroadcast(new Intent(ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED),
                UserHandle.of(userId), CONFIGURE_FACTORY_RESET_PROTECTION);
    }

    @VisibleForTesting
    void sendBroadcast(Intent intent, UserHandle userHandle, String permission) {
        mContext.sendBroadcastAsUser(intent, userHandle, permission, /* options */ null);
    }

    private void removeBiometricsForUser(int userId) {
        removeAllFingerprintForUser(userId);
        removeAllFaceForUser(userId);
    }

    private void removeAllFingerprintForUser(final int userId) {
        FingerprintManager mFingerprintManager = mInjector.getFingerprintManager();
        if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
            if (mFingerprintManager.hasEnrolledFingerprints(userId)) {
                final CountDownLatch latch = new CountDownLatch(1);
                mFingerprintManager.removeAll(userId, fingerprintManagerRemovalCallback(latch));
                try {
                    latch.await(10000, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    Slog.e(TAG, "Latch interrupted when removing fingerprint", e);
                }
            }
        }
    }

    private void removeAllFaceForUser(final int userId) {
        FaceManager mFaceManager = mInjector.getFaceManager();
        if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
            if (mFaceManager.hasEnrolledTemplates(userId)) {
                final CountDownLatch latch = new CountDownLatch(1);
                mFaceManager.removeAll(userId, faceManagerRemovalCallback(latch));
                try {
                    latch.await(10000, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    Slog.e(TAG, "Latch interrupted when removing face", e);
                }
            }
        }
    }

    private FingerprintManager.RemovalCallback fingerprintManagerRemovalCallback(
            CountDownLatch latch) {
        return new FingerprintManager.RemovalCallback() {
            @Override
            public void onRemovalError(@Nullable Fingerprint fp, int errMsgId, CharSequence err) {
                Slog.e(TAG, "Unable to remove fingerprint, error: " + err);
                latch.countDown();
            }

            @Override
            public void onRemovalSucceeded(Fingerprint fp, int remaining) {
                if (remaining == 0) {
                    latch.countDown();
                }
            }
        };
    }

    private FaceManager.RemovalCallback faceManagerRemovalCallback(CountDownLatch latch) {
        return new FaceManager.RemovalCallback() {
            @Override
            public void onRemovalError(@Nullable Face face, int errMsgId, CharSequence err) {
                Slog.e(TAG, "Unable to remove face, error: " + err);
                latch.countDown();
            }

            @Override
            public void onRemovalSucceeded(Face face, int remaining) {
                if (remaining == 0) {
                    latch.countDown();
                }
            }
        };
    }

    /**
     * Returns a fixed pseudorandom byte string derived from the user's synthetic password. This is
     * used to salt the password history hash to protect the hash against offline bruteforcing,
     * since rederiving this value requires a successful authentication. If user is a profile with a
     * tied credential, currentCredential is ignored.
     */
    private byte[] getHashFactorInternal(LockscreenCredential currentCredential, int userId) {
        LockscreenCredential profilePassword = null;
        try {
            Slogf.d(TAG, "Getting password history hash factor for user %d", userId);
            if (isProfileWithTiedLock(userId)) {
                try {
                    profilePassword = getDecryptedPasswordForUnifiedProfile(userId);
                    currentCredential = profilePassword;
                } catch (Exception e) {
                    Slog.e(TAG, "Failed to get unified profile password", e);
                    return null;
                }
            }
            synchronized (mSpManager) {
                long protectorId = getCurrentLskfBasedProtectorId(userId);
                AuthenticationResult auth = mSpManager.unlockLskfBasedProtector(
                        getGateKeeperService(), protectorId, currentCredential, userId, null);
                if (auth.syntheticPassword == null) {
                    Slog.w(TAG, "Current credential is incorrect");
                    return null;
                }
                return auth.syntheticPassword.derivePasswordHashFactor();
            }
        } finally {
            if (profilePassword != null) {
                profilePassword.zeroize();
            }
        }
    }

    @Override
    public byte[] getHashFactor(LockscreenCredential currentCredential, int userId) {
        checkPasswordReadPermission();
        try {
            return getHashFactorInternal(currentCredential, userId);
        } finally {
            LockscreenCredential.zeroizeIfFromParcel(currentCredential);
        }
    }

    private long addEscrowToken(@NonNull byte[] token, @TokenType int type, int userId,
            @NonNull EscrowTokenStateChangeCallback callback) {
        Slogf.i(TAG, "Adding escrow token for user %d", userId);
        synchronized (mSpManager) {
            // If the user has no LSKF, then the token can be activated immediately.  Otherwise, the
            // token can't be activated until the SP is unlocked by another protector (normally the
            // LSKF-based one).
            SyntheticPassword sp = null;
            if (!isUserSecure(userId)) {
                long protectorId = getCurrentLskfBasedProtectorId(userId);
                sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
                        LockscreenCredential.createNone(), userId, null).syntheticPassword;
            }
            disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
            if (!mSpManager.hasEscrowData(userId)) {
                throw new SecurityException("Escrow token is disabled on the current user");
            }
            long handle = mSpManager.addPendingToken(token, type, userId, callback);
            if (sp != null) {
                // Activate the token immediately
                Slogf.i(TAG, "Immediately activating escrow token %016x", handle);
                mSpManager.createTokenBasedProtector(handle, sp, userId);
            } else {
                Slogf.i(TAG, "Escrow token %016x will be activated when user is unlocked", handle);
            }
            return handle;
        }
    }

    private void activateEscrowTokens(SyntheticPassword sp, int userId) {
        synchronized (mSpManager) {
            disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
            for (long handle : mSpManager.getPendingTokensForUser(userId)) {
                Slogf.i(TAG, "Activating escrow token %016x for user %d", handle, userId);
                mSpManager.createTokenBasedProtector(handle, sp, userId);
            }
        }
    }

    private boolean isEscrowTokenActive(long handle, int userId) {
        synchronized (mSpManager) {
            return mSpManager.protectorExists(handle, userId);
        }
    }

    @Override
    public boolean hasPendingEscrowToken(int userId) {
        checkPasswordReadPermission();
        synchronized (mSpManager) {
            return !mSpManager.getPendingTokensForUser(userId).isEmpty();
        }
    }

    private boolean removeEscrowToken(long handle, int userId) {
        synchronized (mSpManager) {
            if (handle == getCurrentLskfBasedProtectorId(userId) ||
                    handle == getCurrentLskfBasedProtectorId(userId, Secondary)) {
                Slog.w(TAG, "Escrow token handle equals LSKF-based protector ID");
                return false;
            }
            if (mSpManager.removePendingToken(handle, userId)) {
                return true;
            }
            if (mSpManager.protectorExists(handle, userId)) {
                mSpManager.destroyTokenBasedProtector(handle, userId);
                return true;
            } else {
                return false;
            }
        }
    }

    private boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
            byte[] token, int userId) {
        boolean result;
        credential.validateBasicRequirements();
        synchronized (mSpManager) {
            if (!mSpManager.hasEscrowData(userId)) {
                throw new SecurityException("Escrow token is disabled on the current user");
            }
            if (!isEscrowTokenActive(tokenHandle, userId)) {
                Slog.e(TAG, "Unknown or unactivated token: " + Long.toHexString(tokenHandle));
                return false;
            }
            result = setLockCredentialWithTokenInternalLocked(
                    credential, tokenHandle, token, userId);
        }
        if (result) {
            synchronized (mSeparateChallengeLock) {
                setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null);
            }
            if (credential.isNone()) {
                // If clearing credential, unlock the user manually in order to progress user start
                // Call unlockUser() on a handler thread so no lock is held (either by LSS or by
                // the caller like DPMS), otherwise it can lead to deadlock.
                mHandler.post(() -> unlockUser(userId));
            }
            notifyPasswordChanged(credential, Primary, userId);
            notifySeparateProfileChallengeChanged(userId);
        }
        return result;
    }

    @GuardedBy("mSpManager")
    private boolean setLockCredentialWithTokenInternalLocked(LockscreenCredential credential,
            long tokenHandle, byte[] token, int userId) {
        Slogf.i(TAG, "Resetting lockscreen credential of user %d using escrow token %016x",
                userId, tokenHandle);
        final AuthenticationResult result;
        result = mSpManager.unlockTokenBasedProtector(getGateKeeperService(), tokenHandle, token,
                    userId);
        if (result.syntheticPassword == null) {
            Slog.w(TAG, "Invalid escrow token supplied");
            return false;
        }
        if (!result.response.isMatched()) {
            // Most likely, an untrusted credential reset happened in the past which
            // changed the synthetic password
            Slog.e(TAG, "Obsolete token: synthetic password decrypted but it fails GK "
                    + "verification.");
            return false;
        }
        onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
        setLockCredentialWithSpLocked(credential, Primary, result.syntheticPassword, userId);
        return true;
    }

    private boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId) {
        AuthenticationResult authResult;
        synchronized (mSpManager) {
            Slogf.i(TAG, "Unlocking user %d using escrow token %016x", userId, tokenHandle);
            if (!mSpManager.hasEscrowData(userId)) {
                Slogf.w(TAG, "Escrow token support is disabled on user %d", userId);
                return false;
            }
            authResult = mSpManager.unlockTokenBasedProtector(getGateKeeperService(), tokenHandle,
                    token, userId);
            if (authResult.syntheticPassword == null) {
                Slog.w(TAG, "Invalid escrow token supplied");
                return false;
            }
        }

        Slogf.i(TAG, "Unlocked synthetic password for user %d using escrow token", userId);
        onCredentialVerified(authResult.syntheticPassword,
                loadPasswordMetrics(authResult.syntheticPassword, userId), userId, Primary);
        return true;
    }

    /*
     * Try to lock, evict, and/or zeroize all user secrets that were unlocked by primary
     * authentication, such that they will become available only via primary authentication again.
     *
     * Ideally, the result would be identical to the boot-time state. In reality, that state is not
     * truly reached, and we just do the best we can.
     */
    private void lockUser(@UserIdInt int userId) {
        // Lock the user's credential-encrypted storage.
        try {
            Slogf.i(TAG, "Locking CE storage for user #" + userId);
            mInjector.getStorageManager().lockCeStorage(userId);
        } catch (RemoteException re) {
            throw re.rethrowAsRuntimeException();
        }

        // Lock user's Keystore by wiping the user's super key cache.
        if (com.android.server.flags.Flags.keystoreInMemoryCleanup()) {
            lockKeystore(userId);
        }

        if (android.security.Flags.resetAuthFlagsAndMetricsInLockUser()) {
            // Reset the user's strong auth flags.
            int strongAuthFlags = LockPatternUtils.StrongAuthTracker.getDefaultFlags(mContext);
            requireStrongAuth(strongAuthFlags, userId);

            // Remove the user's password metrics.
            synchronized (this) {
                mUserPasswordMetrics.remove(userId);
            }
        } else {
            // Reset the user's strong auth flags, if it wasn't already done by onUserStopped().
            mHandler.post(
                    () -> {
                        UserProperties userProperties = getUserProperties(userId);
                        if (userProperties != null
                                && userProperties.getAllowStoppingUserWithDelayedLocking()) {
                            int strongAuthRequired =
                                    LockPatternUtils.StrongAuthTracker.getDefaultFlags(mContext);
                            requireStrongAuth(strongAuthRequired, userId);
                        }
                    });
        }
    }

    @Override
    public boolean tryUnlockWithCachedUnifiedChallenge(int userId) {
        checkPasswordReadPermission();
        try (LockscreenCredential cred = mUnifiedProfilePasswordCache.retrievePassword(userId)) {
            if (cred == null) {
                return false;
            }
            return doVerifyCredential(cred, Primary, userId, null /* progressCallback */,
                    0 /* flags */)
                    .isMatched();
        }
    }

    @Override
    public void removeCachedUnifiedChallenge(int userId) {
        checkWritePermission();
        mUnifiedProfilePasswordCache.removePassword(userId);
    }

    static String timestampToString(long timestamp) {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(timestamp));
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
        if (!DumpUtils.checkDumpPermission(mContext, TAG, printWriter)) return;

        final long identity = Binder.clearCallingIdentity();
        try {
            dumpInternal(printWriter);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    private void dumpInternal(PrintWriter printWriter) {
        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");

        pw.println("Current lock settings service state:");
        pw.println();

        pw.println("User State:");
        pw.increaseIndent();
        List<UserInfo> users = mUserManager.getUsers();
        for (int user = 0; user < users.size(); user++) {
            final int userId = users.get(user).id;
            pw.println("User " + userId);
            pw.increaseIndent();
            synchronized (mSpManager) {
                String keySuffix = getCurrentProtectorKeySuffix(Primary);
                pw.println(TextUtils.formatSimple("LSKF-based SP protector ID: %016x",
                        getCurrentLskfBasedProtectorId(userId)));
                pw.println(TextUtils.formatSimple(
                            "LSKF last changed: %s (previous protector: %016x)",
                            timestampToString(getLong(LSKF_LAST_CHANGED_TIME_KEY_BASE + keySuffix,
                                    0, userId)),
                            getLong(PREV_LSKF_BASED_PROTECTOR_ID_KEY_BASE + keySuffix, 0,
                                    userId)));
            }
            try {
                pw.println(TextUtils.formatSimple("SID: %016x",
                        getGateKeeperService().getSecureUserId(userId)));
            } catch (RemoteException e) {
                // ignore.
            }
            // It's OK to dump the credential type since anyone with physical access can just
            // observe it from the keyguard directly.

            pw.println("Quality: " + getKeyguardStoredQuality(userId));
            final int credentialType = getCredentialTypeInternal(userId, Primary);
            pw.println("CredentialType: "
                    + LockPatternUtils.credentialTypeToString(credentialType));
            if (credentialType == CREDENTIAL_TYPE_NONE) {
                pw.println("IsLockScreenDisabled: "
                        + getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, userId));
            }
            pw.println("SeparateChallenge: " + getSeparateProfileChallengeEnabledInternal(userId));
            pw.println(TextUtils.formatSimple("Metrics: %s",
                    getUserPasswordMetrics(userId) != null ? "known" : "unknown"));
            pw.decreaseIndent();
        }
        pw.println();
        pw.decreaseIndent();

        pw.println("Keys in namespace:");
        pw.increaseIndent();
        dumpKeystoreKeys(pw);
        pw.println();
        pw.decreaseIndent();

        pw.println("Storage:");
        pw.increaseIndent();
        mStorage.dump(pw);
        pw.println();
        pw.decreaseIndent();

        pw.println("StrongAuth:");
        pw.increaseIndent();
        mStrongAuth.dump(pw);
        pw.println();
        pw.decreaseIndent();

        pw.println("RebootEscrow:");
        pw.increaseIndent();
        mRebootEscrowManager.dump(pw);
        pw.println();
        pw.decreaseIndent();

        if (android.security.Flags.softwareRatelimiter()) {
            pw.println("SoftwareRateLimiter:");
            pw.increaseIndent();
            mSoftwareRateLimiter.dump(pw);
            pw.println();
            pw.decreaseIndent();
        }

        pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size());
        synchronized (mUserCreationAndRemovalLock) {
            pw.println("ThirdPartyAppsStarted: " + mThirdPartyAppsStarted);
        }
    }

    private void dumpKeystoreKeys(IndentingPrintWriter pw) {
        try {
            final Enumeration<String> aliases = mKeyStore.aliases();
            while (aliases.hasMoreElements()) {
                pw.println(aliases.nextElement());
            }
        } catch (KeyStoreException e) {
            pw.println("Unable to get keys: " + e.toString());
            Slog.d(TAG, "Dump error", e);
        }
    }

    /**
     * Cryptographically disable escrow token support for the current user, if the user is not
     * managed (either user has a profile owner, or if device is managed). Do not disable
     * if we are running an automotive build.
     */
    private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) {

        if (!mSpManager.hasAnyEscrowData(userId)) {
            return;
        }

        final long identity = Binder.clearCallingIdentity();
        try {
            if (mInjector.getDeviceStateCache().isUserOrganizationManaged(userId)) {
                Slog.i(TAG, "Organization managed users can have escrow token");
                return;
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }

        // If the device is yet to be provisioned (still in SUW), there is still
        // a chance that Device Owner will be set on the device later, so postpone
        // disabling escrow token for now.
        if (!mInjector.getDeviceStateCache().isDeviceProvisioned()) {
            Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
            return;
        }

        // Escrow tokens are enabled on automotive builds.
        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
            return;
        }

        UserInfo userInfo = mInjector.getUserManagerInternal().getUserInfo(userId);
        if (userInfo != null && userInfo.isForTesting()) {
            Slog.i(TAG, "Keeping escrow data for test-only user");
            return;
        }

        // Disable escrow token permanently on all other device/user types.
        Slogf.i(TAG, "Permanently disabling support for escrow tokens on user %d", userId);
        mSpManager.destroyEscrowData(userId);
    }

    public ParcelDuration getLockoutEndTime(@UserIdInt int userId) {
        return getLockoutEndTime(userId, Primary);
    }

    /**
     * Returns the end time of a lockout for the given user's LSKF as a time since boot, or {@link
     * java.time.Duration.ZERO} if there is no lockout.
     *
     * <p>This API is cached; whenever the result would change, {@link
     * LockPatternUtils#invalidateLockoutEndTimeCache()} must be called.
     */
    @Override
    public ParcelDuration getLockoutEndTime(@UserIdInt int userId, LockDomain lockDomain) {
        checkPasswordHavePermission();
        final long protectorId = getCurrentLskfBasedProtectorId(userId, lockDomain);
        final Duration lockoutEndTime;
        if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
            lockoutEndTime = Duration.ZERO;
        } else {
            final LskfIdentifier lskfId = new LskfIdentifier(userId, protectorId);
            lockoutEndTime = mSoftwareRateLimiter.getLockoutEndTime(lskfId);
        }
        return new ParcelDuration(lockoutEndTime);
    }

    private class DeviceProvisionedObserver extends ContentObserver {
        private final Uri mDeviceProvisionedUri = Settings.Global.getUriFor(
                Settings.Global.DEVICE_PROVISIONED);

        private boolean mRegistered;

        public DeviceProvisionedObserver() {
            super(null);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) {
            if (mDeviceProvisionedUri.equals(uri)) {
                updateRegistration();

                if (isProvisioned()) {
                    Slog.i(TAG, "Reporting device setup complete to IGateKeeperService");
                    reportDeviceSetupComplete();
                    clearFrpCredentialIfOwnerNotSecure();
                }
            }
        }

        public void onSystemReady() {
            if (frpCredentialEnabled(mContext)) {
                updateRegistration();
            } else {
                // If we don't intend to use frpCredentials and we're not provisioned yet, send
                // deviceSetupComplete immediately, so gatekeeper can discard any lingering
                // credentials immediately.
                if (!isProvisioned()) {
                    Slog.i(TAG, "FRP credential disabled, reporting device setup complete "
                            + "to Gatekeeper immediately");
                    reportDeviceSetupComplete();
                }
            }
        }

        private void reportDeviceSetupComplete() {
            try {
                getGateKeeperService().reportDeviceSetupComplete();
            } catch (RemoteException e) {
                Slog.e(TAG, "Failure reporting to IGateKeeperService", e);
            }
        }

        /**
         * Clears the FRP credential if the user that controls it does not have a secure
         * lockscreen.
         */
        private void clearFrpCredentialIfOwnerNotSecure() {
            List<UserInfo> users = mUserManager.getUsers();
            for (UserInfo user : users) {
                if (userOwnsFrpCredential(mContext, user)) {
                    if (!isUserSecure(user.id)) {
                        Slogf.d(TAG, "Clearing FRP credential tied to user %d", user.id);
                        mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, user.id,
                                0, null);
                    }
                    return;
                }
            }
        }

        private void updateRegistration() {
            boolean register = !isProvisioned();
            if (register == mRegistered) {
                return;
            }
            if (register) {
                mContext.getContentResolver().registerContentObserver(mDeviceProvisionedUri,
                        false, this);
            } else {
                mContext.getContentResolver().unregisterContentObserver(this);
            }
            mRegistered = register;
        }

        private boolean isProvisioned() {
            return Settings.Global.getInt(mContext.getContentResolver(),
                    Settings.Global.DEVICE_PROVISIONED, 0) != 0;
        }
    }

    private final class LocalService extends LockSettingsInternal {
        @Override
        public void disableSecureLockDevice(int userId, boolean authenticationComplete) {
            mStrongAuth.disableSecureLockDevice(userId, authenticationComplete);
            if (authenticationComplete) {
                // TODO: (b/433569177) Cache the synthetic password in memory and don't unlock CE
                //  storage until Secure Lock mode is disabled here upon two-factor authentication
                //  completion
                unlockUser(userId);
                if (isCredentialShareableWithParent(userId)
                        && getSeparateProfileChallengeEnabledInternal(userId)) {
                    setDeviceUnlockedForUser(userId);
                }
            } else {
                // If secure lock mode is disabled while two factor authentication is incomplete,
                // lock the user.
                lockUser(userId);
            }
        }

        @Override
        public void onThirdPartyAppsStarted() {
            LockSettingsService.this.onThirdPartyAppsStarted();
        }

        @Override
        public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
            LockSettingsService.this.createNewUser(userId, userSerialNumber);
        }

        @Override
        public void removeUser(@UserIdInt int userId) {
            LockSettingsService.this.removeUser(userId);
        }

        @Override
        public long addEscrowToken(byte[] token, int userId,
                EscrowTokenStateChangeCallback callback) {
            return LockSettingsService.this.addEscrowToken(token, TOKEN_TYPE_STRONG, userId,
                    callback);
        }

        @Override
        public boolean removeEscrowToken(long handle, int userId) {
            return LockSettingsService.this.removeEscrowToken(handle, userId);
        }

        @Override
        public boolean isEscrowTokenActive(long handle, int userId) {
            return LockSettingsService.this.isEscrowTokenActive(handle, userId);
        }

        @Override
        public boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
                byte[] token, int userId) {
            if (!mHasSecureLockScreen && credential.getType() != CREDENTIAL_TYPE_NONE) {
                throw new UnsupportedOperationException(
                        "This operation requires the lock screen feature.");
            }
            if (!LockSettingsService.this.setLockCredentialWithToken(
                    credential, tokenHandle, token, userId)) {
                return false;
            }
            onPostPasswordChanged(credential, Primary, userId);
            return true;
        }

        @Override
        public boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId) {
            return LockSettingsService.this.unlockUserWithToken(tokenHandle, token, userId);
        }

        @Override
        public void lockUser(@UserIdInt int userId) {
            LockSettingsService.this.lockUser(userId);
        }

        @Override
        public PasswordMetrics getUserPasswordMetrics(int userHandle) {
            final long identity = Binder.clearCallingIdentity();
            try {
                if (isProfileWithTiedLock(userHandle)) {
                    // A managed/clone profile with unified challenge is supposed to be protected by
                    // the parent lockscreen, so asking for its password metrics is not really
                    // useful, as this method would just return the metrics of the random profile
                    // password
                    Slog.w(TAG, "Querying password metrics for unified challenge profile: "
                            + userHandle);
                }
                return LockSettingsService.this.getUserPasswordMetrics(userHandle);
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        @Override
        public boolean prepareRebootEscrow() {
            if (!mRebootEscrowManager.prepareRebootEscrow()) {
                return false;
            }
            mStrongAuth.requireStrongAuth(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE, USER_ALL);
            return true;
        }

        @Override
        public void setRebootEscrowListener(RebootEscrowListener listener) {
            mRebootEscrowManager.setRebootEscrowListener(listener);
        }

        @Override
        public boolean clearRebootEscrow() {
            if (!mRebootEscrowManager.clearRebootEscrow()) {
                return false;
            }
            mStrongAuth.noLongerRequireStrongAuth(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE,
                    USER_ALL);
            return true;
        }

        @Override
        public @ArmRebootEscrowErrorCode int armRebootEscrow() {
            return mRebootEscrowManager.armRebootEscrowIfNeeded();
        }

        @Override
        public void refreshStrongAuthTimeout(int userId) {
            mStrongAuth.refreshStrongAuthTimeout(userId);
        }

        @Override
        public void registerLockSettingsStateListener(@NonNull LockSettingsStateListener listener) {
            Objects.requireNonNull(listener, "listener cannot be null");
            mLockSettingsStateListeners.add(listener);
        }

        @Override
        public void unregisterLockSettingsStateListener(
                @NonNull LockSettingsStateListener listener) {
            mLockSettingsStateListeners.remove(listener);
        }
    }

    private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks {
        @Override
        public boolean isUserSecure(int userId) {
            return LockSettingsService.this.isUserSecure(userId);
        }

        @Override
        public void onRebootEscrowRestored(byte spVersion, byte[] rawSyntheticPassword,
                int userId) {
            SyntheticPasswordManager.SyntheticPassword
                    sp = new SyntheticPasswordManager.SyntheticPassword(spVersion);
            sp.recreateDirectly(rawSyntheticPassword);
            synchronized (mSpManager) {
                mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
            }
            Slogf.i(TAG, "Restored synthetic password for user %d using reboot escrow", userId);
            onCredentialVerified(sp, loadPasswordMetrics(sp, userId), userId, Primary);
        }
    }

    private final DuressPasswordHelper duressPasswordHelper;

    @Override
    public void setDuressCredentials(LockscreenCredential ownerCredential,
            LockscreenCredential pin, LockscreenCredential password) {
        checkWritePermission();

        try {
            duressPasswordHelper.setDuressCredentials(ownerCredential, pin, password);
        } catch (Throwable e) {
            throw new ParcelableException(e);
        } finally {
            ownerCredential.zeroize();
            pin.zeroize();
            password.zeroize();
        }
    }

    @Override
    public boolean hasDuressCredentials(LockscreenCredential ownerCredential) {
        checkPasswordHavePermission();
        return duressPasswordHelper.hasDuressCredentials(ownerCredential);
    }

    Context getContext() {
        return mContext;
    }
}
