de-HIDL: Sensor configs & Provider merge

1. Map HIDL to AIDL fingerprint sensor configuration
2. Propagate that through FingerprintService and FingerprintProvider
3. Similar implementation for face sensors
4. Use only one provider

Test: atest FingerprintSensorConfigurationsTest
FaceSensorConfigurationsTest AuthServiceTest FaceServiceTest
FingerprintServiceTest FaceProviderTest FingerprintProviderTest
HidlToAidlSensorAdapterTest HidlToAidlSessionAdapterTest SensorTest
Bug: 303480396
bug: 303480830

Change-Id: I7a945e596c6331077e90cbe1500de0bcb212895c
diff --git a/Android.bp b/Android.bp
index 676a0f5..fec5951 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,6 +97,7 @@
         // AIDL sources from external directories
         ":android.hardware.biometrics.common-V4-java-source",
         ":android.hardware.biometrics.fingerprint-V3-java-source",
+        ":android.hardware.biometrics.face-V4-java-source",
         ":android.hardware.gnss-V2-java-source",
         ":android.hardware.graphics.common-V3-java-source",
         ":android.hardware.keymaster-V4-java-source",
diff --git a/core/java/android/hardware/face/FaceSensorConfigurations.aidl b/core/java/android/hardware/face/FaceSensorConfigurations.aidl
new file mode 100644
index 0000000..26367b3
--- /dev/null
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2023 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 android.hardware.face;
+
+parcelable FaceSensorConfigurations;
\ No newline at end of file
diff --git a/core/java/android/hardware/face/FaceSensorConfigurations.java b/core/java/android/hardware/face/FaceSensorConfigurations.java
new file mode 100644
index 0000000..6ef692f
--- /dev/null
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2023 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 android.hardware.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Provides the sensor props for face sensor, if available.
+ * @hide
+ */
+public class FaceSensorConfigurations implements Parcelable {
+    private static final String TAG = "FaceSensorConfigurations";
+
+    private final boolean mResetLockoutRequiresChallenge;
+    private final Map<String, SensorProps[]> mSensorPropsMap;
+
+    public static final Creator<FaceSensorConfigurations> CREATOR =
+            new Creator<FaceSensorConfigurations>() {
+                @Override
+                public FaceSensorConfigurations createFromParcel(Parcel in) {
+                    return new FaceSensorConfigurations(in);
+                }
+
+                @Override
+                public FaceSensorConfigurations[] newArray(int size) {
+                    return new FaceSensorConfigurations[size];
+                }
+            };
+
+    public FaceSensorConfigurations(boolean resetLockoutRequiresChallenge) {
+        mResetLockoutRequiresChallenge = resetLockoutRequiresChallenge;
+        mSensorPropsMap = new HashMap<>();
+    }
+
+    protected FaceSensorConfigurations(Parcel in) {
+        mResetLockoutRequiresChallenge = in.readByte() != 0;
+        mSensorPropsMap = in.readHashMap(null, String.class, SensorProps[].class);
+    }
+
+    /**
+     * Process AIDL instances to extract sensor props and add it to the sensor map.
+     * @param aidlInstances available face AIDL instances
+     * @param getIFace function that provides the daemon for the specific instance
+     */
+    public void addAidlConfigs(@NonNull String[] aidlInstances,
+            @NonNull Function<String, IFace> getIFace) {
+        for (String aidlInstance : aidlInstances) {
+            final String fqName = IFace.DESCRIPTOR + "/" + aidlInstance;
+            IFace face = getIFace.apply(fqName);
+            try {
+                if (face != null) {
+                    mSensorPropsMap.put(aidlInstance, face.getSensorProps());
+                } else {
+                    Slog.e(TAG, "Unable to get declared service: " + fqName);
+                }
+            } catch (RemoteException e) {
+                Log.d(TAG, "Unable to get sensor properties!");
+            }
+        }
+    }
+
+    /**
+     * Parse through HIDL configuration and add it to the sensor map.
+     */
+    public void addHidlConfigs(@NonNull String[] hidlConfigStrings,
+            @NonNull Context context) {
+        final List<HidlFaceSensorConfig> hidlFaceSensorConfigs = new ArrayList<>();
+        for (String hidlConfig: hidlConfigStrings) {
+            final HidlFaceSensorConfig hidlFaceSensorConfig = new HidlFaceSensorConfig();
+            try {
+                hidlFaceSensorConfig.parse(hidlConfig, context);
+            } catch (Exception e) {
+                Log.e(TAG, "HIDL sensor configuration format is incorrect.");
+                continue;
+            }
+            if (hidlFaceSensorConfig.getModality() == TYPE_FACE) {
+                hidlFaceSensorConfigs.add(hidlFaceSensorConfig);
+            }
+        }
+        final String hidlHalInstanceName = "defaultHIDL";
+        mSensorPropsMap.put(hidlHalInstanceName, hidlFaceSensorConfigs.toArray(
+                new SensorProps[hidlFaceSensorConfigs.size()]));
+    }
+
+    /**
+     * Returns true if any face sensors have been added.
+     */
+    public boolean hasSensorConfigurations() {
+        return mSensorPropsMap.size() > 0;
+    }
+
+    /**
+     * Returns true if there is only a single face sensor configuration available.
+     */
+    public boolean isSingleSensorConfigurationPresent() {
+        return mSensorPropsMap.size() == 1;
+    }
+
+    /**
+     * Return sensor props for the given instance. If instance is not available,
+     * then null is returned.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
+        if (mSensorPropsMap.containsKey(instance)) {
+            return new Pair<>(instance, mSensorPropsMap.get(instance));
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the first pair of instance and sensor props, which does not correspond to the given
+     * If instance is not available, then null is returned.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+        Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
+                (instanceName) -> !instanceName.equals(instance)).findFirst();
+        return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
+                this::getSensorPair);
+    }
+
+    /**
+     * Returns the first pair of instance and sensor props that has been added to the map.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPair() {
+        Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
+        return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
+
+    }
+
+    public boolean getResetLockoutRequiresChallenge() {
+        return mResetLockoutRequiresChallenge;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeByte((byte) (mResetLockoutRequiresChallenge ? 1 : 0));
+        dest.writeMap(mSensorPropsMap);
+    }
+}
diff --git a/core/java/android/hardware/face/HidlFaceSensorConfig.java b/core/java/android/hardware/face/HidlFaceSensorConfig.java
new file mode 100644
index 0000000..cab146d
--- /dev/null
+++ b/core/java/android/hardware/face/HidlFaceSensorConfig.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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 android.hardware.face;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.common.SensorStrength;
+import android.hardware.biometrics.face.SensorProps;
+
+import com.android.internal.R;
+
+/**
+ * Parse HIDL face sensor config and map it to SensorProps.aidl to match AIDL.
+ * See core/res/res/values/config.xml config_biometric_sensors
+ * @hide
+ */
+public final class HidlFaceSensorConfig extends SensorProps {
+    private int mSensorId;
+    private int mModality;
+    private int mStrength;
+
+    /**
+     * Parse through the config string and map it to SensorProps.aidl.
+     * @throws IllegalArgumentException when config string has unexpected format
+     */
+    public void parse(@NonNull String config, @NonNull Context context)
+            throws IllegalArgumentException {
+        final String[] elems = config.split(":");
+        if (elems.length < 3) {
+            throw new IllegalArgumentException();
+        }
+        mSensorId = Integer.parseInt(elems[0]);
+        mModality = Integer.parseInt(elems[1]);
+        mStrength = Integer.parseInt(elems[2]);
+        mapHidlToAidlFaceSensorConfigurations(context);
+    }
+
+    @BiometricAuthenticator.Modality
+    public int getModality() {
+        return mModality;
+    }
+
+    private void mapHidlToAidlFaceSensorConfigurations(@NonNull Context context) {
+        commonProps = new CommonProps();
+        commonProps.sensorId = mSensorId;
+        commonProps.sensorStrength = authenticatorStrengthToPropertyStrength(mStrength);
+        halControlsPreview = context.getResources().getBoolean(
+                R.bool.config_faceAuthSupportsSelfIllumination);
+        commonProps.maxEnrollmentsPerUser = context.getResources().getInteger(
+                R.integer.config_faceMaxTemplatesPerUser);
+        commonProps.componentInfo = null;
+        supportsDetectInteraction = false;
+    }
+
+    private byte authenticatorStrengthToPropertyStrength(
+            @BiometricManager.Authenticators.Types int strength) {
+        switch (strength) {
+            case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
+                return SensorStrength.CONVENIENCE;
+            case BiometricManager.Authenticators.BIOMETRIC_WEAK:
+                return SensorStrength.WEAK;
+            case BiometricManager.Authenticators.BIOMETRIC_STRONG:
+                return SensorStrength.STRONG;
+            default:
+                throw new IllegalArgumentException("Unknown strength: " + strength);
+        }
+    }
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 7080133..0096877 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -26,6 +26,7 @@
 import android.hardware.face.Face;
 import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.FaceSensorConfigurations;
 import android.view.Surface;
 
 /**
@@ -167,6 +168,10 @@
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
 
+    //Register all available face sensors.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void registerAuthenticatorsLegacy(in FaceSensorConfigurations faceSensorConfigurations);
+
     // Adds a callback which gets called when the service registers all of the face
     // authenticators. The callback is automatically removed after it's invoked.
     void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
new file mode 100644
index 0000000..ebb05dc
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2023 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 android.hardware.fingerprint;
+
+parcelable FingerprintSensorConfigurations;
\ No newline at end of file
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
new file mode 100644
index 0000000..f214494a
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 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 android.hardware.fingerprint;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Provides the sensor props for fingerprint sensor, if available.
+ * @hide
+ */
+
+public class FingerprintSensorConfigurations implements Parcelable {
+    private static final String TAG = "FingerprintSensorConfigurations";
+
+    private final Map<String, SensorProps[]> mSensorPropsMap;
+    private final boolean mResetLockoutRequiresHardwareAuthToken;
+
+    public static final Creator<FingerprintSensorConfigurations> CREATOR =
+            new Creator<>() {
+                @Override
+                public FingerprintSensorConfigurations createFromParcel(Parcel in) {
+                    return new FingerprintSensorConfigurations(in);
+                }
+
+                @Override
+                public FingerprintSensorConfigurations[] newArray(int size) {
+                    return new FingerprintSensorConfigurations[size];
+                }
+            };
+
+    public FingerprintSensorConfigurations(boolean resetLockoutRequiresHardwareAuthToken) {
+        mResetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
+        mSensorPropsMap = new HashMap<>();
+    }
+
+    /**
+     * Process AIDL instances to extract sensor props and add it to the sensor map.
+     * @param aidlInstances available face AIDL instances
+     * @param getIFingerprint function that provides the daemon for the specific instance
+     */
+    public void addAidlSensors(@NonNull String[] aidlInstances,
+            @NonNull Function<String, IFingerprint> getIFingerprint) {
+        for (String aidlInstance : aidlInstances) {
+            try {
+                final String fqName = IFingerprint.DESCRIPTOR + "/" + aidlInstance;
+                final IFingerprint fp = getIFingerprint.apply(fqName);
+                if (fp != null) {
+                    SensorProps[] props = fp.getSensorProps();
+                    mSensorPropsMap.put(aidlInstance, props);
+                } else {
+                    Log.d(TAG, "IFingerprint null for instance " + aidlInstance);
+                }
+            } catch (RemoteException e) {
+                Log.d(TAG, "Unable to get sensor properties!");
+            }
+        }
+    }
+
+    /**
+     * Parse through HIDL configuration and add it to the sensor map.
+     */
+    public void addHidlSensors(@NonNull String[] hidlConfigStrings,
+            @NonNull Context context) {
+        final List<HidlFingerprintSensorConfig> hidlFingerprintSensorConfigs = new ArrayList<>();
+        for (String hidlConfigString : hidlConfigStrings) {
+            final HidlFingerprintSensorConfig hidlFingerprintSensorConfig =
+                    new HidlFingerprintSensorConfig();
+            try {
+                hidlFingerprintSensorConfig.parse(hidlConfigString, context);
+            } catch (Exception e) {
+                Log.e(TAG, "HIDL sensor configuration format is incorrect.");
+                continue;
+            }
+            if (hidlFingerprintSensorConfig.getModality() == TYPE_FINGERPRINT) {
+                hidlFingerprintSensorConfigs.add(hidlFingerprintSensorConfig);
+            }
+        }
+        final String hidlHalInstanceName = "defaultHIDL";
+        mSensorPropsMap.put(hidlHalInstanceName,
+                hidlFingerprintSensorConfigs.toArray(
+                        new HidlFingerprintSensorConfig[hidlFingerprintSensorConfigs.size()]));
+    }
+
+    protected FingerprintSensorConfigurations(Parcel in) {
+        mResetLockoutRequiresHardwareAuthToken = in.readByte() != 0;
+        mSensorPropsMap = in.readHashMap(null /* loader */, String.class, SensorProps[].class);
+    }
+
+    /**
+     * Returns true if any fingerprint sensors have been added.
+     */
+    public boolean hasSensorConfigurations() {
+        return mSensorPropsMap.size() > 0;
+    }
+
+    /**
+     * Returns true if there is only a single fingerprint sensor configuration available.
+     */
+    public boolean isSingleSensorConfigurationPresent() {
+        return mSensorPropsMap.size() == 1;
+    }
+
+    /**
+     * Return sensor props for the given instance. If instance is not available,
+     * then null is returned.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
+        if (mSensorPropsMap.containsKey(instance)) {
+            return new Pair<>(instance, mSensorPropsMap.get(instance));
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the first pair of instance and sensor props, which does not correspond to the given
+     * If instance is not available, then null is returned.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+        Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
+                (instanceName) -> !instanceName.equals(instance)).findFirst();
+        return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
+                this::getSensorPair);
+    }
+
+    /**
+     * Returns the first pair of instance and sensor props that has been added to the map.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPair() {
+        Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
+        return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
+
+    }
+
+    public boolean getResetLockoutRequiresHardwareAuthToken() {
+        return mResetLockoutRequiresHardwareAuthToken;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeByte((byte) (mResetLockoutRequiresHardwareAuthToken ? 1 : 0));
+        dest.writeMap(mSensorPropsMap);
+    }
+}
diff --git a/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java
new file mode 100644
index 0000000..d481153
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 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 android.hardware.fingerprint;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.common.SensorStrength;
+import android.hardware.biometrics.fingerprint.FingerprintSensorType;
+import android.hardware.biometrics.fingerprint.SensorLocation;
+import android.hardware.biometrics.fingerprint.SensorProps;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Parse HIDL fingerprint sensor config and map it to SensorProps.aidl to match AIDL.
+ * See core/res/res/values/config.xml config_biometric_sensors
+ * @hide
+ */
+public final class HidlFingerprintSensorConfig extends SensorProps {
+    private int mSensorId;
+    private int mModality;
+    private int mStrength;
+
+    /**
+     * Parse through the config string and map it to SensorProps.aidl.
+     * @throws IllegalArgumentException when config string has unexpected format
+     */
+    public void parse(@NonNull String config, @NonNull Context context)
+            throws IllegalArgumentException {
+        final String[] elems = config.split(":");
+        if (elems.length < 3) {
+            throw new IllegalArgumentException();
+        }
+        mSensorId = Integer.parseInt(elems[0]);
+        mModality = Integer.parseInt(elems[1]);
+        mStrength = Integer.parseInt(elems[2]);
+        mapHidlToAidlSensorConfiguration(context);
+    }
+
+    @BiometricAuthenticator.Modality
+    public int getModality() {
+        return mModality;
+    }
+
+    private void mapHidlToAidlSensorConfiguration(@NonNull Context context) {
+        commonProps = new CommonProps();
+        commonProps.componentInfo = null;
+        commonProps.sensorId = mSensorId;
+        commonProps.sensorStrength = authenticatorStrengthToPropertyStrength(mStrength);
+        commonProps.maxEnrollmentsPerUser = context.getResources().getInteger(
+                R.integer.config_fingerprintMaxTemplatesPerUser);
+        halControlsIllumination = false;
+        sensorLocations = new SensorLocation[1];
+
+        final int[] udfpsProps = context.getResources().getIntArray(
+                com.android.internal.R.array.config_udfps_sensor_props);
+        final boolean isUdfps = !ArrayUtils.isEmpty(udfpsProps);
+        // config_is_powerbutton_fps indicates whether device has a power button fingerprint sensor.
+        final boolean isPowerbuttonFps = context.getResources().getBoolean(
+                R.bool.config_is_powerbutton_fps);
+
+        if (isUdfps) {
+            sensorType = FingerprintSensorType.UNKNOWN;
+        } else if (isPowerbuttonFps) {
+            sensorType = FingerprintSensorType.POWER_BUTTON;
+        } else {
+            sensorType = FingerprintSensorType.REAR;
+        }
+
+        if (isUdfps && udfpsProps.length == 3) {
+            setSensorLocation(udfpsProps[0], udfpsProps[1], udfpsProps[2]);
+        } else {
+            setSensorLocation(540 /* sensorLocationX */, 1636 /* sensorLocationY */,
+                    130 /* sensorRadius */);
+        }
+
+    }
+
+    private void setSensorLocation(int sensorLocationX,
+            int sensorLocationY, int sensorRadius) {
+        sensorLocations[0] = new SensorLocation();
+        sensorLocations[0].display = "";
+        sensorLocations[0].sensorLocationX = sensorLocationX;
+        sensorLocations[0].sensorLocationY = sensorLocationY;
+        sensorLocations[0].sensorRadius = sensorRadius;
+    }
+
+    private byte authenticatorStrengthToPropertyStrength(
+            @BiometricManager.Authenticators.Types int strength) {
+        switch (strength) {
+            case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
+                return SensorStrength.CONVENIENCE;
+            case BiometricManager.Authenticators.BIOMETRIC_WEAK:
+                return SensorStrength.WEAK;
+            case BiometricManager.Authenticators.BIOMETRIC_STRONG:
+                return SensorStrength.STRONG;
+            default:
+                throw new IllegalArgumentException("Unknown strength: " + strength);
+        }
+    }
+}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index f594c00..606b171 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -31,6 +31,7 @@
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
 import java.util.List;
 
 /**
@@ -173,6 +174,10 @@
     @EnforcePermission("MANAGE_FINGERPRINT")
     void removeClientActiveCallback(IFingerprintClientActiveCallback callback);
 
+    //Register all available fingerprint sensors.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void registerAuthenticatorsLegacy(in FingerprintSensorConfigurations fingerprintSensorConfigurations);
+
     // Registers all HIDL and AIDL sensors. Only HIDL sensor properties need to be provided, because
     // AIDL sensor properties are retrieved directly from the available HALs. If no HIDL HALs exist,
     // hidlSensors must be non-null and empty. See AuthService.java
diff --git a/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
new file mode 100644
index 0000000..da3a465
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 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 android.hardware.face;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Function;
+
+@Presubmit
+@SmallTest
+public class FaceSensorConfigurationsTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private IFace mFace;
+    @Mock
+    private Function<String, IFace> mGetIFace;
+
+    private final String[] mAidlInstances = new String[]{"default", "virtual"};
+    private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
+    private FaceSensorConfigurations mFaceSensorConfigurations;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mGetIFace.apply(anyString())).thenReturn(mFace);
+        when(mFace.getSensorProps()).thenReturn(new SensorProps[]{});
+        when(mContext.getResources()).thenReturn(mResources);
+    }
+
+    @Test
+    public void testAidlInstanceSensorProps() {
+        mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+        mFaceSensorConfigurations.addAidlConfigs(mAidlInstances, mGetIFace);
+
+        assertThat(mFaceSensorConfigurations.hasSensorConfigurations()).isTrue();
+        assertThat(!mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+        assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+                .isFalse();
+    }
+
+    @Test
+    public void testHidlConfigStrings() {
+        mFaceSensorConfigurations = new FaceSensorConfigurations(true);
+        mFaceSensorConfigurations.addHidlConfigs(mHidlConfigStrings, mContext);
+
+        assertThat(mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+        assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+                .isTrue();
+    }
+
+    @Test
+    public void testHidlConfigStrings_incorrectFormat() {
+        mHidlConfigStrings = new String[]{"0:8:15", "0:2", "0:face:15"};
+        mFaceSensorConfigurations = new FaceSensorConfigurations(true);
+        mFaceSensorConfigurations.addHidlConfigs(mHidlConfigStrings, mContext);
+
+        assertThat(mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+        assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+                .isTrue();
+    }
+}
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
new file mode 100644
index 0000000..613089c
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 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 android.hardware.fingerprint;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Function;
+
+
+@Presubmit
+@SmallTest
+public class FingerprintSensorConfigurationsTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private IFingerprint mFingerprint;
+    @Mock
+    private Function<String, IFingerprint> mGetIFingerprint;
+
+    private final String[] mAidlInstances = new String[]{"default", "virtual"};
+    private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
+    private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mGetIFingerprint.apply(anyString())).thenReturn(mFingerprint);
+        when(mFingerprint.getSensorProps()).thenReturn(new SensorProps[]{});
+        when(mContext.getResources()).thenReturn(mResources);
+    }
+
+    @Test
+    public void testAidlInstanceSensorProps() {
+        mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+                true /* resetLockoutRequiresHardwareAuthToken */);
+        mFingerprintSensorConfigurations.addAidlSensors(mAidlInstances, mGetIFingerprint);
+
+        assertThat(mFingerprintSensorConfigurations.hasSensorConfigurations()).isTrue();
+        assertThat(!mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent())
+                .isTrue();
+        assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+                .isTrue();
+    }
+
+    @Test
+    public void testHidlConfigStrings() {
+        mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+                false /* resetLockoutRequiresHardwareAuthToken */);
+        mFingerprintSensorConfigurations.addHidlSensors(mHidlConfigStrings, mContext);
+
+        assertThat(mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+        assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+                .isFalse();
+    }
+
+    @Test
+    public void testHidlConfigStrings_incorrectFormat() {
+        mHidlConfigStrings = new String[]{"0:8:15", "0:2", "0:fingerprint:15"};
+        mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+                false /* resetLockoutRequiresHardwareAuthToken */);
+        mFingerprintSensorConfigurations.addHidlSensors(mHidlConfigStrings, mContext);
+
+        assertThat(mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+        assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+                .isFalse();
+    }
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index f5a80d8..258e9b4 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -103,7 +103,6 @@
         "android.hardware.power-java_static",
     ],
     srcs: [
-        ":android.hardware.biometrics.face-V4-java-source",
         ":android.hardware.tv.hdmi.connection-V1-java-source",
         ":android.hardware.tv.hdmi.earc-V1-java-source",
         ":statslog-art-java-gen",
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index dafea9a..d5d8fd2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -51,9 +51,13 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.face.FaceSensorConfigurations;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintService;
@@ -122,8 +126,6 @@
 
         /**
          * Allows to test with various device sensor configurations.
-         * @param context
-         * @return
          */
         @VisibleForTesting
         public String[] getConfiguration(Context context) {
@@ -131,6 +133,30 @@
         }
 
         /**
+         * Allows to test with various device sensor configurations.
+         */
+        @VisibleForTesting
+        public String[] getFingerprintConfiguration(Context context) {
+            return getConfiguration(context);
+        }
+
+        /**
+         * Allows to test with various device sensor configurations.
+         */
+        @VisibleForTesting
+        public String[] getFaceConfiguration(Context context) {
+            return getConfiguration(context);
+        }
+
+        /**
+         * Allows to test with various device sensor configurations.
+         */
+        @VisibleForTesting
+        public String[] getIrisConfiguration(Context context) {
+            return getConfiguration(context);
+        }
+
+        /**
          * Allows us to mock FingerprintService for testing
          */
         @VisibleForTesting
@@ -173,6 +199,22 @@
             }
             return false;
         }
