Added Fingerprint Virtual HAL AIDL extension

Bug: 326227403
Test: atest android.hardware.biometrics.fingerprint.* -c
Change-Id: I967c009c99f8dc279f89c21a59cf0462d9590296
diff --git a/biometrics/fingerprint/aidl/default/Android.bp b/biometrics/fingerprint/aidl/default/Android.bp
index 501af07..6d8b68b 100644
--- a/biometrics/fingerprint/aidl/default/Android.bp
+++ b/biometrics/fingerprint/aidl/default/Android.bp
@@ -21,6 +21,7 @@
         "Fingerprint.cpp",
         "Session.cpp",
         "FingerprintConfig.cpp",
+        "VirtualHal.cpp",
         "main.cpp",
     ],
     stl: "c++_static",
@@ -31,7 +32,7 @@
     static_libs: [
         "libandroid.hardware.biometrics.fingerprint.VirtualProps",
         "libbase",
-        "android.hardware.biometrics.fingerprint-V4-ndk",
+        "android.hardware.biometrics.fingerprint-V5-ndk",
         "android.hardware.biometrics.common-V4-ndk",
         "android.hardware.biometrics.common.thread",
         "android.hardware.biometrics.common.util",
@@ -61,7 +62,7 @@
     ],
     static_libs: [
         "libandroid.hardware.biometrics.fingerprint.VirtualProps",
-        "android.hardware.biometrics.fingerprint-V4-ndk",
+        "android.hardware.biometrics.fingerprint-V5-ndk",
         "android.hardware.biometrics.common-V4-ndk",
         "android.hardware.keymaster-V4-ndk",
         "android.hardware.biometrics.common.util",
@@ -89,7 +90,7 @@
     ],
     static_libs: [
         "libandroid.hardware.biometrics.fingerprint.VirtualProps",
-        "android.hardware.biometrics.fingerprint-V4-ndk",
+        "android.hardware.biometrics.fingerprint-V5-ndk",
         "android.hardware.biometrics.common-V4-ndk",
         "android.hardware.keymaster-V4-ndk",
         "android.hardware.biometrics.common.util",
@@ -115,7 +116,7 @@
     ],
     static_libs: [
         "libandroid.hardware.biometrics.fingerprint.VirtualProps",
-        "android.hardware.biometrics.fingerprint-V4-ndk",
+        "android.hardware.biometrics.fingerprint-V5-ndk",
         "android.hardware.biometrics.common-V4-ndk",
         "android.hardware.keymaster-V4-ndk",
         "android.hardware.biometrics.common.util",
@@ -143,7 +144,7 @@
     ],
     static_libs: [
         "libandroid.hardware.biometrics.fingerprint.VirtualProps",
-        "android.hardware.biometrics.fingerprint-V4-ndk",
+        "android.hardware.biometrics.fingerprint-V5-ndk",
         "android.hardware.biometrics.common-V4-ndk",
         "android.hardware.keymaster-V4-ndk",
         "android.hardware.biometrics.common.util",
@@ -155,6 +156,44 @@
     require_root: true,
 }
 
