Add rear fps virtual HAL.

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

Change-Id: Ifecf6b5667352eb2127f820bfde47c7d325ab1b2
This commit is contained in:
Joe Bolinger 2021-12-09 17:00:32 -08:00
parent e6c21ee4cc
commit de94aa0354
10 changed files with 926 additions and 64 deletions

View file

@ -16,6 +16,7 @@ cc_binary {
local_include_dirs: ["include"],
srcs: [
"CancellationSignal.cpp",
"FakeFingerprintEngine.cpp",
"Fingerprint.cpp",
"Session.cpp",
"WorkerThread.cpp",
@ -27,6 +28,7 @@ cc_binary {
"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 @@ cc_test_host {
],
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,
}

View file

@ -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

View file

@ -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 @@ ndk::ScopedAStatus Fingerprint::getSensorProps(std::vector<SensorProps>* out) {
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 */}};

View file

@ -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
```

View file

@ -101,7 +101,7 @@ ndk::ScopedAStatus Session::enroll(const keymaster::HardwareAuthToken& hat,
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 @@ ndk::ScopedAStatus Session::authenticate(int64_t operationId,
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 @@ ndk::ScopedAStatus Session::detectInteraction(std::shared_ptr<common::ICancellat
if (shouldCancel(cancFuture)) {
mCb->onError(Error::CANCELED, 0 /* vendorCode */);
} else {
mEngine->detectInteractionImpl(mCb.get());
mEngine->detectInteractionImpl(mCb.get(), cancFuture);
}
enterIdling();
}));

View file

@ -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"
}
}

View file

@ -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"
}

View file

@ -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;
};

View file

@ -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();
}

View file

@ -15,8 +15,8 @@ cc_test {
],
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: [