+
+        /**
+         * Allows to test with various fingerprint aidl instances.
+         */
+        @VisibleForTesting
+        public String[] getFingerprintAidlInstances() {
+            return ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+        }
+
+        /**
+         * Allows to test with various face aidl instances.
+         */
+        @VisibleForTesting
+        public String[] getFaceAidlInstances() {
+            return ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+        }
     }
 
     private final class AuthServiceImpl extends IAuthService.Stub {
@@ -717,12 +759,129 @@
             hidlConfigs = null;
         }
 
-        // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
-        registerAuthenticators(hidlConfigs);
+        if (com.android.server.biometrics.Flags.deHidl()) {
+            Slog.d(TAG, "deHidl flag is on.");
+            registerAuthenticators();
+        } else {
+            // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
+            registerAuthenticators(hidlConfigs);
+        }
 
         mInjector.publishBinderService(this, mImpl);
     }
 
+    private void registerAuthenticators() {
+        registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
+                mInjector.getFingerprintConfiguration(getContext()));
+        registerFaceSensors(mInjector.getFaceAidlInstances(),
+                mInjector.getFaceConfiguration(getContext()));
+        registerIrisSensors(mInjector.getIrisConfiguration(getContext()));
+    }
+
+    private void registerIrisSensors(String[] hidlConfigStrings) {
+        final SensorConfig[] hidlConfigs;
+        if (!mInjector.isHidlDisabled(getContext())) {
+            final int firstApiLevel = SystemProperties.getInt(SYSPROP_FIRST_API_LEVEL, 0);
+            final int apiLevel = SystemProperties.getInt(SYSPROP_API_LEVEL, firstApiLevel);
+            if (hidlConfigStrings.length == 0 && apiLevel == Build.VERSION_CODES.R) {
+                // For backwards compatibility with R where biometrics could work without being
+                // configured in config_biometric_sensors. In the absence of a vendor provided
+                // configuration, we assume the weakest biometric strength (i.e. convenience).
+                Slog.w(TAG, "Found R vendor partition without config_biometric_sensors");
+                hidlConfigStrings = generateRSdkCompatibleConfiguration();
+            }
+            hidlConfigs = new SensorConfig[hidlConfigStrings.length];
+            for (int i = 0; i < hidlConfigStrings.length; ++i) {
+                hidlConfigs[i] = new SensorConfig(hidlConfigStrings[i]);
+            }
+        } else {
+            hidlConfigs = null;
+        }
+
+        final List<SensorPropertiesInternal> hidlIrisSensors = new ArrayList<>();
+
+        if (hidlConfigs != null) {
+            for (SensorConfig sensor : hidlConfigs) {
+                switch (sensor.modality) {
+                    case TYPE_IRIS:
+                        hidlIrisSensors.add(getHidlIrisSensorProps(sensor.id, sensor.strength));
+                        break;
+
+                    default:
+                        Slog.e(TAG, "Unknown modality: " + sensor.modality);
+                }
+            }
+        }
+
+        final IIrisService irisService = mInjector.getIrisService();
+        if (irisService != null) {
+            try {
+                irisService.registerAuthenticators(hidlIrisSensors);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException when registering iris authenticators", e);
+            }
+        } else if (hidlIrisSensors.size() > 0) {
+            Slog.e(TAG, "HIDL iris configuration exists, but IrisService is null.");
+        }
+    }
+
+    private void registerFaceSensors(final String[] faceAidlInstances,
+            final String[] hidlConfigStrings) {
+        final FaceSensorConfigurations mFaceSensorConfigurations =
+                new FaceSensorConfigurations(hidlConfigStrings != null
+                        && hidlConfigStrings.length > 0);
+
+        if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+            mFaceSensorConfigurations.addHidlConfigs(
+                    hidlConfigStrings, getContext());
+        }
+
+        if (faceAidlInstances != null && faceAidlInstances.length > 0) {
+            mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
+                    name -> IFace.Stub.asInterface(Binder.allowBlocking(
+                            ServiceManager.waitForDeclaredService(name))));
+        }
+
+        final IFaceService faceService = mInjector.getFaceService();
+        if (faceService != null) {
+            try {
+                faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException when registering face authenticators", e);
+            }
+        }  else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
+            Slog.e(TAG, "Face configuration exists, but FaceService is null.");
+        }
+    }
+
+    private void registerFingerprintSensors(final String[] fingerprintAidlInstances,
+            final String[] hidlConfigStrings) {
+        final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
+                new FingerprintSensorConfigurations(!(hidlConfigStrings != null
+                        && hidlConfigStrings.length > 0));
+
+        if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+            mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, getContext());
+        }
+
+        if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
+            mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
+                    name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
+                            ServiceManager.waitForDeclaredService(name))));
+        }
+
+        final IFingerprintService fingerprintService = mInjector.getFingerprintService();
+        if (fingerprintService != null) {
+            try {
+                fingerprintService.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+            }
+        }  else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
+            Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
+        }
+    }
+
     /**
      * Generates an array of string configs with entries that correspond to the biometric features
      * declared on the device. Returns an empty array if no biometric features are declared.
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 037ea38a..89b638b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -202,7 +202,7 @@
     };
 
     @VisibleForTesting
-    BiometricScheduler(@NonNull String tag,
+    public BiometricScheduler(@NonNull String tag,
             @NonNull Handler handler,
             @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index 57feedc..0c3dfa7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -387,6 +387,11 @@
         return isAuthentication || isDetection;
     }
 
+    /** If this operation is {@link StartUserClient}. */
+    public boolean isStartUserOperation() {
+        return mClientMonitor instanceof StartUserClient<?, ?>;
+    }
+
     /** If this operation performs acquisition {@link AcquisitionClient}. */
     public boolean isAcquisitionOperation() {
         return mClientMonitor instanceof AcquisitionClient;
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 7f8f38f..6daaad1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -54,8 +54,6 @@
         // is all done internally.
         super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
                 0 /* cookie */, sensorId, logger, biometricContext);
-        //, BiometricsProtoEnums.ACTION_ENUMERATE,
-          //      BiometricsProtoEnums.CLIENT_UNKNOWN);
         mEnrolledList = enrolledList;
         mUtils = utils;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index 8075470..3753bbd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -135,7 +135,7 @@
         final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
         final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
 
-        if (nextUserId == currentUserId) {
+        if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
             super.startNextOperationIfIdle();
         } else if (currentUserId == UserHandle.USER_NULL) {
             final BaseClientMonitor startClient =
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 578d9dc..6af223b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -36,6 +36,7 @@
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.Face;
 import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceSensorConfigurations;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.FaceServiceReceiver;
 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
@@ -55,6 +56,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Surface;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
@@ -76,6 +78,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * A service to manage multiple clients that want to access the face HAL API.
@@ -86,7 +89,7 @@
 
     protected static final String TAG = "FaceService";
 
-    private final FaceServiceWrapper mServiceWrapper;
+    @VisibleForTesting final FaceServiceWrapper mServiceWrapper;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final LockPatternUtils mLockPatternUtils;
     @NonNull
@@ -94,11 +97,18 @@
     @NonNull
     private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
             mBiometricStateCallback;
+    @NonNull
+    private final FaceProviderFunction mFaceProviderFunction;
+
+    interface FaceProviderFunction {
+        FaceProvider getFaceProvider(Pair<String, SensorProps[]> filteredSensorProps,
+                boolean resetLockoutRequiresChallenge);
+    }
 
     /**
      * Receives the incoming binder calls from FaceManager.
      */
-    private final class FaceServiceWrapper extends IFaceService.Stub {
+    @VisibleForTesting final class FaceServiceWrapper extends IFaceService.Stub {
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@@ -672,7 +682,8 @@
                     final SensorProps[] props = face.getSensorProps();
                     final FaceProvider provider = new FaceProvider(getContext(),
                             mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
-                            BiometricContext.getInstance(getContext()));
+                            BiometricContext.getInstance(getContext()),
+                            false /* resetLockoutRequiresChallenge */);
                     providers.add(provider);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -704,6 +715,55 @@
             });
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        public void registerAuthenticatorsLegacy(
+                FaceSensorConfigurations faceSensorConfigurations) {
+            super.registerAuthenticatorsLegacy_enforcePermission();
+
+            if (!faceSensorConfigurations.hasSensorConfigurations()) {
+                Slog.d(TAG, "No face sensors to register.");
+                return;
+            }
+            mRegistry.registerAll(() -> getProviders(faceSensorConfigurations));
+        }
+
+        private List<ServiceProvider> getProviders(
+                FaceSensorConfigurations faceSensorConfigurations) {
+            final List<ServiceProvider> providers = new ArrayList<>();
+            final Pair<String, SensorProps[]> filteredSensorProps =
+                    filterAvailableHalInstances(faceSensorConfigurations);
+            providers.add(mFaceProviderFunction.getFaceProvider(filteredSensorProps,
+                    faceSensorConfigurations.getResetLockoutRequiresChallenge()));
+            return providers;
+        }
+
+        @NonNull
+        private Pair<String, SensorProps[]> filterAvailableHalInstances(
+                FaceSensorConfigurations faceSensorConfigurations) {
+            Pair<String, SensorProps[]> finalSensorPair = faceSensorConfigurations.getSensorPair();
+
+            if (faceSensorConfigurations.isSingleSensorConfigurationPresent()) {
+                return finalSensorPair;
+            }
+
+            final Pair<String, SensorProps[]> virtualSensorProps = faceSensorConfigurations
+                    .getSensorPairForInstance("virtual");
+
+            if (Utils.isVirtualEnabled(getContext())) {
+                if (virtualSensorProps != null) {
+                    return virtualSensorProps;
+                } else {
+                    Slog.e(TAG, "Could not find virtual interface while it is enabled");
+                    return finalSensorPair;
+                }
+            } else {
+                if (virtualSensorProps != null) {
+                    return faceSensorConfigurations.getSensorPairNotForInstance("virtual");
+                }
+            }
+            return finalSensorPair;
+        }
+
         private Pair<List<FaceSensorPropertiesInternal>, List<String>>
                 filterAvailableHalInstances(
                 @NonNull List<FaceSensorPropertiesInternal> hidlInstances,
@@ -752,20 +812,36 @@
     }
 
     public FaceService(Context context) {
+        this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
+                ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+    }
+
+    @VisibleForTesting FaceService(Context context, FaceProviderFunction faceProviderFunction,
+            Supplier<IBiometricService> biometricServiceSupplier) {
         super(context);
         mServiceWrapper = new FaceServiceWrapper();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
-        mRegistry = new FaceServiceRegistry(mServiceWrapper,
-                () -> IBiometricService.Stub.asInterface(
-                        ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+        mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier);
         mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
             @Override
             public void onAllAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
                 mBiometricStateCallback.start(mRegistry.getProviders());
             }
         });
+
+        if (Flags.deHidl()) {
+            mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
+                    ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
+                            getContext(), mBiometricStateCallback,
+                            filteredSensorProps.second,
+                            filteredSensorProps.first, mLockoutResetDispatcher,
+                            BiometricContext.getInstance(getContext()),
+                            resetLockoutRequiresChallenge));
+        } else {
+            mFaceProviderFunction = ((filteredSensorProps, resetLockoutRequiresChallenge) -> null);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
index e5d4a63..ef2be79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
@@ -24,7 +24,8 @@
  * the user changes.
  */
 public class LockoutHalImpl implements LockoutTracker {
-    private @LockoutMode int mCurrentUserLockoutMode;
+    @LockoutMode
+    private int mCurrentUserLockoutMode;
 
     @Override
     public int getLockoutModeForUser(int userId) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
index 57f5b34..098be21 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -27,6 +27,7 @@
 import android.hardware.keymaster.HardwareAuthToken;
 import android.util.Slog;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -59,6 +60,20 @@
         void onHardwareUnavailable();
     }
 
+    /**
+     * Interface to send results to the AidlResponseHandler's owner.
+     */
+    public interface AidlResponseHandlerCallback {
+        /**
+         * Invoked when enrollment is successful.
+         */
+        void onEnrollSuccess();
+        /**
+         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+         */
+        void onHardwareUnavailable();
+    }
+
     private static final String TAG = "AidlResponseHandler";
 
     @NonNull
@@ -68,7 +83,7 @@
     private final int mSensorId;
     private final int mUserId;
     @NonNull
-    private final LockoutTracker mLockoutCache;
+    private final LockoutTracker mLockoutTracker;
     @NonNull
     private final LockoutResetDispatcher mLockoutResetDispatcher;
 
@@ -76,6 +91,8 @@
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @NonNull
     private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+    @NonNull
+    private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
 
     public AidlResponseHandler(@NonNull Context context,
             @NonNull BiometricScheduler scheduler, int sensorId, int userId,
@@ -83,14 +100,33 @@
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+        this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
+                authSessionCoordinator, hardwareUnavailableCallback,
+                new AidlResponseHandlerCallback() {
+                    @Override
+                    public void onEnrollSuccess() {}
+
+                    @Override
+                    public void onHardwareUnavailable() {}
+                });
+    }
+
+    public AidlResponseHandler(@NonNull Context context,
+            @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+            @NonNull LockoutTracker lockoutTracker,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
+            @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
         mContext = context;
         mScheduler = scheduler;
         mSensorId = sensorId;
         mUserId = userId;
-        mLockoutCache = lockoutTracker;
+        mLockoutTracker = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mAuthSessionCoordinator = authSessionCoordinator;
         mHardwareUnavailableCallback = hardwareUnavailableCallback;
+        mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
     }
 
     @Override
@@ -106,13 +142,13 @@
     @Override
     public void onChallengeGenerated(long challenge) {
         handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId,
-                mUserId, challenge), null);
+                mUserId, challenge));
     }
 
     @Override
     public void onChallengeRevoked(long challenge) {
         handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId,
-                mUserId, challenge), null);
+                mUserId, challenge));
     }
 
     @Override
@@ -123,7 +159,7 @@
                 return;
             }
             c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
-        }, null);
+        });
     }
 
     @Override
@@ -134,7 +170,7 @@
                 return;
             }
             c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
-        }, null);
+        });
     }
 
     @Override
@@ -149,9 +185,13 @@
         handleResponse(ErrorConsumer.class, (c) -> {
             c.onError(error, vendorCode);
             if (error == Error.HW_UNAVAILABLE) {
-                mHardwareUnavailableCallback.onHardwareUnavailable();
+                if (Flags.deHidl()) {
+                    mAidlResponseHandlerCallback.onHardwareUnavailable();
+                } else {
+                    mHardwareUnavailableCallback.onHardwareUnavailable();
+                }
             }
-        }, null);
+        });
     }
 
     @Override
@@ -167,7 +207,12 @@
                 .getUniqueName(mContext, currentUserId);
         final Face face = new Face(name, enrollmentId, mSensorId);
 
-        handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null);
+        handleResponse(FaceEnrollClient.class, (c) -> {
+            c.onEnrollResult(face, remaining);
+            if (remaining == 0) {
+                mAidlResponseHandlerCallback.onEnrollSuccess();
+            }
+        });
     }
 
     @Override
@@ -179,37 +224,37 @@
             byteList.add(b);
         }
         handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
-                true /* authenticated */, byteList), null);
+                true /* authenticated */, byteList));
     }
 
     @Override
     public void onAuthenticationFailed() {
         final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
         handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
-                false /* authenticated */, null /* hat */), null);
+                false /* authenticated */, null /* hat */));
     }
 
     @Override
     public void onLockoutTimed(long durationMillis) {
-        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null);
+        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis));
     }
 
     @Override
     public void onLockoutPermanent() {
-        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent);
     }
 
     @Override
     public void onLockoutCleared() {
         handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared,
                 (c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
-                mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
+                        mLockoutTracker, mLockoutResetDispatcher, mAuthSessionCoordinator,
+                        Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
     }
 
     @Override
     public void onInteractionDetected() {
-        handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null);
+        handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected);
     }
 
     @Override
@@ -219,23 +264,23 @@
                 final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
                 final int finalI = i;
                 handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face,
-                        enrollmentIds.length - finalI - 1), null);
+                        enrollmentIds.length - finalI - 1 /* remaining */));
             }
         } else {
             handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
-                    null /* identifier */, 0 /* remaining */), null);
+                    null /* identifier */, 0 /* remaining */));
         }
     }
 
     @Override
     public void onFeaturesRetrieved(byte[] features) {
         handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */,
-                features), null);
+                features));
     }
 
     @Override
     public void onFeatureSet(byte feature) {
-        handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null);
+        handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */));
     }
 
     @Override
@@ -245,33 +290,32 @@
                 final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
                 final int finalI = i;
                 handleResponse(RemovalConsumer.class,
-                        (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1),
-                        null);
+                        (c) -> c.onRemoved(face,
+                                enrollmentIds.length - finalI - 1 /* remaining */));
             }
         } else {
             handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
-                    0 /* remaining */), null);
+                    0 /* remaining */));
         }
     }
 
     @Override
     public void onAuthenticatorIdRetrieved(long authenticatorId) {
         handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved(
-                authenticatorId), null);
+                authenticatorId));
     }
 
     @Override
     public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
         handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
-                newAuthenticatorId), null);
+                newAuthenticatorId));
     }
 
     /**
      * Handles acquired messages sent by the HAL (specifically for HIDL HAL).
      */
     public void onAcquired(int acquiredInfo, int vendorCode) {
-        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
-                null);
+        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode));
     }
 
     /**
@@ -288,7 +332,7 @@
                 lockoutMode = LockoutTracker.LOCKOUT_TIMED;
             }
 
-            mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode);
+            mLockoutTracker.setLockoutModeForUser(mUserId, lockoutMode);
 
             if (duration == 0) {
                 mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
@@ -296,6 +340,20 @@
         });
     }
 
+    /**
+     * Handle clients which are not supported in HIDL HAL. For face, FaceInvalidationClient
+     * is the only AIDL client which is not supported in HIDL.
+     */
+    public void onUnsupportedClientScheduled() {
+        Slog.e(TAG, "FaceInvalidationClient is not supported in the HAL.");
+        handleResponse(FaceInvalidationClient.class, BaseClientMonitor::cancel);
+    }
+
+    private <T> void handleResponse(@NonNull Class<T> className,
+            @NonNull Consumer<T> action) {
+        handleResponse(className, action, null /* alternateAction */);
+    }
+
     private <T> void handleResponse(@NonNull Class<T> className,
             @NonNull Consumer<T> actionIfClassMatchesClient,
             @Nullable Consumer<BaseClientMonitor> alternateAction) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index e70e25a..af46f44 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -21,7 +21,7 @@
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 
-import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
 
 import java.util.function.Supplier;
 
