Fingerprint virtual HAL checkin (part 3)

- support randomization
- display touch events
- lockout
- cmd and dumpsys

Bug: 230515082
Bug: 230515086
Test: atest FakeFingerprintEngineTest
      atest FakeFingerprintEngineUdfpsTest
      atest --no-bazel-mode VtsHalBiometricsFingerprintTargetTest

Change-Id: Ia5399c86b7fec90b41d426c2f82cb257f4dc9a8a
diff --git a/biometrics/common/util/include/util/Util.h b/biometrics/common/util/include/util/Util.h
index 29ec0f8..da19dc6 100644
--- a/biometrics/common/util/include/util/Util.h
+++ b/biometrics/common/util/include/util/Util.h
@@ -40,7 +40,7 @@
 // by parts of the UI or fail if there is no latency. For example, the
 // Face 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 800
+#define DEFAULT_LATENCY 400
 
 class Util {
   public:
@@ -66,4 +66,4 @@
     }
 };
 
-}  // namespace aidl::android::hardware::biometrics
\ No newline at end of file
+}  // namespace aidl::android::hardware::biometrics
diff --git a/biometrics/fingerprint/aidl/default/Android.bp b/biometrics/fingerprint/aidl/default/Android.bp
index dc0199c..77c74e1 100644
--- a/biometrics/fingerprint/aidl/default/Android.bp
+++ b/biometrics/fingerprint/aidl/default/Android.bp
@@ -15,6 +15,7 @@
     vintf_fragments: ["fingerprint-example.xml"],
     local_include_dirs: ["include"],
     srcs: [
+        "FakeLockoutTracker.cpp",
         "FakeFingerprintEngine.cpp",
         "FakeFingerprintEngineRear.cpp",
         "FakeFingerprintEngineUdfps.cpp",
@@ -40,6 +41,7 @@
     srcs: [
         "tests/FakeFingerprintEngineTest.cpp",
         "FakeFingerprintEngine.cpp",
+        "FakeLockoutTracker.cpp",
     ],
     shared_libs: [
         "libbase",
@@ -65,6 +67,31 @@
         "tests/FakeFingerprintEngineUdfpsTest.cpp",
         "FakeFingerprintEngineUdfps.cpp",
         "FakeFingerprintEngine.cpp",
+        "FakeLockoutTracker.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "android.hardware.biometrics.common.thread",
+    ],
+    static_libs: [
+        "libandroid.hardware.biometrics.fingerprint.VirtualProps",
+        "android.hardware.biometrics.fingerprint-V2-ndk",
+        "android.hardware.biometrics.common-V2-ndk",
+        "android.hardware.keymaster-V3-ndk",
+        "android.hardware.biometrics.common.util",
+    ],
+    vendor: true,
+    test_suites: ["general-tests"],
+    require_root: true,
+}
+
+cc_test {
+    name: "android.hardware.biometrics.fingerprint.FakeLockoutTrackerTest",
+    local_include_dirs: ["include"],
+    srcs: [
+        "tests/FakeLockoutTrackerTest.cpp",
+        "FakeLockoutTracker.cpp",
     ],
     shared_libs: [
         "libbase",
diff --git a/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp b/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp
index 651c9dc..90ec8f2 100644
--- a/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp
+++ b/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -15,6 +15,7 @@
  */
 
 #include "FakeFingerprintEngine.h"
+#include <regex>
 #include "Fingerprint.h"
 
 #include <android-base/logging.h>
@@ -47,7 +48,7 @@
 void FakeFingerprintEngine::enrollImpl(ISessionCallback* cb,
                                        const keymaster::HardwareAuthToken& hat,
                                        const std::future<void>& cancel) {
-    BEGIN_OP(FingerprintHalProperties::operation_enroll_latency().value_or(DEFAULT_LATENCY));
+    BEGIN_OP(getLatency(FingerprintHalProperties::operation_enroll_latency()));
 
     // Do proper HAT verification in the real implementation.
     if (hat.mac.empty()) {
@@ -117,7 +118,7 @@
 
 void FakeFingerprintEngine::authenticateImpl(ISessionCallback* cb, int64_t /* operationId */,
                                              const std::future<void>& cancel) {
-    BEGIN_OP(FingerprintHalProperties::operation_authenticate_latency().value_or(DEFAULT_LATENCY));
+    BEGIN_OP(getLatency(FingerprintHalProperties::operation_authenticate_latency()));
 
     int64_t now = Util::getSystemNanoTime();
     int64_t duration = FingerprintHalProperties::operation_authenticate_duration().value_or(10);
@@ -131,10 +132,23 @@
         return;
     }
 
+    // got lockout?
+    FakeLockoutTracker::LockoutMode lockoutMode = mLockoutTracker.getMode();
+    if (lockoutMode == FakeLockoutTracker::LockoutMode::kPermanent) {
+        LOG(ERROR) << "Fail: lockout permanent";
+        cb->onLockoutPermanent();
+        return;
+    } else if (lockoutMode == FakeLockoutTracker::LockoutMode::kTimed) {
+        int64_t timeLeft = mLockoutTracker.getLockoutTimeLeft();
+        LOG(ERROR) << "Fail: lockout timed " << timeLeft;
+        cb->onLockoutTimed(timeLeft);
+    }
+
     int i = 0;
     do {
         if (FingerprintHalProperties::operation_authenticate_fails().value_or(false)) {
             LOG(ERROR) << "Fail: operation_authenticate_fails";
+            mLockoutTracker.addFailedAttempt();
             cb->onAuthenticationFailed();
             return;
         }
@@ -174,20 +188,30 @@
     auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end();
     if (id > 0 && isEnrolled) {
         cb->onAuthenticationSucceeded(id, {} /* hat */);
+        mLockoutTracker.reset();
         return;
     } else {
         LOG(ERROR) << "Fail: fingerprint not enrolled";
         cb->onAuthenticationFailed();
+        mLockoutTracker.addFailedAttempt();
     }
 }
 
 void FakeFingerprintEngine::detectInteractionImpl(ISessionCallback* cb,
                                                   const std::future<void>& cancel) {
-    BEGIN_OP(FingerprintHalProperties::operation_detect_interaction_latency().value_or(
-            DEFAULT_LATENCY));
+    BEGIN_OP(getLatency(FingerprintHalProperties::operation_detect_interaction_latency()));
 
     int64_t duration =
             FingerprintHalProperties::operation_detect_interaction_duration().value_or(10);
+
+    auto detectInteractionSupported =
+            FingerprintHalProperties::detect_interaction().value_or(false);
+    if (!detectInteractionSupported) {
+        LOG(ERROR) << "Detect interaction is not supported";
+        cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+        return;
+    }
+
     auto acquired = FingerprintHalProperties::operation_detect_interaction_acquired().value_or("1");
     auto acquiredInfos = parseIntSequence(acquired);
     int N = acquiredInfos.size();
@@ -308,6 +332,7 @@
     }
     FingerprintHalProperties::lockout(false);
     cb->onLockoutCleared();
+    mLockoutTracker.reset();
 }
 
 ndk::ScopedAStatus FakeFingerprintEngine::onPointerDownImpl(int32_t /*pointerId*/, int32_t /*x*/,
@@ -383,49 +408,52 @@
     return res;
 }
 
-std::vector<std::vector<int32_t>> FakeFingerprintEngine::parseEnrollmentCapture(
-        const std::string& str) {
+bool FakeFingerprintEngine::parseEnrollmentCaptureSingle(const std::string& str,
+                                                         std::vector<std::vector<int32_t>>& res) {
     std::vector<int32_t> defaultAcquiredInfo = {(int32_t)AcquiredInfo::GOOD};
-    std::vector<std::vector<int32_t>> res;
-    int i = 0, N = str.length();
-    std::size_t found = 0;
     bool aborted = true;
 
-    while (found != std::string::npos) {
-        std::string durationStr, acquiredStr;
-        found = str.find_first_of("-,", i);
-        if (found == std::string::npos) {
-            if (N - i < 1) break;
-            durationStr = str.substr(i, N - i);
-        } else {
-            durationStr = str.substr(i, found - i);
-            if (str[found] == '-') {
-                found = str.find_first_of('[', found + 1);
-                if (found == std::string::npos) break;
-                i = found + 1;
-                found = str.find_first_of(']', found + 1);
-                if (found == std::string::npos) break;
-                acquiredStr = str.substr(i, found - i);
-                found = str.find_first_of(',', found + 1);
-            }
-        }
-        std::vector<int32_t> duration{0};
-        if (!ParseInt(durationStr, &duration[0])) break;
-        res.push_back(duration);
-        if (!acquiredStr.empty()) {
-            std::vector<int32_t> acquiredInfo = parseIntSequence(acquiredStr);
-            if (acquiredInfo.empty()) break;
-            res.push_back(acquiredInfo);
+    do {
+        std::smatch sms;
+        // Parses strings like "1000-[5,1]" or "500"
+        std::regex ex("((\\d+)(-\\[([\\d|,]+)\\])?)");
+        if (!regex_match(str.cbegin(), str.cend(), sms, ex)) break;
+        int32_t duration;
+        if (!ParseInt(sms.str(2), &duration)) break;
+        res.push_back({duration});
+        if (!sms.str(4).empty()) {
+            auto acqv = parseIntSequence(sms.str(4));
+            if (acqv.empty()) break;
+            res.push_back(acqv);
         } else
             res.push_back(defaultAcquiredInfo);
+        aborted = false;
+    } while (0);
 
-        i = found + 1;
-        if (found == std::string::npos || found == N - 1) aborted = false;
+    return !aborted;
+}
+
+std::vector<std::vector<int32_t>> FakeFingerprintEngine::parseEnrollmentCapture(
+        const std::string& str) {
+    std::vector<std::vector<int32_t>> res;
+
+    std::string s(str);
+    s.erase(std::remove_if(s.begin(), s.end(), ::isspace), s.end());
+    bool aborted = false;
+    std::smatch sms;
+    // Parses strings like "1000-[5,1],500,800-[6,5,1]"
+    //                      ---------- --- -----------
+    //  into parts:             A       B       C
+    while (regex_search(s, sms, std::regex("^(,)?(\\d+(-\\[[\\d|,]+\\])?)"))) {
+        if (!parseEnrollmentCaptureSingle(sms.str(2), res)) {
+            aborted = true;
+            break;
+        }
+        s = sms.suffix();
     }
-
-    if (aborted) {
-        LOG(ERROR) << "Failed to parse enrollment captures:" + str;
+    if (aborted || s.length() != 0) {
         res.clear();
+        LOG(ERROR) << "Failed to parse enrollment captures:" + str;
     }
 
     return res;
@@ -455,4 +483,34 @@
     return res;
 }
 
+int32_t FakeFingerprintEngine::getLatency(
+        const std::vector<std::optional<std::int32_t>>& latencyIn) {
+    int32_t res = DEFAULT_LATENCY;
+
+    std::vector<int32_t> latency;
+    for (auto x : latencyIn)
+        if (x.has_value()) latency.push_back(*x);
+
+    switch (latency.size()) {
+        case 0:
+            break;
+        case 1:
+            res = latency[0];
+            break;
+        case 2:
+            res = getRandomInRange(latency[0], latency[1]);
+            break;
+        default:
+            LOG(ERROR) << "ERROR: unexpected input of size " << latency.size();
+            break;
+    }
+
+    return res;
+}
+
+int32_t FakeFingerprintEngine::getRandomInRange(int32_t bound1, int32_t bound2) {
+    std::uniform_int_distribution<int32_t> dist(std::min(bound1, bound2), std::max(bound1, bound2));
+    return dist(mRandom);
+}
+
 }  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/FakeFingerprintEngineUdfps.cpp b/biometrics/fingerprint/aidl/default/FakeFingerprintEngineUdfps.cpp
index d8579a4..3cdfc70 100644
--- a/biometrics/fingerprint/aidl/default/FakeFingerprintEngineUdfps.cpp
+++ b/biometrics/fingerprint/aidl/default/FakeFingerprintEngineUdfps.cpp
@@ -23,10 +23,16 @@
 #include "util/CancellationSignal.h"
 #include "util/Util.h"
 
+#undef LOG_TAG
+#define LOG_TAG "FingerprintVirtualHalUdfps"
+
 using namespace ::android::fingerprint::virt;
 
 namespace aidl::android::hardware::biometrics::fingerprint {
 
+FakeFingerprintEngineUdfps::FakeFingerprintEngineUdfps()
+    : FakeFingerprintEngine(), mWorkMode(WorkMode::kIdle), mPointerDownTime(0), mUiReadyTime(0) {}
+
 SensorLocation FakeFingerprintEngineUdfps::defaultSensorLocation() {
     return {0 /* displayId (not used) */, defaultSensorLocationX /* sensorLocationX */,
             defaultSensorLocationY /* sensorLocationY */, defaultSensorRadius /* sensorRadius */,
@@ -37,22 +43,95 @@
                                                                  int32_t /*x*/, int32_t /*y*/,
                                                                  float /*minor*/, float /*major*/) {
     BEGIN_OP(0);
-
-    // TODO(b/230515082): if need to handle display touch events
-
+    // verify whetehr touch coordinates/area matching sensor location ?
+    mPointerDownTime = Util::getSystemNanoTime();
+    if (FingerprintHalProperties::control_illumination().value_or(false)) {
+        fingerDownAction();
+    }
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus FakeFingerprintEngineUdfps::onPointerUpImpl(int32_t /*pointerId*/) {
     BEGIN_OP(0);
-    // TODO(b/230515082)
+    mUiReadyTime = 0;
+    mPointerDownTime = 0;
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus FakeFingerprintEngineUdfps::onUiReadyImpl() {
     BEGIN_OP(0);
-    // TODO(b/230515082)
+
+    if (Util::hasElapsed(mPointerDownTime, uiReadyTimeoutInMs * 100)) {
+        LOG(ERROR) << "onUiReady() arrives too late after onPointerDown()";
+    } else {
+        fingerDownAction();
+    }
     return ndk::ScopedAStatus::ok();
 }
 
+void FakeFingerprintEngineUdfps::fingerDownAction() {
+    switch (mWorkMode) {
+        case WorkMode::kAuthenticate:
+            onAuthenticateFingerDown();
+            break;
+        case WorkMode::kEnroll:
+            onEnrollFingerDown();
+            break;
+        case WorkMode::kDetectInteract:
+            onDetectInteractFingerDown();
+            break;
+        default:
+            LOG(WARNING) << "unexpected call: onUiReady()";
+            break;
+    }
+
+    mUiReadyTime = 0;
+    mPointerDownTime = 0;
+}
+
+void FakeFingerprintEngineUdfps::onAuthenticateFingerDown() {
+    FakeFingerprintEngine::authenticateImpl(mCb, mOperationId, mCancelVec[0]);
+}
+
+void FakeFingerprintEngineUdfps::onEnrollFingerDown() {
+    // Any use case to emulate display touch for each capture during enrollment?
+    FakeFingerprintEngine::enrollImpl(mCb, mHat, mCancelVec[0]);
+}
+
+void FakeFingerprintEngineUdfps::onDetectInteractFingerDown() {
+    FakeFingerprintEngine::detectInteractionImpl(mCb, mCancelVec[0]);
+}
+
+void FakeFingerprintEngineUdfps::enrollImpl(ISessionCallback* cb,
+                                            const keymaster::HardwareAuthToken& hat,
+                                            const std::future<void>& cancel) {
+    updateContext(WorkMode::kEnroll, cb, const_cast<std::future<void>&>(cancel), 0, hat);
+}
+
+void FakeFingerprintEngineUdfps::authenticateImpl(ISessionCallback* cb, int64_t operationId,
+                                                  const std::future<void>& cancel) {
+    updateContext(WorkMode::kAuthenticate, cb, const_cast<std::future<void>&>(cancel), operationId,
+                  keymaster::HardwareAuthToken());
+}
+
+void FakeFingerprintEngineUdfps::detectInteractionImpl(ISessionCallback* cb,
+                                                       const std::future<void>& cancel) {
+    updateContext(WorkMode::kDetectInteract, cb, const_cast<std::future<void>&>(cancel), 0,
+                  keymaster::HardwareAuthToken());
+}
+
+void FakeFingerprintEngineUdfps::updateContext(WorkMode mode, ISessionCallback* cb,
+                                               std::future<void>& cancel, int64_t operationId,
+                                               const keymaster::HardwareAuthToken& hat) {
+    mPointerDownTime = 0;
+    mUiReadyTime = 0;
+    mCancelVec.clear();
+
+    mCancelVec.push_back(std::move(cancel));
+    mWorkMode = mode;
+    mCb = cb;
+    mOperationId = operationId;
+    mHat = hat;
+}
+
 }  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/FakeLockoutTracker.cpp b/biometrics/fingerprint/aidl/default/FakeLockoutTracker.cpp
new file mode 100644
index 0000000..5996406
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/FakeLockoutTracker.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 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 "FakeLockoutTracker.h"
+#include <fingerprint.sysprop.h>
+#include "util/Util.h"
+
+using namespace ::android::fingerprint::virt;
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+void FakeLockoutTracker::reset() {
+    mFailedCount = 0;
+    mLockoutTimedStart = 0;
+    mCurrentMode = LockoutMode::kNone;
+}
+
+void FakeLockoutTracker::addFailedAttempt() {
+    bool enabled = FingerprintHalProperties::lockout_enable().value_or(false);
+    if (enabled) {
+        mFailedCount++;
+        int32_t lockoutTimedThreshold =
+                FingerprintHalProperties::lockout_timed_threshold().value_or(5);
+        int32_t lockoutPermanetThreshold =
+                FingerprintHalProperties::lockout_permanent_threshold().value_or(20);
+        if (mFailedCount >= lockoutPermanetThreshold) {
+            mCurrentMode = LockoutMode::kPermanent;
+            FingerprintHalProperties::lockout(true);
+        } else if (mFailedCount >= lockoutTimedThreshold) {
+            if (mCurrentMode == LockoutMode::kNone) {
+                mCurrentMode = LockoutMode::kTimed;
+                mLockoutTimedStart = Util::getSystemNanoTime();
+            }
+        }
+    } else {
+        reset();
+    }
+}
+
+FakeLockoutTracker::LockoutMode FakeLockoutTracker::getMode() {
+    if (mCurrentMode == LockoutMode::kTimed) {
+        int32_t lockoutTimedDuration =
+                FingerprintHalProperties::lockout_timed_duration().value_or(10 * 100);
+        if (Util::hasElapsed(mLockoutTimedStart, lockoutTimedDuration)) {
+            mCurrentMode = LockoutMode::kNone;
+            mLockoutTimedStart = 0;
+        }
+    }
+
+    return mCurrentMode;
+}
+
+int64_t FakeLockoutTracker::getLockoutTimeLeft() {
+    int64_t res = 0;
+
+    if (mLockoutTimedStart > 0) {
+        auto now = Util::getSystemNanoTime();
+        auto left = now - mLockoutTimedStart;
+        res = (left > 0) ? (left / 1000000LL) : 0;
+    }
+
+    return res;
+}
+}  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/Fingerprint.cpp b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
index 74e7caf..be93224 100644
--- a/biometrics/fingerprint/aidl/default/Fingerprint.cpp
+++ b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
@@ -15,11 +15,13 @@
  */
 
 #include "Fingerprint.h"
-
-#include <fingerprint.sysprop.h>
 #include "Session.h"
 
+#include <fingerprint.sysprop.h>
+
+#include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 
 using namespace ::android::fingerprint::virt;
 
@@ -64,7 +66,6 @@
             {HW_COMPONENT_ID, HW_VERSION, FW_VERSION, SERIAL_NUMBER, "" /* softwareVersion */},
             {SW_COMPONENT_ID, "" /* hardwareVersion */, "" /* firmwareVersion */,
              "" /* serialNumber */, SW_VERSION}};
-
     auto sensorId = FingerprintHalProperties::sensor_id().value_or(SENSOR_ID);
     auto sensorStrength =
             FingerprintHalProperties::sensor_strength().value_or((int)SENSOR_STRENGTH);
@@ -80,7 +81,8 @@
 
     SensorLocation sensorLocation = mEngine->getSensorLocation();
 
-    LOG(INFO) << "sensor type:" << (int)mSensorType << " location:" << sensorLocation.toString();
+    LOG(INFO) << "sensor type:" << ::android::internal::ToString(mSensorType)
+              << " location:" << sensorLocation.toString();
 
     *out = {{commonProps,
              mSensorType,
@@ -104,4 +106,96 @@
     return ndk::ScopedAStatus::ok();
 }
 
+binder_status_t Fingerprint::dump(int fd, const char** /*args*/, uint32_t numArgs) {
+    if (fd < 0) {
+        LOG(ERROR) << "Fingerprint::dump fd invalid: " << fd;
+        return STATUS_BAD_VALUE;
+    } else {
+        LOG(INFO) << "Fingerprint::dump fd:" << fd << "numArgs:" << numArgs;
+    }
+
+    dprintf(fd, "----- FingerprintVirtualHal::dump -----\n");
+    std::vector<SensorProps> sps(1);
+    getSensorProps(&sps);
+    for (auto& sp : sps) {
+        ::android::base::WriteStringToFd(sp.toString(), fd);
+    }
+    ::android::base::WriteStringToFd(mEngine->toString(), fd);
+
+    fsync(fd);
+    return STATUS_OK;
+}
+
+binder_status_t Fingerprint::handleShellCommand(int in, int out, int err, const char** args,
+                                                uint32_t numArgs) {
+    LOG(INFO) << "Fingerprint::handleShellCommand in:" << in << " out:" << out << " err:" << err
+              << " numArgs:" << numArgs;
+
+    if (numArgs == 0) {
+        LOG(INFO) << "Fingerprint::handleShellCommand: available commands";
+        onHelp(out);
+        return STATUS_OK;
+    }
+
+    for (auto&& str : std::vector<std::string_view>(args, args + numArgs)) {
+        std::string option = str.data();
+        if (option.find("clearconfig") != std::string::npos ||
+            option.find("resetconfig") != std::string::npos) {
+            resetConfigToDefault();
+        }
+        if (option.find("help") != std::string::npos) {
+            onHelp(out);
+        }
+    }
+
+    return STATUS_OK;
+}
+
+void Fingerprint::onHelp(int fd) {
+    dprintf(fd, "Virtual HAL commands:\n");
+    dprintf(fd, "         help: print this help\n");
+    dprintf(fd, "  resetconfig: reset all configuration to default\n");
+    dprintf(fd, "\n");
+    fsync(fd);
+}
+
+void Fingerprint::resetConfigToDefault() {
+    LOG(INFO) << "reset virtual HAL configuration to default";
+#define RESET_CONFIG_O(__NAME__) \
+    if (FingerprintHalProperties::__NAME__()) FingerprintHalProperties::__NAME__(std::nullopt)
+#define RESET_CONFIG_V(__NAME__)                       \
+    if (!FingerprintHalProperties::__NAME__().empty()) \
+    FingerprintHalProperties::__NAME__({std::nullopt})
+
+    RESET_CONFIG_O(type);
+    RESET_CONFIG_V(enrollments);
+    RESET_CONFIG_O(enrollment_hit);
+    RESET_CONFIG_O(authenticator_id);
+    RESET_CONFIG_O(challenge);
+    RESET_CONFIG_O(lockout);
+    RESET_CONFIG_O(operation_authenticate_fails);
+    RESET_CONFIG_O(operation_detect_interaction_error);
+    RESET_CONFIG_O(operation_enroll_error);
+    RESET_CONFIG_V(operation_authenticate_latency);
+    RESET_CONFIG_V(operation_detect_interaction_latency);
+    RESET_CONFIG_V(operation_enroll_latency);
+    RESET_CONFIG_O(operation_authenticate_duration);
+    RESET_CONFIG_O(operation_authenticate_error);
+    RESET_CONFIG_O(sensor_location);
+    RESET_CONFIG_O(operation_authenticate_acquired);
+    RESET_CONFIG_O(operation_detect_interaction_duration);
+    RESET_CONFIG_O(operation_detect_interaction_acquired);
+    RESET_CONFIG_O(sensor_id);
+    RESET_CONFIG_O(sensor_strength);
+    RESET_CONFIG_O(max_enrollments);
+    RESET_CONFIG_O(navigation_guesture);
+    RESET_CONFIG_O(detect_interaction);
+    RESET_CONFIG_O(display_touch);
+    RESET_CONFIG_O(control_illumination);
+    RESET_CONFIG_O(lockout_enable);
+    RESET_CONFIG_O(lockout_timed_threshold);
+    RESET_CONFIG_O(lockout_timed_duration);
+    RESET_CONFIG_O(lockout_permanent_threshold);
+}
+
 }  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/README.md b/biometrics/fingerprint/aidl/default/README.md
index ad471f7..49b6c9d 100644
--- a/biometrics/fingerprint/aidl/default/README.md
+++ b/biometrics/fingerprint/aidl/default/README.md
@@ -57,6 +57,7 @@
       ```shell
       $ adb shell setprop vendor.fingerprint.virtual.next_enrollment 1:100,100,100:true
       ```
+
 3. Navigate to `Settings -> Security -> Fingerprint Unlock` and follow the
    prompts.
 4. Verify the enrollments in the UI:
@@ -119,6 +120,38 @@
 ```
 For vendor specific error, errorCode = 1000 + vendorErrorCode
 
+## Latency Insertion
+Three HAL operations (authenticate, enrollment and detect interaction) latency can be optionally specified in multiple ways
+1. default latency is fixed at 400 ms if not specified via sysprop
+2. specify authenticate operation latency to 900 ms
+      ```shell adb shell setprop vendor.fingerprint.virtual.operation_authenticate_latency 900```
+3. specify authenticate operation latency between 600 to 1200 ms in unifrom distribution
+      ```shelladb shell setprop vendor.fingerprint.virtual.operation_authenticate_latency 600,1200```
+
+## Lockout
+To force the device into lockout state
+```shell
+$ adb shell setprop persist.vendor.fingerprint.virtual.lockout true
+```
+To test permanent lockout based on the failed authentication attempts (e.g. 7)
+```shell
+$ adb shell setprop persist.vendor.fingerprint.virtual.lockout_permanent_threshold 7
+$ adb shell setprop persist.vendor.fingerprint.virtual.lockout_enable true
+```
+To test timed lockout based on the failed authentication attempts (e.g. 8 seconds on 5 attempts)
+```shell
+$ adb shell setprop persist.vendor.fingerprint.virtual.lockout_timed_duration 8000
+$ adb shell setprop persist.vendor.fingerprint.virtual.lockout_timed_threshold 5
+$ adb shell setprop persist.vendor.fingerprint.virtual.lockout_enable true
+```
+
+## Reset all configurations to default
+The following command will reset virtual configurations (related system properties) to default value.
+```shell
+$ adb shell cmd android.hardware.biometrics.fingerprint.IFingerprint/virtual resetconfig
+$ adb reboot
+```
+
 ## View HAL State
 
 To view all the properties of the HAL (see `fingerprint.sysprop` file for the API):
@@ -126,3 +159,7 @@
 ```shell
 $ adb shell getprop | grep vendor.fingerprint.virtual
 ```
+To dump virtual HAL internal data
+```shell
+adb shell dumpsys android.hardware.biometrics.fingerprint.IFingerprint/virtual
+```
diff --git a/biometrics/fingerprint/aidl/default/Session.cpp b/biometrics/fingerprint/aidl/default/Session.cpp
index e51f677..7ab5af3 100644
--- a/biometrics/fingerprint/aidl/default/Session.cpp
+++ b/biometrics/fingerprint/aidl/default/Session.cpp
@@ -20,6 +20,9 @@
 
 #include "util/CancellationSignal.h"
 
+#undef LOG_TAG
+#define LOG_TAG "FingerprintVirtualHalSession"
+
 namespace aidl::android::hardware::biometrics::fingerprint {
 
 Session::Session(int sensorId, int userId, std::shared_ptr<ISessionCallback> cb,
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
index fa21663..e69de29 100644
--- 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
@@ -1,155 +0,0 @@
-props {
-  owner: Vendor
-  module: "android.fingerprint.virt.FingerprintHalProperties"
-  prop {
-    api_name: "authenticator_id"
-    type: Long
-    access: ReadWrite
-    prop_name: "persist.vendor.fingerprint.virtual.authenticator_id"
-  }
-  prop {
-    api_name: "challenge"
-    type: Long
-    access: ReadWrite
-    prop_name: "vendor.fingerprint.virtual.challenge"
-  }
-  prop {
-    api_name: "control_illumination"
-    access: ReadWrite
-    prop_name: "persist.vendor.fingerprint.virtual.udfps.control_illumination"
-  }
-  prop {
-    api_name: "detect_interaction"
-    access: ReadWrite
-    prop_name: "persist.vendor.fingerprint.virtual.detect_interaction"
-  }
-  prop {
-    api_name: "display_touch"
-    access: ReadWrite
-    prop_name: "persist.vendor.fingerprint.virtual.udfps.display_touch"
-  }
-  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: "persist.vendor.fingerprint.virtual.lockout"
-  }
-  prop {
-    api_name: "max_enrollments"
-    type: Integer
-    access: ReadWrite
-    prop_name: "persist.vendor.fingerprint.virtual.max_enrollments"
-  }
-  prop {
-    api_name: "navigation_guesture"
-    access: ReadWrite
-    prop_name: "persist.vendor.fingerprint.virtual.navigation_guesture"
-  }
-  prop {
-    api_name: "next_enrollment"
-    type: String
-    access: ReadWrite
-    prop_name: "vendor.fingerprint.virtual.next_enrollment"
-  }
-  prop {
-    api_name: "operation_authenticate_acquired"
-    type: String
-    access: ReadWrite
-    prop_name: "vendor.fingerprint.virtual.operation_authenticate_acquired"
-  }
-  prop {
-    api_name: "operation_authenticate_duration"
-    type: Integer
-    access: ReadWrite
-    prop_name: "vendor.fingerprint.virtual.operation_authenticate_duration"
-  }
-  prop {
-    api_name: "operation_authenticate_error"
-    type: Integer
-    access: ReadWrite
-    prop_name: "vendor.fingerprint.virtual.operation_authenticate_error"
-  }
-  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_acquired"
-    type: String
-    access: ReadWrite
-    prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_acquired"
-  }
-  prop {
-    api_name: "operation_detect_interaction_duration"
-    type: Integer
-    access: ReadWrite
-    prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_duration"
-  }
-  prop {
-    api_name: "operation_detect_interaction_error"
-    type: Integer
-    access: ReadWrite
-    prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_error"
-  }
-  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_error"
-    type: Integer
-    access: ReadWrite
-    prop_name: "vendor.fingerprint.virtual.operation_enroll_error"
-  }
-  prop {
-    api_name: "operation_enroll_latency"
-    type: Integer
-    access: ReadWrite
-    prop_name: "vendor.fingerprint.virtual.operation_enroll_latency"
-  }
-  prop {
-    api_name: "sensor_id"
-    type: Integer
-    access: ReadWrite
-    prop_name: "persist.vendor.fingerprint.virtual.sensor_id"
-  }
-  prop {
-    api_name: "sensor_location"
-    type: String
-    access: ReadWrite
-    prop_name: "persist.vendor.fingerprint.virtual.sensor_location"
-  }
-  prop {
-    api_name: "sensor_strength"
-    type: Integer
-    access: ReadWrite
-    prop_name: "persist.vendor.fingerprint.virtual.sensor_strength"
-  }
-  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
index 9b8fada..6a6c297 100644
--- a/biometrics/fingerprint/aidl/default/fingerprint.sysprop
+++ b/biometrics/fingerprint/aidl/default/fingerprint.sysprop
@@ -7,7 +7,7 @@
 prop {
     prop_name: "persist.vendor.fingerprint.virtual.type"
     type: String
-    scope: Public
+    scope: Internal
     access: ReadWrite
     enum_values: "default|rear|udfps|side"
     api_name: "type"
@@ -17,7 +17,7 @@
 prop {
     prop_name: "persist.vendor.fingerprint.virtual.enrollments"
     type: IntegerList
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "enrollments"
 }
@@ -27,7 +27,7 @@
 prop {
     prop_name: "vendor.fingerprint.virtual.enrollment_hit"
     type: Integer
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "enrollment_hit"
 }
@@ -42,7 +42,7 @@
 prop {
     prop_name: "vendor.fingerprint.virtual.next_enrollment"
     type: String
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "next_enrollment"
 }
@@ -51,7 +51,7 @@
 prop {
     prop_name: "persist.vendor.fingerprint.virtual.authenticator_id"
     type: Long
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "authenticator_id"
 }
@@ -60,25 +60,16 @@
 prop {
     prop_name: "vendor.fingerprint.virtual.challenge"
     type: Long
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "challenge"
 }
 
-# if locked out
-prop {
-    prop_name: "persist.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
+    scope: Internal
     access: ReadWrite
     api_name: "operation_authenticate_fails"
 }
@@ -91,7 +82,7 @@
 prop {
     prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_error"
     type: Integer
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "operation_detect_interaction_error"
 }
@@ -100,34 +91,40 @@
 prop {
     prop_name: "vendor.fingerprint.virtual.operation_enroll_error"
     type: Integer
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "operation_enroll_error"
 }
 
 # add a latency to authentication operations
+#   default to 400ms
+#   [x] = x ms
+#   [x,y] = randomly between x and y ms
+#   others = invalid
 prop {
     prop_name: "vendor.fingerprint.virtual.operation_authenticate_latency"
-    type: Integer
-    scope: Public
+    type: IntegerList
+    scope: Internal
     access: ReadWrite
     api_name: "operation_authenticate_latency"
 }
 
 # add a latency to detectInteraction operations
+#   refer to `operation_authenticate_latency` above for usage
 prop {
     prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_latency"
-    type: Integer
-    scope: Public
+    type: IntegerList
+    scope: Internal
     access: ReadWrite
     api_name: "operation_detect_interaction_latency"
 }
 
 # add a latency to enroll operations
+#   refer to `operation_authenticate_latency` above for usage
 prop {
     prop_name: "vendor.fingerprint.virtual.operation_enroll_latency"
-    type: Integer
-    scope: Public
+    type: IntegerList
+    scope: Internal
     access: ReadWrite
     api_name: "operation_enroll_latency"
 }
@@ -137,7 +134,7 @@
 prop {
     prop_name: "vendor.fingerprint.virtual.operation_authenticate_duration"
     type: Integer
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "operation_authenticate_duration"
 }
@@ -146,7 +143,7 @@
 prop {
     prop_name: "vendor.fingerprint.virtual.operation_authenticate_error"
     type: Integer
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "operation_authenticate_error"
 }
@@ -156,7 +153,7 @@
 prop {
     prop_name: "persist.vendor.fingerprint.virtual.sensor_location"
     type: String
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "sensor_location"
 }
@@ -165,7 +162,7 @@
 prop {
     prop_name: "vendor.fingerprint.virtual.operation_authenticate_acquired"
     type: String
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "operation_authenticate_acquired"
 }
@@ -175,7 +172,7 @@
 prop {
     prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_duration"
     type: Integer
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "operation_detect_interaction_duration"
 }
@@ -187,7 +184,7 @@
 prop {
     prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_acquired"
     type: String
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "operation_detect_interaction_acquired"
 }
@@ -196,7 +193,7 @@
 prop {
     prop_name: "persist.vendor.fingerprint.virtual.sensor_id"
     type: Integer
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "sensor_id"
 }
@@ -206,7 +203,7 @@
 prop {
     prop_name: "persist.vendor.fingerprint.virtual.sensor_strength"
     type: Integer
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "sensor_strength"
 }
@@ -216,7 +213,7 @@
 prop {
     prop_name: "persist.vendor.fingerprint.virtual.max_enrollments"
     type: Integer
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "max_enrollments"
 }
@@ -225,7 +222,7 @@
 prop {
     prop_name: "persist.vendor.fingerprint.virtual.navigation_guesture"
     type: Boolean
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "navigation_guesture"
 }
@@ -234,7 +231,7 @@
 prop {
     prop_name: "persist.vendor.fingerprint.virtual.detect_interaction"
     type: Boolean
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "detect_interaction"
 }
@@ -243,7 +240,7 @@
 prop {
     prop_name: "persist.vendor.fingerprint.virtual.udfps.display_touch"
     type: Boolean
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "display_touch"
 }
@@ -252,7 +249,52 @@
 prop {
     prop_name: "persist.vendor.fingerprint.virtual.udfps.control_illumination"
     type: Boolean
-    scope: Public
+    scope: Internal
     access: ReadWrite
     api_name: "control_illumination"
 }
+
+# force to be locked out (default: false)
+prop {
+    prop_name: "persist.vendor.fingerprint.virtual.lockout"
+    type: Boolean
+    scope: Internal
+    access: ReadWrite
+    api_name: "lockout"
+}
+
+# whether support lockout based on the failed auth attempts (default: false)
+prop {
+    prop_name: "persist.vendor.fingerprint.virtual.lockout_enable"
+    type: Boolean
+    scope: Internal
+    access: ReadWrite
+    api_name: "lockout_enable"
+}
+
+# temporarily lockout threshold in number of consecutive failed auth attempts (default: 5)
+prop {
+    prop_name: "persist.vendor.fingerprint.virtual.lockout_timed_threshold"
+    type: Integer
+    scope: Internal
+    access: ReadWrite
+    api_name: "lockout_timed_threshold"
+}
+
+# temporary lockout duration in ms (default: 10000ms)
+prop {
+    prop_name: "persist.vendor.fingerprint.virtual.lockout_timed_duration"
+    type: Integer
+    scope: Internal
+    access: ReadWrite
+    api_name: "lockout_timed_duration"
+}
+
+# permanently lockout threshold  in number of consecutive failed auth attempts (default: 20)
+prop {
+    prop_name: "persist.vendor.fingerprint.virtual.lockout_permanent_threshold"
+    type: Integer
+    scope: Internal
+    access: ReadWrite
+    api_name: "lockout_permanent_threshold"
+}
diff --git a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
index 22b1744..1279cd9 100644
--- a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
+++ b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -15,8 +15,13 @@
  */
 
 #pragma once
+
+#define LOG_TAG "FingerprintVirtualHal"
+
 #include <aidl/android/hardware/biometrics/common/SensorStrength.h>
 #include <aidl/android/hardware/biometrics/fingerprint/ISessionCallback.h>
+#include <android/binder_to_string.h>
+#include <string>
 
 #include <random>
 
@@ -24,6 +29,8 @@
 #include <future>
 #include <vector>
 
+#include "FakeLockoutTracker.h"
+
 using namespace ::aidl::android::hardware::biometrics::common;
 
 namespace aidl::android::hardware::biometrics::fingerprint {
@@ -36,11 +43,11 @@
 
     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);
+    virtual void enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
+                            const std::future<void>& cancel);
+    virtual void authenticateImpl(ISessionCallback* cb, int64_t operationId,
+                                  const std::future<void>& cancel);
+    virtual 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);
@@ -63,13 +70,29 @@
 
     std::vector<std::vector<int32_t>> parseEnrollmentCapture(const std::string& str);
 
+    int32_t getLatency(const std::vector<std::optional<std::int32_t>>& latencyVec);
+
     std::mt19937 mRandom;
 
+    virtual std::string toString() const {
+        std::ostringstream os;
+        os << "----- FakeFingerprintEngine:: -----" << std::endl;
+        os << "acquiredVendorInfoBase:" << FINGERPRINT_ACQUIRED_VENDOR_BASE;
+        os << ", errorVendorBase:" << FINGERPRINT_ERROR_VENDOR_BASE << std::endl;
+        os << mLockoutTracker.toString();
+        return os.str();
+    }
+
   private:
     static constexpr int32_t FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000;
     static constexpr int32_t FINGERPRINT_ERROR_VENDOR_BASE = 1000;
     std::pair<AcquiredInfo, int32_t> convertAcquiredInfo(int32_t code);
     std::pair<Error, int32_t> convertError(int32_t code);
+    bool parseEnrollmentCaptureSingle(const std::string& str,
+                                      std::vector<std::vector<int32_t>>& res);
+    int32_t getRandomInRange(int32_t bound1, int32_t bound2);
+
+    FakeLockoutTracker mLockoutTracker;
 };
 
 }  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineRear.h b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineRear.h
index 1600a4b..14d5399 100644
--- a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineRear.h
+++ b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineRear.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
diff --git a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineSide.h b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineSide.h
index 4e44d16..c2fc005 100644
--- a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineSide.h
+++ b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineSide.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
diff --git a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineUdfps.h b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineUdfps.h
index b86af73..c5e93e7 100644
--- a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineUdfps.h
+++ b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngineUdfps.h
@@ -28,17 +28,55 @@
     static constexpr int32_t defaultSensorLocationY = 1600;
     static constexpr int32_t defaultSensorRadius = 150;
 
-    FakeFingerprintEngineUdfps() : FakeFingerprintEngine() {}
+    static constexpr int32_t uiReadyTimeoutInMs = 5000;
+
+    FakeFingerprintEngineUdfps();
     ~FakeFingerprintEngineUdfps() {}
 
-    virtual ndk::ScopedAStatus onPointerDownImpl(int32_t pointerId, int32_t x, int32_t y,
-                                                 float minor, float major) override;
+    ndk::ScopedAStatus onPointerDownImpl(int32_t pointerId, int32_t x, int32_t y, float minor,
+                                         float major) override;
 
-    virtual ndk::ScopedAStatus onPointerUpImpl(int32_t pointerId) override;
+    ndk::ScopedAStatus onPointerUpImpl(int32_t pointerId) override;
 
-    virtual ndk::ScopedAStatus onUiReadyImpl() override;
+    ndk::ScopedAStatus onUiReadyImpl() override;
 
-    virtual SensorLocation defaultSensorLocation() override;
+    SensorLocation defaultSensorLocation() override;
+
+    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);
+
+    enum class WorkMode : int8_t { kIdle = 0, kAuthenticate, kEnroll, kDetectInteract };
+
+    WorkMode getWorkMode() { return mWorkMode; }
+
+    std::string toString() const {
+        std::ostringstream os;
+        os << FakeFingerprintEngine::toString();
+        os << "----- FakeFingerprintEngineUdfps -----" << std::endl;
+        os << "mWorkMode:" << (int)mWorkMode;
+        os << ", mUiReadyTime:" << mUiReadyTime;
+        os << ", mPointerDownTime:" << mPointerDownTime << std::endl;
+        return os.str();
+    }
+
+  private:
+    void onAuthenticateFingerDown();
+    void onEnrollFingerDown();
+    void onDetectInteractFingerDown();
+    void fingerDownAction();
+    void updateContext(WorkMode mode, ISessionCallback* cb, std::future<void>& cancel,
+                       int64_t operationId, const keymaster::HardwareAuthToken& hat);
+
+    WorkMode mWorkMode;
+    ISessionCallback* mCb;
+    keymaster::HardwareAuthToken mHat;
+    std::vector<std::future<void>> mCancelVec;
+    int64_t mOperationId;
+    int64_t mPointerDownTime;
+    int64_t mUiReadyTime;
 };
 
 }  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/include/FakeLockoutTracker.h b/biometrics/fingerprint/aidl/default/include/FakeLockoutTracker.h
new file mode 100644
index 0000000..a1b6128
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/include/FakeLockoutTracker.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 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 <android/binder_to_string.h>
+#include <stdint.h>
+#include <string>
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+class FakeLockoutTracker {
+  public:
+    FakeLockoutTracker() : mFailedCount(0) {}
+    ~FakeLockoutTracker() {}
+
+    enum class LockoutMode : int8_t { kNone = 0, kTimed, kPermanent };
+
+    void reset();
+    LockoutMode getMode();
+    void addFailedAttempt();
+    int64_t getLockoutTimeLeft();
+    inline std::string toString() const {
+        std::ostringstream os;
+        os << "----- FakeLockoutTracker:: -----" << std::endl;
+        os << "FakeLockoutTracker::mFailedCount:" << mFailedCount;
+        os << ", FakeLockoutTracker::mCurrentMode:" << (int)mCurrentMode;
+        os << std::endl;
+        return os.str();
+    }
+
+  private:
+    int32_t mFailedCount;
+    int64_t mLockoutTimedStart;
+    LockoutMode mCurrentMode;
+};
+
+}  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/include/Fingerprint.h b/biometrics/fingerprint/aidl/default/include/Fingerprint.h
index 64aafa3..fc4fb8d 100644
--- a/biometrics/fingerprint/aidl/default/include/Fingerprint.h
+++ b/biometrics/fingerprint/aidl/default/include/Fingerprint.h
@@ -16,8 +16,6 @@
 
 #pragma once
 
-#define LOG_TAG "FingerprintVirtualHal"
-
 #include <aidl/android/hardware/biometrics/fingerprint/BnFingerprint.h>
 
 #include "FakeFingerprintEngine.h"
@@ -39,8 +37,13 @@
     ndk::ScopedAStatus createSession(int32_t sensorId, int32_t userId,
                                      const std::shared_ptr<ISessionCallback>& cb,
                                      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);
 
   private:
+    void resetConfigToDefault();
+    void onHelp(int);
+
     std::unique_ptr<FakeFingerprintEngine> mEngine;
     WorkerThread mWorker;
     std::shared_ptr<Session> mSession;
diff --git a/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp
index 32d01f4..a200b39 100644
--- a/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp
+++ b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -21,6 +21,7 @@
 #include <aidl/android/hardware/biometrics/fingerprint/BnSessionCallback.h>
 
 #include "FakeFingerprintEngine.h"
+#include "util/Util.h"
 
 using namespace ::android::fingerprint::virt;
 using namespace ::aidl::android::hardware::biometrics::fingerprint;
@@ -118,9 +119,9 @@
 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);
+        FingerprintHalProperties::operation_enroll_latency({0});
+        FingerprintHalProperties::operation_authenticate_latency({0});
+        FingerprintHalProperties::operation_detect_interaction_latency({0});
         mCallback = ndk::SharedRefBase::make<TestSessionCallback>();
     }
 
@@ -128,6 +129,9 @@
         FingerprintHalProperties::operation_authenticate_error(0);
         FingerprintHalProperties::operation_detect_interaction_error(0);
         FingerprintHalProperties::operation_authenticate_acquired("");
+        FingerprintHalProperties::operation_enroll_latency({});
+        FingerprintHalProperties::operation_authenticate_latency({});
+        FingerprintHalProperties::operation_detect_interaction_latency({});
     }
 
     FakeFingerprintEngine mEngine;
@@ -291,6 +295,7 @@
 }
 
 TEST_F(FakeFingerprintEngineTest, InteractionDetect) {
+    FingerprintHalProperties::detect_interaction(true);
     FingerprintHalProperties::enrollments({1, 2});
     FingerprintHalProperties::enrollment_hit(2);
     FingerprintHalProperties::operation_detect_interaction_acquired("");
@@ -300,6 +305,7 @@
 }
 
 TEST_F(FakeFingerprintEngineTest, InteractionDetectCancel) {
+    FingerprintHalProperties::detect_interaction(true);
     FingerprintHalProperties::enrollments({1, 2});
     FingerprintHalProperties::enrollment_hit(2);
     mCancel.set_value();
@@ -309,6 +315,7 @@
 }
 
 TEST_F(FakeFingerprintEngineTest, InteractionDetectNotSet) {
+    FingerprintHalProperties::detect_interaction(true);
     FingerprintHalProperties::enrollments({1, 2});
     FingerprintHalProperties::enrollment_hit({});
     mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
@@ -323,6 +330,7 @@
 }
 
 TEST_F(FakeFingerprintEngineTest, InteractionDetectError) {
+    FingerprintHalProperties::detect_interaction(true);
     FingerprintHalProperties::operation_detect_interaction_error(8);
     mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
     ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
@@ -331,6 +339,7 @@
 }
 
 TEST_F(FakeFingerprintEngineTest, InteractionDetectAcquired) {
+    FingerprintHalProperties::detect_interaction(true);
     FingerprintHalProperties::enrollments({1, 2});
     FingerprintHalProperties::enrollment_hit(2);
     FingerprintHalProperties::operation_detect_interaction_acquired("4,1013");
@@ -435,12 +444,29 @@
     std::vector<std::string> badStr{"10c",         "100-5",   "100-[5,6,7", "100-5,6,7]",
                                     "100,2x0,300", "200-[f]", "a,b"};
     std::vector<std::vector<int32_t>> ecV;
-    for (const auto s : badStr) {
+    for (const auto& s : badStr) {
         ecV = mEngine.parseEnrollmentCapture(s);
         ASSERT_EQ(ecV.size(), 0);
     }
 }
 
+TEST_F(FakeFingerprintEngineTest, randomLatency) {
+    FingerprintHalProperties::operation_detect_interaction_latency({});
+    ASSERT_EQ(DEFAULT_LATENCY,
+              mEngine.getLatency(FingerprintHalProperties::operation_detect_interaction_latency()));
+    FingerprintHalProperties::operation_detect_interaction_latency({10});
+    ASSERT_EQ(10,
+              mEngine.getLatency(FingerprintHalProperties::operation_detect_interaction_latency()));
+    FingerprintHalProperties::operation_detect_interaction_latency({1, 1000});
+    std::set<int32_t> latencySet;
+    for (int i = 0; i < 100; i++) {
+        latencySet.insert(mEngine.getLatency(
+                FingerprintHalProperties::operation_detect_interaction_latency()));
+    }
+    ASSERT_TRUE(latencySet.size() > 95);
+    FingerprintHalProperties::operation_detect_interaction_latency({});
+}
+
 }  // namespace aidl::android::hardware::biometrics::fingerprint
 
 int main(int argc, char** argv) {
diff --git a/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineUdfpsTest.cpp b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineUdfpsTest.cpp
index 7c0021f..f551899 100644
--- a/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineUdfpsTest.cpp
+++ b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineUdfpsTest.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <aidl/android/hardware/biometrics/fingerprint/BnSessionCallback.h>
 #include <android/binder_process.h>
 #include <fingerprint.sysprop.h>
 #include <gtest/gtest.h>
@@ -29,6 +30,69 @@
 
 namespace aidl::android::hardware::biometrics::fingerprint {
 
+class TestSessionCallback : public BnSessionCallback {
+  public:
+    ndk::ScopedAStatus onChallengeGenerated(int64_t /*challenge*/) override {
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onChallengeRevoked(int64_t /*challenge*/) override {
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onError(fingerprint::Error /*error*/, int32_t /*vendorCode*/) override {
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onEnrollmentProgress(int32_t /*enrollmentId*/,
+                                              int32_t /*remaining*/) override {
+        mEnrollmentProgress++;
+        return ndk::ScopedAStatus::ok();
+    };
+
+    ::ndk::ScopedAStatus onAuthenticationSucceeded(int32_t /*enrollmentId*/,
+                                                   const keymaster::HardwareAuthToken&) override {
+        mAuthenticationSuccess++;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticationFailed() override {
+        mAuthenticationFailure++;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onInteractionDetected() override {
+        mDetectInteraction++;
+        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 {
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onEnrollmentsRemoved(
+            const std::vector<int32_t>& /*enrollmentIds*/) override {
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticatorIdRetrieved(int64_t /*authenticatorId*/) override {
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticatorIdInvalidated(int64_t /*authenticatorId*/) override {
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onLockoutPermanent() override { 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(); }
+
+    int32_t getAuthenticationCount() { return mAuthenticationSuccess + mAuthenticationFailure; }
+    int32_t getDetectInteractionCount() { return mDetectInteraction; }
+
+    int32_t mAuthenticationSuccess = 0;
+    int32_t mAuthenticationFailure = 0;
+    int32_t mEnrollmentProgress = 0;
+    int32_t mDetectInteraction = 0;
+};
+
 class FakeFingerprintEngineUdfpsTest : public ::testing::Test {
   protected:
     void SetUp() override {}
@@ -65,30 +129,56 @@
 }
 
 TEST_F(FakeFingerprintEngineUdfpsTest, getSensorLocationBad) {
-    FingerprintHalProperties::sensor_location("");
-    SensorLocation sc = mEngine.getSensorLocation();
-    ASSERT_TRUE(isDefaultLocation(sc));
-
-    auto loc = "100";
-    FingerprintHalProperties::sensor_location(loc);
-    sc = mEngine.getSensorLocation();
-    ASSERT_TRUE(isDefaultLocation(sc));
-
-    loc = "10:20";
-    FingerprintHalProperties::sensor_location(loc);
-    sc = mEngine.getSensorLocation();
-    ASSERT_TRUE(isDefaultLocation(sc));
-
-    loc = "10,20,5";
-    FingerprintHalProperties::sensor_location(loc);
-    sc = mEngine.getSensorLocation();
-    ASSERT_TRUE(isDefaultLocation(sc));
-
-    loc = "a:b:c";
-    FingerprintHalProperties::sensor_location(loc);
-    sc = mEngine.getSensorLocation();
-    ASSERT_TRUE(isDefaultLocation(sc));
+    const std::vector<std::string> badStr{"", "100", "10:20", "10,20,5", "a:b:c"};
+    SensorLocation sc;
+    for (const auto& s : badStr) {
+        FingerprintHalProperties::sensor_location(s);
+        sc = mEngine.getSensorLocation();
+        ASSERT_TRUE(isDefaultLocation(sc));
+    }
 }
 
+TEST_F(FakeFingerprintEngineUdfpsTest, initialization) {
+    ASSERT_TRUE(mEngine.getWorkMode() == FakeFingerprintEngineUdfps::WorkMode::kIdle);
+}
+
+TEST_F(FakeFingerprintEngineUdfpsTest, authenticate) {
+    std::shared_ptr<TestSessionCallback> cb = ndk::SharedRefBase::make<TestSessionCallback>();
+    std::promise<void> cancel;
+    mEngine.authenticateImpl(cb.get(), 1, cancel.get_future());
+    ASSERT_TRUE(mEngine.getWorkMode() == FakeFingerprintEngineUdfps::WorkMode::kAuthenticate);
+    mEngine.onPointerDownImpl(1, 2, 3, 4.0, 5.0);
+    ASSERT_EQ(cb->getAuthenticationCount(), 0);
+    mEngine.onUiReadyImpl();
+    ASSERT_EQ(cb->getAuthenticationCount(), 1);
+}
+
+TEST_F(FakeFingerprintEngineUdfpsTest, enroll) {
+    std::shared_ptr<TestSessionCallback> cb = ndk::SharedRefBase::make<TestSessionCallback>();
+    std::promise<void> cancel;
+    keymaster::HardwareAuthToken hat{.mac = {5, 6}};
+    FingerprintHalProperties::next_enrollment("5:0,0:true");
+    mEngine.enrollImpl(cb.get(), hat, cancel.get_future());
+    ASSERT_TRUE(mEngine.getWorkMode() == FakeFingerprintEngineUdfps::WorkMode::kEnroll);
+    mEngine.onPointerDownImpl(1, 2, 3, 4.0, 5.0);
+    ASSERT_EQ(cb->mEnrollmentProgress, 0);
+    mEngine.onUiReadyImpl();
+    ASSERT_TRUE(cb->mEnrollmentProgress > 0);
+}
+
+TEST_F(FakeFingerprintEngineUdfpsTest, detectInteraction) {
+    FingerprintHalProperties::detect_interaction(true);
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit(2);
+    FingerprintHalProperties::operation_detect_interaction_acquired("");
+    std::shared_ptr<TestSessionCallback> cb = ndk::SharedRefBase::make<TestSessionCallback>();
+    std::promise<void> cancel;
+    mEngine.detectInteractionImpl(cb.get(), cancel.get_future());
+    ASSERT_TRUE(mEngine.getWorkMode() == FakeFingerprintEngineUdfps::WorkMode::kDetectInteract);
+    mEngine.onPointerDownImpl(1, 2, 3, 4.0, 5.0);
+    ASSERT_EQ(cb->getDetectInteractionCount(), 0);
+    mEngine.onUiReadyImpl();
+    ASSERT_EQ(cb->getDetectInteractionCount(), 1);
+}
 // More
 }  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/tests/FakeLockoutTrackerTest.cpp b/biometrics/fingerprint/aidl/default/tests/FakeLockoutTrackerTest.cpp
new file mode 100644
index 0000000..1b071ee
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/tests/FakeLockoutTrackerTest.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 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 "FakeLockoutTracker.h"
+#include "util/Util.h"
+
+using namespace ::android::fingerprint::virt;
+using namespace ::aidl::android::hardware::biometrics::fingerprint;
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+class FakeLockoutTrackerTest : public ::testing::Test {
+  protected:
+    static constexpr int32_t LOCKOUT_TIMED_THRESHOLD = 3;
+    static constexpr int32_t LOCKOUT_PERMANENT_THRESHOLD = 5;
+    static constexpr int32_t LOCKOUT_TIMED_DURATION = 100;
+
+    void SetUp() override {
+        FingerprintHalProperties::lockout_timed_threshold(LOCKOUT_TIMED_THRESHOLD);
+        FingerprintHalProperties::lockout_timed_duration(LOCKOUT_TIMED_DURATION);
+        FingerprintHalProperties::lockout_permanent_threshold(LOCKOUT_PERMANENT_THRESHOLD);
+    }
+
+    void TearDown() override {
+        // reset to default
+        FingerprintHalProperties::lockout_timed_threshold(5);
+        FingerprintHalProperties::lockout_timed_duration(20);
+        FingerprintHalProperties::lockout_permanent_threshold(10000);
+        FingerprintHalProperties::lockout_enable(false);
+        FingerprintHalProperties::lockout(false);
+    }
+
+    FakeLockoutTracker mLockoutTracker;
+};
+
+TEST_F(FakeLockoutTrackerTest, addFailedAttemptDisable) {
+    FingerprintHalProperties::lockout_enable(false);
+    for (int i = 0; i < LOCKOUT_TIMED_THRESHOLD + 1; i++) mLockoutTracker.addFailedAttempt();
+    ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kNone);
+    mLockoutTracker.reset();
+}
+
+TEST_F(FakeLockoutTrackerTest, addFailedAttemptLockoutTimed) {
+    FingerprintHalProperties::lockout_enable(true);
+    for (int i = 0; i < LOCKOUT_TIMED_THRESHOLD; i++) mLockoutTracker.addFailedAttempt();
+    ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kTimed);
+    // time left
+    int N = 5;
+    int64_t prevTimeLeft = INT_MIN;
+    for (int i = 0; i < N; i++) {
+        SLEEP_MS(LOCKOUT_TIMED_DURATION / N + 1);
+        int64_t currTimeLeft = mLockoutTracker.getLockoutTimeLeft();
+        ASSERT_TRUE(currTimeLeft > prevTimeLeft);
+        prevTimeLeft = currTimeLeft;
+    }
+    ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kNone);
+    mLockoutTracker.reset();
+}
+
+TEST_F(FakeLockoutTrackerTest, addFailedAttemptPermanent) {
+    FingerprintHalProperties::lockout_enable(true);
+    for (int i = 0; i < LOCKOUT_PERMANENT_THRESHOLD - 1; i++) mLockoutTracker.addFailedAttempt();
+    ASSERT_NE(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kPermanent);
+    mLockoutTracker.addFailedAttempt();
+    ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kPermanent);
+    ASSERT_TRUE(FingerprintHalProperties::lockout());
+    mLockoutTracker.reset();
+}
+
+}  // namespace aidl::android::hardware::biometrics::fingerprint
+
+int main(int argc, char** argv) {
+    testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}