Add rear fps virtual HAL.

Bug: 228638448
Test: atest FakeFingerprintEngineTest
Test: manual (see README.md)

Change-Id: Ifecf6b5667352eb2127f820bfde47c7d325ab1b2
diff --git a/biometrics/fingerprint/aidl/default/Android.bp b/biometrics/fingerprint/aidl/default/Android.bp
index 430bf3c..e6628f2 100644
--- a/biometrics/fingerprint/aidl/default/Android.bp
+++ b/biometrics/fingerprint/aidl/default/Android.bp
@@ -16,6 +16,7 @@
     local_include_dirs: ["include"],
     srcs: [
         "CancellationSignal.cpp",
+        "FakeFingerprintEngine.cpp",
         "Fingerprint.cpp",
         "Session.cpp",
         "WorkerThread.cpp",
@@ -27,6 +28,7 @@
         "android.hardware.biometrics.fingerprint-V2-ndk",
         "android.hardware.biometrics.common-V2-ndk",
     ],
+    static_libs: ["android.hardware.biometrics.fingerprint.VirtualProps"],
 }
 
 cc_test_host {
@@ -41,3 +43,33 @@
     ],
     test_suites: ["general-tests"],
 }
+
+cc_test {
+    name: "android.hardware.biometrics.fingerprint.FakeFingerprintEngineTest",
+    local_include_dirs: ["include"],
+    srcs: [
+        "CancellationSignal.cpp",
+        "tests/FakeFingerprintEngineTest.cpp",
+        "FakeFingerprintEngine.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+    ],
+    static_libs: [
+        "android.hardware.biometrics.fingerprint.VirtualProps",
+        "android.hardware.biometrics.fingerprint-V2-ndk",
+        "android.hardware.biometrics.common-V2-ndk",
+        "android.hardware.keymaster-V3-ndk",
+    ],
+    vendor: true,
+    test_suites: ["general-tests"],
+    require_root: true,
+}
+
+sysprop_library {
+    name: "android.hardware.biometrics.fingerprint.VirtualProps",
+    srcs: ["fingerprint.sysprop"],
+    property_owner: "Vendor",
+    vendor: true,
+}
diff --git a/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp b/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp
new file mode 100644
index 0000000..d1fe183
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2021 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 "FakeFingerprintEngine.h"
+
+#include <fingerprint.sysprop.h>
+#include "CancellationSignal.h"
+
+#include <android-base/logging.h>
+#include <chrono>
+#include <regex>
+#include <thread>
+
+#define SLEEP_MS(x) \
+    if (x > 0) std::this_thread::sleep_for(std::chrono::milliseconds(x))
+#define BEGIN_OP(x)            \
+    do {                       \
+        LOG(INFO) << __func__; \
+        SLEEP_MS(x);           \
+    } while (0)
+#define IS_TRUE(x) ((x == "1") || (x == "true"))
+
+// This is for non-test situations, such as casual cuttlefish users, that don't
+// set an explicit value.
+// Some operations (i.e. enroll, authenticate) will be executed in tight loops
+// by parts of the UI or fail if there is no latency. For example, the
+// fingerprint settings page constantly runs auth and the enrollment UI uses a
+// cancel/restart cycle that requires some latency while the activities change.
+#define DEFAULT_LATENCY 2000
+
+using namespace ::android::fingerprint::virt;
+using namespace ::aidl::android::hardware::biometrics::fingerprint;
+
+int64_t getSystemNanoTime() {
+    timespec now;
+    clock_gettime(CLOCK_MONOTONIC, &now);
+    return now.tv_sec * 1000000000LL + now.tv_nsec;
+}
+
+bool hasElapsed(int64_t start, int64_t durationMillis) {
+    auto now = getSystemNanoTime();
+    if (now < start) return true;
+    if (durationMillis <= 0) return true;
+    return ((now - start) / 1000000LL) > durationMillis;
+}
+
+std::vector<std::string> split(const std::string& str, const std::string& sep) {
+    std::regex regex(sep);
+    std::vector<std::string> parts(std::sregex_token_iterator(str.begin(), str.end(), regex, -1),
+                                   std::sregex_token_iterator());
+    return parts;
+}
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+void FakeFingerprintEngine::generateChallengeImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+    std::uniform_int_distribution<int64_t> dist;
+    auto challenge = dist(mRandom);
+    FingerprintHalProperties::challenge(challenge);
+    cb->onChallengeGenerated(challenge);
+}
+
+void FakeFingerprintEngine::revokeChallengeImpl(ISessionCallback* cb, int64_t challenge) {
+    BEGIN_OP(0);
+    FingerprintHalProperties::challenge({});
+    cb->onChallengeRevoked(challenge);
+}
+
+void FakeFingerprintEngine::enrollImpl(ISessionCallback* cb,
+                                       const keymaster::HardwareAuthToken& hat,
+                                       const std::future<void>& cancel) {
+    BEGIN_OP(FingerprintHalProperties::operation_enroll_latency().value_or(DEFAULT_LATENCY));
+
+    // Do proper HAT verification in the real implementation.
+    if (hat.mac.empty()) {
+        LOG(ERROR) << "Fail: hat";
+        cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+        return;
+    }
+
+    if (FingerprintHalProperties::operation_enroll_fails().value_or(false)) {
+        LOG(ERROR) << "Fail: operation_enroll_fails";
+        cb->onError(Error::VENDOR, 0 /* vendorError */);
+        return;
+    }
+
+    // format is "<id>:<progress_ms>,<progress_ms>,...:<result>
+    auto nextEnroll = FingerprintHalProperties::next_enrollment().value_or("");
+    auto parts = split(nextEnroll, ":");
+    if (parts.size() != 3) {
+        LOG(ERROR) << "Fail: invalid next_enrollment";
+        cb->onError(Error::VENDOR, 0 /* vendorError */);
+        return;
+    }
+    auto enrollmentId = std::stoi(parts[0]);
+    auto progress = split(parts[1], ",");
+    for (size_t i = 0; i < progress.size(); i++) {
+        auto left = progress.size() - i - 1;
+        SLEEP_MS(std::stoi(progress[i]));
+
+        if (shouldCancel(cancel)) {
+            LOG(ERROR) << "Fail: cancel";
+            cb->onError(Error::CANCELED, 0 /* vendorCode */);
+            return;
+        }
+
+        cb->onAcquired(AcquiredInfo::GOOD, 0 /* vendorCode */);
+        if (left == 0 && !IS_TRUE(parts[2])) {  // end and failed
+            LOG(ERROR) << "Fail: requested by caller: " << nextEnroll;
+            FingerprintHalProperties::next_enrollment({});
+            cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
+        } else {  // progress and update props if last time
+            if (left == 0) {
+                auto enrollments = FingerprintHalProperties::enrollments();
+                enrollments.emplace_back(enrollmentId);
+                FingerprintHalProperties::enrollments(enrollments);
+                FingerprintHalProperties::next_enrollment({});
+                LOG(INFO) << "Enrolled: " << enrollmentId;
+            }
+            cb->onEnrollmentProgress(enrollmentId, left);
+        }
+    }
+}
+
+void FakeFingerprintEngine::authenticateImpl(ISessionCallback* cb, int64_t /* operationId */,
+                                             const std::future<void>& cancel) {
+    BEGIN_OP(FingerprintHalProperties::operation_authenticate_latency().value_or(DEFAULT_LATENCY));
+
+    auto now = getSystemNanoTime();
+    int64_t duration = FingerprintHalProperties::operation_authenticate_duration().value_or(0);
+    do {
+        if (FingerprintHalProperties::operation_authenticate_fails().value_or(false)) {
+            LOG(ERROR) << "Fail: operation_authenticate_fails";
+            cb->onError(Error::VENDOR, 0 /* vendorError */);
+            return;
+        }
+
+        if (FingerprintHalProperties::lockout().value_or(false)) {
+            LOG(ERROR) << "Fail: lockout";
+            cb->onLockoutPermanent();
+            cb->onError(Error::HW_UNAVAILABLE, 0 /* vendorError */);
+            return;
+        }
+
+        if (shouldCancel(cancel)) {
+            LOG(ERROR) << "Fail: cancel";
+            cb->onError(Error::CANCELED, 0 /* vendorCode */);
+            return;
+        }
+
+        auto id = FingerprintHalProperties::enrollment_hit().value_or(0);
+        auto enrolls = FingerprintHalProperties::enrollments();
+        auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end();
+        if (id > 0 && isEnrolled) {
+            cb->onAuthenticationSucceeded(id, {} /* hat */);
+            return;
+        }
+
+        SLEEP_MS(100);
+    } while (!hasElapsed(now, duration));
+
+    LOG(ERROR) << "Fail: not enrolled";
+    cb->onAuthenticationFailed();
+    cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+}
+
+void FakeFingerprintEngine::detectInteractionImpl(ISessionCallback* cb,
+                                                  const std::future<void>& cancel) {
+    BEGIN_OP(FingerprintHalProperties::operation_detect_interaction_latency().value_or(
+            DEFAULT_LATENCY));
+
+    if (FingerprintHalProperties::operation_detect_interaction_fails().value_or(false)) {
+        LOG(ERROR) << "Fail: operation_detect_interaction_fails";
+        cb->onError(Error::VENDOR, 0 /* vendorError */);
+        return;
+    }
+
+    if (shouldCancel(cancel)) {
+        LOG(ERROR) << "Fail: cancel";
+        cb->onError(Error::CANCELED, 0 /* vendorCode */);
+        return;
+    }
+
+    auto id = FingerprintHalProperties::enrollment_hit().value_or(0);
+    auto enrolls = FingerprintHalProperties::enrollments();
+    auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end();
+    if (id <= 0 || !isEnrolled) {
+        LOG(ERROR) << "Fail: not enrolled";
+        cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+        return;
+    }
+
+    cb->onInteractionDetected();
+}
+
+void FakeFingerprintEngine::enumerateEnrollmentsImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+
+    std::vector<int32_t> ids;
+    for (auto& enrollment : FingerprintHalProperties::enrollments()) {
+        auto id = enrollment.value_or(0);
+        if (id > 0) {
+            ids.push_back(id);
+        }
+    }
+
+    cb->onEnrollmentsEnumerated(ids);
+}
+
+void FakeFingerprintEngine::removeEnrollmentsImpl(ISessionCallback* cb,
+                                                  const std::vector<int32_t>& enrollmentIds) {
+    BEGIN_OP(0);
+
+    std::vector<std::optional<int32_t>> newEnrollments;
+    std::vector<int32_t> removed;
+    for (auto& enrollment : FingerprintHalProperties::enrollments()) {
+        auto id = enrollment.value_or(0);
+        if (std::find(enrollmentIds.begin(), enrollmentIds.end(), id) != enrollmentIds.end()) {
+            removed.push_back(id);
+        } else if (id > 0) {
+            newEnrollments.emplace_back(id);
+        }
+    }
+    FingerprintHalProperties::enrollments(newEnrollments);
+
+    cb->onEnrollmentsRemoved(enrollmentIds);
+}
+
+void FakeFingerprintEngine::getAuthenticatorIdImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+    cb->onAuthenticatorIdRetrieved(FingerprintHalProperties::authenticator_id().value_or(0));
+}
+
+void FakeFingerprintEngine::invalidateAuthenticatorIdImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+    auto id = FingerprintHalProperties::authenticator_id().value_or(0);
+    auto newId = id + 1;
+    FingerprintHalProperties::authenticator_id(newId);
+    cb->onAuthenticatorIdInvalidated(newId);
+}
+
+void FakeFingerprintEngine::resetLockoutImpl(ISessionCallback* cb,
+                                             const keymaster::HardwareAuthToken& /*hat*/) {
+    BEGIN_OP(0);
+    FingerprintHalProperties::lockout(false);
+    cb->onLockoutCleared();
+}
+
+}  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/Fingerprint.cpp b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
index 1f14de6..71dc660 100644
--- a/biometrics/fingerprint/aidl/default/Fingerprint.cpp
+++ b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
@@ -16,15 +16,19 @@
 
 #include "Fingerprint.h"
 