@@ -47,7 +47,7 @@
 
     public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId,
             AidlResponseHandler aidlResponseHandler) {
-        mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler);
+        mSession = new HidlToAidlSessionAdapter(context, session, userId, aidlResponseHandler);
         mHalInterfaceVersion = 0;
         mUserId = userId;
         mAidlResponseHandler = aidlResponseHandler;
@@ -64,7 +64,7 @@
     }
 
     /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
-    AidlResponseHandler getHalSessionCallback() {
+    public AidlResponseHandler getHalSessionCallback() {
         return mAidlResponseHandler;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index c15049b..c41b706 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -34,7 +34,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -75,8 +75,8 @@
     protected void startHalOperation() {
         try {
             ISession session = getFreshDaemon().getSession();
-            if (session instanceof AidlToHidlAdapter) {
-                ((AidlToHidlAdapter) session).setFeature(mFeature);
+            if (session instanceof HidlToAidlSessionAdapter) {
+                ((HidlToAidlSessionAdapter) session).setFeature(mFeature);
             }
             session.getFeatures();
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index dd9c6d5..9fa15b8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -24,6 +24,7 @@
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IInvalidationCallback;
@@ -51,6 +52,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -64,11 +66,13 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.InvalidationRequesterClient;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorList;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 import com.android.server.biometrics.sensors.face.ServiceProvider;
 import com.android.server.biometrics.sensors.face.UsageStats;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSensorAdapter;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -152,9 +156,11 @@
             @NonNull SensorProps[] props,
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull BiometricContext biometricContext) {
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresChallenge) {
         this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                biometricContext, null /* daemon */);
+                biometricContext, null /* daemon */, resetLockoutRequiresChallenge,
+                false /* testHalEnabled */);
     }
 
     @VisibleForTesting FaceProvider(@NonNull Context context,
@@ -163,7 +169,8 @@
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext,
-            IFace daemon) {
+            @Nullable IFace daemon, boolean resetLockoutRequiresChallenge,
+            boolean testHalEnabled) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
         mHalInstanceName = halInstanceName;
@@ -176,48 +183,118 @@
         mBiometricContext = biometricContext;
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
+        mTestHalEnabled = testHalEnabled;
 
-        AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
-                new AuthenticationStatsBroadcastReceiver(
-                        mContext,
-                        BiometricsProtoEnums.MODALITY_FACE,
-                        (AuthenticationStatsCollector collector) -> {
-                            Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
-                            mAuthenticationStatsCollector = collector;
-                        });
+        initAuthenticationBroadcastReceiver();
+        initSensors(resetLockoutRequiresChallenge, props);
+    }
 
-        for (SensorProps prop : props) {
-            final int sensorId = prop.commonProps.sensorId;
+    private void initAuthenticationBroadcastReceiver() {
+        new AuthenticationStatsBroadcastReceiver(
+                mContext,
+                BiometricsProtoEnums.MODALITY_FACE,
+                (AuthenticationStatsCollector collector) -> {
+                    Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
+                    mAuthenticationStatsCollector = collector;
+                });
+    }
 
-            final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-            if (prop.commonProps.componentInfo != null) {
-                for (ComponentInfo info : prop.commonProps.componentInfo) {
-                    componentInfo.add(new ComponentInfoInternal(info.componentId,
-                            info.hardwareVersion, info.firmwareVersion, info.serialNumber,
-                            info.softwareVersion));
+    private void initSensors(boolean resetLockoutRequiresChallenge, SensorProps[] props) {
+        if (Flags.deHidl()) {
+            if (resetLockoutRequiresChallenge) {
+                Slog.d(getTag(), "Adding HIDL configs");
+                for (SensorProps prop : props) {
+                    addHidlSensors(prop, resetLockoutRequiresChallenge);
+                }
+            } else {
+                Slog.d(getTag(), "Adding AIDL configs");
+                for (SensorProps prop : props) {
+                    addAidlSensors(prop, resetLockoutRequiresChallenge);
                 }
             }
+        } else {
+            for (SensorProps prop : props) {
+                final int sensorId = prop.commonProps.sensorId;
 
-            final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
-                    prop.commonProps.sensorId, prop.commonProps.sensorStrength,
-                    prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
-                    prop.supportsDetectInteraction, prop.halControlsPreview,
-                    false /* resetLockoutRequiresChallenge */);
-            final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
-                    internalProp, lockoutResetDispatcher, mBiometricContext);
-            final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
-                    sensor.getLazySession().get().getUserId();
-            mFaceSensors.addSensor(sensorId, sensor, userId,
-                    new SynchronousUserSwitchObserver() {
-                        @Override
-                        public void onUserSwitching(int newUserId) {
-                            scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
-                        }
-                    });
-            Slog.d(getTag(), "Added: " + internalProp);
+                final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+                if (prop.commonProps.componentInfo != null) {
+                    for (ComponentInfo info : prop.commonProps.componentInfo) {
+                        componentInfo.add(new ComponentInfoInternal(info.componentId,
+                                info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+                                info.softwareVersion));
+                    }
+                }
+
+                final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+                        prop.commonProps.sensorId, prop.commonProps.sensorStrength,
+                        prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
+                        prop.supportsDetectInteraction, prop.halControlsPreview,
+                        false /* resetLockoutRequiresChallenge */);
+                final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this,
+                        mContext, mHandler, internalProp, mLockoutResetDispatcher,
+                        mBiometricContext);
+                sensor.init(mLockoutResetDispatcher, this);
+                final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                        sensor.getLazySession().get().getUserId();
+                mFaceSensors.addSensor(sensorId, sensor, userId,
+                        new SynchronousUserSwitchObserver() {
+                            @Override
+                            public void onUserSwitching(int newUserId) {
+                                scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                            }
+                        });
+                Slog.d(getTag(), "Added: " + internalProp);
+            }
         }
     }
 
+    private void addHidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
+        final int sensorId = prop.commonProps.sensorId;
+        final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/" + sensorId, this,
+                mContext, mHandler, prop, mLockoutResetDispatcher,
+                mBiometricContext, resetLockoutRequiresChallenge,
+                () -> {
+                    //TODO: update to make this testable
+                    scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+                            null /* callback */);
+                    scheduleGetFeature(sensorId, new Binder(), ActivityManager.getCurrentUser(),
+                            BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null,
+                            mContext.getOpPackageName());
+                });
+        sensor.init(mLockoutResetDispatcher, this);
+        final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                sensor.getLazySession().get().getUserId();
+        mFaceSensors.addSensor(sensorId, sensor, userId,
+                new SynchronousUserSwitchObserver() {
+                    @Override
+                    public void onUserSwitching(int newUserId) {
+                        scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                        scheduleGetFeature(sensorId, new Binder(), newUserId,
+                                BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION,
+                                null, mContext.getOpPackageName());
+                    }
+                });
+        Slog.d(getTag(), "Added: " + mFaceSensors.get(sensorId));
+    }
+
+    private void addAidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
+        final int sensorId = prop.commonProps.sensorId;
+        final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
+                mHandler, prop, mLockoutResetDispatcher, mBiometricContext,
+                resetLockoutRequiresChallenge);
+        sensor.init(mLockoutResetDispatcher, this);
+        final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                sensor.getLazySession().get().getUserId();
+        mFaceSensors.addSensor(sensorId, sensor, userId,
+                new SynchronousUserSwitchObserver() {
+                    @Override
+                    public void onUserSwitching(int newUserId) {
+                        scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                    }
+                });
+        Slog.d(getTag(), "Added: " + mFaceSensors.get(sensorId));
+    }
+
     private String getTag() {
         return "FaceProvider/" + mHalInstanceName;
     }
@@ -290,7 +367,10 @@
         }
     }
 
-    private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
+    /**
+     * Schedules FaceGetAuthenticatorIdClient for specific sensor and user.
+     */
+    protected void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
         mHandler.post(() -> {
             final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
                     mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
@@ -365,8 +445,12 @@
 
     @Override
     public int getLockoutModeForUser(int sensorId, int userId) {
-        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
-                Utils.getCurrentStrength(sensorId));
+        if (Flags.deHidl()) {
+            return mFaceSensors.get(sensorId).getLockoutModeForUser(userId);
+        } else {
+            return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                    Utils.getCurrentStrength(sensorId));
+        }
     }
 
     @Override
@@ -376,13 +460,18 @@
 
     @Override
     public boolean isHardwareDetected(int sensorId) {
-        return hasHalInstance();
+        if (Flags.deHidl()) {
+            return mFaceSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
+        } else {
+            return hasHalInstance();
+        }
     }
 
     @Override
     public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull IFaceServiceReceiver receiver, String opPackageName) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
@@ -416,6 +505,7 @@
             @Nullable Surface previewSurface, boolean debugConsent) {
         final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final int maxTemplatesPerUser = mFaceSensors.get(
                     sensorId).getSensorProperties().maxEnrollmentsPerUser;
             final FaceEnrollClient client = new FaceEnrollClient(mContext,
@@ -427,18 +517,23 @@
                             BiometricsProtoEnums.CLIENT_UNKNOWN,
                             mAuthenticationStatsCollector),
                     mBiometricContext, maxTemplatesPerUser, debugConsent);
-            scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
-                            mBiometricStateCallback, new ClientMonitorCallback() {
-                        @Override
-                        public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                                boolean success) {
-                            ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
-                            if (success) {
-                                scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
-                                scheduleInvalidationRequest(sensorId, userId);
+            if (Flags.deHidl()) {
+                scheduleForSensor(sensorId, client, mBiometricStateCallback);
+            } else {
+                scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+                        mBiometricStateCallback, new ClientMonitorCallback() {
+                            @Override
+                            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                                    boolean success) {
+                                ClientMonitorCallback.super.onClientFinished(clientMonitor,
+                                        success);
+                                if (success) {
+                                    scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+                                    scheduleInvalidationRequest(sensorId, userId);
+                                }
                             }
-                        }
-                    }));
+                        }));
+            }
         });
         return id;
     }
@@ -486,6 +581,13 @@
             final int userId = options.getUserId();
             final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
+            final LockoutTracker lockoutTracker;
+            if (Flags.deHidl()) {
+                lockoutTracker = mFaceSensors.get(sensorId).getLockoutTracker(true /* forAuth */);
+            } else {
+                lockoutTracker = null;
+            }
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
                     mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
@@ -493,7 +595,7 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                             mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
-                    mUsageStats, null /* lockoutTracker */,
+                    mUsageStats, lockoutTracker,
                     allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
@@ -555,6 +657,7 @@
     private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds,
             int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final FaceRemovalClient client = new FaceRemovalClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), faceIds, userId,
@@ -571,6 +674,7 @@
     @Override
     public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final FaceResetLockoutClient client = new FaceResetLockoutClient(
                     mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
@@ -578,8 +682,8 @@
                             BiometricsProtoEnums.CLIENT_UNKNOWN,
                             mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken,
-                    mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
-                    Utils.getCurrentStrength(sensorId));
+                    mFaceSensors.get(sensorId).getLockoutTracker(false/* forAuth */),
+                    mLockoutResetDispatcher, Utils.getCurrentStrength(sensorId));
 
             scheduleForSensor(sensorId, client);
         });
@@ -590,6 +694,7 @@
             boolean enabled, @NonNull byte[] hardwareAuthToken,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final List<Face> faces = FaceUtils.getInstance(sensorId)
                     .getBiometricsForUser(mContext, userId);
             if (faces.isEmpty()) {
@@ -610,6 +715,7 @@
     public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final List<Face> faces = FaceUtils.getInstance(sensorId)
                     .getBiometricsForUser(mContext, userId);
             if (faces.isEmpty()) {
@@ -641,6 +747,7 @@
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final FaceInternalCleanupClient client =
                     new FaceInternalCleanupClient(mContext,
                             mFaceSensors.get(sensorId).getLazySession(), userId,
@@ -760,4 +867,8 @@
         }
         biometricScheduler.startWatchdog();
     }
+
+    public boolean getTestHalEnabled() {
+        return mTestHalEnabled;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 77b5592..d02eefa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -34,6 +35,7 @@
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
 
 import java.util.function.Supplier;
 
@@ -47,7 +49,7 @@
     private static final String TAG = "FaceResetLockoutClient";
 
     private final HardwareAuthToken mHardwareAuthToken;
-    private final LockoutTracker mLockoutCache;
+    private final LockoutTracker mLockoutTracker;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final int mBiometricStrength;
 
@@ -60,7 +62,7 @@
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
                 0 /* cookie */, sensorId, logger, biometricContext);
         mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
-        mLockoutCache = lockoutTracker;
+        mLockoutTracker = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mBiometricStrength = biometricStrength;
     }
@@ -79,7 +81,11 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().getSession().resetLockout(mHardwareAuthToken);
+            final ISession session = getFreshDaemon().getSession();
+            session.resetLockout(mHardwareAuthToken);
+            if (session instanceof HidlToAidlSessionAdapter) {
+                mCallback.onClientFinished(this, true /* success */);
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to reset lockout", e);
             mCallback.onClientFinished(this, false /* success */);
@@ -87,7 +93,7 @@
     }
 
     void onLockoutCleared() {
-        resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
+        resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutTracker,
                 mLockoutResetDispatcher, getBiometricContext().getAuthSessionCoordinator(),
                 mBiometricStrength, getRequestId());
         mCallback.onClientFinished(this, true /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 54e66eb..3e5c599 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -21,16 +21,20 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.common.ComponentInfo;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
@@ -38,6 +42,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -49,12 +54,15 @@
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Supplier;
 
@@ -71,25 +79,53 @@
     @NonNull private final IBinder mToken;
     @NonNull private final Handler mHandler;
     @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
-    @NonNull private final UserAwareBiometricScheduler mScheduler;
-    @NonNull private final LockoutCache mLockoutCache;
+    @NonNull private BiometricScheduler mScheduler;
+    @Nullable private LockoutTracker mLockoutTracker;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
 
-    @NonNull private final Supplier<AidlSession> mLazySession;
+    @NonNull private Supplier<AidlSession> mLazySession;
     @Nullable AidlSession mCurrentSession;
+    @NonNull BiometricContext mBiometricContext;
 
 
     Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull BiometricContext biometricContext, AidlSession session) {
+            @NonNull BiometricContext biometricContext, @Nullable AidlSession session) {
         mTag = tag;
         mProvider = provider;
         mContext = context;
         mToken = new Binder();
         mHandler = handler;
         mSensorProperties = sensorProperties;
-        mScheduler = new UserAwareBiometricScheduler(tag,
+        mBiometricContext = biometricContext;
+        mAuthenticatorIds = new HashMap<>();
+    }
+
+    Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+            @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext) {
+        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+                biometricContext, null);
+    }
+
+    public Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+            @NonNull Handler handler, @NonNull SensorProps prop,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresChallenge) {
+        this(tag, provider, context, handler,
+                getFaceSensorPropertiesInternal(prop, resetLockoutRequiresChallenge),
+                lockoutResetDispatcher, biometricContext, null);
+    }
+
+    /**
+     * Initialize biometric scheduler, lockout tracker and session for the sensor.
+     */
+    public void init(LockoutResetDispatcher lockoutResetDispatcher,
+            FaceProvider provider) {
+        mScheduler = new UserAwareBiometricScheduler(mTag,
                 BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
                 () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
                 new UserAwareBiometricScheduler.UserSwitchCallback() {
@@ -98,7 +134,7 @@
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
                                 mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), biometricContext,
+                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
                                 () -> mCurrentSession = null);
                     }
 
@@ -107,13 +143,36 @@
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
 
-                        final AidlResponseHandler resultController = new AidlResponseHandler(
-                                mContext, mScheduler, sensorId, newUserId,
-                                mLockoutCache, lockoutResetDispatcher,
-                                biometricContext.getAuthSessionCoordinator(), () -> {
-                            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-                            mCurrentSession = null;
-                        });
+                        final AidlResponseHandler resultController;
+                        if (Flags.deHidl()) {
+                            resultController = new AidlResponseHandler(
+                                    mContext, mScheduler, sensorId, newUserId,
+                                    mLockoutTracker, lockoutResetDispatcher,
+                                    mBiometricContext.getAuthSessionCoordinator(), () -> {},
+                                    new AidlResponseHandler.AidlResponseHandlerCallback() {
+                                        @Override
+                                        public void onEnrollSuccess() {
+                                            mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+                                                    newUserId);
+                                            mProvider.scheduleInvalidationRequest(sensorId,
+                                                    newUserId);
+                                        }
+
+                                        @Override
+                                        public void onHardwareUnavailable() {
+                                            Slog.e(mTag, "Face sensor hardware unavailable.");
+                                            mCurrentSession = null;
+                                        }
+                                    });
+                        } else {
+                            resultController = new AidlResponseHandler(
+                                    mContext, mScheduler, sensorId, newUserId,
+                                    mLockoutTracker, lockoutResetDispatcher,
+                                    mBiometricContext.getAuthSessionCoordinator(), () -> {
+                                Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+                                mCurrentSession = null;
+                            });
+                        }
 
                         final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
                                 (userIdStarted, newSession, halInterfaceVersion) -> {
@@ -136,32 +195,42 @@
 
                         return new FaceStartUserClient(mContext, provider::getHalInstance,
                                 mToken, newUserId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), biometricContext,
+                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
                                 resultController, userStartedCallback);
                     }
                 });
-        mLockoutCache = new LockoutCache();
-        mAuthenticatorIds = new HashMap<>();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+        mLockoutTracker = new LockoutCache();
     }
 
-    Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
-            @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull BiometricContext biometricContext) {
-        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
-                biometricContext, null);
+    private static FaceSensorPropertiesInternal getFaceSensorPropertiesInternal(SensorProps prop,
+            boolean resetLockoutRequiresChallenge) {
+        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+        if (prop.commonProps.componentInfo != null) {
+            for (ComponentInfo info : prop.commonProps.componentInfo) {
+                componentInfo.add(new ComponentInfoInternal(info.componentId,
+                        info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+                        info.softwareVersion));
+            }
+        }
+        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+                prop.commonProps.sensorId, prop.commonProps.sensorStrength,
+                prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
+                prop.supportsDetectInteraction, prop.halControlsPreview,
+                resetLockoutRequiresChallenge);
+
+        return internalProp;
     }
 