+cc_test {
+    name: "android.hardware.biometrics.fingerprint.VirtualHalTest",
+    local_include_dirs: ["include"],
+    srcs: [
+        "tests/VirtualHalTest.cpp",
+        "Session.cpp",
+        "VirtualHal.cpp",
+        "FakeFingerprintEngineRear.cpp",
+        "FakeFingerprintEngineUdfps.cpp",
+        "FakeFingerprintEngineSide.cpp",
+        "FakeFingerprintEngine.cpp",
+        "FakeLockoutTracker.cpp",
+        "Fingerprint.cpp",
+        "FingerprintConfig.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+    ],
+    static_libs: [
+        "libandroid.hardware.biometrics.fingerprint.VirtualProps",
+        "android.hardware.biometrics.fingerprint-V5-ndk",
+        "android.hardware.biometrics.common-V4-ndk",
+        "android.hardware.keymaster-V4-ndk",
+        "android.hardware.biometrics.common.util",
+        "android.hardware.biometrics.common.thread",
+        "android.hardware.biometrics.common.config",
+    ],
+    product_variables: {
+        debuggable: {
+            cflags: ["-DFPS_DEBUGGABLE"],
+        },
+    },
+    vendor: true,
+    test_suites: ["general-tests"],
+    require_root: true,
+}
+
 sysprop_library {
     name: "android.hardware.biometrics.fingerprint.VirtualProps",
     srcs: ["fingerprint.sysprop"],
diff --git a/biometrics/fingerprint/aidl/default/Fingerprint.cpp b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
index dded54b..e407f17 100644
--- a/biometrics/fingerprint/aidl/default/Fingerprint.cpp
+++ b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
@@ -171,7 +171,7 @@
 }
 
 void Fingerprint::clearConfigSysprop() {
-    LOG(INFO) << __func__ << ": clear all systprop configuration";
+    LOG(INFO) << __func__ << ": clear all sysprop configuration";
 #define RESET_CONFIG_O(__NAME__) \
     if (FingerprintHalProperties::__NAME__()) FingerprintHalProperties::__NAME__(std::nullopt)
 #define RESET_CONFIG_V(__NAME__)                       \
@@ -209,4 +209,19 @@
     RESET_CONFIG_O(lockout_permanent_threshold);
 }
 
+const char* Fingerprint::type2String(FingerprintSensorType type) {
+    switch (type) {
+        case FingerprintSensorType::REAR:
+            return "rear";
+        case FingerprintSensorType::POWER_BUTTON:
+            return "side";
+        case FingerprintSensorType::UNDER_DISPLAY_OPTICAL:
+            return "udfps";
+        case FingerprintSensorType::UNDER_DISPLAY_ULTRASONIC:
+            return "udfps";
+        default:
+            return "unknown";
+    }
+}
+
 }  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/VirtualHal.cpp b/biometrics/fingerprint/aidl/default/VirtualHal.cpp