+#include <fingerprint.sysprop.h>
 #include "Session.h"
 
+#include <android-base/logging.h>
+
+using namespace ::android::fingerprint::virt;
+
 namespace aidl::android::hardware::biometrics::fingerprint {
 namespace {
 constexpr size_t MAX_WORKER_QUEUE_SIZE = 5;
 constexpr int SENSOR_ID = 1;
 constexpr common::SensorStrength SENSOR_STRENGTH = common::SensorStrength::STRONG;
 constexpr int MAX_ENROLLMENTS_PER_USER = 5;
-constexpr FingerprintSensorType SENSOR_TYPE = FingerprintSensorType::REAR;
 constexpr bool SUPPORTS_NAVIGATION_GESTURES = true;
 constexpr char HW_COMPONENT_ID[] = "fingerprintSensor";
 constexpr char HW_VERSION[] = "vendor/model/revision";
@@ -51,8 +55,18 @@
                                      0 /* sensorLocationY */, 0 /* sensorRadius */,
                                      "" /* display */};
 
+    FingerprintSensorType sensorType = FingerprintSensorType::UNKNOWN;
+    std::string sensorTypeProp = FingerprintHalProperties::type().value_or("");
+    if (sensorTypeProp == "" || sensorTypeProp == "default" || sensorTypeProp == "rear") {
+        sensorType = FingerprintSensorType::REAR;
+    }
+    if (sensorType == FingerprintSensorType::UNKNOWN) {
+        UNIMPLEMENTED(FATAL) << "unrecognized or unimplemented fingerprint behavior: "
+                             << sensorTypeProp;
+    }
+
     *out = {{commonProps,
-             SENSOR_TYPE,
+             sensorType,
              {sensorLocation},
              SUPPORTS_NAVIGATION_GESTURES,
              false /* supportsDetectInteraction */}};
diff --git a/biometrics/fingerprint/aidl/default/README.md b/biometrics/fingerprint/aidl/default/README.md
new file mode 100644
index 0000000..046602f
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/README.md
@@ -0,0 +1,74 @@
+# Virtual Fingerprint HAL
+
+This is a virtual HAL implementation that is backed by system properties
+instead of actual hardware. It's intended for testing and UI development
+on debuggable builds to allow devices to masquerade as alternative device
+types and for emulators.
+
+## Getting Started
+
+First, set the type of sensor the device should use, enable the virtual
+extensions in the framework, and reboot.
+
+This doesn't work with HIDL and you typically need to have a PIN or password
+set for things to work correctly, so this is a good time to set those too.
+
+```shell
+$ adb root
+$ adb shell settings put secure biometric_virtual_enabled 1
+$ adb shell setprop persist.vendor.fingerprint.virtual.type rear
+$ adb shell locksettings set-pin 0000
+$ adb shell settings put secure com.android.server.biometrics.AuthService.hidlDisabled 1
+$ adb reboot
+```
+
+### Enrollments
+
+Next, setup enrollments on the device. This can either be done through
+the UI, or via adb. 
+
+#### UI Enrollment
+
+  1. Tee up the results of the enrollment before starting the process:
+
+        ```shell
+        $ adb shell setprop vendor.fingerprint.virtual.next_enrollment 1:100,100,100:true
+        ```
+  2. Navigate to `Settings -> Security -> Fingerprint Unlock` and follow the prompts.
+  3. Verify the enrollments in the UI:
+
+        ```shell
+        $ adb shell getprop persist.vendor.fingerprint.virtual.enrollments
+        ```
+
+#### Direct Enrollment
+
+To set enrollment directly without the UI:
+
+```shell
+$ adb root
+$ adb shell setprop persist.vendor.fingerprint.virtual.enrollments 1
+$ adb shell cmd fingerprint sync
+```
+
+Note: You may need to do this twice. The templates are checked
+as part of some lazy operations, like user switching and startup, which can 
+cause the framework to delete the enrollments before the sync operation runs.
+Until this is fixed, just run the commands twice as a workaround.
+
+### Authenticate
+
+To authenticate successfully set the enrolled id that should succeed. Unset it
+or change the value to make authenticate operations fail:
+
+````shell
+$ adb shell setprop vendor.fingerprint.virtual.enrollment_hit 1
+````
+
+### View HAL State
+
+To view all the properties of the HAL (see `fingerprint.sysprop` for the API):
+
+```shell
+$ adb shell getprop | grep vendor.fingerprint.virtual
+```
diff --git a/biometrics/fingerprint/aidl/default/Session.cpp b/biometrics/fingerprint/aidl/default/Session.cpp
index 452ed12..078d3d9 100644
--- a/biometrics/fingerprint/aidl/default/Session.cpp
+++ b/biometrics/fingerprint/aidl/default/Session.cpp
@@ -101,7 +101,7 @@
         if (shouldCancel(cancFuture)) {
             mCb->onError(Error::CANCELED, 0 /* vendorCode */);
         } else {
-            mEngine->enrollImpl(mCb.get(), hat);
+            mEngine->enrollImpl(mCb.get(), hat, cancFuture);
         }
         enterIdling();
     }));