-    @NonNull Supplier<AidlSession> getLazySession() {
+    @NonNull public Supplier<AidlSession> getLazySession() {
         return mLazySession;
     }
 
-    @NonNull FaceSensorPropertiesInternal getSensorProperties() {
+    @NonNull protected FaceSensorPropertiesInternal getSensorProperties() {
         return mSensorProperties;
     }
 
-    @VisibleForTesting @Nullable AidlSession getSessionForUser(int userId) {
+    @VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
         if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
             return mCurrentSession;
         } else {
@@ -174,15 +243,18 @@
                 mProvider, this);
     }
 
-    @NonNull BiometricScheduler getScheduler() {
+    @NonNull public BiometricScheduler getScheduler() {
         return mScheduler;
     }
 
-    @NonNull LockoutCache getLockoutCache() {
-        return mLockoutCache;
+    @NonNull protected LockoutTracker getLockoutTracker(boolean forAuth) {
+        if (forAuth) {
+            return null;
+        }
+        return mLockoutTracker;
     }
 
-    @NonNull Map<Integer, Long> getAuthenticatorIds() {
+    @NonNull protected Map<Integer, Long> getAuthenticatorIds() {
         return mAuthenticatorIds;
     }
 
@@ -253,4 +325,49 @@
         mScheduler.reset();
         mCurrentSession = null;
     }
+
+    protected BiometricContext getBiometricContext() {
+        return mBiometricContext;
+    }
+
+    protected Handler getHandler() {
+        return mHandler;
+    }
+
+    protected Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Schedules FaceUpdateActiveUserClient for user id.
+     */
+    public void scheduleFaceUpdateActiveUserClient(int userId) {}
+
+    /**
+     * Returns true if the sensor hardware is detected.
+     */
+    public boolean isHardwareDetected(String halInstanceName) {
+        if (mTestHalEnabled) {
+            return true;
+        }
+        return ServiceManager.checkService(IFace.DESCRIPTOR + "/" + halInstanceName) != null;
+    }
+
+    /**
+     * Returns lockout mode of this sensor.
+     */
+    @LockoutTracker.LockoutMode
+    public int getLockoutModeForUser(int userId) {
+        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                Utils.getCurrentStrength(mSensorProperties.sensorId));
+    }
+
+    public void setScheduler(BiometricScheduler scheduler) {
+        mScheduler = scheduler;
+    }
+
+    public void setLazySession(
+            Supplier<AidlSession> lazySession) {
+        mLazySession = lazySession;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
index 8385c3f..0c34d70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
@@ -27,13 +27,14 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
 
 import java.io.File;
 import java.util.Map;
 import java.util.function.Supplier;
 
-public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace> {
+public class FaceUpdateActiveUserClient extends StartUserClient<IBiometricsFace, AidlSession> {
     private static final String TAG = "FaceUpdateActiveUserClient";
     private static final String FACE_DATA_DIR = "facedata";
 
@@ -45,8 +46,18 @@
             int sensorId, @NonNull BiometricLogger logger,
             @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
             @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, logger, biometricContext);
+        this(context, lazyDaemon, (newUserId, newUser, halInterfaceVersion) -> {},
+                userId, owner, sensorId, logger, biometricContext, hasEnrolledBiometrics,
+                authenticatorIds);
+    }
+
+    FaceUpdateActiveUserClient(@NonNull Context context,
+            @NonNull Supplier<IBiometricsFace> lazyDaemon, UserStartedCallback userStartedCallback,
+            int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
+            @NonNull Map<Integer, Long> authenticatorIds) {
+        super(context, lazyDaemon, null /* token */, userId, sensorId, logger, biometricContext,
+                userStartedCallback);
         mHasEnrolledBiometrics = hasEnrolledBiometrics;
         mAuthenticatorIds = authenticatorIds;
     }
@@ -77,6 +88,7 @@
             daemon.setActiveUser(getTargetUserId(), storePath.getAbsolutePath());
             mAuthenticatorIds.put(getTargetUserId(),
                     mHasEnrolledBiometrics ? daemon.getAuthenticatorId().value : 0L);
+            mUserStartedCallback.onUserStarted(getTargetUserId(), null, 0);
             mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to setActiveUser: " + e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
index 36a9790..7a574ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
@@ -112,4 +112,8 @@
     void onAuthenticatorIdRetrieved(long authenticatorId) {
         mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId);
     }
+
+    void onUnsupportedClientScheduled() {
+        mAidlResponseHandler.onUnsupportedClientScheduled();
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
new file mode 100644
index 0000000..6355cb5
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2023 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.biometrics.sensors.face.hidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.os.Handler;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.face.FaceUtils;
+import com.android.server.biometrics.sensors.face.LockoutHalImpl;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.aidl.Sensor;
+
+/**
+ * Convert HIDL sensor configurations to an AIDL Sensor.
+ */
+public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRecipient{
+
+    private static final String TAG = "HidlToAidlSensorAdapter";
+
+    private IBiometricsFace mDaemon;
+    private AidlSession mSession;
+    private int mCurrentUserId = UserHandle.USER_NULL;
+    private final Runnable mInternalCleanupAndGetFeatureRunnable;
+    private final FaceProvider mFaceProvider;
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
+    private final AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    private final StartUserClient.UserStartedCallback<AidlSession> mUserStartedCallback =
+            (newUserId, newUser, halInterfaceVersion) -> {
+                if (newUserId != mCurrentUserId) {
+                    handleUserChanged(newUserId);
+                }
+            };
+    private LockoutHalImpl mLockoutTracker;
+
+    public HidlToAidlSensorAdapter(@NonNull String tag,
+            @NonNull FaceProvider provider,
+            @NonNull Context context,
+            @NonNull Handler handler,
+            @NonNull SensorProps prop,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresChallenge,
+            @NonNull Runnable internalCleanupAndGetFeatureRunnable) {
+        this(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+                resetLockoutRequiresChallenge, internalCleanupAndGetFeatureRunnable,
+                new AuthSessionCoordinator(), null /* daemon */,
+                null /* onEnrollSuccessCallback */);
+    }
+
+    @VisibleForTesting
+    HidlToAidlSensorAdapter(@NonNull String tag,
+            @NonNull FaceProvider provider,
+            @NonNull Context context,
+            @NonNull Handler handler,
+            @NonNull SensorProps prop,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresChallenge,
+            @NonNull Runnable internalCleanupAndGetFeatureRunnable,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @Nullable IBiometricsFace daemon,
+            @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+        super(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+                resetLockoutRequiresChallenge);
+        mInternalCleanupAndGetFeatureRunnable = internalCleanupAndGetFeatureRunnable;
+        mFaceProvider = provider;
+        mLockoutResetDispatcher = lockoutResetDispatcher;
+        mAuthSessionCoordinator = authSessionCoordinator;
+        mDaemon = daemon;
+        mAidlResponseHandlerCallback = aidlResponseHandlerCallback == null
+                ? new AidlResponseHandler.AidlResponseHandlerCallback() {
+                    @Override
+                    public void onEnrollSuccess() {
+                        scheduleFaceUpdateActiveUserClient(mCurrentUserId);
+                    }
+
+                    @Override
+                    public void onHardwareUnavailable() {
+                        mDaemon = null;
+                        mCurrentUserId = UserHandle.USER_NULL;
+                    }
+                } : aidlResponseHandlerCallback;
+    }
+
+    @Override
+    public void scheduleFaceUpdateActiveUserClient(int userId) {
+        getScheduler().scheduleClientMonitor(getFaceUpdateActiveUserClient(userId));
+    }
+
+    @Override
+    public void serviceDied(long cookie) {
+        Slog.d(TAG, "HAL died.");
+        mDaemon = null;
+    }
+
+    @Override
+    public boolean isHardwareDetected(String halInstanceName) {
+        return getIBiometricsFace() != null;
+    }
+
+    @Override
+    @LockoutTracker.LockoutMode
+    public int getLockoutModeForUser(int userId) {
+        return mLockoutTracker.getLockoutModeForUser(userId);
+    }
+
+    @Override
+    public void init(LockoutResetDispatcher lockoutResetDispatcher,
+            FaceProvider provider) {
+        setScheduler(new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
+                null /* gestureAvailabilityTracker */));
+        setLazySession(this::getSession);
+        mLockoutTracker = new LockoutHalImpl();
+    }
+
+    @Override
+    @VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
+        if (mSession != null && mSession.getUserId() == userId) {
+            return mSession;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    protected LockoutTracker getLockoutTracker(boolean forAuth) {
+        return mLockoutTracker;
+    }
+
+    @NonNull AidlSession getSession() {
+        if (mDaemon != null && mSession != null) {
+            return mSession;
+        } else {
+            return mSession = new AidlSession(getContext(), this::getIBiometricsFace,
+                    mCurrentUserId, getAidlResponseHandler());
+        }
+    }
+
+    private AidlResponseHandler getAidlResponseHandler() {
+        return new AidlResponseHandler(getContext(), getScheduler(), getSensorProperties().sensorId,
+                mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
+                mAuthSessionCoordinator, () -> {}, mAidlResponseHandlerCallback);
+    }
+
+    private IBiometricsFace getIBiometricsFace() {
+        if (mFaceProvider.getTestHalEnabled()) {
+            final TestHal testHal = new TestHal(getContext(), getSensorProperties().sensorId);
+            testHal.setCallback(new HidlToAidlCallbackConverter(getAidlResponseHandler()));
+            return testHal;
+        }
+
+        if (mDaemon != null) {
+            return mDaemon;
+        }
+
+        Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+                + getScheduler().getCurrentClient());
+
+        try {
+            mDaemon = IBiometricsFace.getService();
+        } catch (java.util.NoSuchElementException e) {
+            // Service doesn't exist or cannot be opened.
+            Slog.w(TAG, "NoSuchElementException", e);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get face HAL", e);
+        }
+
+        if (mDaemon == null) {
+            Slog.w(TAG, "Face HAL not available");
+            return null;
+        }
+
+        mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+        scheduleLoadAuthenticatorIds();
+        mInternalCleanupAndGetFeatureRunnable.run();
+        return mDaemon;
+    }
+
+    @VisibleForTesting void handleUserChanged(int newUserId) {
+        Slog.d(TAG, "User changed. Current user is " + newUserId);
+        mSession = null;
+        mCurrentUserId = newUserId;
+    }
+
+    private void scheduleLoadAuthenticatorIds() {
+        // Note that this can be performed on the scheduler (as opposed to being done immediately
+        // when the HAL is (re)loaded, since
+        // 1) If this is truly the first time it's being performed (e.g. system has just started),
+        //    this will be run very early and way before any applications need to generate keys.
+        // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
+        //    just been reloaded), the framework already has a cache of the authenticatorIds. This
+        //    is safe because authenticatorIds only change when A) new template has been enrolled,
+        //    or B) all templates are removed.
+        getHandler().post(() -> {
+            for (UserInfo user : UserManager.get(getContext()).getAliveUsers()) {
+                final int targetUserId = user.id;
+                if (!getAuthenticatorIds().containsKey(targetUserId)) {
+                    scheduleFaceUpdateActiveUserClient(targetUserId);
+                }
+            }
+        });
+    }
+
+    private FaceUpdateActiveUserClient getFaceUpdateActiveUserClient(int userId) {
+        return new FaceUpdateActiveUserClient(getContext(), this::getIBiometricsFace,
+                mUserStartedCallback, userId, TAG, getSensorProperties().sensorId,
+                BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
+                !FaceUtils.getInstance(getSensorProperties().sensorId).getBiometricsForUser(
+                        getContext(), userId).isEmpty(),
+                getAuthenticatorIds());
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
similarity index 88%
rename from services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
rename to services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
index 489b213..5daf2d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
@@ -47,34 +47,37 @@
 import java.util.function.Supplier;
 
 /**
- * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ * Adapter to convert HIDL methods into AIDL interface {@link ISession}.
  */
-public class AidlToHidlAdapter implements ISession {
+public class HidlToAidlSessionAdapter implements ISession {
 
-    private final String TAG = "AidlToHidlAdapter";
+    private static final String TAG = "HidlToAidlSessionAdapter";
+
     private static final int CHALLENGE_TIMEOUT_SEC = 600;
     @DurationMillisLong
     private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
     @DurationMillisLong
     private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000;
     private static final int INVALID_VALUE = -1;
+    @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+
     private final Clock mClock;
     private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
-    @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+    private final int mUserId;
+    private final Context mContext;
+
     private long mGenerateChallengeCreatedAt = INVALID_VALUE;
     private long mGenerateChallengeResult = INVALID_VALUE;
     @NonNull private Supplier<IBiometricsFace> mSession;
-    private final int mUserId;
     private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
-    private final Context mContext;
     private int mFeature = INVALID_VALUE;
 
-    public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session,
+    public HidlToAidlSessionAdapter(Context context, Supplier<IBiometricsFace> session,
             int userId, AidlResponseHandler aidlResponseHandler) {
         this(context, session, userId, aidlResponseHandler, Clock.systemUTC());
     }
 
-    AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
+    HidlToAidlSessionAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
             AidlResponseHandler aidlResponseHandler, Clock clock) {
         mSession = session;
         mUserId = userId;
@@ -83,42 +86,11 @@
         setCallback(aidlResponseHandler);
     }
 
-    private void setCallback(AidlResponseHandler aidlResponseHandler) {
-        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
-        try {
-            mSession.get().setCallback(mHidlToAidlCallbackConverter);
-        } catch (RemoteException e) {
-            Slog.d(TAG, "Failed to set callback");
-        }
-    }
-
     @Override
     public IBinder asBinder() {
         return null;
     }
 
-    private boolean isGeneratedChallengeCacheValid() {
-        return mGenerateChallengeCreatedAt != INVALID_VALUE
-                && mGenerateChallengeResult != INVALID_VALUE
-                && mClock.millis() - mGenerateChallengeCreatedAt
-                < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
-    }
-
-    private void incrementChallengeCount() {
-        mGeneratedChallengeCount.add(0, mClock.millis());
-    }
-
-    private int decrementChallengeCount() {
-        final long now = mClock.millis();
-        // ignore values that are old in case generate/revoke calls are not matched
-        // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
-        mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
-        if (!mGeneratedChallengeCount.isEmpty()) {
-            mGeneratedChallengeCount.remove(0);
-        }
-        return mGeneratedChallengeCount.size();
-    }
-
     @Override
     public void generateChallenge() throws RemoteException {
         incrementChallengeCount();
@@ -150,7 +122,7 @@
 
     @Override
     public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException {
-        //unsupported in HIDL
+        Slog.e(TAG, "getEnrollmentConfig unsupported in HIDL");
         return null;
     }
 
@@ -244,19 +216,6 @@
         }
     }
 
-    private int getFaceId() {
-        FaceManager faceManager = mContext.getSystemService(FaceManager.class);
-        List<Face> faces = faceManager.getEnrolledFaces(mUserId);
-        if (faces.isEmpty()) {
-            Slog.d(TAG, "No faces to get feature from.");
-            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
-                    BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
-            return INVALID_VALUE;
-        }
-
-        return faces.get(0).getBiometricId();
-    }
-
     @Override
     public void getAuthenticatorId() throws RemoteException {
         long authenticatorId = mSession.get().getAuthenticatorId().value;
@@ -265,7 +224,8 @@
 
     @Override
     public void invalidateAuthenticatorId() throws RemoteException {
-        //unsupported in HIDL
+        Slog.e(TAG, "invalidateAuthenticatorId unsupported in HIDL");
+        mHidlToAidlCallbackConverter.onUnsupportedClientScheduled();
     }
 
     @Override
@@ -279,47 +239,105 @@
 
     @Override
     public void close() throws RemoteException {
-        //Unsupported in HIDL
+        Slog.e(TAG, "close unsupported in HIDL");
     }
 
     @Override
     public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
             throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Slog.e(TAG, "authenticateWithContext unsupported in HIDL");
+        return authenticate(operationId);
     }
 
     @Override
     public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features,
             NativeHandle previewSurface, OperationContext context) throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Slog.e(TAG, "enrollWithContext unsupported in HIDL");
+        return enroll(hat, type, features, previewSurface);
     }
 
     @Override
     public ICancellationSignal detectInteractionWithContext(OperationContext context)
             throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Slog.e(TAG, "detectInteractionWithContext unsupported in HIDL");
+        return detectInteraction();
     }
 
     @Override
     public void onContextChanged(OperationContext context) throws RemoteException {
-        //Unsupported in HIDL
+        Slog.e(TAG, "onContextChanged unsupported in HIDL");
     }
 
     @Override
     public int getInterfaceVersion() throws RemoteException {
-        //Unsupported in HIDL
+        Slog.e(TAG, "getInterfaceVersion unsupported in HIDL");
         return 0;
     }
 
     @Override
     public String getInterfaceHash() throws RemoteException {
+        Slog.e(TAG, "getInterfaceHash unsupported in HIDL");
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
         //Unsupported in HIDL
         return null;
     }
 
+    private boolean isGeneratedChallengeCacheValid() {
+        return mGenerateChallengeCreatedAt != INVALID_VALUE
+                && mGenerateChallengeResult != INVALID_VALUE
+                && mClock.millis() - mGenerateChallengeCreatedAt
+                < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
+    }
+
+    private void incrementChallengeCount() {
+        mGeneratedChallengeCount.add(0, mClock.millis());
+    }
+
+    private int decrementChallengeCount() {
+        final long now = mClock.millis();
+        // ignore values that are old in case generate/revoke calls are not matched
+        // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
+        mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
+        if (!mGeneratedChallengeCount.isEmpty()) {
+            mGeneratedChallengeCount.remove(0);
+        }
+        return mGeneratedChallengeCount.size();
+    }
+
+    private void setCallback(AidlResponseHandler aidlResponseHandler) {
+        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+        try {
+            if (mSession.get() != null) {
+                long halId = mSession.get().setCallback(mHidlToAidlCallbackConverter).value;
+                Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
+                if (halId == 0) {
+                    Slog.d(TAG, "Unable to set HIDL callback.");
+                }
+            } else {
+                Slog.e(TAG, "Unable to set HIDL callback. HIDL daemon is null.");
+            }
+        } catch (RemoteException e) {
+            Slog.d(TAG, "Failed to set callback");
+        }
+    }
+
+    private int getFaceId() {
+        FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+        List<Face> faces = faceManager.getEnrolledFaces(mUserId);
+        if (faces.isEmpty()) {
+            Slog.d(TAG, "No faces to get feature from.");
+            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+                    BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
+            return INVALID_VALUE;
+        }
+
+        return faces.get(0).getBiometricId();
+    }
+
     /**
      * Cancellation in HIDL occurs for the entire session, instead of a specific client.
      */
@@ -345,10 +363,4 @@
             return null;
         }
     }
-
-    @Override
-    public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
-        //Unsupported in HIDL
-        return null;
-    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 83b306b..e01d672 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -45,9 +45,11 @@
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintServiceReceiver;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -80,6 +82,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -127,6 +130,8 @@
     @NonNull
     private final Function<String, FingerprintProvider> mFingerprintProvider;
     @NonNull
+    private final FingerprintProviderFunction mFingerprintProviderFunction;
+    @NonNull
     private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
             mBiometricStateCallback;
     @NonNull
@@ -136,6 +141,11 @@
     @NonNull
     private final FingerprintServiceRegistry mRegistry;
 
+    interface FingerprintProviderFunction {
+        FingerprintProvider getFingerprintProvider(Pair<String, SensorProps[]> filteredSensorProp,
+                boolean resetLockoutRequiresHardwareAuthToken);
+    }
+
     /** Receives the incoming binder calls from FingerprintManager. */
     @VisibleForTesting
     final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
@@ -874,6 +884,18 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
+        public void registerAuthenticatorsLegacy(
+                @NonNull FingerprintSensorConfigurations fingerprintSensorConfigurations) {
+            super.registerAuthenticatorsLegacy_enforcePermission();
+            if (!fingerprintSensorConfigurations.hasSensorConfigurations()) {
+                Slog.d(TAG, "No fingerprint sensors available.");
+                return;
+            }
+            mRegistry.registerAll(() -> getProviders(fingerprintSensorConfigurations));
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override // Binder call
         public void registerAuthenticators(
                 @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
             super.registerAuthenticators_enforcePermission();
@@ -1021,7 +1043,8 @@
                 () -> IBiometricService.Stub.asInterface(
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
                 () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
-                null /* fingerprintProvider */);
+                null /* fingerprintProvider */,
+                null /* fingerprintProviderFunction */);
     }
 
     @VisibleForTesting
@@ -1029,7 +1052,8 @@
             BiometricContext biometricContext,
             Supplier<IBiometricService> biometricServiceSupplier,
             Supplier<String[]> aidlInstanceNameSupplier,
-            Function<String, FingerprintProvider> fingerprintProvider) {
+            Function<String, FingerprintProvider> fingerprintProvider,
+            FingerprintProviderFunction fingerprintProviderFunction) {
         super(context);
         mBiometricContext = biometricContext;
         mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
@@ -1049,7 +1073,8 @@
                             return new FingerprintProvider(getContext(),
                                     mBiometricStateCallback, mAuthenticationStateListeners,
                                     fp.getSensorProps(), name, mLockoutResetDispatcher,
-                                    mGestureAvailabilityDispatcher, mBiometricContext);
+                                    mGestureAvailabilityDispatcher, mBiometricContext,
+                                    true /* resetLockoutRequiresHardwareAuthToken */);
                         } catch (RemoteException e) {
                             Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
                         }
@@ -1059,6 +1084,22 @@
 
                     return null;
                 };
+        if (Flags.deHidl()) {
+            mFingerprintProviderFunction = fingerprintProviderFunction == null
+                    ? (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) ->
+                            new FingerprintProvider(
+                                    getContext(), mBiometricStateCallback,
+                                    mAuthenticationStateListeners,
+                                    filteredSensorProps.second,
+                                    filteredSensorProps.first, mLockoutResetDispatcher,
+                                    mGestureAvailabilityDispatcher,
+                                    mBiometricContext,
+                                    resetLockoutRequiresHardwareAuthToken)
+                    : fingerprintProviderFunction;
+        } else {
+            mFingerprintProviderFunction =
+                    (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) -> null;
+        }
         mHandler = new Handler(Looper.getMainLooper());
         mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
         mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1070,6 +1111,44 @@
         });
     }
 
+    @NonNull
+    private List<ServiceProvider> getProviders(@NonNull FingerprintSensorConfigurations
+            fingerprintSensorConfigurations) {
+        final List<ServiceProvider> providers = new ArrayList<>();
+        final Pair<String, SensorProps[]> filteredSensorProps = filterAvailableHalInstances(
+                fingerprintSensorConfigurations);
+        providers.add(mFingerprintProviderFunction.getFingerprintProvider(filteredSensorProps,
+                fingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken()));
+
+        return providers;
+    }
+
+    @NonNull
+    private Pair<String, SensorProps[]> filterAvailableHalInstances(
+            FingerprintSensorConfigurations fingerprintSensorConfigurations) {
+        Pair<String, SensorProps[]> finalSensorPair =
+                fingerprintSensorConfigurations.getSensorPair();
+        if (fingerprintSensorConfigurations.isSingleSensorConfigurationPresent()) {
+            return finalSensorPair;
+        }
+
+        final Pair<String, SensorProps[]> virtualSensorPropsPair = fingerprintSensorConfigurations
+                .getSensorPairForInstance("virtual");
+        if (Utils.isVirtualEnabled(getContext())) {
+            if (virtualSensorPropsPair != null) {
+                return virtualSensorPropsPair;
+            } else {
+                Slog.e(TAG, "Could not find virtual interface while it is enabled");
+                return finalSensorPair;
+            }
+        } else {
+            if (virtualSensorPropsPair != null) {
+                return fingerprintSensorConfigurations.getSensorPairNotForInstance("virtual");
+            }
+        }
+        return finalSensorPair;
+    }
+
     private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
                 filterAvailableHalInstances(
             @NonNull List<FingerprintSensorPropertiesInternal> hidlInstances,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
index 4a01943..bd21cf4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -25,6 +25,7 @@
 import android.hardware.keymaster.HardwareAuthToken;
 import android.util.Slog;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -34,9 +35,9 @@
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.RemovalConsumer;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 
@@ -59,6 +60,21 @@
         void onHardwareUnavailable();
     }
 