new file mode 100644
index 0000000..d2baaf5
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/VirtualHal.cpp
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <unordered_map>
+
+#include "VirtualHal.h"
+
+#include <android-base/logging.h>
+
+#include "util/CancellationSignal.h"
+
+#undef LOG_TAG
+#define LOG_TAG "FingerprintVirtualHalAidl"
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+::ndk::ScopedAStatus VirtualHal::setEnrollments(const std::vector<int32_t>& enrollments) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().setopt<OptIntVec>("enrollments", intVec2OptIntVec(enrollments));
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setEnrollmentHit(int32_t enrollment_hit) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<std::int32_t>("enrollment_hit", enrollment_hit);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setAuthenticatorId(int64_t in_id) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int64_t>("authenticator_id", in_id);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setChallenge(int64_t in_challenge) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int64_t>("challenge", in_challenge);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setOperationAuthenticateFails(bool in_fail) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<bool>("operation_authenticate_fails", in_fail);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setOperationAuthenticateLatency(
+        const std::vector<int32_t>& in_latency) {
+    ndk::ScopedAStatus status = sanityCheckLatency(in_latency);
+    if (!status.isOk()) {
+        return status;
+    }
+
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().setopt<OptIntVec>("operation_authenticate_latency",
+                                         intVec2OptIntVec(in_latency));
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setOperationAuthenticateDuration(int32_t in_duration) {
+    if (in_duration < 0) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IVirtualHal::STATUS_INVALID_PARAMETER, "Error: duration can not be negative"));
+    }
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int32_t>("operation_authenticate_duration", in_duration);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setOperationAuthenticateError(int32_t in_error) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int32_t>("operation_authenticate_error", in_error);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setOperationAuthenticateAcquired(
+        const std::vector<int32_t>& in_acquired) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().setopt<OptIntVec>("operation_authenticate_acquired",
+                                         intVec2OptIntVec(in_acquired));
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setOperationEnrollError(int32_t in_error) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int32_t>("operation_enroll_error", in_error);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setOperationEnrollLatency(const std::vector<int32_t>& in_latency) {
+    ndk::ScopedAStatus status = sanityCheckLatency(in_latency);
+    if (!status.isOk()) {
+        return status;
+    }
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().setopt<OptIntVec>("operation_enroll_latency", intVec2OptIntVec(in_latency));
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setOperationDetectInteractionLatency(
+        const std::vector<int32_t>& in_latency) {
+    ndk::ScopedAStatus status = sanityCheckLatency(in_latency);
+    if (!status.isOk()) {
+        return status;
+    }
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().setopt<OptIntVec>("operation_detect_interact_latency",
+                                         intVec2OptIntVec(in_latency));
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setOperationDetectInteractionError(int32_t in_error) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int32_t>("operation_detect_interaction_error", in_error);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setOperationDetectInteractionDuration(int32_t in_duration) {
+    if (in_duration < 0) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IVirtualHal::STATUS_INVALID_PARAMETER, "Error: duration can not be negative"));
+    }
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int32_t>("operation_detect_interaction_duration", in_duration);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setOperationDetectInteractionAcquired(
+        const std::vector<int32_t>& in_acquired) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().setopt<OptIntVec>("operation_detect_interaction_acquired",
+                                         intVec2OptIntVec(in_acquired));
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setLockout(bool in_lockout) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<bool>("lockout", in_lockout);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setLockoutEnable(bool in_enable) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<bool>("lockout_enable", in_enable);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setLockoutTimedThreshold(int32_t in_threshold) {
+    if (in_threshold < 0) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IVirtualHal::STATUS_INVALID_PARAMETER, "Error: threshold can not be negative"));
+    }
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int32_t>("lockout_timed_threshold", in_threshold);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setLockoutTimedDuration(int32_t in_duration) {
+    if (in_duration < 0) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IVirtualHal::STATUS_INVALID_PARAMETER, "Error: duration can not be negative"));
+    }
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int32_t>("lockout_timed_duration", in_duration);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setLockoutPermanentThreshold(int32_t in_threshold) {
+    if (in_threshold < 0) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IVirtualHal::STATUS_INVALID_PARAMETER, "Error: threshold can not be negative"));
+    }
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int32_t>("lockout_permanent_threshold", in_threshold);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setType(
+        ::aidl::android::hardware::biometrics::fingerprint::FingerprintSensorType in_type) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<std::string>("type", Fingerprint::type2String(in_type));
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setSensorId(int32_t in_id) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int32_t>("sensor_id", in_id);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setSensorStrength(SensorStrength in_strength) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int32_t>("sensor_strength", (int32_t)in_strength);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setMaxEnrollmentPerUser(int32_t in_max) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<int32_t>("max_enrollments", in_max);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setSensorLocation(const SensorLocation& in_loc) {
+    std::string str = std::to_string(in_loc.sensorLocationX) + ":" +
+                      std::to_string(in_loc.sensorLocationY) + ":" +
+                      std::to_string(in_loc.sensorRadius);
+    ;
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<std::string>("sensor_location", str);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setNavigationGuesture(bool in_v) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<bool>("navigation_guesture", in_v);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setDetectInteraction(bool in_v) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<bool>("detect_interaction", in_v);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setDisplayTouch(bool in_v) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<bool>("display_touch", in_v);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus VirtualHal::setControlIllumination(bool in_v) {
+    Fingerprint::cfg().sourcedFromAidl();
+    Fingerprint::cfg().set<bool>("control_illumination", in_v);
+    return ndk::ScopedAStatus::ok();
+}
+
+OptIntVec VirtualHal::intVec2OptIntVec(const std::vector<int32_t>& in_vec) {
+    OptIntVec optIntVec;
+    std::transform(in_vec.begin(), in_vec.end(), std::back_inserter(optIntVec),
+                   [](int value) { return std::optional<int>(value); });
+    return optIntVec;
+}
+
+::ndk::ScopedAStatus VirtualHal::sanityCheckLatency(const std::vector<int32_t>& in_latency) {
+    if (in_latency.size() == 0 || in_latency.size() > 2) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IVirtualHal::STATUS_INVALID_PARAMETER,
+                "Error: input input array must contain 1 or 2 elements"));
+    }
+
+    for (auto x : in_latency) {
+        if (x < 0) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IVirtualHal::STATUS_INVALID_PARAMETER,
+                    "Error: input data must not be negative"));
+        }
+    }
+
+    return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/fingerprint-example.xml b/biometrics/fingerprint/aidl/default/fingerprint-example.xml
index 827813f..ee529e9 100644
--- a/biometrics/fingerprint/aidl/default/fingerprint-example.xml
+++ b/biometrics/fingerprint/aidl/default/fingerprint-example.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.biometrics.fingerprint</name>
-        <version>4</version>
+        <version>5</version>
         <fqname>IFingerprint/virtual</fqname>
     </hal>
 </manifest>