@@ -123,7 +123,7 @@
         if (shouldCancel(cancFuture)) {
             mCb->onError(Error::CANCELED, 0 /* vendorCode */);
         } else {
-            mEngine->authenticateImpl(mCb.get(), operationId);
+            mEngine->authenticateImpl(mCb.get(), operationId, cancFuture);
         }
         enterIdling();
     }));
@@ -144,7 +144,7 @@
         if (shouldCancel(cancFuture)) {
             mCb->onError(Error::CANCELED, 0 /* vendorCode */);
         } else {
-            mEngine->detectInteractionImpl(mCb.get());
+            mEngine->detectInteractionImpl(mCb.get(), cancFuture);
         }
         enterIdling();
     }));
diff --git a/biometrics/fingerprint/aidl/default/api/android.hardware.biometrics.fingerprint.VirtualProps-current.txt b/biometrics/fingerprint/aidl/default/api/android.hardware.biometrics.fingerprint.VirtualProps-current.txt
new file mode 100644
index 0000000..4724ff4
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/api/android.hardware.biometrics.fingerprint.VirtualProps-current.txt
@@ -0,0 +1,85 @@
+props {
+  owner: Vendor
+  module: "android.fingerprint.virt.FingerprintHalProperties"
+  prop {
+    api_name: "authenticator_id"
+    type: Long
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.authenticator_id"
+  }
+  prop {
+    api_name: "challenge"
+    type: Long
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.challenge"
+  }
+  prop {
+    api_name: "enrollment_hit"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.enrollment_hit"
+  }
+  prop {
+    api_name: "enrollments"
+    type: IntegerList
+    access: ReadWrite
+    prop_name: "persist.vendor.fingerprint.virtual.enrollments"
+  }
+  prop {
+    api_name: "lockout"
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.lockout"
+  }
+  prop {
+    api_name: "next_enrollment"
+    type: String
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.next_enrollment"
+  }
+  prop {
+    api_name: "operation_authenticate_duration"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_duration"
+  }
+  prop {
+    api_name: "operation_authenticate_fails"
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_fails"
+  }
+  prop {
+    api_name: "operation_authenticate_latency"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_latency"
+  }
+  prop {
+    api_name: "operation_detect_interaction_fails"
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_fails"
+  }
+  prop {
+    api_name: "operation_detect_interaction_latency"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_latency"
+  }
+  prop {
+    api_name: "operation_enroll_fails"
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_enroll_fails"
+  }
+  prop {
+    api_name: "operation_enroll_latency"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_enroll_latency"
+  }
+  prop {
+    api_name: "type"
+    type: String
+    access: ReadWrite
+    prop_name: "persist.vendor.fingerprint.virtual.type"
+    enum_values: "default|rear|udfps|side"
+  }
+}
diff --git a/biometrics/fingerprint/aidl/default/fingerprint.sysprop b/biometrics/fingerprint/aidl/default/fingerprint.sysprop
new file mode 100644
index 0000000..12c8648
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/fingerprint.sysprop
@@ -0,0 +1,135 @@
+# fingerprint.sysprop
+# module becomes static class (Java) / namespace (C++) for serving API
+module: "android.fingerprint.virt.FingerprintHalProperties"
+owner: Vendor
+
+# type of fingerprint sensor
+prop {
+    prop_name: "persist.vendor.fingerprint.virtual.type"
+    type: String
+    scope: Public
+    access: ReadWrite
+    enum_values: "default|rear|udfps|side"
+    api_name: "type"
+}
+
+# ids of call current enrollments
+prop {
+    prop_name: "persist.vendor.fingerprint.virtual.enrollments"
+    type: IntegerList
+    scope: Public
+    access: ReadWrite
+    api_name: "enrollments"
+}
+
+# authenticate and detectInteraction will succeed with this
+# enrollment id, when present, otherwise they will error
+prop {
+    prop_name: "vendor.fingerprint.virtual.enrollment_hit"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "enrollment_hit"
+}
+
+# the next enrollment in the format: "<id>:<delay>,<delay>,...:<result>"
+# for example: "2:0:true"
+# this property is reset after enroll completes
+prop {
+    prop_name: "vendor.fingerprint.virtual.next_enrollment"
+    type: String
+    scope: Public
+    access: ReadWrite
+    api_name: "next_enrollment"
+}
+
+# value for getAuthenticatorId or 0
+prop {
+    prop_name: "vendor.fingerprint.virtual.authenticator_id"
+    type: Long
+    scope: Public
+    access: ReadWrite
+    api_name: "authenticator_id"
+}
+
+# value for generateChallenge
+prop {
+    prop_name: "vendor.fingerprint.virtual.challenge"
+    type: Long
+    scope: Public
+    access: ReadWrite
+    api_name: "challenge"
+}
+
+# if locked out
+prop {
+    prop_name: "vendor.fingerprint.virtual.lockout"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "lockout"
+}
+
+# force all authenticate operations to fail
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_fails"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_authenticate_fails"
+}
+
+# force all detectInteraction operations to fail
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_fails"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_detect_interaction_fails"
+}
+
+# force all enroll operations to fail
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_enroll_fails"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_enroll_fails"
+}
+
+# add a latency to authentication operations
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_latency"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_authenticate_latency"
+}
+
+# add a latency to detectInteraction operations
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_latency"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_detect_interaction_latency"
+}
+
+# add a latency to enroll operations
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_enroll_latency"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_enroll_latency"
+}
+
+# millisecond duration for authenticate operations
+# (waits for changes to enrollment_hit)
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_duration"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_authenticate_duration"
+}
diff --git a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
index b927770..8659b79 100644
--- a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
+++ b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
@@ -16,71 +16,33 @@
 
 #pragma once
 