+    /**
+     * Interface to send results to the AidlResponseHandler's owner.
+     */
+    public interface AidlResponseHandlerCallback {
+        /**
+         * Invoked when enrollment is successful.
+         */
+        void onEnrollSuccess();
+
+        /**
+         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+         */
+        void onHardwareUnavailable();
+    }
+
     private static final String TAG = "AidlResponseHandler";
 
     @NonNull
@@ -68,28 +84,49 @@
     private final int mSensorId;
     private final int mUserId;
     @NonNull
-    private final LockoutCache mLockoutCache;
+    private final LockoutTracker mLockoutTracker;
     @NonNull
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @NonNull
     private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+    @NonNull
+    private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
 
     public AidlResponseHandler(@NonNull Context context,
             @NonNull BiometricScheduler scheduler, int sensorId, int userId,
-            @NonNull LockoutCache lockoutTracker,
+            @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+        this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
+                authSessionCoordinator, hardwareUnavailableCallback,
+                new AidlResponseHandlerCallback() {
+                    @Override
+                    public void onEnrollSuccess() {}
+
+                    @Override
+                    public void onHardwareUnavailable() {}
+                });
+    }
+
+    public AidlResponseHandler(@NonNull Context context,
+            @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+            @NonNull LockoutTracker lockoutTracker,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
+            @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
         mContext = context;
         mScheduler = scheduler;
         mSensorId = sensorId;
         mUserId = userId;
-        mLockoutCache = lockoutTracker;
+        mLockoutTracker = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mAuthSessionCoordinator = authSessionCoordinator;
         mHardwareUnavailableCallback = hardwareUnavailableCallback;
+        mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
     }
 
     @Override
@@ -105,27 +142,26 @@
     @Override
     public void onChallengeGenerated(long challenge) {
         handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(
-                mSensorId, mUserId, challenge), null);
+                mSensorId, mUserId, challenge));
     }
 
     @Override
     public void onChallengeRevoked(long challenge) {
         handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(
-                challenge), null);
+                challenge));
     }
 
     /**
      * Handles acquired messages sent by the HAL (specifically for HIDL HAL).
      */
     public void onAcquired(int acquiredInfo, int vendorCode) {
-        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
-                null);
+        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode));
     }
 
     @Override
     public void onAcquired(byte info, int vendorCode) {
         handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(
-                AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null);
+                AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode));
     }
 
     /**
@@ -135,9 +171,13 @@
         handleResponse(ErrorConsumer.class, (c) -> {
             c.onError(error, vendorCode);
             if (error == Error.HW_UNAVAILABLE) {
-                mHardwareUnavailableCallback.onHardwareUnavailable();
+                if (Flags.deHidl()) {
+                    mAidlResponseHandlerCallback.onHardwareUnavailable();
+                } else {
+                    mHardwareUnavailableCallback.onHardwareUnavailable();
+                }
             }
-        }, null);
+        });
     }
 
     @Override
@@ -158,8 +198,12 @@
                 .getUniqueName(mContext, currentUserId);
         final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
                 enrollmentId, mSensorId);
-        handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint,
-                remaining), null);
+        handleResponse(FingerprintEnrollClient.class, (c) -> {
+            c.onEnrollResult(fingerprint, remaining);
+            if (remaining == 0) {
+                mAidlResponseHandlerCallback.onEnrollSuccess();
+            }
+        });
     }
 
     @Override
@@ -184,13 +228,12 @@
 
     @Override
     public void onLockoutTimed(long durationMillis) {
-        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis),
-                null);
+        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis));
     }
 
     @Override
     public void onLockoutPermanent() {
-        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent);
     }
 
     @Override
@@ -198,7 +241,7 @@
         handleResponse(FingerprintResetLockoutClient.class,
                 FingerprintResetLockoutClient::onLockoutCleared,
                 (c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone(
-                        mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher,
+                        mSensorId, mUserId, mLockoutTracker, mLockoutResetDispatcher,
                         mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId),
                         -1 /* requestId */));
     }
@@ -206,49 +249,74 @@
     @Override
     public void onInteractionDetected() {
         handleResponse(FingerprintDetectClient.class,
-                FingerprintDetectClient::onInteractionDetected, null);
+                FingerprintDetectClient::onInteractionDetected);
     }
 
     @Override
     public void onEnrollmentsEnumerated(int[] enrollmentIds) {
         if (enrollmentIds.length > 0) {
             for (int i = 0; i < enrollmentIds.length; i++) {
-                final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
-                int finalI = i;
-                handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp,
-                        enrollmentIds.length - finalI - 1), null);
+                onEnrollmentEnumerated(enrollmentIds[i],
+                        enrollmentIds.length - i - 1 /* remaining */);
             }
         } else {
-            handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null,
-                    0), null);
+            handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
+                    null /* identifier */,
+                    0 /* remaining */));
         }
     }
 
+    /**
+     * Handle enumerated fingerprint.
+     */
+    public void onEnrollmentEnumerated(int enrollmentId, int remaining) {
+        final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+        handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp, remaining));
+    }
+
+    /**
+     * Handle removal of fingerprint.
+     */
+    public void onEnrollmentRemoved(int enrollmentId, int remaining) {
+        final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+        handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp, remaining));
+    }
+
     @Override
     public void onEnrollmentsRemoved(int[] enrollmentIds) {
         if (enrollmentIds.length > 0) {
             for (int i  = 0; i < enrollmentIds.length; i++) {
-                final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
-                int finalI = i;
-                handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp,
-                        enrollmentIds.length - finalI - 1), null);
+                onEnrollmentRemoved(enrollmentIds[i], enrollmentIds.length - i - 1 /* remaining */);
             }
         } else {
-            handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0),
-                    null);
+            handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
+                    0 /* remaining */));
         }
     }
 
     @Override
     public void onAuthenticatorIdRetrieved(long authenticatorId) {
         handleResponse(FingerprintGetAuthenticatorIdClient.class,
-                (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null);
+                (c) -> c.onAuthenticatorIdRetrieved(authenticatorId));
     }
 
     @Override
     public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
         handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
-                newAuthenticatorId), null);
+                newAuthenticatorId));
+    }
+
+    /**
+     * Handle clients which are not supported in HIDL HAL.
+     */
+    public <T extends BaseClientMonitor> void onUnsupportedClientScheduled(Class<T> className) {
+        Slog.e(TAG, className + " is not supported in the HAL.");
+        handleResponse(className, (c) -> c.cancel());
+    }
+
+    private <T> void handleResponse(@NonNull Class<T> className,
+            @NonNull Consumer<T> action) {
+        handleResponse(className, action, null /* alternateAction */);
     }
 
     private <T> void handleResponse(@NonNull Class<T> className,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 299a310..8ff105b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -20,7 +20,7 @@
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 
-import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.fingerprint.hidl.HidlToAidlSessionAdapter;
 
 import java.util.function.Supplier;
 
@@ -45,7 +45,7 @@
 
     public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session,
             int userId, AidlResponseHandler aidlResponseHandler) {
-        mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler);
+        mSession = new HidlToAidlSessionAdapter(session, userId, aidlResponseHandler);
         mHalInterfaceVersion = 0;
         mUserId = userId;
         mAidlResponseHandler = aidlResponseHandler;
@@ -62,7 +62,7 @@
     }
 
     /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
-    AidlResponseHandler getHalSessionCallback() {
+    public AidlResponseHandler getHalSessionCallback() {
         return mAidlResponseHandler;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index ea1a622..0353969 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -30,7 +30,7 @@
 import java.util.Map;
 import java.util.function.Supplier;
 
-class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
+public class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
 
     private static final String TAG = "FingerprintGetAuthenticatorIdClient";
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 032ab87..88a11d9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -59,6 +59,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -73,6 +74,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.InvalidationRequesterClient;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorList;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
@@ -80,6 +82,7 @@
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
 import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.hidl.HidlToAidlSensorAdapter;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -165,10 +168,12 @@
             @NonNull SensorProps[] props, @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            @NonNull BiometricContext biometricContext) {
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresHardwareAuthToken) {
         this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
                 lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
-                null /* daemon */);
+                null /* daemon */, resetLockoutRequiresHardwareAuthToken,
+                false /* testHalEnabled */);
     }
 
     @VisibleForTesting FingerprintProvider(@NonNull Context context,
@@ -178,7 +183,9 @@
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
-            IFingerprint daemon) {
+            @Nullable IFingerprint daemon,
+            boolean resetLockoutRequiresHardwareAuthToken,
+            boolean testHalEnabled) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
         mAuthenticationStateListeners = authenticationStateListeners;
@@ -191,62 +198,136 @@
         mBiometricContext = biometricContext;
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
+        mTestHalEnabled = testHalEnabled;
 
-        AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
-                new AuthenticationStatsBroadcastReceiver(
-                        mContext,
-                        BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                        (AuthenticationStatsCollector collector) -> {
-                            Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
-                            mAuthenticationStatsCollector = collector;
-                        });
+        initAuthenticationBroadcastReceiver();
+        initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
+    }
 
-        final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
+    private void initAuthenticationBroadcastReceiver() {
+        new AuthenticationStatsBroadcastReceiver(
+                mContext,
+                BiometricsProtoEnums.MODALITY_FINGERPRINT,
+                (AuthenticationStatsCollector collector) -> {
+                    Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
+                    mAuthenticationStatsCollector = collector;
+                });
+    }
 
-        for (SensorProps prop : props) {
-            final int sensorId = prop.commonProps.sensorId;
-
-            final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-            if (prop.commonProps.componentInfo != null) {
-                for (ComponentInfo info : prop.commonProps.componentInfo) {
-                    componentInfo.add(new ComponentInfoInternal(info.componentId,
-                            info.hardwareVersion, info.firmwareVersion, info.serialNumber,
-                            info.softwareVersion));
+    private void initSensors(boolean resetLockoutRequiresHardwareAuthToken, SensorProps[] props,
+            GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+        if (Flags.deHidl()) {
+            if (!resetLockoutRequiresHardwareAuthToken) {
+                Slog.d(getTag(), "Adding HIDL configs");
+                for (SensorProps sensorConfig: props) {
+                    addHidlSensors(sensorConfig, gestureAvailabilityDispatcher,
+                            resetLockoutRequiresHardwareAuthToken);
+                }
+            } else {
+                Slog.d(getTag(), "Adding AIDL configs");
+                final List<SensorLocationInternal> workaroundLocations =
+                        getWorkaroundSensorProps(mContext);
+                for (SensorProps prop : props) {
+                    addAidlSensors(prop, gestureAvailabilityDispatcher, workaroundLocations,
+                            resetLockoutRequiresHardwareAuthToken);
                 }
             }
+        } else {
+            final List<SensorLocationInternal> workaroundLocations =
+                    getWorkaroundSensorProps(mContext);
 
-            final FingerprintSensorPropertiesInternal internalProp =
-                    new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
-                            prop.commonProps.sensorStrength,
-                            prop.commonProps.maxEnrollmentsPerUser,
-                            componentInfo,
-                            prop.sensorType,
-                            prop.halControlsIllumination,
-                            true /* resetLockoutRequiresHardwareAuthToken */,
-                            !workaroundLocations.isEmpty() ? workaroundLocations :
-                                    Arrays.stream(prop.sensorLocations).map(location ->
-                                                    new SensorLocationInternal(
-                                                            location.display,
-                                                            location.sensorLocationX,
-                                                            location.sensorLocationY,
-                                                            location.sensorRadius))
-                                            .collect(Collectors.toList()));
-            final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
-                    internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
-                    mBiometricContext);
-            final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
-                    sensor.getLazySession().get().getUserId();
-            mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
-                    new SynchronousUserSwitchObserver() {
-                        @Override
-                        public void onUserSwitching(int newUserId) {
-                            scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
-                        }
-                    });
-            Slog.d(getTag(), "Added: " + internalProp);
+            for (SensorProps prop : props) {
+                final int sensorId = prop.commonProps.sensorId;
+                final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+                if (prop.commonProps.componentInfo != null) {
+                    for (ComponentInfo info : prop.commonProps.componentInfo) {
+                        componentInfo.add(new ComponentInfoInternal(info.componentId,
+                                info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+                                info.softwareVersion));
+                    }
+                }
+                final FingerprintSensorPropertiesInternal internalProp =
+                        new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
+                                prop.commonProps.sensorStrength,
+                                prop.commonProps.maxEnrollmentsPerUser,
+                                componentInfo,
+                                prop.sensorType,
+                                prop.halControlsIllumination,
+                                true /* resetLockoutRequiresHardwareAuthToken */,
+                                !workaroundLocations.isEmpty() ? workaroundLocations :
+                                        Arrays.stream(prop.sensorLocations).map(
+                                                        location -> new SensorLocationInternal(
+                                                                location.display,
+                                                                location.sensorLocationX,
+                                                                location.sensorLocationY,
+                                                                location.sensorRadius))
+                                                .collect(Collectors.toList()));
+                final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
+                        mHandler, internalProp, mLockoutResetDispatcher,
+                        gestureAvailabilityDispatcher, mBiometricContext);
+                sensor.init(gestureAvailabilityDispatcher,
+                        mLockoutResetDispatcher);
+                final int sessionUserId =
+                        sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                                sensor.getLazySession().get().getUserId();
+                mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+                        new SynchronousUserSwitchObserver() {
+                            @Override
+                            public void onUserSwitching(int newUserId) {
+                                scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                            }
+                        });
+                Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+            }
         }
     }
 
+    private void addHidlSensors(@NonNull SensorProps prop,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            boolean resetLockoutRequiresHardwareAuthToken) {
+        final int sensorId = prop.commonProps.sensorId;
+        final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/"
+                + sensorId, this, mContext, mHandler,
+                prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
+                mBiometricContext, resetLockoutRequiresHardwareAuthToken,
+                () -> scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+                        null /* callback */));
+        sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
+        final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                sensor.getLazySession().get().getUserId();
+        mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+                new SynchronousUserSwitchObserver() {
+                    @Override
+                    public void onUserSwitching(int newUserId) {
+                        scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                    }
+                });
+        Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+    }
+
+    private void addAidlSensors(@NonNull SensorProps prop,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            List<SensorLocationInternal> workaroundLocations,
+            boolean resetLockoutRequiresHardwareAuthToken) {
+        final int sensorId = prop.commonProps.sensorId;
+        final Sensor sensor = new Sensor(getTag() + "/" + sensorId,
+                this, mContext, mHandler,
+                prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
+                mBiometricContext, workaroundLocations,
+                resetLockoutRequiresHardwareAuthToken);
+        sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
+        final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                sensor.getLazySession().get().getUserId();
+        mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+                new SynchronousUserSwitchObserver() {
+                    @Override
+                    public void onUserSwitching(int newUserId) {
+                        scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                    }
+                });
+        Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+    }
+
     private String getTag() {
         return "FingerprintProvider/" + mHalInstanceName;
     }
@@ -351,7 +432,10 @@
         }
     }
 
-    private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
+    /**
+     * Schedules FingerprintGetAuthenticatorIdClient for specific sensor and user.
+     */
+    protected void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
         mHandler.post(() -> {
             final FingerprintGetAuthenticatorIdClient client =
                     new FingerprintGetAuthenticatorIdClient(mContext,
@@ -387,8 +471,8 @@
                             BiometricsProtoEnums.CLIENT_UNKNOWN,
                             mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken,
-                    mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
-                    Utils.getCurrentStrength(sensorId));
+                    mFingerprintSensors.get(sensorId).getLockoutTracker(false /* forAuth */),
+                    mLockoutResetDispatcher, Utils.getCurrentStrength(sensorId));
             scheduleForSensor(sensorId, client);
         });
     }
@@ -443,18 +527,23 @@
                     mFingerprintSensors.get(sensorId).getSensorProperties(),
                     mUdfpsOverlayController, mSidefpsController,
                     mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason);
-            scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
-                    mBiometricStateCallback, new ClientMonitorCallback() {
-                @Override
-                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                        boolean success) {
-                    ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
-                    if (success) {
-                        scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
-                        scheduleInvalidationRequest(sensorId, userId);
-                    }
-                }
-            }));
+            if (Flags.deHidl()) {
+                scheduleForSensor(sensorId, client, mBiometricStateCallback);
+            } else {
+                scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+                        mBiometricStateCallback, new ClientMonitorCallback() {
+                            @Override
+                            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                                    boolean success) {
+                                ClientMonitorCallback.super.onClientFinished(
+                                        clientMonitor, success);
+                                if (success) {
+                                    scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+                                    scheduleInvalidationRequest(sensorId, userId);
+                                }
+                            }
+                        }));
+            }
         });
         return id;
     }
@@ -497,6 +586,13 @@
             final int userId = options.getUserId();
             final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+            final LockoutTracker lockoutTracker;
+            if (Flags.deHidl()) {
+                lockoutTracker = mFingerprintSensors.get(sensorId)
+                        .getLockoutTracker(true /* forAuth */);
+            } else {
+                lockoutTracker = null;
+            }
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
@@ -510,7 +606,7 @@
                     mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
                     Utils.getCurrentStrength(sensorId),
                     SystemClock.elapsedRealtimeClock(),
-                    null /* lockoutTracker */);
+                    lockoutTracker);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
 
                 @Override
@@ -636,6 +732,9 @@
 
     @Override
     public boolean isHardwareDetected(int sensorId) {
+        if (Flags.deHidl()) {
+            return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
+        }
         return hasHalInstance();
     }
 
@@ -674,8 +773,12 @@
 
     @Override
     public int getLockoutModeForUser(int sensorId, int userId) {
-        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
-                Utils.getCurrentStrength(sensorId));
+        if (Flags.deHidl()) {
+            return mFingerprintSensors.get(sensorId).getLockoutModeForUser(userId);
+        } else {
+            return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                    Utils.getCurrentStrength(sensorId));
+        }
     }
 
     @Override
@@ -829,6 +932,10 @@
         mTestHalEnabled = enabled;
     }
 
+    public boolean getTestHalEnabled() {
+        return mTestHalEnabled;
+    }
+
     // TODO(b/174868353): workaround for gaps in HAL interface (remove and get directly from HAL)
     // reads values via an overlay instead of querying the HAL
     @NonNull
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index ec225a6..387ae12 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -60,7 +60,8 @@
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
                 0 /* cookie */, sensorId, biometricLogger, biometricContext);
-        mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
+        mHardwareAuthToken = hardwareAuthToken == null ? null :
+                HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
         mLockoutCache = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mBiometricStrength = biometricStrength;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 893cb8f..dd887bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -21,21 +21,28 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.SensorLocationInternal;
+import android.hardware.biometrics.common.ComponentInfo;
+import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -48,15 +55,20 @@
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 /**
  * Maintains the state of a single sensor within an instance of the
@@ -73,15 +85,17 @@
     @NonNull private final IBinder mToken;
     @NonNull private final Handler mHandler;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
-    @NonNull private final UserAwareBiometricScheduler mScheduler;
-    @NonNull private final LockoutCache mLockoutCache;
+    @NonNull private BiometricScheduler mScheduler;
+    @NonNull private LockoutTracker mLockoutTracker;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
+    @NonNull private final BiometricContext mBiometricContext;
 
     @Nullable AidlSession mCurrentSession;
-    @NonNull private final Supplier<AidlSession> mLazySession;
+    @NonNull private Supplier<AidlSession> mLazySession;
 
-    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
-            @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+    public Sensor(@NonNull String tag, @NonNull FingerprintProvider provider,
+            @NonNull Context context, @NonNull Handler handler,
+            @NonNull FingerprintSensorPropertiesInternal sensorProperties,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext, AidlSession session) {
@@ -91,8 +105,38 @@
         mToken = new Binder();
         mHandler = handler;
         mSensorProperties = sensorProperties;
-        mLockoutCache = new LockoutCache();
-        mScheduler = new UserAwareBiometricScheduler(tag,
+        mBiometricContext = biometricContext;
+        mAuthenticatorIds = new HashMap<>();
+        mCurrentSession = session;
+    }
+
+    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+            @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext) {
+        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+                gestureAvailabilityDispatcher, biometricContext, null);
+    }
+
+    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+            @NonNull Handler handler, @NonNull SensorProps sensorProp,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext,
+            @NonNull List<SensorLocationInternal> workaroundLocation,
+            boolean resetLockoutRequiresHardwareAuthToken) {
+        this(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(sensorProp,
+                        workaroundLocation, resetLockoutRequiresHardwareAuthToken),
+                lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, null);
+    }
+
+    /**
+     * Initialize biometric scheduler, lockout tracker and session for the sensor.
+     */
+    public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            LockoutResetDispatcher lockoutResetDispatcher) {
+        mScheduler = new UserAwareBiometricScheduler(mTag,
                 BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
                 gestureAvailabilityDispatcher,
                 () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
@@ -102,7 +146,7 @@
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new FingerprintStopUserClient(mContext, mLazySession, mToken,
                                 userId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), biometricContext,
+                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
                                 () -> mCurrentSession = null);
                     }
 