diff --git a/biometrics/fingerprint/aidl/default/include/Fingerprint.h b/biometrics/fingerprint/aidl/default/include/Fingerprint.h
index 1576f07..90f89cb 100644
--- a/biometrics/fingerprint/aidl/default/include/Fingerprint.h
+++ b/biometrics/fingerprint/aidl/default/include/Fingerprint.h
@@ -40,6 +40,7 @@
                                      std::shared_ptr<ISession>* out) override;
     binder_status_t dump(int fd, const char** args, uint32_t numArgs);
     binder_status_t handleShellCommand(int in, int out, int err, const char** argv, uint32_t argc);
+    bool connected() { return mEngine != nullptr; }
 
     static FingerprintConfig& cfg() {
         static FingerprintConfig* cfg = nullptr;
@@ -49,9 +50,10 @@
         }
         return *cfg;
     }
+    void resetConfigToDefault();
+    static const char* type2String(FingerprintSensorType type);
 
   private:
-    void resetConfigToDefault();
     void onHelp(int);
     void onSimFingerDown();
     void clearConfigSysprop();
diff --git a/biometrics/fingerprint/aidl/default/include/VirtualHal.h b/biometrics/fingerprint/aidl/default/include/VirtualHal.h
new file mode 100644
index 0000000..6cc4b66
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/include/VirtualHal.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/biometrics/fingerprint/BnVirtualHal.h>
+
+#include "Fingerprint.h"
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+class VirtualHal : public BnVirtualHal {
+  public:
+    VirtualHal(Fingerprint* fp) : mFp(fp) {}
+
+    ::ndk::ScopedAStatus setEnrollments(const std::vector<int32_t>& in_id) override;
+    ::ndk::ScopedAStatus setEnrollmentHit(int32_t in_hit_id) override;
+    ::ndk::ScopedAStatus setAuthenticatorId(int64_t in_id) override;
+    ::ndk::ScopedAStatus setChallenge(int64_t in_challenge) override;
+    ::ndk::ScopedAStatus setOperationAuthenticateFails(bool in_fail) override;
+    ::ndk::ScopedAStatus setOperationAuthenticateLatency(
+            const std::vector<int32_t>& in_latency) override;
+    ::ndk::ScopedAStatus setOperationAuthenticateDuration(int32_t in_duration) override;
+    ::ndk::ScopedAStatus setOperationAuthenticateError(int32_t in_error) override;
+    ::ndk::ScopedAStatus setOperationAuthenticateAcquired(
+            const std::vector<int32_t>& in_acquired) override;
+    ::ndk::ScopedAStatus setOperationEnrollError(int32_t in_error) override;
+    ::ndk::ScopedAStatus setOperationEnrollLatency(const std::vector<int32_t>& in_latency) override;
+    ::ndk::ScopedAStatus setOperationDetectInteractionLatency(
+            const std::vector<int32_t>& in_latency) override;
+    ::ndk::ScopedAStatus setOperationDetectInteractionError(int32_t in_error) override;
+    ::ndk::ScopedAStatus setOperationDetectInteractionDuration(int32_t in_duration) override;
+    ::ndk::ScopedAStatus setOperationDetectInteractionAcquired(
+            const std::vector<int32_t>& in_acquired) override;
+    ::ndk::ScopedAStatus setLockout(bool in_lockout) override;
+    ::ndk::ScopedAStatus setLockoutEnable(bool in_enable) override;
+    ::ndk::ScopedAStatus setLockoutTimedThreshold(int32_t in_threshold) override;
+    ::ndk::ScopedAStatus setLockoutTimedDuration(int32_t in_duration) override;
+    ::ndk::ScopedAStatus setLockoutPermanentThreshold(int32_t in_threshold) override;
+    ::ndk::ScopedAStatus setType(
+            ::aidl::android::hardware::biometrics::fingerprint::FingerprintSensorType in_type)
+            override;
+    ::ndk::ScopedAStatus setSensorId(int32_t in_id) override;
+    ::ndk::ScopedAStatus setSensorStrength(SensorStrength in_strength) override;
+    ::ndk::ScopedAStatus setMaxEnrollmentPerUser(int32_t in_max) override;
+    ::ndk::ScopedAStatus setSensorLocation(
+            const ::aidl::android::hardware::biometrics::fingerprint::SensorLocation& in_loc)
+            override;
+    ::ndk::ScopedAStatus setNavigationGuesture(bool in_v) override;
+    ::ndk::ScopedAStatus setDetectInteraction(bool in_v) override;
+    ::ndk::ScopedAStatus setDisplayTouch(bool in_v) override;
+    ::ndk::ScopedAStatus setControlIllumination(bool in_v) override;
+
+  private:
+    OptIntVec intVec2OptIntVec(const std::vector<int32_t>& intVec);
+    ::ndk::ScopedAStatus sanityCheckLatency(const std::vector<int32_t>& in_latency);
+    Fingerprint* mFp;
+};
+
+}  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/main.cpp b/biometrics/fingerprint/aidl/default/main.cpp
index 7df015b..ba0c8ec 100644
--- a/biometrics/fingerprint/aidl/default/main.cpp
+++ b/biometrics/fingerprint/aidl/default/main.cpp
@@ -15,23 +15,34 @@
  */
 
 #include "Fingerprint.h"