-#include <android-base/logging.h>
+#include <aidl/android/hardware/biometrics/fingerprint/ISessionCallback.h>
+
 #include <random>
 
+#include "CancellationSignal.h"
+
+using namespace ::aidl::android::hardware::biometrics::common;
+
 namespace aidl::android::hardware::biometrics::fingerprint {
 
+// A fake engine that is backed by system properties instead of hardware.
 class FakeFingerprintEngine {
   public:
     FakeFingerprintEngine() : mRandom(std::mt19937::default_seed) {}
 
-    void generateChallengeImpl(ISessionCallback* cb) {
-        LOG(INFO) << "generateChallengeImpl";
-        std::uniform_int_distribution<int64_t> dist;
-        auto challenge = dist(mRandom);
-        cb->onChallengeGenerated(challenge);
-    }
-
-    void revokeChallengeImpl(ISessionCallback* cb, int64_t challenge) {
-        LOG(INFO) << "revokeChallengeImpl";
-        cb->onChallengeRevoked(challenge);
-    }
-
-    void enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat) {
-        LOG(INFO) << "enrollImpl";
-        // Do proper HAT verification in the real implementation.
-        if (hat.mac.empty()) {
-            cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
-            return;
-        }
-        cb->onEnrollmentProgress(0 /* enrollmentId */, 0 /* remaining */);
-    }
-
-    void authenticateImpl(ISessionCallback* cb, int64_t /* operationId */) {
-        LOG(INFO) << "authenticateImpl";
-        cb->onAuthenticationSucceeded(0 /* enrollmentId */, {} /* hat */);
-    }
-
-    void detectInteractionImpl(ISessionCallback* cb) {
-        LOG(INFO) << "detectInteractionImpl";
-        cb->onInteractionDetected();
-    }
-
-    void enumerateEnrollmentsImpl(ISessionCallback* cb) {
-        LOG(INFO) << "enumerateEnrollmentsImpl";
-        cb->onEnrollmentsEnumerated({} /* enrollmentIds */);
-    }
-
-    void removeEnrollmentsImpl(ISessionCallback* cb, const std::vector<int32_t>& enrollmentIds) {
-        LOG(INFO) << "removeEnrollmentsImpl";
-        cb->onEnrollmentsRemoved(enrollmentIds);
-    }
-
-    void getAuthenticatorIdImpl(ISessionCallback* cb) {
-        LOG(INFO) << "getAuthenticatorIdImpl";
-        cb->onAuthenticatorIdRetrieved(0 /* authenticatorId */);
-    }
-
-    void invalidateAuthenticatorIdImpl(ISessionCallback* cb) {
-        LOG(INFO) << "invalidateAuthenticatorIdImpl";
-        cb->onAuthenticatorIdInvalidated(0 /* newAuthenticatorId */);
-    }
-
-    void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/) {
-        LOG(INFO) << "resetLockoutImpl";
-        cb->onLockoutCleared();
-    }
+    void generateChallengeImpl(ISessionCallback* cb);
+    void revokeChallengeImpl(ISessionCallback* cb, int64_t challenge);
+    void enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
+                    const std::future<void>& cancel);
+    void authenticateImpl(ISessionCallback* cb, int64_t operationId,
+                          const std::future<void>& cancel);
+    void detectInteractionImpl(ISessionCallback* cb, const std::future<void>& cancel);
+    void enumerateEnrollmentsImpl(ISessionCallback* cb);
+    void removeEnrollmentsImpl(ISessionCallback* cb, const std::vector<int32_t>& enrollmentIds);
+    void getAuthenticatorIdImpl(ISessionCallback* cb);
+    void invalidateAuthenticatorIdImpl(ISessionCallback* cb);
+    void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/);
 
     std::mt19937 mRandom;
 };
