mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 16:50:18 +00:00
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:
69
biometrics/common/util/include/util/Util.h
Normal file
69
biometrics/common/util/include/util/Util.h
Normal 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
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
318
biometrics/face/aidl/default/FakeFaceEngine.cpp
Normal file
318
biometrics/face/aidl/default/FakeFaceEngine.cpp
Normal 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
|
||||
65
biometrics/face/aidl/default/FakeFaceEngine.h
Normal file
65
biometrics/face/aidl/default/FakeFaceEngine.h
Normal 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
|
||||
77
biometrics/face/aidl/default/README.md
Normal file
77
biometrics/face/aidl/default/README.md
Normal 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
|
||||
```
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
159
biometrics/face/aidl/default/face.sysprop
Normal file
159
biometrics/face/aidl/default/face.sysprop
Normal 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"
|
||||
}
|
||||
383
biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp
Normal file
383
biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp
Normal 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
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user