+#include "VirtualHal.h"
 
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 
 using aidl::android::hardware::biometrics::fingerprint::Fingerprint;
+using aidl::android::hardware::biometrics::fingerprint::VirtualHal;
 
 int main() {
     LOG(INFO) << "Fingerprint HAL started";
     ABinderProcess_setThreadPoolMaxThreadCount(0);
     std::shared_ptr<Fingerprint> hal = ndk::SharedRefBase::make<Fingerprint>();
+    auto binder = hal->asBinder();
 
-    const std::string instance = std::string(Fingerprint::descriptor) + "/virtual";
-    binder_status_t status =
-            AServiceManager_registerLazyService(hal->asBinder().get(), instance.c_str());
-    CHECK_EQ(status, STATUS_OK);
-    AServiceManager_forceLazyServicesPersist(true);
+    std::shared_ptr<VirtualHal> hal_ext = ndk::SharedRefBase::make<VirtualHal>(hal.get());
+    auto binder_ext = hal_ext->asBinder();
+
+    if (hal->connected()) {
+        CHECK(STATUS_OK == AIBinder_setExtension(binder.get(), binder_ext.get()));
+        const std::string instance = std::string(Fingerprint::descriptor) + "/virtual";
+        binder_status_t status =
+                AServiceManager_registerLazyService(binder.get(), instance.c_str());
+        CHECK_EQ(status, STATUS_OK);
+        AServiceManager_forceLazyServicesPersist(true);
+    } else {
+        LOG(ERROR) << "Fingerprint HAL is not connected";
+    }
 
     ABinderProcess_joinThreadPool();
     return EXIT_FAILURE;  // should not reach
diff --git a/biometrics/fingerprint/aidl/default/tests/VirtualHalTest.cpp b/biometrics/fingerprint/aidl/default/tests/VirtualHalTest.cpp
new file mode 100644
index 0000000..d8495d1
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/tests/VirtualHalTest.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <android/binder_process.h>
+#include <fingerprint.sysprop.h>
+#include <gtest/gtest.h>
+
+#include <android-base/logging.h>
+
+#include "Fingerprint.h"
+#include "VirtualHal.h"
+
+using namespace ::android::fingerprint::virt;
+using namespace ::aidl::android::hardware::biometrics::fingerprint;
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+class VirtualHalTest : public ::testing::Test {
+  public:
+    static const int32_t STATUS_FAILED_TO_SET_PARAMETER = 2;
+
+  protected:
+    void SetUp() override {
+        mHal = ndk::SharedRefBase::make<Fingerprint>();
+        mVhal = ndk::SharedRefBase::make<VirtualHal>(mHal.get());
+        ASSERT_TRUE(mVhal != nullptr);
+        mHal->resetConfigToDefault();
+    }
+
+    void TearDown() override { mHal->resetConfigToDefault(); }
+
+    std::shared_ptr<VirtualHal> mVhal;
+
+    ndk::ScopedAStatus validateNonNegativeInputOfInt32(const char* name,
+                                                       ndk::ScopedAStatus (VirtualHal::*f)(int32_t),
+                                                       const std::vector<int32_t>& in_good);
+
+  private:
+    std::shared_ptr<Fingerprint> mHal;
+};
+
+ndk::ScopedAStatus VirtualHalTest::validateNonNegativeInputOfInt32(
+        const char* name, ndk::ScopedAStatus (VirtualHal::*f)(int32_t),
+        const std::vector<int32_t>& in_params_good) {
+    ndk::ScopedAStatus status;
+    for (auto& param : in_params_good) {
+        status = (*mVhal.*f)(param);
+        if (!status.isOk()) return status;
+        if (Fingerprint::cfg().get<int32_t>(name) != param) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    VirtualHalTest::STATUS_FAILED_TO_SET_PARAMETER,
+                    "Error: fail to set non-negative parameter"));
+        }
+    }
+
+    int32_t old_param = Fingerprint::cfg().get<int32_t>(name);
+    status = (*mVhal.*f)(-1);
+    if (status.isOk()) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                VirtualHalTest::STATUS_FAILED_TO_SET_PARAMETER, "Error: should return NOK"));
+    }
+    if (status.getServiceSpecificError() != IVirtualHal::STATUS_INVALID_PARAMETER) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                VirtualHalTest::STATUS_FAILED_TO_SET_PARAMETER,
+                "Error: unexpected return error code"));
+    }
+    if (Fingerprint::cfg().get<int32_t>(name) != old_param) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                VirtualHalTest::STATUS_FAILED_TO_SET_PARAMETER,
+                "Error: unexpected parameter change on failed attempt"));
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+TEST_F(VirtualHalTest, init) {
+    mVhal->setLockout(false);
+    ASSERT_TRUE(Fingerprint::cfg().get<bool>("lockout") == false);
+    ASSERT_TRUE(Fingerprint::cfg().get<std::string>("type") == "rear");
+    ASSERT_TRUE(Fingerprint::cfg().get<std::int32_t>("sensor_strength") == 2);
+    std::int64_t id = Fingerprint::cfg().get<std::int64_t>("authenticator_id");
+    ASSERT_TRUE(Fingerprint::cfg().get<std::int64_t>("authenticator_id") == 0);
+    ASSERT_TRUE(Fingerprint::cfg().getopt<OptIntVec>("enrollments") == OptIntVec());
+}
+
+TEST_F(VirtualHalTest, enrollment_hit_int32) {
+    mVhal->setEnrollmentHit(11);
+    ASSERT_TRUE(Fingerprint::cfg().get<int32_t>("enrollment_hit") == 11);
+}
+
+TEST_F(VirtualHalTest, authenticator_id_int64) {
+    mVhal->setAuthenticatorId(12345678900);
+    ASSERT_TRUE(Fingerprint::cfg().get<int64_t>("authenticator_id") == 12345678900);
+}
+
+TEST_F(VirtualHalTest, opeationAuthenticateFails_bool) {
+    mVhal->setOperationAuthenticateFails(true);
+    ASSERT_TRUE(Fingerprint::cfg().get<bool>("operation_authenticate_fails"));
+}
+
+TEST_F(VirtualHalTest, operationAuthenticateAcquired_int32_vector) {
+    std::vector<int32_t> ac{1, 2, 3, 4, 5, 6, 7};
+    mVhal->setOperationAuthenticateAcquired(ac);
+    OptIntVec ac_get = Fingerprint::cfg().getopt<OptIntVec>("operation_authenticate_acquired");
+    ASSERT_TRUE(ac_get.size() == ac.size());
+    for (int i = 0; i < ac.size(); i++) {
+        ASSERT_TRUE(ac[i] == ac_get[i]);
+    }
+}
+
+TEST_F(VirtualHalTest, type) {
+    struct {
+        FingerprintSensorType type;
+        const char* typeStr;
+    } typeMap[] = {{FingerprintSensorType::REAR, "rear"},
+                   {FingerprintSensorType::POWER_BUTTON, "side"},
+                   {FingerprintSensorType::UNDER_DISPLAY_OPTICAL, "udfps"},
+                   {FingerprintSensorType::UNDER_DISPLAY_ULTRASONIC, "udfps"},
+                   {FingerprintSensorType::UNKNOWN, "unknown"}};
+    for (auto const& x : typeMap) {
+        mVhal->setType(x.type);
+        ASSERT_TRUE(Fingerprint::cfg().get<std::string>("type") == x.typeStr);
+    }
+}
+
+TEST_F(VirtualHalTest, sensorStrength) {
+    SensorStrength strengths[] = {SensorStrength::CONVENIENCE, SensorStrength::WEAK,
+                                  SensorStrength::STRONG};
+
+    for (auto const& strength : strengths) {
+        mVhal->setSensorStrength(strength);
+        ASSERT_TRUE(Fingerprint::cfg().get<int32_t>("sensor_strength") == (int32_t)(strength));
+    }
+}
+
+TEST_F(VirtualHalTest, sensorLocation) {
+    SensorLocation loc = {.sensorLocationX = 1, .sensorLocationY = 2, .sensorRadius = 3};
+    mVhal->setSensorLocation(loc);
+    ASSERT_TRUE(Fingerprint::cfg().get<std::string>("sensor_location") == "1:2:3");
+}
+
+TEST_F(VirtualHalTest, setLatency) {
+    ndk::ScopedAStatus status;
+    std::vector<int32_t> in_lats[] = {{1}, {2, 3}, {5, 4}};
+    for (auto const& in_lat : in_lats) {
+        status = mVhal->setOperationAuthenticateLatency(in_lat);
+        ASSERT_TRUE(status.isOk());
+        OptIntVec out_lat = Fingerprint::cfg().getopt<OptIntVec>("operation_authenticate_latency");
+        ASSERT_TRUE(in_lat.size() == out_lat.size());
+        for (int i = 0; i < in_lat.size(); i++) {
+            ASSERT_TRUE(in_lat[i] == out_lat[i]);
+        }
+    }
+
+    std::vector<int32_t> bad_in_lats[] = {{}, {1, 2, 3}, {1, -3}};
+    for (auto const& in_lat : bad_in_lats) {
+        status = mVhal->setOperationAuthenticateLatency(in_lat);
+        ASSERT_TRUE(!status.isOk());
+        ASSERT_TRUE(status.getServiceSpecificError() == IVirtualHal::STATUS_INVALID_PARAMETER);
+    }
+}
+
+TEST_F(VirtualHalTest, setOperationAuthenticateDuration) {
+    ndk::ScopedAStatus status = validateNonNegativeInputOfInt32(
+            "operation_authenticate_duration", &IVirtualHal::setOperationAuthenticateDuration,
+            {0, 33});
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(VirtualHalTest, setOperationDetectInteractionDuration) {
+    ndk::ScopedAStatus status = validateNonNegativeInputOfInt32(
+            "operation_detect_interaction_duration",
+            &IVirtualHal::setOperationDetectInteractionDuration, {0, 34});
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(VirtualHalTest, setLockoutTimedDuration) {
+    ndk::ScopedAStatus status = validateNonNegativeInputOfInt32(
+            "lockout_timed_duration", &IVirtualHal::setLockoutTimedDuration, {0, 35});
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(VirtualHalTest, setLockoutTimedThreshold) {
+    ndk::ScopedAStatus status = validateNonNegativeInputOfInt32(
+            "lockout_timed_threshold", &IVirtualHal::setLockoutTimedThreshold, {0, 36});
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(VirtualHalTest, setLockoutPermanentThreshold) {
+    ndk::ScopedAStatus status = validateNonNegativeInputOfInt32(
+            "lockout_permanent_threshold", &IVirtualHal::setLockoutPermanentThreshold, {0, 37});
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(VirtualHalTest, setOthers) {
+    // Verify that there is no CHECK() failures
+    mVhal->setEnrollments({7, 6, 5});
+    mVhal->setChallenge(111222333444555666);
+    mVhal->setOperationAuthenticateError(4);
+    mVhal->setOperationEnrollError(5);
+    mVhal->setOperationEnrollLatency({4, 5});
+    mVhal->setOperationDetectInteractionError(6);
+    mVhal->setOperationDetectInteractionAcquired({4, 3, 2});
+    mVhal->setLockout(false);
+    mVhal->setLockoutEnable(false);
+    mVhal->setSensorId(5);
+    mVhal->setMaxEnrollmentPerUser(6);
+    mVhal->setNavigationGuesture(false);
+    mVhal->setDetectInteraction(false);
+    mVhal->setDisplayTouch(false);
+    mVhal->setControlIllumination(false);
+}
+
+}  // namespace aidl::android::hardware::biometrics::fingerprint
+
+int main(int argc, char** argv) {
+    testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}