diff --git a/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp
new file mode 100644
index 0000000..742d933
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2021 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 <aidl/android/hardware/biometrics/fingerprint/BnSessionCallback.h>
+
+#include "FakeFingerprintEngine.h"
+
+using namespace ::android::fingerprint::virt;
+using namespace ::aidl::android::hardware::biometrics::fingerprint;
+using namespace ::aidl::android::hardware::keymaster;
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+class TestSessionCallback : public BnSessionCallback {
+  public:
+    ndk::ScopedAStatus onChallengeGenerated(int64_t challenge) override {
+        mLastChallenge = challenge;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onChallengeRevoked(int64_t challenge) override {
+        mLastChallengeRevoked = challenge;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onError(fingerprint::Error error, int32_t) override {
+        mError = error;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onEnrollmentProgress(int32_t enrollmentId, int32_t remaining) override {
+        if (remaining == 0) mLastEnrolled = enrollmentId;
+        return ndk::ScopedAStatus::ok();
+    };
+
+    ::ndk::ScopedAStatus onAuthenticationSucceeded(int32_t enrollmentId,
+                                                   const keymaster::HardwareAuthToken&) override {
+        mLastAuthenticated = enrollmentId;
+        mAuthenticateFailed = false;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticationFailed() override {
+        mLastAuthenticated = 0;
+        mAuthenticateFailed = true;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onInteractionDetected() override {
+        mInteractionDetectedCount++;
+        return ndk::ScopedAStatus::ok();
+    };
+    ndk::ScopedAStatus onAcquired(AcquiredInfo /*info*/, int32_t /*vendorCode*/) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onEnrollmentsEnumerated(
+            const std::vector<int32_t>& enrollmentIds) override {
+        mLastEnrollmentEnumerated = enrollmentIds;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onEnrollmentsRemoved(const std::vector<int32_t>& enrollmentIds) override {
+        mLastEnrollmentRemoved = enrollmentIds;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticatorIdRetrieved(int64_t authenticatorId) override {
+        mLastAuthenticatorId = authenticatorId;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticatorIdInvalidated(int64_t authenticatorId) override {
+        mLastAuthenticatorId = authenticatorId;
+        mAuthenticatorIdInvalidated = true;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onLockoutPermanent() override {
+        mLockoutPermanent = true;
+        return ndk::ScopedAStatus::ok();
+    };
+    ndk::ScopedAStatus onLockoutTimed(int64_t /* timeout */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ndk::ScopedAStatus onLockoutCleared() override { return ndk::ScopedAStatus::ok(); }
+    ndk::ScopedAStatus onSessionClosed() override { return ndk::ScopedAStatus::ok(); }
+
+    Error mError = Error::UNKNOWN;
+    int64_t mLastChallenge = -1;
+    int64_t mLastChallengeRevoked = -1;
+    int32_t mLastEnrolled = -1;
+    int32_t mLastAuthenticated = -1;
+    int64_t mLastAuthenticatorId = -1;
+    std::vector<int32_t> mLastEnrollmentEnumerated;
+    std::vector<int32_t> mLastEnrollmentRemoved;
+    bool mAuthenticateFailed = false;
+    bool mAuthenticatorIdInvalidated = false;
+    bool mLockoutPermanent = false;
+    int mInteractionDetectedCount = 0;
+};
+
+class FakeFingerprintEngineTest : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        FingerprintHalProperties::operation_enroll_latency(0);
+        FingerprintHalProperties::operation_authenticate_latency(0);
+        FingerprintHalProperties::operation_detect_interaction_latency(0);
+        mCallback = ndk::SharedRefBase::make<TestSessionCallback>();
+    }
+
+    FakeFingerprintEngine mEngine;
+    std::shared_ptr<TestSessionCallback> mCallback;
+    std::promise<void> mCancel;
+};
+
+TEST_F(FakeFingerprintEngineTest, GenerateChallenge) {
+    mEngine.generateChallengeImpl(mCallback.get());
+    ASSERT_EQ(FingerprintHalProperties::challenge().value(), mCallback->mLastChallenge);
+}
+
+TEST_F(FakeFingerprintEngineTest, RevokeChallenge) {
+    auto challenge = FingerprintHalProperties::challenge().value_or(10);
+    mEngine.revokeChallengeImpl(mCallback.get(), challenge);
+    ASSERT_FALSE(FingerprintHalProperties::challenge().has_value());
+    ASSERT_EQ(challenge, mCallback->mLastChallengeRevoked);
+}
+
+TEST_F(FakeFingerprintEngineTest, ResetLockout) {
+    FingerprintHalProperties::lockout(true);
+    mEngine.resetLockoutImpl(mCallback.get(), {});
+    ASSERT_FALSE(FingerprintHalProperties::lockout().value_or(true));
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticatorId) {
+    FingerprintHalProperties::authenticator_id(50);
+    mEngine.getAuthenticatorIdImpl(mCallback.get());
+    ASSERT_EQ(50, mCallback->mLastAuthenticatorId);
+    ASSERT_FALSE(mCallback->mAuthenticatorIdInvalidated);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticatorIdInvalidate) {
+    FingerprintHalProperties::authenticator_id(500);
+    mEngine.invalidateAuthenticatorIdImpl(mCallback.get());
+    ASSERT_NE(500, FingerprintHalProperties::authenticator_id().value());
+    ASSERT_TRUE(mCallback->mAuthenticatorIdInvalidated);
+}
+
+TEST_F(FakeFingerprintEngineTest, Enroll) {
+    FingerprintHalProperties::enrollments({});
+    FingerprintHalProperties::next_enrollment("4:0,0:true");
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mEngine.enrollImpl(mCallback.get(), hat, mCancel.get_future());
+    ASSERT_FALSE(FingerprintHalProperties::next_enrollment().has_value());
+    ASSERT_EQ(1, FingerprintHalProperties::enrollments().size());
+    ASSERT_EQ(4, FingerprintHalProperties::enrollments()[0].value());
+    ASSERT_EQ(4, mCallback->mLastEnrolled);
+}
+
+TEST_F(FakeFingerprintEngineTest, EnrollCancel) {
+    FingerprintHalProperties::enrollments({});
+    auto next = "4:0,0:true";
+    FingerprintHalProperties::next_enrollment(next);
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mCancel.set_value();
+    mEngine.enrollImpl(mCallback.get(), hat, mCancel.get_future());
+    ASSERT_EQ(Error::CANCELED, mCallback->mError);
+    ASSERT_EQ(-1, mCallback->mLastEnrolled);
+    ASSERT_EQ(0, FingerprintHalProperties::enrollments().size());
+    ASSERT_EQ(next, FingerprintHalProperties::next_enrollment().value_or(""));
+}
+
+TEST_F(FakeFingerprintEngineTest, EnrollFail) {
+    FingerprintHalProperties::enrollments({});
+    auto next = "2:0,0:false";
+    FingerprintHalProperties::next_enrollment(next);
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mEngine.enrollImpl(mCallback.get(), hat, mCancel.get_future());
+    ASSERT_EQ(Error::UNABLE_TO_PROCESS, mCallback->mError);
+    ASSERT_EQ(-1, mCallback->mLastEnrolled);
+    ASSERT_EQ(0, FingerprintHalProperties::enrollments().size());
+    ASSERT_FALSE(FingerprintHalProperties::next_enrollment().has_value());
+}
+
+TEST_F(FakeFingerprintEngineTest, Authenticate) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit(2);
+    mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+    ASSERT_FALSE(mCallback->mAuthenticateFailed);
+    ASSERT_EQ(2, mCallback->mLastAuthenticated);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateCancel) {
+    FingerprintHalProperties::enrollments({2});
+    FingerprintHalProperties::enrollment_hit(2);
+    mCancel.set_value();
+    mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+    ASSERT_EQ(Error::CANCELED, mCallback->mError);
+    ASSERT_EQ(-1, mCallback->mLastAuthenticated);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateNotSet) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit({});
+    mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+    ASSERT_TRUE(mCallback->mAuthenticateFailed);
+    ASSERT_EQ(mCallback->mError, Error::UNABLE_TO_PROCESS);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateNotEnrolled) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit(3);
+    mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+    ASSERT_TRUE(mCallback->mAuthenticateFailed);
+    ASSERT_EQ(mCallback->mError, Error::UNABLE_TO_PROCESS);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateLockout) {
+    FingerprintHalProperties::enrollments({22, 2});
+    FingerprintHalProperties::enrollment_hit(2);
+    FingerprintHalProperties::lockout(true);
+    mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+    ASSERT_TRUE(mCallback->mLockoutPermanent);
+    ASSERT_NE(mCallback->mError, Error::UNKNOWN);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetect) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit(2);
+    mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+    ASSERT_EQ(1, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetectCancel) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit(2);
+    mCancel.set_value();
+    mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+    ASSERT_EQ(Error::CANCELED, mCallback->mError);
+    ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetectNotSet) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit({});
+    mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+    ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetectNotEnrolled) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit(25);
+    mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+    ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, EnumerateEnrolled) {
+    FingerprintHalProperties::enrollments({2, 4, 8});
+    mEngine.enumerateEnrollmentsImpl(mCallback.get());
+    ASSERT_EQ(3, mCallback->mLastEnrollmentEnumerated.size());
+    for (auto id : FingerprintHalProperties::enrollments()) {
+        ASSERT_TRUE(std::find(mCallback->mLastEnrollmentEnumerated.begin(),
+                              mCallback->mLastEnrollmentEnumerated.end(),
+                              id) != mCallback->mLastEnrollmentEnumerated.end());
+    }
+}
+
+TEST_F(FakeFingerprintEngineTest, RemoveEnrolled) {
+    FingerprintHalProperties::enrollments({2, 4, 8, 1});
+    mEngine.removeEnrollmentsImpl(mCallback.get(), {2, 8});
+    auto enrolls = FingerprintHalProperties::enrollments();
+    ASSERT_EQ(2, mCallback->mLastEnrollmentRemoved.size());
+    for (auto id : {2, 8}) {
+        ASSERT_TRUE(std::find(mCallback->mLastEnrollmentRemoved.begin(),
+                              mCallback->mLastEnrollmentRemoved.end(),
+                              id) != mCallback->mLastEnrollmentRemoved.end());
+    }
+    ASSERT_EQ(2, enrolls.size());
+    for (auto id : {1, 4}) {
+        ASSERT_TRUE(std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end());
+    }
+}
+
+}  // namespace aidl::android::hardware::biometrics::fingerprint
+
+int main(int argc, char** argv) {
+    testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/biometrics/fingerprint/aidl/vts/Android.bp b/biometrics/fingerprint/aidl/vts/Android.bp
index 30d5624..a474f66 100644
--- a/biometrics/fingerprint/aidl/vts/Android.bp
+++ b/biometrics/fingerprint/aidl/vts/Android.bp
@@ -15,8 +15,8 @@
     ],
     srcs: ["VtsHalBiometricsFingerprintTargetTest.cpp"],
     static_libs: [
-        "android.hardware.biometrics.common-V1-ndk",
-        "android.hardware.biometrics.fingerprint-V1-ndk",
+        "android.hardware.biometrics.common-V2-ndk",
+        "android.hardware.biometrics.fingerprint-V2-ndk",
         "android.hardware.keymaster-V3-ndk",
     ],
     shared_libs: [