Add virtual Face HAL

Fixes: 230514750
Test: atest VtsHalBiometricsFaceTargetTest
Test: atest VtsHalBiometricsFingerprintTargetTest
Test: atest android.hardware.biometrics.face.FakeFaceEngineTest
Test: atest android.hardware.biometrics.face.FakeFingerprintEngineTest
Test: See README.md
Test: Verified that face and fingerprint get reset upon authenticating.

Change-Id: I57c1a61bec960e3be28736e6050be662ef412d8c
This commit is contained in:
Joshua McCloskey
2022-05-10 05:18:20 +00:00
parent c8c0bad864
commit db009a58cb
12 changed files with 1282 additions and 115 deletions

View File

@@ -0,0 +1,69 @@
/*
* 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.
*/
#pragma once
#include <android-base/logging.h>
#include <chrono>
#include <regex>
#include <thread>
#include <vector>
namespace aidl::android::hardware::biometrics {
#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
// 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
class Util {
public:
static int64_t getSystemNanoTime() {
timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return now.tv_sec * 1000000000LL + now.tv_nsec;
}
static 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;
}
static 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

View File

@@ -18,10 +18,43 @@ cc_binary {
"libbinder_ndk",
"android.hardware.biometrics.face-V2-ndk",
"android.hardware.biometrics.common-V2-ndk",
"android.hardware.biometrics.common.thread",
"android.hardware.biometrics.common.util",
],
srcs: [
"main.cpp",
"Face.cpp",
"FakeFaceEngine.cpp",
"Session.cpp",
],
static_libs: ["android.hardware.biometrics.face.VirtualProps"],
}
sysprop_library {
name: "android.hardware.biometrics.face.VirtualProps",
srcs: ["face.sysprop"],
property_owner: "Vendor",
vendor: true,
}
cc_test {
name: "android.hardware.biometrics.face.FakeFaceEngineTest",
srcs: [
"tests/FakeFaceEngineTest.cpp",
"FakeFaceEngine.cpp",
],
shared_libs: [
"libbase",
"libbinder_ndk",
],
static_libs: [
"android.hardware.biometrics.face.VirtualProps",
"android.hardware.biometrics.face-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,
}

View File

@@ -17,12 +17,14 @@
#include "Face.h"
#include "Session.h"
#include "FakeFaceEngine.h"
namespace aidl::android::hardware::biometrics::face {
const int kSensorId = 4;
const common::SensorStrength kSensorStrength = common::SensorStrength::STRONG;
const common::SensorStrength kSensorStrength = FakeFaceEngine::GetSensorStrength();
const int kMaxEnrollmentsPerUser = 5;
const FaceSensorType kSensorType = FaceSensorType::RGB;
const FaceSensorType kSensorType = FakeFaceEngine::GetSensorType();
const bool kHalControlsPreview = true;
const std::string kHwComponentId = "faceSensor";
const std::string kHardwareVersion = "vendor/model/revision";
@@ -69,7 +71,7 @@ ndk::ScopedAStatus Face::getSensorProps(std::vector<SensorProps>* return_val) {
ndk::ScopedAStatus Face::createSession(int32_t /*sensorId*/, int32_t /*userId*/,
const std::shared_ptr<ISessionCallback>& cb,
std::shared_ptr<ISession>* return_val) {
*return_val = SharedRefBase::make<Session>(cb);
*return_val = SharedRefBase::make<Session>(std::make_unique<FakeFaceEngine>(), cb);
return ndk::ScopedAStatus::ok();
}

View File

@@ -0,0 +1,318 @@
#include "FakeFaceEngine.h"
#include <android-base/logging.h>
#include <face.sysprop.h>
#include "util/CancellationSignal.h"
#include "util/Util.h"
using namespace ::android::face::virt;
namespace aidl::android::hardware::biometrics::face {
FaceSensorType FakeFaceEngine::GetSensorType() {
std::string type = FaceHalProperties::type().value_or("");
if (type == "IR") {
return FaceSensorType::IR;
} else {
FaceHalProperties::type("RGB");
return FaceSensorType::RGB;
}
}
common::SensorStrength FakeFaceEngine::GetSensorStrength() {
std::string strength = FaceHalProperties::strength().value_or("");
if (strength == "convenience") {
return common::SensorStrength::CONVENIENCE;
} else if (strength == "weak") {
return common::SensorStrength::WEAK;
} else {
FaceHalProperties::strength("strong");
return common::SensorStrength::STRONG;
}
}
void FakeFaceEngine::generateChallengeImpl(ISessionCallback* cb) {
BEGIN_OP(0);
std::uniform_int_distribution<int64_t> dist;
auto challenge = dist(mRandom);
FaceHalProperties::challenge(challenge);
cb->onChallengeGenerated(challenge);
}
void FakeFaceEngine::revokeChallengeImpl(ISessionCallback* cb, int64_t challenge) {
BEGIN_OP(0);
FaceHalProperties::challenge({});
cb->onChallengeRevoked(challenge);
}
void FakeFaceEngine::getEnrollmentConfigImpl(ISessionCallback* /*cb*/,
std::vector<EnrollmentStageConfig>* /*return_val*/) {}
void FakeFaceEngine::enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
EnrollmentType /*enrollmentType*/,
const std::vector<Feature>& /*features*/,
const std::future<void>& cancel) {
BEGIN_OP(FaceHalProperties::operation_start_enroll_latency().value_or(0));
// format is "<id>,<bucket_id>:<delay>:<succeeds>,<bucket_id>:<delay>:<succeeds>...
auto nextEnroll = FaceHalProperties::next_enrollment().value_or("");
// Erase the next enrollment
FaceHalProperties::next_enrollment({});
AuthenticationFrame frame;
frame.data.acquiredInfo = AcquiredInfo::START;
frame.data.vendorCode = 0;
cb->onAuthenticationFrame(frame);
// 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 (FaceHalProperties::operation_enroll_fails().value_or(false)) {
LOG(ERROR) << "Fail: operation_enroll_fails";
cb->onError(Error::VENDOR, 0 /* vendorError */);
return;
}
auto parts = Util::split(nextEnroll, ",");
if (parts.size() < 2) {
LOG(ERROR) << "Fail: invalid next_enrollment for : " << nextEnroll;
cb->onError(Error::VENDOR, 0 /* vendorError */);
return;
}
auto enrollmentId = std::stoi(parts[0]);
const int numBuckets = parts.size() - 1;
for (size_t i = 1; i < parts.size(); i++) {
auto enrollHit = Util::split(parts[i], ":");
if (enrollHit.size() != 3) {
LOG(ERROR) << "Error when unpacking enrollment hit: " << parts[i];
cb->onError(Error::VENDOR, 0 /* vendorError */);
}
std::string bucket = enrollHit[0];
std::string delay = enrollHit[1];
std::string succeeds = enrollHit[2];
SLEEP_MS(std::stoi(delay));
if (shouldCancel(cancel)) {
LOG(ERROR) << "Fail: cancel";
cb->onError(Error::CANCELED, 0 /* vendorCode */);
return;
}
if (!IS_TRUE(succeeds)) { // end and failed
LOG(ERROR) << "Fail: requested by caller: " << parts[i];
cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
return;
}
EnrollmentFrame frame;
frame.data.acquiredInfo = AcquiredInfo::GOOD;
frame.data.vendorCode = 0;
cb->onEnrollmentFrame(frame);
frame.data.acquiredInfo = AcquiredInfo::VENDOR;
frame.data.vendorCode = std::stoi(bucket);
cb->onEnrollmentFrame(frame);
int remainingBuckets = numBuckets - i;
if (remainingBuckets > 0) {
cb->onEnrollmentProgress(enrollmentId, remainingBuckets);
}
}
auto enrollments = FaceHalProperties::enrollments();
enrollments.push_back(enrollmentId);
FaceHalProperties::enrollments(enrollments);
LOG(INFO) << "enrolled : " << enrollmentId;
cb->onEnrollmentProgress(enrollmentId, 0);
}
void FakeFaceEngine::authenticateImpl(ISessionCallback* cb, int64_t /*operationId*/,
const std::future<void>& cancel) {
BEGIN_OP(FaceHalProperties::operation_authenticate_latency().value_or(0));
// Signal to the framework that we have begun authenticating.
AuthenticationFrame frame;
frame.data.acquiredInfo = AcquiredInfo::START;
frame.data.vendorCode = 0;
cb->onAuthenticationFrame(frame);
// Also signal that we have opened the camera.
frame = {};
frame.data.acquiredInfo = AcquiredInfo::FIRST_FRAME_RECEIVED;
frame.data.vendorCode = 0;
cb->onAuthenticationFrame(frame);
auto now = Util::getSystemNanoTime();
int64_t duration = FaceHalProperties::operation_authenticate_duration().value_or(0);
if (duration > 0) {
do {
SLEEP_MS(5);
} while (!Util::hasElapsed(now, duration));
}
if (FaceHalProperties::operation_authenticate_fails().value_or(false)) {
LOG(ERROR) << "Fail: operation_authenticate_fails";
cb->onError(Error::VENDOR, 0 /* vendorError */);
return;
}
if (FaceHalProperties::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 = FaceHalProperties::enrollment_hit().value_or(0);
auto enrolls = FaceHalProperties::enrollments();
auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end();
if (id < 0 || !isEnrolled) {
LOG(ERROR) << (isEnrolled ? "invalid enrollment hit" : "Fail: not enrolled");
cb->onAuthenticationFailed();
cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
return;
}
cb->onAuthenticationSucceeded(id, {} /* hat */);
}
void FakeFaceEngine::detectInteractionImpl(ISessionCallback* cb, const std::future<void>& cancel) {
BEGIN_OP(FaceHalProperties::operation_detect_interaction_latency().value_or(0));
if (FaceHalProperties::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 = FaceHalProperties::enrollment_hit().value_or(0);
auto enrolls = FaceHalProperties::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 FakeFaceEngine::enumerateEnrollmentsImpl(ISessionCallback* cb) {
BEGIN_OP(0);
std::vector<int32_t> enrollments;
for (const auto& enrollmentId : FaceHalProperties::enrollments()) {
if (enrollmentId) {
enrollments.push_back(*enrollmentId);
}
}
cb->onEnrollmentsEnumerated(enrollments);
}
void FakeFaceEngine::removeEnrollmentsImpl(ISessionCallback* cb,
const std::vector<int32_t>& enrollmentIds) {
BEGIN_OP(0);
std::vector<std::optional<int32_t>> newEnrollments;
for (const auto& enrollment : FaceHalProperties::enrollments()) {
auto id = enrollment.value_or(0);
if (std::find(enrollmentIds.begin(), enrollmentIds.end(), id) == enrollmentIds.end()) {
newEnrollments.emplace_back(id);
}
}
FaceHalProperties::enrollments(newEnrollments);
cb->onEnrollmentsRemoved(enrollmentIds);
}
void FakeFaceEngine::getFeaturesImpl(ISessionCallback* cb) {
BEGIN_OP(0);
if (FaceHalProperties::enrollments().empty()) {
cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
return;
}
std::vector<Feature> featuresToReturn = {};
for (const auto& feature : FaceHalProperties::features()) {
if (feature) {
featuresToReturn.push_back((Feature)(*feature));
}
}
cb->onFeaturesRetrieved(featuresToReturn);
}
void FakeFaceEngine::setFeatureImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
Feature feature, bool enabled) {
BEGIN_OP(0);
if (FaceHalProperties::enrollments().empty()) {
LOG(ERROR) << "Unable to set feature, enrollments are empty";
cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
return;
}
if (hat.mac.empty()) {
LOG(ERROR) << "Unable to set feature, invalid hat";
cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
return;
}
auto features = FaceHalProperties::features();
auto itr = std::find_if(features.begin(), features.end(), [feature](const auto& theFeature) {
return *theFeature == (int)feature;
});
if (!enabled && (itr != features.end())) {
features.erase(itr);
} else if (enabled && (itr == features.end())) {
features.push_back((int)feature);
}
FaceHalProperties::features(features);
cb->onFeatureSet(feature);
}
void FakeFaceEngine::getAuthenticatorIdImpl(ISessionCallback* cb) {
BEGIN_OP(0);
// If this is a weak HAL return 0 per the spec.
if (GetSensorStrength() != common::SensorStrength::STRONG) {
cb->onAuthenticatorIdRetrieved(0);
} else {
cb->onAuthenticatorIdRetrieved(FaceHalProperties::authenticator_id().value_or(0));
}
}
void FakeFaceEngine::invalidateAuthenticatorIdImpl(ISessionCallback* cb) {
BEGIN_OP(0);
int64_t authenticatorId = FaceHalProperties::authenticator_id().value_or(0);
int64_t newId = authenticatorId + 1;
FaceHalProperties::authenticator_id(newId);
cb->onAuthenticatorIdInvalidated(newId);
}
void FakeFaceEngine::resetLockoutImpl(ISessionCallback* cb,
const keymaster::HardwareAuthToken& /*hat*/) {
BEGIN_OP(0);
FaceHalProperties::lockout(false);
cb->onLockoutCleared();
}
} // namespace aidl::android::hardware::biometrics::face

View File

@@ -0,0 +1,65 @@
/*
* 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 <aidl/android/hardware/biometrics/common/SensorStrength.h>
#include <aidl/android/hardware/biometrics/face/BnSession.h>
#include <aidl/android/hardware/biometrics/face/FaceSensorType.h>
#include <aidl/android/hardware/biometrics/face/ISessionCallback.h>
#include <random>
#include <future>
#include <vector>
namespace aidl::android::hardware::biometrics::face {
namespace face = aidl::android::hardware::biometrics::face;
namespace common = aidl::android::hardware::biometrics::common;
namespace keymaster = aidl::android::hardware::keymaster;
using aidl::android::hardware::common::NativeHandle;
// A fake engine that is backed by system properties instead of hardware.
class FakeFaceEngine {
public:
FakeFaceEngine() : mRandom(std::mt19937::default_seed) {}
static face::FaceSensorType GetSensorType();
static common::SensorStrength GetSensorStrength();
void generateChallengeImpl(ISessionCallback* cb);
void revokeChallengeImpl(ISessionCallback* cb, int64_t challenge);
void getEnrollmentConfigImpl(ISessionCallback* cb,
std::vector<EnrollmentStageConfig>* return_val);
void enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
EnrollmentType enrollmentType, const std::vector<Feature>& features,
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 getFeaturesImpl(ISessionCallback* cb);
void setFeatureImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
Feature feature, bool enabled);
void getAuthenticatorIdImpl(ISessionCallback* cb);
void invalidateAuthenticatorIdImpl(ISessionCallback* cb);
void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/);
std::mt19937 mRandom;
};
} // namespace aidl::android::hardware::biometrics::face

View File

@@ -0,0 +1,77 @@
# Virtual Face 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.
## Device Selection
You can either run the FakeFaceEngine on a [real device](#actual-device) or a [virtual device/cuttlefish](#getting-started-on-a-virtual-device-cuttlefish). This document should
help you to get started on either one.
After setting up a device, go ahead and try out [enrolling](#enrolling) & [authenticating](#authenticating)
### Getting started on a Virtual Device (cuttlefish)
Note, I'm running this via a cloudtop virtual device.
1. Setup cuttlefish on cloudtop, See [this](https://g3doc.corp.google.com/company/teams/android/teampages/acloud/getting_started.md?cl=head) for more details.
2. acloud create --local-image
3. Enter in the shell command to disable hidl
```shell
$ adb root
$ adb shell settings put secure com.android.server.biometrics.AuthService.hidlDisabled 1
$ adb reboot
```
4. You should now be able to do fake enrollments and authentications (as seen down below)
### Actual Device
1. Modify your real devices make file (I.E. vendor/google/products/{YOUR_DEVICE}.mk)
2. Ensure that there is no other face HAL that is being included by the device
3. Add the following
```
PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.hardware.biometrics.face.xml:$(TARGET_COPY_OUT_PRODUCT)/etc/permissions/android.hardware.biometrics.face.xml
PRODUCT_PACKAGES += \
android.hardware.biometrics.face-service.example \
```
4. Now build and flash m -j120 && flash
5. Run the following commands
```shell
# This is a temporary workaround
$ adb root
$ adb shell setprop persist.vendor.face.virtual.type RGB
$ adb shell setprop persist.vendor.face.virtual.strength strong
$ adb shell locksettings set-pin 0000
$ adb reboot
```
## Enrolling
```shell
# authenticar_id,bucket_id:duration:(true|false)....
$ adb shell setprop vendor.face.virtual.next_enrollment 1,0:500:true,5:250:true,10:150:true,15:500:true
$ adb shell am start -n com.android.settings/.biometrics.face.FaceEnrollIntroduction
# If you would like to get rid of the enrollment, run the follwoing command
$ adb shell setprop persist.vendor.face.virtual.enrollments \"\"
```
## Authenticating
```shell
# If enrollment hasn't been setup
$ adb shell setprop persist.vendor.face.virtual.enrollments 1
$ adb shell cmd face sync
# After enrollment has been setup
$ adb shell setprop vendor.face.virtual.operation_authenticate_duration 800
$ adb shell setprop vendor.face.virtual.enrollment_hit 1
# Power button press to simulate auth
$ adb shell input keyevent 26
```

View File

@@ -14,139 +14,135 @@
* limitations under the License.
*/
#include <aidl/android/hardware/biometrics/common/BnCancellationSignal.h>
#include <android-base/logging.h>
#include "Session.h"
namespace aidl::android::hardware::biometrics::face {
class CancellationSignal : public common::BnCancellationSignal {
private:
std::shared_ptr<ISessionCallback> cb_;
constexpr size_t MAX_WORKER_QUEUE_SIZE = 5;
public:
explicit CancellationSignal(std::shared_ptr<ISessionCallback> cb) : cb_(std::move(cb)) {}
ndk::ScopedAStatus cancel() override {
cb_->onError(Error::CANCELED, 0 /* vendorCode */);
return ndk::ScopedAStatus::ok();
}
};
Session::Session(std::shared_ptr<ISessionCallback> cb)
: cb_(std::move(cb)), mRandom(std::mt19937::default_seed) {}
Session::Session(std::unique_ptr<FakeFaceEngine> engine, std::shared_ptr<ISessionCallback> cb)
: mEngine(std::move(engine)), mCb(std::move(cb)), mRandom(std::mt19937::default_seed) {
mThread = std::make_unique<WorkerThread>(MAX_WORKER_QUEUE_SIZE);
}
ndk::ScopedAStatus Session::generateChallenge() {
LOG(INFO) << "generateChallenge";
if (cb_) {
std::uniform_int_distribution<int64_t> dist;
auto challenge = dist(mRandom);
cb_->onChallengeGenerated(challenge);
}
mThread->schedule(Callable::from([this] { mEngine->generateChallengeImpl(mCb.get()); }));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::revokeChallenge(int64_t challenge) {
LOG(INFO) << "revokeChallenge";
if (cb_) {
cb_->onChallengeRevoked(challenge);
}
mThread->schedule(Callable::from(
[this, challenge] { mEngine->revokeChallengeImpl(mCb.get(), challenge); }));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::getEnrollmentConfig(EnrollmentType /*enrollmentType*/,
std::vector<EnrollmentStageConfig>* return_val) {
*return_val = {};
ndk::ScopedAStatus Session::getEnrollmentConfig(
EnrollmentType /*enrollmentType*/, std::vector<EnrollmentStageConfig>* cancellationSignal) {
*cancellationSignal = {};
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::enroll(
const keymaster::HardwareAuthToken& /*hat*/, EnrollmentType /*enrollmentType*/,
const std::vector<Feature>& /*features*/,
const std::optional<NativeHandle>& /*previewSurface*/,
std::shared_ptr<biometrics::common::ICancellationSignal>* /*return_val*/) {
const keymaster::HardwareAuthToken& hat, EnrollmentType enrollmentType,
const std::vector<Feature>& features, const std::optional<NativeHandle>& /*previewSurface*/,
std::shared_ptr<biometrics::common::ICancellationSignal>* cancellationSignal) {
LOG(INFO) << "enroll";
if (cb_) {
cb_->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
}
std::promise<void> cancellationPromise;
auto cancFuture = cancellationPromise.get_future();
mThread->schedule(Callable::from(
[this, hat, enrollmentType, features, cancFuture = std::move(cancFuture)] {
mEngine->enrollImpl(mCb.get(), hat, enrollmentType, features, cancFuture);
}));
*cancellationSignal = SharedRefBase::make<CancellationSignal>(std::move(cancellationPromise));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::authenticate(int64_t /*keystoreOperationId*/,
std::shared_ptr<common::ICancellationSignal>* return_val) {
ndk::ScopedAStatus Session::authenticate(
int64_t keystoreOperationId,
std::shared_ptr<common::ICancellationSignal>* cancellationSignal) {
LOG(INFO) << "authenticate";
if (cb_) {
cb_->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
}
*return_val = SharedRefBase::make<CancellationSignal>(cb_);
std::promise<void> cancellationPromise;
auto cancFuture = cancellationPromise.get_future();
mThread->schedule(
Callable::from([this, keystoreOperationId, cancFuture = std::move(cancFuture)] {
mEngine->authenticateImpl(mCb.get(), keystoreOperationId, cancFuture);
}));
*cancellationSignal = SharedRefBase::make<CancellationSignal>(std::move(cancellationPromise));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::detectInteraction(
std::shared_ptr<common::ICancellationSignal>* /*return_val*/) {
std::shared_ptr<common::ICancellationSignal>* cancellationSignal) {
LOG(INFO) << "detectInteraction";
std::promise<void> cancellationPromise;
auto cancFuture = cancellationPromise.get_future();
mThread->schedule(Callable::from([this, cancFuture = std::move(cancFuture)] {
mEngine->detectInteractionImpl(mCb.get(), cancFuture);
}));
*cancellationSignal = SharedRefBase::make<CancellationSignal>(std::move(cancellationPromise));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::enumerateEnrollments() {
LOG(INFO) << "enumerateEnrollments";
if (cb_) {
cb_->onEnrollmentsEnumerated(std::vector<int32_t>());
}
mThread->schedule(Callable::from([this] { mEngine->enumerateEnrollmentsImpl(mCb.get()); }));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::removeEnrollments(const std::vector<int32_t>& /*enrollmentIds*/) {
ndk::ScopedAStatus Session::removeEnrollments(const std::vector<int32_t>& enrollmentIds) {
LOG(INFO) << "removeEnrollments";
if (cb_) {
cb_->onEnrollmentsRemoved(std::vector<int32_t>());
}
mThread->schedule(Callable::from(
[this, enrollmentIds] { mEngine->removeEnrollmentsImpl(mCb.get(), enrollmentIds); }));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::getFeatures() {
LOG(INFO) << "getFeatures";
if (cb_) {
// Must error out with UNABLE_TO_PROCESS when no faces are enrolled.
cb_->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
}
mThread->schedule(Callable::from([this] { mEngine->getFeaturesImpl(mCb.get()); }));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::setFeature(const keymaster::HardwareAuthToken& /*hat*/,
Feature /*feature*/, bool /*enabled*/) {
ndk::ScopedAStatus Session::setFeature(const keymaster::HardwareAuthToken& hat, Feature feature,
bool enabled) {
LOG(INFO) << "setFeature";
mThread->schedule(Callable::from([this, hat, feature, enabled] {
mEngine->setFeatureImpl(mCb.get(), hat, feature, enabled);
}));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::getAuthenticatorId() {
LOG(INFO) << "getAuthenticatorId";
if (cb_) {
cb_->onAuthenticatorIdRetrieved(0 /* authenticatorId */);
}
mThread->schedule(Callable::from([this] { mEngine->getAuthenticatorIdImpl(mCb.get()); }));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::invalidateAuthenticatorId() {
LOG(INFO) << "invalidateAuthenticatorId";
if (cb_) {
cb_->onAuthenticatorIdInvalidated(0);
}
mThread->schedule(
Callable::from([this] { mEngine->invalidateAuthenticatorIdImpl(mCb.get()); }));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::resetLockout(const keymaster::HardwareAuthToken& /*hat*/) {
ndk::ScopedAStatus Session::resetLockout(const keymaster::HardwareAuthToken& hat) {
LOG(INFO) << "resetLockout";
if (cb_) {
cb_->onLockoutCleared();
}
mThread->schedule(Callable::from([this, hat] { mEngine->resetLockoutImpl(mCb.get(), hat); }));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Session::close() {
if (cb_) {
cb_->onSessionClosed();
if (mCb) {
mCb->onSessionClosed();
}
return ndk::ScopedAStatus::ok();
}

View File

@@ -21,6 +21,10 @@
#include <aidl/android/hardware/biometrics/face/BnSession.h>
#include <aidl/android/hardware/biometrics/face/ISessionCallback.h>
#include "FakeFaceEngine.h"
#include "thread/WorkerThread.h"
#include "util/CancellationSignal.h"
namespace aidl::android::hardware::biometrics::face {
namespace common = aidl::android::hardware::biometrics::common;
@@ -30,7 +34,7 @@ using aidl::android::hardware::common::NativeHandle;
class Session : public BnSession {
public:
explicit Session(std::shared_ptr<ISessionCallback> cb);
explicit Session(std::unique_ptr<FakeFaceEngine> engine, std::shared_ptr<ISessionCallback> cb);
ndk::ScopedAStatus generateChallenge() override;
@@ -85,8 +89,11 @@ class Session : public BnSession {
ndk::ScopedAStatus onContextChanged(const common::OperationContext& context) override;
private:
std::shared_ptr<ISessionCallback> cb_;
std::unique_ptr<FakeFaceEngine> mEngine;
std::shared_ptr<ISessionCallback> mCb;
std::mt19937 mRandom;
std::unique_ptr<WorkerThread> mThread;
std::shared_ptr<CancellationSignal> mCancellationSignal;
};
} // namespace aidl::android::hardware::biometrics::face

View File

@@ -0,0 +1,98 @@
props {
owner: Vendor
module: "android.face.virt.FaceHalProperties"
prop {
api_name: "authenticator_id"
type: Long
access: ReadWrite
prop_name: "vendor.face.virtual.authenticator_id"
}
prop {
api_name: "challenge"
type: Long
access: ReadWrite
prop_name: "vendor.face.virtual.challenge"
}
prop {
api_name: "enrollment_hit"
type: Integer
access: ReadWrite
prop_name: "vendor.face.virtual.enrollment_hit"
}
prop {
api_name: "enrollments"
type: IntegerList
access: ReadWrite
prop_name: "persist.vendor.face.virtual.enrollments"
}
prop {
api_name: "features"
type: IntegerList
access: ReadWrite
prop_name: "persist.vendor.face.virtual.features"
}
prop {
api_name: "lockout"
access: ReadWrite
prop_name: "vendor.face.virtual.lockout"
}
prop {
api_name: "next_enrollment"
type: String
access: ReadWrite
prop_name: "vendor.face.virtual.next_enrollment"
}
prop {
api_name: "operation_authenticate_duration"
type: Integer
access: ReadWrite
prop_name: "vendor.face.virtual.operation_authenticate_duration"
}
prop {
api_name: "operation_authenticate_fails"
access: ReadWrite
prop_name: "vendor.face.virtual.operation_authenticate_fails"
}
prop {
api_name: "operation_authenticate_latency"
type: Integer
access: ReadWrite
prop_name: "vendor.face.virtual.operation_authenticate_latency"
}
prop {
api_name: "operation_detect_interaction_fails"
access: ReadWrite
prop_name: "vendor.face.virtual.operation_detect_interaction_fails"
}
prop {
api_name: "operation_detect_interaction_latency"
type: Integer
access: ReadWrite
prop_name: "vendor.face.virtual.operation_detect_interaction_latency"
}
prop {
api_name: "operation_enroll_fails"
access: ReadWrite
prop_name: "vendor.face.virtual.operation_enroll_fails"
}
prop {
api_name: "operation_start_enroll_latency"
type: Integer
access: ReadWrite
prop_name: "vendor.face.virtual.operation_start_enroll_latency"
}
prop {
api_name: "strength"
type: String
access: ReadWrite
prop_name: "persist.vendor.face.virtual.strength"
enum_values: "convenience|weak|strong"
}
prop {
api_name: "type"
type: String
access: ReadWrite
prop_name: "persist.vendor.face.virtual.type"
enum_values: "IR|RGB"
}
}

View File

@@ -0,0 +1,159 @@
# face.sysprop
# module becomes static class (Java) / namespace (C++) for serving API
module: "android.face.virt.FaceHalProperties"
owner: Vendor
# type of face sensor
prop {
prop_name: "persist.vendor.face.virtual.type"
type: String
scope: Public
access: ReadWrite
enum_values: "IR|RGB"
api_name: "type"
}
# the strength of the sensor
prop {
prop_name: "persist.vendor.face.virtual.strength"
type: String
scope: Public
access: ReadWrite
enum_values: "convenience|weak|strong"
api_name: "strength"
}
# ids of current enrollments
prop {
prop_name: "persist.vendor.face.virtual.enrollments"
type: IntegerList
scope: Public
access: ReadWrite
api_name: "enrollments"
}
# List of features
prop {
prop_name: "persist.vendor.face.virtual.features"
type: IntegerList
scope: Public
access: ReadWrite
api_name: "features"
}
# authenticate and detectInteraction will succeed with this
# enrollment id, when present, otherwise they will error
prop {
prop_name: "vendor.face.virtual.enrollment_hit"
type: Integer
scope: Public
access: ReadWrite
api_name: "enrollment_hit"
}
# The initial latency for enrollment
prop {
prop_name: "vendor.face.virtual.operation_start_enroll_latency"
type: Integer
scope: Public
access: ReadWrite
api_name: "operation_start_enroll_latency"
}
# the next enrollment in the format:
# "<id>,<bucket_id>:<delay>:<succeeds>,<bucket_id>..."
# for example: "0:1,0:100:1,1:200:1" indicating that bucket 0 took
# 50 milliseconds, bucket 1 took 100 milliseconds, bucket 2 took 200 milliseconds.
# Note that it is up to the configuration to determine how many buckets are required
# to complete an enrollment
prop {
prop_name: "vendor.face.virtual.next_enrollment"
type: String
scope: Public
access: ReadWrite
api_name: "next_enrollment"
}
# value for getAuthenticatorId or 0
prop {
prop_name: "vendor.face.virtual.authenticator_id"
type: Long
scope: Public
access: ReadWrite
api_name: "authenticator_id"
}
# value for generateChallenge
prop {
prop_name: "vendor.face.virtual.challenge"
type: Long
scope: Public
access: ReadWrite
api_name: "challenge"
}
# if locked out
prop {
prop_name: "vendor.face.virtual.lockout"
type: Boolean
scope: Public
access: ReadWrite
api_name: "lockout"
}
# force all authenticate operations to fail
prop {
prop_name: "vendor.face.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.face.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.face.virtual.operation_enroll_fails"
type: Boolean
scope: Public
access: ReadWrite
api_name: "operation_enroll_fails"
}
# add a latency to authentication operations
# Note that this latency is the initial authentication latency that occurs before
# the HAL will send AcquiredInfo::START and AcquiredInfo::FIRST_FRAME_RECEIVED
prop {
prop_name: "vendor.face.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.face.virtual.operation_detect_interaction_latency"
type: Integer
scope: Public
access: ReadWrite
api_name: "operation_detect_interaction_latency"
}
# millisecond duration for authenticate operations
# (waits for changes to enrollment_hit)
prop {
prop_name: "vendor.face.virtual.operation_authenticate_duration"
type: Integer
scope: Public
access: ReadWrite
api_name: "operation_authenticate_duration"
}

View File

@@ -0,0 +1,383 @@
/*
* 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 <face.sysprop.h>
#include <gtest/gtest.h>
#include <aidl/android/hardware/biometrics/face/BnSessionCallback.h>
#include <android-base/logging.h>
#include "FakeFaceEngine.h"
using namespace ::android::face::virt;
using namespace ::aidl::android::hardware::biometrics::face;
using namespace ::aidl::android::hardware::keymaster;
namespace aidl::android::hardware::biometrics::face {
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(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 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 onEnrollmentFrame(const EnrollmentFrame& frame) override {
mEnrollmentFrames.push_back(frame.data.vendorCode);
return ndk::ScopedAStatus::ok();
}
::ndk::ScopedAStatus onEnrollmentsEnumerated(
const std::vector<int32_t>& enrollmentIds) override {
mLastEnrollmentsEnumerated = 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 onAuthenticationFrame(const AuthenticationFrame& /*authFrame*/) override {
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 {
mLockoutPermanent = false;
return ndk::ScopedAStatus::ok();
}
::ndk::ScopedAStatus onSessionClosed() override { return ndk::ScopedAStatus::ok(); }
::ndk::ScopedAStatus onFeaturesRetrieved(const std::vector<Feature>& features) override {
mFeatures = features;
return ndk::ScopedAStatus::ok();
}
::ndk::ScopedAStatus onFeatureSet(Feature feature) override {
mLastFeatureSet = feature;
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> mLastEnrollmentsEnumerated;
std::vector<int32_t> mLastEnrollmentRemoved;
std::vector<Feature> mFeatures;
Feature mLastFeatureSet;
std::vector<int32_t> mEnrollmentFrames;
bool mAuthenticateFailed = false;
bool mAuthenticatorIdInvalidated = false;
bool mLockoutPermanent = false;
int mInteractionDetectedCount = 0;
};
class FakeFaceEngineTest : public ::testing::Test {
protected:
void SetUp() override {
LOG(ERROR) << "JRM SETUP";
mCallback = ndk::SharedRefBase::make<TestSessionCallback>();
FaceHalProperties::enrollments({});
FaceHalProperties::challenge({});
FaceHalProperties::features({});
FaceHalProperties::authenticator_id({});
FaceHalProperties::strength("");
}
FakeFaceEngine mEngine;
std::shared_ptr<TestSessionCallback> mCallback;
std::promise<void> mCancel;
};
TEST_F(FakeFaceEngineTest, one_eq_one) {
ASSERT_EQ(1, 1);
}
TEST_F(FakeFaceEngineTest, GenerateChallenge) {
mEngine.generateChallengeImpl(mCallback.get());
ASSERT_EQ(FaceHalProperties::challenge().value(), mCallback->mLastChallenge);
}
TEST_F(FakeFaceEngineTest, RevokeChallenge) {
auto challenge = FaceHalProperties::challenge().value_or(10);
mEngine.revokeChallengeImpl(mCallback.get(), challenge);
ASSERT_FALSE(FaceHalProperties::challenge().has_value());
ASSERT_EQ(challenge, mCallback->mLastChallengeRevoked);
}
TEST_F(FakeFaceEngineTest, ResetLockout) {
FaceHalProperties::lockout(true);
mEngine.resetLockoutImpl(mCallback.get(), {});
ASSERT_FALSE(mCallback->mLockoutPermanent);
ASSERT_FALSE(FaceHalProperties::lockout().value_or(true));
}
TEST_F(FakeFaceEngineTest, AuthenticatorId) {
FaceHalProperties::authenticator_id(50);
mEngine.getAuthenticatorIdImpl(mCallback.get());
ASSERT_EQ(50, mCallback->mLastAuthenticatorId);
ASSERT_FALSE(mCallback->mAuthenticatorIdInvalidated);
}
TEST_F(FakeFaceEngineTest, GetAuthenticatorIdWeakReturnsZero) {
FaceHalProperties::strength("weak");
FaceHalProperties::authenticator_id(500);
mEngine.getAuthenticatorIdImpl(mCallback.get());
ASSERT_EQ(0, mCallback->mLastAuthenticatorId);
ASSERT_FALSE(mCallback->mAuthenticatorIdInvalidated);
}
TEST_F(FakeFaceEngineTest, AuthenticatorIdInvalidate) {
FaceHalProperties::authenticator_id(500);
mEngine.invalidateAuthenticatorIdImpl(mCallback.get());
ASSERT_NE(500, FaceHalProperties::authenticator_id().value());
ASSERT_TRUE(mCallback->mAuthenticatorIdInvalidated);
}
TEST_F(FakeFaceEngineTest, Enroll) {
FaceHalProperties::next_enrollment("1,0:30:true,1:0:true,2:0:true,3:0:true,4:0:true");
keymaster::HardwareAuthToken hat{.mac = {2, 4}};
mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
mCancel.get_future());
ASSERT_FALSE(FaceHalProperties::next_enrollment().has_value());
ASSERT_EQ(1, FaceHalProperties::enrollments().size());
ASSERT_EQ(1, FaceHalProperties::enrollments()[0].value());
ASSERT_EQ(1, mCallback->mLastEnrolled);
}
TEST_F(FakeFaceEngineTest, EnrollFails) {
FaceHalProperties::next_enrollment("1,0:30:true,1:0:true,2:0:true,3:0:true,4:0:false");
keymaster::HardwareAuthToken hat{.mac = {2, 4}};
mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
mCancel.get_future());
ASSERT_FALSE(FaceHalProperties::next_enrollment().has_value());
ASSERT_EQ(0, FaceHalProperties::enrollments().size());
}
TEST_F(FakeFaceEngineTest, EnrollCancel) {
FaceHalProperties::next_enrollment("1,0:30:true,1:0:true,2:0:true,3:0:true,4:0:false");
keymaster::HardwareAuthToken hat{.mac = {2, 4}};
mCancel.set_value();
mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
mCancel.get_future());
ASSERT_EQ(Error::CANCELED, mCallback->mError);
ASSERT_EQ(-1, mCallback->mLastEnrolled);
ASSERT_EQ(0, FaceHalProperties::enrollments().size());
ASSERT_FALSE(FaceHalProperties::next_enrollment().has_value());
}
TEST_F(FakeFaceEngineTest, Authenticate) {
FaceHalProperties::enrollments({100});
FaceHalProperties::enrollment_hit(100);
mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, mCancel.get_future());
ASSERT_EQ(100, mCallback->mLastAuthenticated);
ASSERT_FALSE(mCallback->mAuthenticateFailed);
}
TEST_F(FakeFaceEngineTest, AuthenticateCancel) {
FaceHalProperties::enrollments({100});
FaceHalProperties::enrollment_hit(100);
mCancel.set_value();
mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, mCancel.get_future());
ASSERT_EQ(Error::CANCELED, mCallback->mError);
}
TEST_F(FakeFaceEngineTest, AuthenticateFailedForUnEnrolled) {
FaceHalProperties::enrollments({3});
FaceHalProperties::enrollment_hit(100);
mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, mCancel.get_future());
ASSERT_EQ(Error::UNABLE_TO_PROCESS, mCallback->mError);
ASSERT_TRUE(mCallback->mAuthenticateFailed);
}
TEST_F(FakeFaceEngineTest, DetectInteraction) {
FaceHalProperties::enrollments({100});
FaceHalProperties::enrollment_hit(100);
ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
ASSERT_EQ(1, mCallback->mInteractionDetectedCount);
}
TEST_F(FakeFaceEngineTest, DetectInteractionCancel) {
FaceHalProperties::enrollments({100});
FaceHalProperties::enrollment_hit(100);
mCancel.set_value();
mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
ASSERT_EQ(Error::CANCELED, mCallback->mError);
}
TEST_F(FakeFaceEngineTest, GetFeatureEmpty) {
mEngine.getFeaturesImpl(mCallback.get());
ASSERT_TRUE(mCallback->mFeatures.empty());
}
TEST_F(FakeFaceEngineTest, SetFeature) {
FaceHalProperties::enrollments({1});
keymaster::HardwareAuthToken hat{.mac = {2, 4}};
mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
auto features = mCallback->mFeatures;
ASSERT_TRUE(features.empty());
ASSERT_EQ(Feature::REQUIRE_ATTENTION, mCallback->mLastFeatureSet);
mEngine.getFeaturesImpl(mCallback.get());
features = mCallback->mFeatures;
ASSERT_FALSE(features.empty());
ASSERT_NE(features.end(),
std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
}
TEST_F(FakeFaceEngineTest, ToggleFeature) {
FaceHalProperties::enrollments({1});
keymaster::HardwareAuthToken hat{.mac = {2, 4}};
mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
mEngine.getFeaturesImpl(mCallback.get());
auto features = mCallback->mFeatures;
ASSERT_FALSE(features.empty());
ASSERT_NE(features.end(),
std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, false);
mEngine.getFeaturesImpl(mCallback.get());
features = mCallback->mFeatures;
ASSERT_TRUE(features.empty());
}
TEST_F(FakeFaceEngineTest, TurningOffNonExistentFeatureDoesNothing) {
FaceHalProperties::enrollments({1});
keymaster::HardwareAuthToken hat{.mac = {2, 4}};
mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, false);
mEngine.getFeaturesImpl(mCallback.get());
auto features = mCallback->mFeatures;
ASSERT_TRUE(features.empty());
}
TEST_F(FakeFaceEngineTest, SetMultipleFeatures) {
FaceHalProperties::enrollments({1});
keymaster::HardwareAuthToken hat{.mac = {2, 4}};
mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_DIVERSE_POSES, true);
mEngine.setFeatureImpl(mCallback.get(), hat, Feature::DEBUG, true);
mEngine.getFeaturesImpl(mCallback.get());
auto features = mCallback->mFeatures;
ASSERT_EQ(3, features.size());
ASSERT_NE(features.end(),
std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
ASSERT_NE(features.end(),
std::find(features.begin(), features.end(), Feature::REQUIRE_DIVERSE_POSES));
ASSERT_NE(features.end(), std::find(features.begin(), features.end(), Feature::DEBUG));
}
TEST_F(FakeFaceEngineTest, SetMultipleFeaturesAndTurnOffSome) {
FaceHalProperties::enrollments({1});
keymaster::HardwareAuthToken hat{.mac = {2, 4}};
mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_DIVERSE_POSES, true);
mEngine.setFeatureImpl(mCallback.get(), hat, Feature::DEBUG, true);
mEngine.setFeatureImpl(mCallback.get(), hat, Feature::DEBUG, false);
mEngine.getFeaturesImpl(mCallback.get());
auto features = mCallback->mFeatures;
ASSERT_EQ(2, features.size());
ASSERT_NE(features.end(),
std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
ASSERT_NE(features.end(),
std::find(features.begin(), features.end(), Feature::REQUIRE_DIVERSE_POSES));
ASSERT_EQ(features.end(), std::find(features.begin(), features.end(), Feature::DEBUG));
}
TEST_F(FakeFaceEngineTest, Enumerate) {
FaceHalProperties::enrollments({120, 3});
mEngine.enumerateEnrollmentsImpl(mCallback.get());
auto enrolls = mCallback->mLastEnrollmentsEnumerated;
ASSERT_FALSE(enrolls.empty());
ASSERT_NE(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 120));
ASSERT_NE(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 3));
}
TEST_F(FakeFaceEngineTest, RemoveEnrollments) {
FaceHalProperties::enrollments({120, 3, 100});
mEngine.removeEnrollmentsImpl(mCallback.get(), {120, 100});
mEngine.enumerateEnrollmentsImpl(mCallback.get());
auto enrolls = mCallback->mLastEnrollmentsEnumerated;
ASSERT_FALSE(enrolls.empty());
ASSERT_EQ(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 120));
ASSERT_NE(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 3));
ASSERT_EQ(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 100));
}
TEST_F(FakeFaceEngineTest, ResetLockoutWithAuth) {
FaceHalProperties::lockout(true);
FaceHalProperties::enrollments({33});
FaceHalProperties::enrollment_hit(33);
auto cancelFuture = mCancel.get_future();
mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, cancelFuture);
ASSERT_TRUE(mCallback->mLockoutPermanent);
mEngine.resetLockoutImpl(mCallback.get(), {} /* hat */);
ASSERT_FALSE(mCallback->mLockoutPermanent);
FaceHalProperties::enrollment_hit(33);
mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, cancelFuture);
ASSERT_EQ(33, mCallback->mLastAuthenticated);
ASSERT_FALSE(mCallback->mAuthenticateFailed);
}
} // namespace aidl::android::hardware::biometrics::face

View File

@@ -19,51 +19,11 @@
#include <android-base/logging.h>
#include <fingerprint.sysprop.h>
#include <chrono>
#include <regex>
#include <thread>
#include "util/CancellationSignal.h"
#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
#include "util/Util.h"
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 {
@@ -101,14 +61,14 @@ void FakeFingerprintEngine::enrollImpl(ISessionCallback* cb,
// format is "<id>:<progress_ms>,<progress_ms>,...:<result>
auto nextEnroll = FingerprintHalProperties::next_enrollment().value_or("");
auto parts = split(nextEnroll, ":");
auto parts = Util::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], ",");
auto progress = Util::split(parts[1], ",");
for (size_t i = 0; i < progress.size(); i++) {
auto left = progress.size() - i - 1;
SLEEP_MS(std::stoi(progress[i]));
@@ -141,7 +101,7 @@ void FakeFingerprintEngine::authenticateImpl(ISessionCallback* cb, int64_t /* op
const std::future<void>& cancel) {
BEGIN_OP(FingerprintHalProperties::operation_authenticate_latency().value_or(DEFAULT_LATENCY));
auto now = getSystemNanoTime();
auto now = Util::getSystemNanoTime();
int64_t duration = FingerprintHalProperties::operation_authenticate_duration().value_or(0);
do {
if (FingerprintHalProperties::operation_authenticate_fails().value_or(false)) {
@@ -172,7 +132,7 @@ void FakeFingerprintEngine::authenticateImpl(ISessionCallback* cb, int64_t /* op
}
SLEEP_MS(100);
} while (!hasElapsed(now, duration));
} while (!Util::hasElapsed(now, duration));
LOG(ERROR) << "Fail: not enrolled";
cb->onAuthenticationFailed();