@@ -111,13 +155,38 @@
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
 
-                        final AidlResponseHandler resultController = new AidlResponseHandler(
-                                mContext, mScheduler, sensorId, newUserId,
-                                mLockoutCache, lockoutResetDispatcher,
-                                biometricContext.getAuthSessionCoordinator(), () -> {
-                            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-                            mCurrentSession = null;
-                        });
+                        final AidlResponseHandler resultController;
+
+                        if (Flags.deHidl()) {
+                            resultController = new AidlResponseHandler(
+                                    mContext, mScheduler, sensorId, newUserId,
+                                    mLockoutTracker, lockoutResetDispatcher,
+                                    mBiometricContext.getAuthSessionCoordinator(), () -> {},
+                                    new AidlResponseHandler.AidlResponseHandlerCallback() {
+                                        @Override
+                                        public void onEnrollSuccess() {
+                                            mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+                                                    newUserId);
+                                            mProvider.scheduleInvalidationRequest(sensorId,
+                                                    newUserId);
+                                        }
+
+                                        @Override
+                                        public void onHardwareUnavailable() {
+                                            Slog.e(mTag,
+                                                    "Fingerprint sensor hardware unavailable.");
+                                            mCurrentSession = null;
+                                        }
+                                    });
+                        } else {
+                            resultController = new AidlResponseHandler(
+                                    mContext, mScheduler, sensorId, newUserId,
+                                    mLockoutTracker, lockoutResetDispatcher,
+                                    mBiometricContext.getAuthSessionCoordinator(), () -> {
+                                Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+                                mCurrentSession = null;
+                            });
+                        }
 
                         final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
                                 (userIdStarted, newSession, halInterfaceVersion) -> {
@@ -133,40 +202,58 @@
                                                         + "sensor: "
                                                         + sensorId
                                                         + ", user: " + userIdStarted);
-                                        provider.scheduleInvalidationRequest(sensorId,
+                                        mProvider.scheduleInvalidationRequest(sensorId,
                                                 userIdStarted);
                                     }
                                 };
 
-                        return new FingerprintStartUserClient(mContext, provider::getHalInstance,
+                        return new FingerprintStartUserClient(mContext, mProvider::getHalInstance,
                                 mToken, newUserId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), biometricContext,
+                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
                                 resultController, userStartedCallback);
                     }
                 });
-        mAuthenticatorIds = new HashMap<>();
+        mLockoutTracker = new LockoutCache();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
-        mCurrentSession = session;
     }
 
-    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
-            @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            @NonNull BiometricContext biometricContext) {
-        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
-                gestureAvailabilityDispatcher, biometricContext, null);
+    protected static FingerprintSensorPropertiesInternal getFingerprintSensorPropertiesInternal(
+            SensorProps prop, List<SensorLocationInternal> workaroundLocations,
+            boolean resetLockoutRequiresHardwareAuthToken) {
+        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+        if (prop.commonProps.componentInfo != null) {
+            for (ComponentInfo info : prop.commonProps.componentInfo) {
+                componentInfo.add(new ComponentInfoInternal(info.componentId,
+                        info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+                        info.softwareVersion));
+            }
+        }
+        return new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
+                prop.commonProps.sensorStrength,
+                prop.commonProps.maxEnrollmentsPerUser,
+                componentInfo,
+                prop.sensorType,
+                prop.halControlsIllumination,
+                resetLockoutRequiresHardwareAuthToken,
+                !workaroundLocations.isEmpty() ? workaroundLocations :
+                        Arrays.stream(prop.sensorLocations).map(location ->
+                                        new SensorLocationInternal(
+                                                location.display,
+                                                location.sensorLocationX,
+                                                location.sensorLocationY,
+                                                location.sensorRadius))
+                                .collect(Collectors.toList()));
     }
 
-    @NonNull Supplier<AidlSession> getLazySession() {
+    @NonNull public Supplier<AidlSession> getLazySession() {
         return mLazySession;
     }
 
-    @NonNull FingerprintSensorPropertiesInternal getSensorProperties() {
+    @NonNull public FingerprintSensorPropertiesInternal getSensorProperties() {
         return mSensorProperties;
     }
 
-    @Nullable AidlSession getSessionForUser(int userId) {
+    @Nullable protected AidlSession getSessionForUser(int userId) {
         if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
             return mCurrentSession;
         } else {
@@ -180,15 +267,18 @@
                 biometricStateCallback, mProvider, this);
     }
 
-    @NonNull BiometricScheduler getScheduler() {
+    @NonNull public BiometricScheduler getScheduler() {
         return mScheduler;
     }
 
-    @NonNull LockoutCache getLockoutCache() {
-        return mLockoutCache;
+    @NonNull protected LockoutTracker getLockoutTracker(boolean forAuth) {
+        if (forAuth) {
+            return null;
+        }
+        return mLockoutTracker;
     }
 
-    @NonNull Map<Integer, Long> getAuthenticatorIds() {
+    @NonNull public Map<Integer, Long> getAuthenticatorIds() {
         return mAuthenticatorIds;
     }
 
@@ -262,4 +352,49 @@
         mScheduler.reset();
         mCurrentSession = null;
     }
+
+    @NonNull protected Handler getHandler() {
+        return mHandler;
+    }
+
+    @NonNull protected Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Returns true if the sensor hardware is detected.
+     */
+    protected boolean isHardwareDetected(String halInstance) {
+        if (mTestHalEnabled) {
+            return true;
+        }
+        return (ServiceManager.checkService(IFingerprint.DESCRIPTOR + "/" + halInstance)
+                != null);
+    }
+
+    @NonNull protected BiometricContext getBiometricContext() {
+        return mBiometricContext;
+    }
+
+    /**
+     * Returns lockout mode of this sensor.
+     */
+    @LockoutTracker.LockoutMode
+    public int getLockoutModeForUser(int userId) {
+        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                Utils.getCurrentStrength(mSensorProperties.sensorId));
+    }
+
+    public void setScheduler(BiometricScheduler scheduler) {
+        mScheduler = scheduler;
+    }
+
+    public void setLazySession(
+            Supplier<AidlSession> lazySession) {
+        mLazySession = lazySession;
+    }
+
+    public FingerprintProvider getProvider() {
+        return mProvider;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index a4e6025..5c5b992 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -29,7 +29,8 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
 
 import java.io.File;
 import java.util.Map;
@@ -38,7 +39,8 @@
 /**
  * Sets the HAL's current active user, and updates the framework's authenticatorId cache.
  */
-public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometricsFingerprint> {
+public class FingerprintUpdateActiveUserClient extends
+        StartUserClient<IBiometricsFingerprint, AidlSession> {
 
     private static final String TAG = "FingerprintUpdateActiveUserClient";
     private static final String FP_DATA_DIR = "fpdata";
@@ -53,11 +55,24 @@
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
             @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            Supplier<Integer> currentUserId,
+            @NonNull Supplier<Integer> currentUserId,
             boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
             boolean forceUpdateAuthenticatorId) {
-        super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, logger, biometricContext);
+        this(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, currentUserId,
+                hasEnrolledBiometrics, authenticatorIds, forceUpdateAuthenticatorId,
+                (newUserId, newUser, halInterfaceVersion) -> {});
+    }
+
+    FingerprintUpdateActiveUserClient(@NonNull Context context,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull Supplier<Integer> currentUserId,
+            boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
+            boolean forceUpdateAuthenticatorId,
+            @NonNull UserStartedCallback<AidlSession> userStartedCallback) {
+        super(context, lazyDaemon, null /* token */, userId, sensorId, logger, biometricContext,
+                userStartedCallback);
         mCurrentUserId = currentUserId;
         mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
         mHasEnrolledBiometrics = hasEnrolledBiometrics;
@@ -70,6 +85,7 @@
 
         if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
             Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
+            mUserStartedCallback.onUserStarted(getTargetUserId(), null, 0);
             callback.onClientFinished(this, true /* success */);
             return;
         }
@@ -119,6 +135,7 @@
             getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
             mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
                     ? getFreshDaemon().getAuthenticatorId() : 0L);
+            mUserStartedCallback.onUserStarted(targetId, null, 0);
             mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to setActiveGroup: " + e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
index c3e5cbe..e9a48e7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
@@ -20,6 +20,7 @@
 import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
 
 import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
 
 import java.util.ArrayList;
@@ -73,12 +74,12 @@
 
     @Override
     public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
-        mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId});
+        mAidlResponseHandler.onEnrollmentRemoved(fingerId, remaining);
     }
 
     @Override
     public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
-        mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId});
+        mAidlResponseHandler.onEnrollmentEnumerated(fingerId, remaining);
     }
 
     void onChallengeGenerated(long challenge) {
@@ -92,4 +93,8 @@
     void onResetLockout() {
         mAidlResponseHandler.onLockoutCleared();
     }
+
+    <T extends BaseClientMonitor> void unsupportedClientScheduled(Class<T> className) {
+        mAidlResponseHandler.onUnsupportedClientScheduled(className);
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
new file mode 100644
index 0000000..0bb61415
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2023 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.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.os.Handler;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
+import com.android.server.biometrics.sensors.fingerprint.aidl.Sensor;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL sensor configurations to an AIDL Sensor.
+ */
+public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRecipient {
+    private static final String TAG = "HidlToAidlSensorAdapter";
+
+    private final Runnable mInternalCleanupRunnable;
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+    private LockoutFrameworkImpl mLockoutTracker;
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
+    private final AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    private int mCurrentUserId = UserHandle.USER_NULL;
+    private IBiometricsFingerprint mDaemon;
+    private AidlSession mSession;
+
+    private final StartUserClient.UserStartedCallback<AidlSession> mUserStartedCallback =
+            (newUserId, newUser, halInterfaceVersion) -> {
+                if (mCurrentUserId != newUserId) {
+                    handleUserChanged(newUserId);
+                }
+            };
+
+    public HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+            @NonNull Context context, @NonNull Handler handler,
+            @NonNull SensorProps prop,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresHardwareAuthToken,
+            @NonNull Runnable internalCleanupRunnable) {
+        this(tag, provider, context, handler, prop, lockoutResetDispatcher,
+                gestureAvailabilityDispatcher, biometricContext,
+                resetLockoutRequiresHardwareAuthToken, internalCleanupRunnable,
+                new AuthSessionCoordinator(), null /* daemon */,
+                null /* onEnrollSuccessCallback */);
+    }
+
+    @VisibleForTesting
+    HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+            @NonNull Context context, @NonNull Handler handler,
+            @NonNull SensorProps prop,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresHardwareAuthToken,
+            @NonNull Runnable internalCleanupRunnable,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @Nullable IBiometricsFingerprint daemon,
+            @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+        super(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(prop,
+                        new ArrayList<>(), resetLockoutRequiresHardwareAuthToken),
+                lockoutResetDispatcher,
+                gestureAvailabilityDispatcher,
+                biometricContext, null /* session */);
+        mLockoutResetDispatcher = lockoutResetDispatcher;
+        mInternalCleanupRunnable = internalCleanupRunnable;
+        mAuthSessionCoordinator = authSessionCoordinator;
+        mDaemon = daemon;
+        mAidlResponseHandlerCallback = aidlResponseHandlerCallback == null
+                ? new AidlResponseHandler.AidlResponseHandlerCallback() {
+                    @Override
+                    public void onEnrollSuccess() {
+                        getScheduler()
+                                .scheduleClientMonitor(getFingerprintUpdateActiveUserClient(
+                                        mCurrentUserId, true /* forceUpdateAuthenticatorIds */));
+                    }
+
+                    @Override
+                    public void onHardwareUnavailable() {
+                        mDaemon = null;
+                        mSession = null;
+                        mCurrentUserId = UserHandle.USER_NULL;
+                    }
+                } : aidlResponseHandlerCallback;
+    }
+
+    @Override
+    public void serviceDied(long cookie) {
+        Slog.d(TAG, "HAL died.");
+        mSession = null;
+        mDaemon = null;
+    }
+
+    @Override
+    @LockoutTracker.LockoutMode
+    public int getLockoutModeForUser(int userId) {
+        return mLockoutTracker.getLockoutModeForUser(userId);
+    }
+
+    @Override
+    public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            LockoutResetDispatcher lockoutResetDispatcher) {
+        setLazySession(this::getSession);
+        setScheduler(new UserAwareBiometricScheduler(TAG,
+                BiometricScheduler.sensorTypeFromFingerprintProperties(getSensorProperties()),
+                gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchCallback()));
+        mLockoutTracker = new LockoutFrameworkImpl(getContext(),
+                userId -> mLockoutResetDispatcher.notifyLockoutResetCallbacks(
+                        getSensorProperties().sensorId));
+    }
+
+    @Override
+    @Nullable
+    protected AidlSession getSessionForUser(int userId) {
+        if (mSession != null && mSession.getUserId() == userId) {
+            return mSession;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    protected boolean isHardwareDetected(String halInstance) {
+        return getIBiometricsFingerprint() != null;
+    }
+
+    @NonNull
+    @Override
+    protected LockoutTracker getLockoutTracker(boolean forAuth) {
+        return mLockoutTracker;
+    }
+
+    private synchronized AidlSession getSession() {
+        if (mSession != null && mDaemon != null) {
+            return mSession;
+        } else {
+            return mSession = new AidlSession(this::getIBiometricsFingerprint,
+                    mCurrentUserId, getAidlResponseHandler());
+        }
+    }
+
+    private AidlResponseHandler getAidlResponseHandler() {
+        return new AidlResponseHandler(getContext(),
+                getScheduler(),
+                getSensorProperties().sensorId,
+                mCurrentUserId,
+                mLockoutTracker,
+                mLockoutResetDispatcher,
+                mAuthSessionCoordinator,
+                () -> {}, mAidlResponseHandlerCallback);
+    }
+
+    @VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() {
+        if (getProvider().getTestHalEnabled()) {
+            final TestHal testHal = new TestHal(getContext(), getSensorProperties().sensorId);
+            testHal.setNotify(new HidlToAidlCallbackConverter(getAidlResponseHandler()));
+            return testHal;
+        }
+
+        if (mDaemon != null) {
+            return mDaemon;
+        }
+
+        try {
+            mDaemon = IBiometricsFingerprint.getService();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get fingerprint HAL", e);
+        } catch (java.util.NoSuchElementException e) {
+            // Service doesn't exist or cannot be opened.
+            Slog.w(TAG, "NoSuchElementException", e);
+        }
+
+        if (mDaemon == null) {
+            Slog.w(TAG, "Fingerprint HAL not available");
+            return null;
+        }
+
+        mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+        Slog.d(TAG, "Fingerprint HAL ready");
+
+        scheduleLoadAuthenticatorIds();
+        mInternalCleanupRunnable.run();
+        return mDaemon;
+    }
+
+    private UserAwareBiometricScheduler.UserSwitchCallback getUserSwitchCallback() {
+        return new UserAwareBiometricScheduler.UserSwitchCallback() {
+            @NonNull
+            @Override
+            public StopUserClient<?> getStopUserClient(int userId) {
+                return new StopUserClient<IBiometricsFingerprint>(getContext(),
+                        HidlToAidlSensorAdapter.this::getIBiometricsFingerprint,
+                        null /* token */, userId, getSensorProperties().sensorId,
+                        BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
+                        () -> {
+                            mCurrentUserId = UserHandle.USER_NULL;
+                            mSession = null;
+                        }) {
+                    @Override
+                    public void start(@NonNull ClientMonitorCallback callback) {
+                        super.start(callback);
+                        startHalOperation();
+                    }
+
+                    @Override
+                    protected void startHalOperation() {
+                        onUserStopped();
+                    }
+
+                    @Override
+                    public void unableToStart() {
+                        getCallback().onClientFinished(this, false /* success */);
+                    }
+                };
+            }
+
+            @NonNull
+            @Override
+            public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+                return getFingerprintUpdateActiveUserClient(newUserId,
+                        false /* forceUpdateAuthenticatorId */);
+            }
+        };
+    }
+
+    private FingerprintUpdateActiveUserClient getFingerprintUpdateActiveUserClient(int newUserId,
+            boolean forceUpdateAuthenticatorIds) {
+        return new FingerprintUpdateActiveUserClient(getContext(),
+                this::getIBiometricsFingerprint, newUserId, TAG,
+                getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()),
+                getBiometricContext(), () -> mCurrentUserId,
+                !FingerprintUtils.getInstance(getSensorProperties().sensorId)
+                        .getBiometricsForUser(getContext(),
+                newUserId).isEmpty(), getAuthenticatorIds(), forceUpdateAuthenticatorIds,
+                mUserStartedCallback);
+    }
+
+    private void scheduleLoadAuthenticatorIds() {
+        getHandler().post(() -> {
+            for (UserInfo user : UserManager.get(getContext()).getAliveUsers()) {
+                final int targetUserId = user.id;
+                if (!getAuthenticatorIds().containsKey(targetUserId)) {
+                    getScheduler().scheduleClientMonitor(getFingerprintUpdateActiveUserClient(
+                            targetUserId, true /* forceUpdateAuthenticatorIds */));
+                }
+            }
+        });
+    }
+
+    @VisibleForTesting void handleUserChanged(int newUserId) {
+        Slog.d(TAG, "User changed. Current user is " + newUserId);
+        mSession = null;
+        mCurrentUserId = newUserId;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
similarity index 73%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
index b48d232..2fc00e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
@@ -25,20 +25,25 @@
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGetAuthenticatorIdClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInvalidationClient;
 
 import java.util.function.Supplier;
 
 /**
- * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ * Adapter to convert HIDL methods into AIDL interface {@link ISession}.
  */
-public class AidlToHidlAdapter implements ISession {
-    private final String TAG = "AidlToHidlAdapter";
+public class HidlToAidlSessionAdapter implements ISession {
+
+    private final String TAG = "HidlToAidlSessionAdapter";
+
     @VisibleForTesting
     static final int ENROLL_TIMEOUT_SEC = 60;
     @NonNull
@@ -46,22 +51,13 @@
     private final int mUserId;
     private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
 
-    public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId,
+    public HidlToAidlSessionAdapter(Supplier<IBiometricsFingerprint> session, int userId,
             AidlResponseHandler aidlResponseHandler) {
         mSession = session;
         mUserId = userId;
         setCallback(aidlResponseHandler);
     }
 
-    private void setCallback(AidlResponseHandler aidlResponseHandler) {
-        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
-        try {
-            mSession.get().setNotify(mHidlToAidlCallbackConverter);
-        } catch (RemoteException e) {
-            Slog.d(TAG, "Failed to set callback");
-        }
-    }
-
     @Override
     public IBinder asBinder() {
         return null;
@@ -125,12 +121,16 @@
 
     @Override
     public void getAuthenticatorId() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "getAuthenticatorId unsupported in HIDL");
+        mHidlToAidlCallbackConverter.unsupportedClientScheduled(
+                FingerprintGetAuthenticatorIdClient.class);
     }
 
     @Override
     public void invalidateAuthenticatorId() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "invalidateAuthenticatorId unsupported in HIDL");
+        mHidlToAidlCallbackConverter.unsupportedClientScheduled(
+                FingerprintInvalidationClient.class);
     }
 
     @Override
@@ -140,72 +140,92 @@
 
     @Override
     public void close() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "close unsupported in HIDL");
     }
 
     @Override
     public void onUiReady() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "onUiReady unsupported in HIDL");
     }
 
     @Override
     public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
             throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Log.e(TAG, "authenticateWithContext unsupported in HIDL");
+        return authenticate(operationId);
     }
 
     @Override
     public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context)
             throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Log.e(TAG, "enrollWithContext unsupported in HIDL");
+        return enroll(hat);
     }
 
     @Override
     public ICancellationSignal detectInteractionWithContext(OperationContext context)
             throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Log.e(TAG, "enrollWithContext unsupported in HIDL");
+        return detectInteraction();
     }
 
     @Override
     public void onPointerDownWithContext(PointerContext context) throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "onPointerDownWithContext unsupported in HIDL");
+        onPointerDown(context.pointerId, (int) context.x, (int) context.y, context.minor,
+                context.major);
     }
 
     @Override
     public void onPointerUpWithContext(PointerContext context) throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "onPointerUpWithContext unsupported in HIDL");
+        onPointerUp(context.pointerId);
     }
 
     @Override
     public void onContextChanged(OperationContext context) throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "onContextChanged unsupported in HIDL");
     }
 
     @Override
     public void onPointerCancelWithContext(PointerContext context) throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "onPointerCancelWithContext unsupported in HIDL");
     }
 
     @Override
     public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "setIgnoreDisplayTouches unsupported in HIDL");
     }
 
     @Override
     public int getInterfaceVersion() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "getInterfaceVersion unsupported in HIDL");
         return 0;
     }
 
     @Override
     public String getInterfaceHash() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "getInterfaceHash unsupported in HIDL");
         return null;
     }
 
+    private void setCallback(AidlResponseHandler aidlResponseHandler) {
+        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+        try {
+            if (mSession.get() != null) {
+                long halId = mSession.get().setNotify(mHidlToAidlCallbackConverter);
+                Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId);
+                if (halId == 0) {
+                    Slog.d(TAG, "Unable to set HIDL callback.");
+                }
+            } else {
+                Slog.e(TAG, "Unable to set HIDL callback. HIDL daemon is null.");
+            }
+        } catch (RemoteException e) {
+            Slog.d(TAG, "Failed to set callback");
+        }
+    }
+
     private class Cancellation extends ICancellationSignal.Stub {
 
         Cancellation() {}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index 0730c67..2f77275 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
 
+import android.annotation.NonNull;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -31,15 +32,18 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
+import java.util.function.Function;
+
 /**
  * Tracks and enforces biometric lockout for biometric sensors that do not support lockout in the
  * HAL.
  */
 public class LockoutFrameworkImpl implements LockoutTracker {
 
-    private static final String TAG = "LockoutTracker";
+    private static final String TAG = "LockoutFrameworkImpl";
     private static final String ACTION_LOCKOUT_RESET =
             "com.android.server.biometrics.sensors.fingerprint.ACTION_LOCKOUT_RESET";
     private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
@@ -65,22 +69,32 @@
         void onLockoutReset(int userId);
     }
 
-    private final Context mContext;
     private final LockoutResetCallback mLockoutResetCallback;
     private final SparseBooleanArray mTimedLockoutCleared;
     private final SparseIntArray mFailedAttempts;
     private final AlarmManager mAlarmManager;
     private final LockoutReceiver mLockoutReceiver;
     private final Handler mHandler;
+    private final Function<Integer, PendingIntent> mLockoutResetIntent;
 
-    public LockoutFrameworkImpl(Context context, LockoutResetCallback lockoutResetCallback) {
-        mContext = context;
+    public LockoutFrameworkImpl(@NonNull Context context,
+            @NonNull LockoutResetCallback lockoutResetCallback) {
+        this(context, lockoutResetCallback, (userId) -> PendingIntent.getBroadcast(context, userId,
+                new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+    }
+
+    @VisibleForTesting
+    LockoutFrameworkImpl(@NonNull Context context,
+            @NonNull LockoutResetCallback lockoutResetCallback,
+            @NonNull Function<Integer, PendingIntent> lockoutResetIntent) {
         mLockoutResetCallback = lockoutResetCallback;
         mTimedLockoutCleared = new SparseBooleanArray();
         mFailedAttempts = new SparseIntArray();
         mAlarmManager = context.getSystemService(AlarmManager.class);
         mLockoutReceiver = new LockoutReceiver();
         mHandler = new Handler(Looper.getMainLooper());
+        mLockoutResetIntent = lockoutResetIntent;
 
         context.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
                 RESET_FINGERPRINT_LOCKOUT, null /* handler */, Context.RECEIVER_EXPORTED);
@@ -129,34 +143,18 @@
         return LOCKOUT_NONE;
     }
 
-    /**
-     * Clears lockout for Fingerprint HIDL HAL
-     */
     @Override
-    public void setLockoutModeForUser(int userId, int mode) {
-        mFailedAttempts.put(userId, 0);
-        mTimedLockoutCleared.put(userId, true);
-        // If we're asked to reset failed attempts externally (i.e. from Keyguard),
-        // the alarm might still be pending; remove it.
-        cancelLockoutResetForUser(userId);
-        mLockoutResetCallback.onLockoutReset(userId);
-    }
+    public void setLockoutModeForUser(int userId, int mode) {}
 
     private void cancelLockoutResetForUser(int userId) {
-        mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
+        mAlarmManager.cancel(mLockoutResetIntent.apply(userId));
     }
 
     private void scheduleLockoutResetForUser(int userId) {
         mHandler.post(() -> {
             mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
-                getLockoutResetIntentForUser(userId));
+                    SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
+                    mLockoutResetIntent.apply(userId));
         });
     }
-
-    private PendingIntent getLockoutResetIntentForUser(int userId) {
-        return PendingIntent.getBroadcast(mContext, userId,
-                new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 3b5cae3..88b2ed4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -50,14 +50,22 @@
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.face.FaceSensorConfigurations;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.iris.IIrisService;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.InstrumentationRegistry;
@@ -89,6 +97,9 @@
 
     @Rule
     public MockitoRule mockitorule = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -118,6 +129,10 @@
     @Captor
     private ArgumentCaptor<List<FingerprintSensorPropertiesInternal>> mFingerprintPropsCaptor;
     @Captor
+    private ArgumentCaptor<FingerprintSensorConfigurations> mFingerprintSensorConfigurationsCaptor;
+    @Captor
+    private ArgumentCaptor<FaceSensorConfigurations> mFaceSensorConfigurationsCaptor;
+    @Captor
     private ArgumentCaptor<List<FaceSensorPropertiesInternal>> mFacePropsCaptor;
 
     @Before
@@ -143,6 +158,9 @@
         when(mContext.getResources()).thenReturn(mResources);
         when(mInjector.getBiometricService()).thenReturn(mBiometricService);
         when(mInjector.getConfiguration(any())).thenReturn(config);
+        when(mInjector.getFaceConfiguration(any())).thenReturn(config);
+        when(mInjector.getFingerprintConfiguration(any())).thenReturn(config);
+        when(mInjector.getIrisConfiguration(any())).thenReturn(config);
         when(mInjector.getFingerprintService()).thenReturn(mFingerprintService);
         when(mInjector.getFaceService()).thenReturn(mFaceService);
         when(mInjector.getIrisService()).thenReturn(mIrisService);
@@ -173,12 +191,13 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
     public void testRegisterAuthenticator_registerAuthenticators() throws Exception {
         final int fingerprintId = 0;
         final int fingerprintStrength = 15;
 
         final int faceId = 1;
-        final int faceStrength = 4095;
+        final int faceStrength = 15;
 
         final String[] config = {
                 // ID0:Fingerprint:Strong
@@ -206,6 +225,51 @@
                 Utils.authenticatorStrengthToPropertyStrength(faceStrength));
     }
 
+    @Test
+    @RequiresFlagsEnabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
+    public void testRegisterAuthenticator_registerAuthenticatorsLegacy() throws RemoteException {
+        final int fingerprintId = 0;
+        final int fingerprintStrength = 15;
+
+        final int faceId = 1;
+        final int faceStrength = 4095;
+
+        final String[] config = {
+                // ID0:Fingerprint:Strong
+                String.format("%d:2:%d", fingerprintId, fingerprintStrength),
+                // ID2:Face:Convenience
+                String.format("%d:8:%d", faceId, faceStrength)
+        };
+
+        when(mInjector.getFingerprintConfiguration(any())).thenReturn(config);
+        when(mInjector.getFaceConfiguration(any())).thenReturn(config);
+        when(mInjector.getFingerprintAidlInstances()).thenReturn(new String[]{});
+        when(mInjector.getFaceAidlInstances()).thenReturn(new String[]{});
+
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        verify(mFingerprintService).registerAuthenticatorsLegacy(
+                mFingerprintSensorConfigurationsCaptor.capture());
+
+        final SensorProps[] fingerprintProp = mFingerprintSensorConfigurationsCaptor.getValue()
+                .getSensorPairForInstance("defaultHIDL").second;
+
+        assertEquals(fingerprintProp[0].commonProps.sensorId, fingerprintId);
+        assertEquals(fingerprintProp[0].commonProps.sensorStrength,
+                Utils.authenticatorStrengthToPropertyStrength(fingerprintStrength));
+
+        verify(mFaceService).registerAuthenticatorsLegacy(
+                mFaceSensorConfigurationsCaptor.capture());
+
+        final android.hardware.biometrics.face.SensorProps[] faceProp =
+                mFaceSensorConfigurationsCaptor.getValue()
+                        .getSensorPairForInstance("defaultHIDL").second;
+
+        assertEquals(faceProp[0].commonProps.sensorId, faceId);
+        assertEquals(faceProp[0].commonProps.sensorStrength,
+                Utils.authenticatorStrengthToPropertyStrength(faceStrength));
+    }
 
     // TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
new file mode 100644
index 0000000..c9e1c4a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2023 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.biometrics.sensors.face;
+
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceSensorConfigurations;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.biometrics.Flags;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@SmallTest
+public class FaceServiceTest {
+    private static final int ID_DEFAULT = 2;
+    private static final int ID_VIRTUAL = 6;
+    private static final String NAME_DEFAULT = "default";
+    private static final String NAME_VIRTUAL = "virtual";
+
+    @Rule
+    public final MockitoRule mMockito = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+    @Rule
+    public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
+    @Mock
+    FaceProvider mFaceProviderDefault;
+    @Mock
+    FaceProvider mFaceProviderVirtual;
+    @Mock
+    IFace mDefaultFaceDaemon;
+    @Mock
+    IFace mVirtualFaceDaemon;
+    @Mock
+    IBiometricService mIBiometricService;
+
+    private final SensorProps mDefaultSensorProps = new SensorProps();
+    private final SensorProps mVirtualSensorProps = new SensorProps();
+    private FaceService mFaceService;
+    private final FaceSensorPropertiesInternal mSensorPropsDefault =
+            new FaceSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
+                    2 /* maxEnrollmentsPerUser */,
+                    List.of(),
+                    TYPE_UNKNOWN,
+                    true /* supportsFaceDetection */,
+                    true /* supportsSelfIllumination */,
+                    false /* resetLockoutRequiresChallenge */);
+    private final FaceSensorPropertiesInternal mSensorPropsVirtual =
+            new FaceSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG,
+                    2 /* maxEnrollmentsPerUser */,
+                    List.of(),
+                    TYPE_UNKNOWN,
+                    true /* supportsFaceDetection */,
+                    true /* supportsSelfIllumination */,
+                    false /* resetLockoutRequiresChallenge */);
+    private FaceSensorConfigurations mFaceSensorConfigurations;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mDefaultFaceDaemon.getSensorProps()).thenReturn(
+                new SensorProps[]{mDefaultSensorProps});
+        when(mVirtualFaceDaemon.getSensorProps()).thenReturn(
+                new SensorProps[]{mVirtualSensorProps});
+        when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
+        when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+
+        mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+        mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
+                (name) -> {
+                    if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+                        return mDefaultFaceDaemon;
+                    } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+                        return mVirtualFaceDaemon;
+                    }
+                    return null;
+                });
+    }
+
+    private void initService() {
+        mFaceService = new FaceService(mContext,
+                (filteredSensorProps, resetLockoutRequiresChallenge) -> {
+                    if (NAME_DEFAULT.equals(filteredSensorProps.first)) return mFaceProviderDefault;
+                    if (NAME_VIRTUAL.equals(filteredSensorProps.first)) return mFaceProviderVirtual;
+                    return null;
+                }, () -> mIBiometricService);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+        initService();
+
+        mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT),
+                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)),
+                any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
+        initService();
+        Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+                Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
+
+        mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+        mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+        mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL},
+                (name) -> {
+                    if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+                        return mDefaultFaceDaemon;
+                    } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+                        return mVirtualFaceDaemon;
+                    }
+                    return null;
+                });
+        initService();
+
+        mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+    }
+
+    private void waitForRegistration() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mFaceService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
+                new IFaceAuthenticatorsRegisteredCallback.Stub() {
+                    public void onAllAuthenticatorsRegistered(
+                            List<FaceSensorPropertiesInternal> sensors) {
+                        latch.countDown();
+                    }
+                });
+        latch.await(10, TimeUnit.SECONDS);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index f43120d..8b1a291 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -32,15 +35,19 @@
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.HidlFaceSensorConfig;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -49,6 +56,7 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -59,6 +67,10 @@
 @SmallTest
 public class FaceProviderTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final String TAG = "FaceProviderTest";
 
     private static final float FRR_THRESHOLD = 0.2f;
@@ -109,7 +121,7 @@
 
         mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
                 mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
-                mDaemon);
+                mDaemon, false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
     }
 
     @Test
@@ -124,10 +136,38 @@
 
             assertThat(currentClient).isInstanceOf(FaceInternalCleanupClient.class);
             assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
-            assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+            assertThat(currentClient.getTargetUserId()).isEqualTo(USER_SYSTEM);
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testAddingHidlSensors() {
+        when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
+        when(mResources.getBoolean(anyInt())).thenReturn(false);
+
+        final int faceId = 0;
+        final int faceStrength = 15;
+        final String config = String.format("%d:8:%d", faceId, faceStrength);
+        final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
+        faceSensorConfig.parse(config, mContext);
+        final HidlFaceSensorConfig[] hidlFaceSensorConfig =
+                new HidlFaceSensorConfig[]{faceSensorConfig};
+        mFaceProvider = new FaceProvider(mContext,
+                mBiometricStateCallback, hidlFaceSensorConfig, TAG,
+                mLockoutResetDispatcher, mBiometricContext, mDaemon,
+                true /* resetLockoutRequiresChallenge */,
+                true /* testHalEnabled */);
+
+        assertThat(mFaceProvider.mFaceSensors.get(faceId)
+                .getLazySession().get().getUserId()).isEqualTo(USER_NULL);
+
+        waitForIdle();
+
+        assertThat(mFaceProvider.mFaceSensors.get(faceId)
+                .getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
+    }
+
     @SuppressWarnings("rawtypes")
     @Test
     public void halServiceDied_resetsAllSchedulers() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 7a293e8..e7f7195 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -157,7 +157,7 @@
                 sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
         final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext,
                 null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);
-
+        sensor.init(mLockoutResetDispatcher, mFaceProvider);
         mScheduler.reset();
 
         assertNull(mScheduler.getCurrentClient());
@@ -185,6 +185,7 @@
                 sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
         final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
                 internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession);
+        sensor.init(mLockoutResetDispatcher, mFaceProvider);
         mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
         sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
                 USER_ID, mHalCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
new file mode 100644
index 0000000..4e43332
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2023 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.biometrics.sensors.face.hidl;
+
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalUint64;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.face.Face;
+import android.hardware.face.HidlFaceSensorConfig;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class HidlToAidlSensorAdapterTest {
+    private static final String TAG = "HidlToAidlSensorAdapterTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final byte[] HAT = new byte[69];
+    @Rule
+    public final MockitoRule mMockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricService mBiometricService;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcherForSensor;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcherForClient;
+    @Mock
+    private BiometricLogger mLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+    @Mock
+    private FaceProvider mFaceProvider;
+    @Mock
+    private Runnable mInternalCleanupAndGetFeatureRunnable;
+    @Mock
+    private IBiometricsFace mDaemon;
+    @Mock
+    AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    @Mock
+    BiometricUtils<Face> mBiometricUtils;
+
+    private final TestLooper mLooper = new TestLooper();
+    private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
+    private final TestableContext mContext = new TestableContext(
+            ApplicationProvider.getApplicationContext());
+
+    @Before
+    public void setUp() throws RemoteException {
+        final OptionalUint64 result = new OptionalUint64();
+        result.status = Status.OK;
+
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        when(mDaemon.setCallback(any())).thenReturn(result);
+        doAnswer((answer) -> {
+            mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+                    .onLockoutChanged(0);
+            return null;
+        }).when(mDaemon).resetLockout(any());
+        doAnswer((answer) -> {
+            mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+                    .onEnrollmentProgress(1, 0);
+            return null;
+        }).when(mDaemon).enroll(any(), anyInt(), any());
+
+        mContext.getOrCreateTestableResources();
+
+        final String config = String.format("%d:8:15", SENSOR_ID);
+        final BiometricScheduler scheduler = new BiometricScheduler(TAG,
+                new Handler(mLooper.getLooper()),
+                BiometricScheduler.SENSOR_TYPE_FACE,
+                null /* gestureAvailabilityTracker */,
+                mBiometricService, 10 /* recentOperationsLimit */);
+        final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
+        faceSensorConfig.parse(config, mContext);
+        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG, mFaceProvider,
+                mContext, new Handler(mLooper.getLooper()), faceSensorConfig,
+                mLockoutResetDispatcherForSensor, mBiometricContext,
+                false /* resetLockoutRequiresChallenge */, mInternalCleanupAndGetFeatureRunnable,
+                mAuthSessionCoordinator, mDaemon, mAidlResponseHandlerCallback);
+        mHidlToAidlSensorAdapter.init(mLockoutResetDispatcherForSensor, mFaceProvider);
+        mHidlToAidlSensorAdapter.setScheduler(scheduler);
+        mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
+    }
+
+    @Test
+    public void lockoutTimedResetViaClient() {
+        setLockoutTimed();
+
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+                new FaceResetLockoutClient(mContext, mHidlToAidlSensorAdapter.getLazySession(),
+                        USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+                        mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+                        mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+        mLooper.dispatchAll();
+
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false/* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mLockoutResetDispatcherForClient, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutTimedResetViaCallback() {
+        setLockoutTimed();
+
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutChanged(0);
+        mLooper.dispatchAll();
+
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(eq(
+                SENSOR_ID));
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID))
+                .isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutPermanentResetViaCallback() {
+        setLockoutPermanent();
+
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutChanged(0);
+        mLooper.dispatchAll();
+
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(eq(
+                SENSOR_ID));
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutPermanentResetViaClient() {
+        setLockoutPermanent();
+
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+                new FaceResetLockoutClient(mContext,
+                        mHidlToAidlSensorAdapter.getLazySession(),
+                        USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+                        mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+                        mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+        mLooper.dispatchAll();
+
+        verify(mLockoutResetDispatcherForClient, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void verifyOnEnrollSuccessCallback() {
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(new FaceEnrollClient(mContext,
+                mHidlToAidlSensorAdapter.getLazySession(), null /* token */, null /* listener */,
+                USER_ID, HAT, TAG, 1 /* requestId */, mBiometricUtils,
+                new int[]{} /* disabledFeatures */, ENROLL_TIMEOUT_SEC, null /* previewSurface */,
+                SENSOR_ID, mLogger, mBiometricContext, 1 /* maxTemplatesPerUser */,
+                false /* debugConsent */));
+        mLooper.dispatchAll();
+
+        verify(mAidlResponseHandlerCallback).onEnrollSuccess();
+    }
+
+    private void setLockoutTimed() {
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+                .onLockoutChanged(1);
+        mLooper.dispatchAll();
+
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID))
+                .isEqualTo(LockoutTracker.LOCKOUT_TIMED);
+    }
+
+    private void setLockoutPermanent() {
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+                .onLockoutChanged(-1);
+        mLooper.dispatchAll();
+
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_PERMANENT);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
similarity index 80%
rename from services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
rename to services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
index 9a40e8a..b9a4fb4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.biometrics.sensors.face.hidl;
 
-import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
 import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -68,7 +68,7 @@
 
 @Presubmit
 @SmallTest
-public class AidlToHidlAdapterTest {
+public class HidlToAidlSessionAdapterTest {
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
 
@@ -84,25 +84,32 @@
     private Clock mClock;
 
     private final long mChallenge = 100L;
-    private AidlToHidlAdapter mAidlToHidlAdapter;
+    private HidlToAidlSessionAdapter mHidlToAidlSessionAdapter;
     private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */);
     private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY;
     private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION};
 
     @Before
     public void setUp() throws RemoteException {
+        final OptionalUint64 setCallbackResult = new OptionalUint64();
+        setCallbackResult.value = 1;
+
+        when(mSession.setCallback(any())).thenReturn(setCallbackResult);
+
         TestableContext testableContext = new TestableContext(
                 InstrumentationRegistry.getInstrumentation().getContext());
         testableContext.addMockSystemService(FaceManager.class, mFaceManager);
-        mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */,
-                mAidlResponseHandler, mClock);
+
+        mHidlToAidlSessionAdapter = new HidlToAidlSessionAdapter(testableContext, () -> mSession,
+                0 /* userId */, mAidlResponseHandler, mClock);
         mHardwareAuthToken.timestamp = new Timestamp();
         mHardwareAuthToken.mac = new byte[10];
-        final OptionalUint64 result = new OptionalUint64();
-        result.status = Status.OK;
-        result.value = mChallenge;
 
-        when(mSession.generateChallenge(anyInt())).thenReturn(result);
+        final OptionalUint64 generateChallengeResult = new OptionalUint64();
+        generateChallengeResult.status = Status.OK;
+        generateChallengeResult.value = mChallenge;
+
+        when(mSession.generateChallenge(anyInt())).thenReturn(generateChallengeResult);
         when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace));
     }
 
@@ -112,16 +119,16 @@
 
         final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class);
 
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
 
         verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC);
         verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture());
         assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge);
 
         forwardTime(10 /* seconds */);
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
         forwardTime(20 /* seconds */);
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
 
         //Confirms that the challenge is cached and the hal method is not called again
         verifyNoMoreInteractions(mSession);
@@ -129,7 +136,7 @@
                 .onChallengeGenerated(mChallenge);
 
         forwardTime(60 /* seconds */);
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
 
         //HAL method called after challenge has timed out
         verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC);
@@ -138,11 +145,11 @@
     @Test
     public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException {
         for (int i = 0; i < 3; i++) {
-            mAidlToHidlAdapter.generateChallenge();
+            mHidlToAidlSessionAdapter.generateChallenge();
             forwardTime(10 /* seconds */);
         }
         for (int i = 0; i < 3; i++) {
-            mAidlToHidlAdapter.revokeChallenge(0);
+            mHidlToAidlSessionAdapter.revokeChallenge(0);
             forwardTime((i + 1) * 10 /* seconds */);
         }
 
@@ -151,20 +158,19 @@
 
     @Test
     public void testRevokeChallenge_timeout() throws RemoteException {
-        mAidlToHidlAdapter.generateChallenge();
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
         forwardTime(700);
-        mAidlToHidlAdapter.generateChallenge();
-        mAidlToHidlAdapter.revokeChallenge(0);
+        mHidlToAidlSessionAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.revokeChallenge(0);
 
         verify(mSession).revokeChallenge();
     }
 
     @Test
     public void testEnroll() throws RemoteException {
-        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken,
-                EnrollmentType.DEFAULT, mFeatures,
-                null /* previewSurface */);
+        ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.enroll(
+                mHardwareAuthToken, EnrollmentType.DEFAULT, mFeatures, null /* previewSurface */);
         ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class);
 
         verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture());
@@ -182,7 +188,8 @@
     @Test
     public void testAuthenticate() throws RemoteException {
         final int operationId = 2;
-        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+        ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.authenticate(
+                operationId);
 
         verify(mSession).authenticate(operationId);
 
@@ -193,7 +200,7 @@
 
     @Test
     public void testDetectInteraction() throws RemoteException {
-        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+        ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.detectInteraction();
 
         verify(mSession).authenticate(0);
 
@@ -204,7 +211,7 @@
 
     @Test
     public void testEnumerateEnrollments() throws RemoteException {
-        mAidlToHidlAdapter.enumerateEnrollments();
+        mHidlToAidlSessionAdapter.enumerateEnrollments();
 
         verify(mSession).enumerate();
     }
@@ -212,7 +219,7 @@
     @Test
     public void testRemoveEnrollment() throws RemoteException {
         final int[] enrollments = new int[]{1};
-        mAidlToHidlAdapter.removeEnrollments(enrollments);
+        mHidlToAidlSessionAdapter.removeEnrollments(enrollments);
 
         verify(mSession).remove(enrollments[0]);
     }
@@ -226,8 +233,8 @@
 
         when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
 
-        mAidlToHidlAdapter.setFeature(mFeature);
-        mAidlToHidlAdapter.getFeatures();
+        mHidlToAidlSessionAdapter.setFeature(mFeature);
+        mHidlToAidlSessionAdapter.getFeatures();
 
         verify(mSession).getFeature(eq(mFeature), anyInt());
         verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
@@ -244,8 +251,8 @@
 
         when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
 
-        mAidlToHidlAdapter.setFeature(mFeature);
-        mAidlToHidlAdapter.getFeatures();
+        mHidlToAidlSessionAdapter.setFeature(mFeature);
+        mHidlToAidlSessionAdapter.getFeatures();
 
         verify(mSession).getFeature(eq(mFeature), anyInt());
         verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
@@ -260,8 +267,8 @@
 
         when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
 
-        mAidlToHidlAdapter.setFeature(mFeature);
-        mAidlToHidlAdapter.getFeatures();
+        mHidlToAidlSessionAdapter.setFeature(mFeature);
+        mHidlToAidlSessionAdapter.getFeatures();
 
         verify(mSession).getFeature(eq(mFeature), anyInt());
         verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
@@ -270,7 +277,7 @@
 
     @Test
     public void testGetFeatures_featureNotSet() throws RemoteException {
-        mAidlToHidlAdapter.getFeatures();
+        mHidlToAidlSessionAdapter.getFeatures();
 
         verify(mSession, never()).getFeature(eq(mFeature), anyInt());
         verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
@@ -283,7 +290,7 @@
 
         when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK);
 
-        mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+        mHidlToAidlSessionAdapter.setFeature(mHardwareAuthToken, feature, enabled);
 
         verify(mAidlResponseHandler).onFeatureSet(feature);
     }
@@ -296,7 +303,7 @@
         when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt()))
                 .thenReturn(Status.INTERNAL_ERROR);
 
-        mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+        mHidlToAidlSessionAdapter.setFeature(mHardwareAuthToken, feature, enabled);
 
         verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN,
                 0 /* vendorCode */);
@@ -311,7 +318,7 @@
 
         when(mSession.getAuthenticatorId()).thenReturn(result);
 
-        mAidlToHidlAdapter.getAuthenticatorId();
+        mHidlToAidlSessionAdapter.getAuthenticatorId();
 
         verify(mSession).getAuthenticatorId();
         verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId);
@@ -319,7 +326,7 @@
 
     @Test
     public void testResetLockout() throws RemoteException {
-        mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+        mHidlToAidlSessionAdapter.resetLockout(mHardwareAuthToken);
 
         ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class);
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 2aa62d9..f570ba2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -42,13 +42,19 @@
 import android.app.AppOpsManager;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.Binder;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.testing.TestableContext;
 
@@ -58,6 +64,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -89,6 +96,9 @@
     @Rule
     public final MockitoRule mMockito = MockitoJUnit.rule();
     @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+    @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
     @Rule
@@ -110,6 +120,10 @@
     private IBinder mToken;
     @Mock
     private VirtualDeviceManagerInternal mVdmInternal;
+    @Mock
+    private IFingerprint mDefaultFingerprintDaemon;
+    @Mock
+    private IFingerprint mVirtualFingerprintDaemon;
 
     @Captor
     private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor;
@@ -126,7 +140,10 @@
                     List.of(),
                     TYPE_UDFPS_OPTICAL,
                     false /* resetLockoutRequiresHardwareAuthToken */);
+    private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
     private FingerprintService mService;
+    private final SensorProps mDefaultSensorProps = new SensorProps();
+    private final SensorProps mVirtualSensorProps = new SensorProps();
 
     @Before
     public void setup() throws Exception {
@@ -139,6 +156,10 @@
                 .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
         when(mFingerprintVirtual.containsSensor(anyInt()))
                 .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
+        when(mDefaultFingerprintDaemon.getSensorProps()).thenReturn(
+                new SensorProps[]{mDefaultSensorProps});
+        when(mVirtualFingerprintDaemon.getSensorProps()).thenReturn(
+                new SensorProps[]{mVirtualSensorProps});
 
         mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
         for (int permission : List.of(OP_USE_BIOMETRIC, OP_USE_FINGERPRINT)) {
@@ -150,6 +171,18 @@
             mContext.getTestablePermissions().setPermission(
                     permission, PackageManager.PERMISSION_GRANTED);
         }
+
+        mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+                true /* resetLockoutRequiresHardwareAuthToken */);
+        mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
+                (name) -> {
+                    if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+                        return mDefaultFingerprintDaemon;
+                    } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+                        return mVirtualFingerprintDaemon;
+                    }
+                    return null;
+                });
     }
 
     private void initServiceWith(String... aidlInstances) {
@@ -160,6 +193,10 @@
                     if (NAME_DEFAULT.equals(name)) return mFingerprintDefault;
                     if (NAME_VIRTUAL.equals(name)) return mFingerprintVirtual;
                     return null;
+                }, (sensorPropsPair, resetLockoutRequiresHardwareAuthToken) -> {
+                    if (NAME_DEFAULT.equals(sensorPropsPair.first)) return mFingerprintDefault;
+                    if (NAME_VIRTUAL.equals(sensorPropsPair.first)) return mFingerprintVirtual;
+                    return null;
                 });
     }
 
@@ -180,6 +217,17 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+        initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+
+        mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
+    }
+
+    @Test
     public void registerAuthenticators_virtualOnly() throws Exception {
         initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
         Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
@@ -192,6 +240,19 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
+        initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+        Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+                Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
+
+        mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+    }
+
+    @Test
     public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
         initServiceWith(NAME_VIRTUAL);
 
@@ -201,6 +262,28 @@
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+        mFingerprintSensorConfigurations =
+                new FingerprintSensorConfigurations(true);
+        mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL},
+                        (name) -> {
+                            if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+                                return mDefaultFingerprintDaemon;
+                            } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+                                return mVirtualFingerprintDaemon;
+                            }
+                            return null;
+                        });
+        initServiceWith(NAME_VIRTUAL);
+
+        mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+    }
+
     private void waitForRegistration() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         mService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 4cfb83f..bf5986c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -34,17 +37,20 @@
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.SensorLocation;
 import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.HidlFingerprintSensorConfig;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -52,6 +58,7 @@
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -64,6 +71,10 @@
 
     private static final String TAG = "FingerprintProviderTest";
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Mock
     private Context mContext;
     @Mock
@@ -115,7 +126,8 @@
         mFingerprintProvider = new FingerprintProvider(mContext,
                 mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
                 mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
-                mDaemon);
+                mDaemon, false /* resetLockoutRequiresHardwareAuthToken */,
+                true /* testHalEnabled */);
     }
 
     @Test
@@ -123,17 +135,42 @@
         waitForIdle();
 
         for (SensorProps prop : mSensorProps) {
-            final BiometricScheduler scheduler =
-                    mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
-                            .getScheduler();
-            BaseClientMonitor currentClient = scheduler.getCurrentClient();
-
-            assertThat(currentClient).isInstanceOf(FingerprintInternalCleanupClient.class);
-            assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
-            assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+            final Sensor sensor =
+                    mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId);
+            assertThat(sensor.getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testAddingHidlSensors() {
+        when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
+        when(mResources.getBoolean(anyInt())).thenReturn(false);
+
+        final int fingerprintId = 0;
+        final int fingerprintStrength = 15;
+        final String config = String.format("%d:2:%d", fingerprintId, fingerprintStrength);
+        final HidlFingerprintSensorConfig fingerprintSensorConfig =
+                new HidlFingerprintSensorConfig();
+        fingerprintSensorConfig.parse(config, mContext);
+        HidlFingerprintSensorConfig[] hidlFingerprintSensorConfigs =
+                new HidlFingerprintSensorConfig[]{fingerprintSensorConfig};
+        mFingerprintProvider = new FingerprintProvider(mContext,
+                mBiometricStateCallback, mAuthenticationStateListeners,
+                hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
+                mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
+                false /* resetLockoutRequiresHardwareAuthToken */,
+                true /* testHalEnabled */);
+
+        assertThat(mFingerprintProvider.mFingerprintSensors.get(fingerprintId)
+                .getLazySession().get().getUserId()).isEqualTo(USER_NULL);
+
+        waitForIdle();
+
+        assertThat(mFingerprintProvider.mFingerprintSensors.get(fingerprintId)
+                .getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
+    }
+
     @SuppressWarnings("rawtypes")
     @Test
     public void halServiceDied_resetsAllSchedulers() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 4102600..126a05e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -96,7 +96,7 @@
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
 
-    private UserAwareBiometricScheduler mScheduler;
+    private BiometricScheduler mScheduler;
     private AidlResponseHandler mHalCallback;
 
     @Before
@@ -164,7 +164,8 @@
         final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext,
                 null /* handler */, internalProp, mLockoutResetDispatcher,
                 mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession);
-        mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
+        sensor.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcher);
+        mScheduler = sensor.getScheduler();
         sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
                 USER_ID, mHalCallback);
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
new file mode 100644
index 0000000..89a4961
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2023 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.biometrics.sensors.fingerprint.hidl;
+
+import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.HidlFingerprintSensorConfig;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class HidlToAidlSensorAdapterTest {
+
+    private static final String TAG = "HidlToAidlSensorAdapterTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final byte[] HAT = new byte[69];
+
+    @Rule
+    public final MockitoRule mMockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricService mBiometricService;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcherForSensor;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcherForClient;
+    @Mock
+    private BiometricLogger mLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+    @Mock
+    private FingerprintProvider mFingerprintProvider;
+    @Mock
+    private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+    @Mock
+    private Runnable mInternalCleanupRunnable;
+    @Mock
+    private AlarmManager mAlarmManager;
+    @Mock
+    private IBiometricsFingerprint mDaemon;
+    @Mock
+    private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    @Mock
+    private BiometricUtils<Fingerprint> mBiometricUtils;
+    @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
+
+    private final TestLooper mLooper = new TestLooper();
+    private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
+    private final TestableContext mContext = new TestableContext(
+            ApplicationProvider.getApplicationContext());
+
+    private final UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback =
+            new UserAwareBiometricScheduler.UserSwitchCallback() {
+                @NonNull
+                @Override
+                public StopUserClient<?> getStopUserClient(int userId) {
+                    return new StopUserClient<IBiometricsFingerprint>(mContext,
+                            mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null, USER_ID,
+                            SENSOR_ID, mLogger, mBiometricContext, () -> {}) {
+                        @Override
+                        protected void startHalOperation() {
+                            getCallback().onClientFinished(this, true /* success */);
+                        }
+
+                        @Override
+                        public void unableToStart() {}
+                    };
+                }
+
+                @NonNull
+                @Override
+                public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+                    return new StartUserClient<IBiometricsFingerprint, AidlSession>(mContext,
+                            mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null,
+                            USER_ID, SENSOR_ID,
+                            mLogger, mBiometricContext,
+                            (newUserId1, newUser, halInterfaceVersion) ->
+                                    mHidlToAidlSensorAdapter.handleUserChanged(newUserId1)) {
+                        @Override
+                        public void start(@NonNull ClientMonitorCallback callback) {
+                            super.start(callback);
+                            startHalOperation();
+                        }
+
+                        @Override
+                        protected void startHalOperation() {
+                            mUserStartedCallback.onUserStarted(USER_ID, null, 0);
+                            getCallback().onClientFinished(this, true /* success */);
+                        }
+
+                        @Override
+                        public void unableToStart() {}
+                    };
+                }
+            };;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        doAnswer((answer) -> {
+            mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+                    .onEnrollmentProgress(1 /* enrollmentId */, 0 /* remaining */);
+            return null;
+        }).when(mDaemon).enroll(any(), anyInt(), anyInt());
+
+        mContext.addMockSystemService(AlarmManager.class, mAlarmManager);
+        mContext.getOrCreateTestableResources();
+
+        final String config = String.format("%d:2:15", SENSOR_ID);
+        final UserAwareBiometricScheduler scheduler = new UserAwareBiometricScheduler(TAG,
+                new Handler(mLooper.getLooper()),
+                BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+                null /* gestureAvailabilityDispatcher */,
+                mBiometricService,
+                () -> USER_ID,
+                mUserSwitchCallback);
+        final HidlFingerprintSensorConfig fingerprintSensorConfig =
+                new HidlFingerprintSensorConfig();
+        fingerprintSensorConfig.parse(config, mContext);
+        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG,
+                mFingerprintProvider, mContext, new Handler(mLooper.getLooper()),
+                fingerprintSensorConfig, mLockoutResetDispatcherForSensor,
+                mGestureAvailabilityDispatcher, mBiometricContext,
+                false /* resetLockoutRequiresHardwareAuthToken */,
+                mInternalCleanupRunnable, mAuthSessionCoordinator, mDaemon,
+                mAidlResponseHandlerCallback);
+        mHidlToAidlSensorAdapter.init(mGestureAvailabilityDispatcher,
+                mLockoutResetDispatcherForSensor);
+        mHidlToAidlSensorAdapter.setScheduler(scheduler);
+        mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
+    }
+
+    @Test
+    public void lockoutTimedResetViaClient() {
+        setLockoutTimed();
+
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+                new FingerprintResetLockoutClient(mContext,
+                        mHidlToAidlSensorAdapter.getLazySession(),
+                        USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+                        mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+                        mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+        mLooper.dispatchAll();
+
+        verify(mAlarmManager).setExact(anyInt(), anyLong(), any());
+        verify(mLockoutResetDispatcherForClient).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutTimedResetViaCallback() {
+        setLockoutTimed();
+
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutCleared();
+        mLooper.dispatchAll();
+
+        verify(mLockoutResetDispatcherForSensor, times(2)).notifyLockoutResetCallbacks(eq(
+                SENSOR_ID));
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutPermanentResetViaCallback() {
+        setLockoutPermanent();
+
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutCleared();
+        mLooper.dispatchAll();
+
+        verify(mLockoutResetDispatcherForSensor, times(2)).notifyLockoutResetCallbacks(eq(
+                SENSOR_ID));
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutPermanentResetViaClient() {
+        setLockoutPermanent();
+
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+                new FingerprintResetLockoutClient(mContext,
+                        mHidlToAidlSensorAdapter.getLazySession(),
+                        USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+                        mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+                        mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+        mLooper.dispatchAll();
+
+        verify(mAlarmManager, atLeast(1)).setExact(anyInt(), anyLong(), any());
+        verify(mLockoutResetDispatcherForClient).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void verifyOnEnrollSuccessCallback() {
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(new FingerprintEnrollClient(
+                mContext, mHidlToAidlSensorAdapter.getLazySession(), null /* token */,
+                1 /* requestId */, null /* listener */, USER_ID, HAT, TAG, mBiometricUtils,
+                SENSOR_ID, mLogger, mBiometricContext,
+                mHidlToAidlSensorAdapter.getSensorProperties(), null, null,
+                mAuthenticationStateListeners, 5 /* maxTemplatesPerUser */, ENROLL_ENROLL));
+        mLooper.dispatchAll();
+
+        verify(mAidlResponseHandlerCallback).onEnrollSuccess();
+    }
+
+    private void setLockoutPermanent() {
+        for (int i = 0; i < 20; i++) {
+            mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                    .addFailedAttemptForUser(USER_ID);
+        }
+
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_PERMANENT);
+    }
+
+    private void setLockoutTimed() {
+        for (int i = 0; i < 5; i++) {
+            mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                    .addFailedAttemptForUser(USER_ID);
+        }
+
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_TIMED);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
similarity index 79%
rename from services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
rename to services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
index b78ba82..d723e87 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
@@ -41,7 +41,7 @@
 
 @Presubmit
 @SmallTest
-public class AidlToHidlAdapterTest {
+public class HidlToAidlSessionAdapterTest {
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
 
@@ -54,11 +54,11 @@
 
     private final long mChallenge = 100L;
     private final int mUserId = 0;
-    private AidlToHidlAdapter mAidlToHidlAdapter;
+    private HidlToAidlSessionAdapter mHidlToAidlSessionAdapter;
 
     @Before
     public void setUp() {
-        mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId,
+        mHidlToAidlSessionAdapter = new HidlToAidlSessionAdapter(() -> mSession, mUserId,
                 mAidlResponseHandler);
         mHardwareAuthToken.timestamp = new Timestamp();
         mHardwareAuthToken.mac = new byte[10];
@@ -67,7 +67,7 @@
     @Test
     public void testGenerateChallenge() throws RemoteException {
         when(mSession.preEnroll()).thenReturn(mChallenge);
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
 
         verify(mSession).preEnroll();
         verify(mAidlResponseHandler).onChallengeGenerated(mChallenge);
@@ -75,7 +75,7 @@
 
     @Test
     public void testRevokeChallenge() throws RemoteException {
-        mAidlToHidlAdapter.revokeChallenge(mChallenge);
+        mHidlToAidlSessionAdapter.revokeChallenge(mChallenge);
 
         verify(mSession).postEnroll();
         verify(mAidlResponseHandler).onChallengeRevoked(0L);
@@ -84,9 +84,9 @@
     @Test
     public void testEnroll() throws RemoteException {
         final ICancellationSignal cancellationSignal =
-                mAidlToHidlAdapter.enroll(mHardwareAuthToken);
+                mHidlToAidlSessionAdapter.enroll(mHardwareAuthToken);
 
-        verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC));
+        verify(mSession).enroll(any(), anyInt(), eq(HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC));
 
         cancellationSignal.cancel();
 
@@ -96,7 +96,8 @@
     @Test
     public void testAuthenticate() throws RemoteException {
         final int operationId = 2;
-        final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+        final ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.authenticate(
+                operationId);
 
         verify(mSession).authenticate(operationId, mUserId);
 
@@ -107,7 +108,8 @@
 
     @Test
     public void testDetectInteraction() throws RemoteException {
-        final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+        final ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter
+                .detectInteraction();
 
         verify(mSession).authenticate(0 /* operationId */, mUserId);
 
@@ -118,7 +120,7 @@
 
     @Test
     public void testEnumerateEnrollments() throws RemoteException {
-        mAidlToHidlAdapter.enumerateEnrollments();
+        mHidlToAidlSessionAdapter.enumerateEnrollments();
 
         verify(mSession).enumerate();
     }
@@ -126,7 +128,7 @@
     @Test
     public void testRemoveEnrollment() throws RemoteException {
         final int[] enrollmentIds = new int[]{1};
-        mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+        mHidlToAidlSessionAdapter.removeEnrollments(enrollmentIds);
 
         verify(mSession).remove(mUserId, enrollmentIds[0]);
     }
@@ -134,14 +136,14 @@
     @Test
     public void testRemoveMultipleEnrollments() throws RemoteException {
         final int[] enrollmentIds = new int[]{1, 2};
-        mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+        mHidlToAidlSessionAdapter.removeEnrollments(enrollmentIds);
 
         verify(mSession).remove(mUserId, 0);
     }
 
     @Test
     public void testResetLockout() throws RemoteException {
-        mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+        mHidlToAidlSessionAdapter.resetLockout(mHardwareAuthToken);
 
         verify(mAidlResponseHandler).onLockoutCleared();
     }