Face Virtual HAL enrollment basic support via SUW

Bug: 294254230
Test: Manually perform face enrollment via Settings
Change-Id: I30f6ffc3cde615b9cab55ef060623464a7799100
This commit is contained in:
Jeff Pu 2023-12-04 16:53:04 +00:00
parent 761095c9ac
commit 84c60185f1
3 changed files with 126 additions and 86 deletions

View file

@ -53,15 +53,6 @@ void FakeFaceEngine::enrollImpl(ISessionCallback* cb, const keymaster::HardwareA
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()) {
@ -70,66 +61,81 @@ void FakeFaceEngine::enrollImpl(ISessionCallback* cb, const keymaster::HardwareA
return;
}
if (FaceHalProperties::operation_enroll_fails().value_or(false)) {
LOG(ERROR) << "Fail: operation_enroll_fails";
// Format: <id>:<progress_ms-[acquiredInfo,...],...:<success>
// ------:-----------------------------------------:--------------
// | | |--->enrollment success (true/false)
// | |--> progress_steps
// |
// |-->enrollment id
//
//
// progress_steps
// <progress_duration>-[acquiredInfo,...]+
// ---------------------------- ---------------------
// | |-> sequence of acquiredInfo code
// | --> time duration of the step in ms
//
// E.g. 1:2000-[21,1108,5,6,1],1000-[1113,4,1]:true
// A success enrollement of id 1 by 2 steps
// 1st step lasts 2000ms with acquiredInfo codes (21,1108,5,6,1)
// 2nd step lasts 1000ms with acquiredInfo codes (1113,4,1)
//
std::string defaultNextEnrollment =
"1:1000-[21,7,1,1103],1500-[1108,1],2000-[1113,1],2500-[1118,1]:true";
auto nextEnroll = FaceHalProperties::next_enrollment().value_or(defaultNextEnrollment);
auto parts = Util::split(nextEnroll, ":");
if (parts.size() != 3) {
LOG(ERROR) << "Fail: invalid next_enrollment:" << nextEnroll;
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];
auto progress = Util::parseEnrollmentCapture(parts[1]);
for (size_t i = 0; i < progress.size(); i += 2) {
auto left = (progress.size() - i) / 2 - 1;
auto duration = progress[i][0];
auto acquired = progress[i + 1];
auto N = acquired.size();
SLEEP_MS(std::stoi(delay));
for (int j = 0; j < N; j++) {
SLEEP_MS(duration / N);
if (shouldCancel(cancel)) {
LOG(ERROR) << "Fail: cancel";
cb->onError(Error::CANCELED, 0 /* vendorCode */);
return;
if (shouldCancel(cancel)) {
LOG(ERROR) << "Fail: cancel";
cb->onError(Error::CANCELED, 0 /* vendorCode */);
return;
}
EnrollmentFrame frame = {};
auto ac = convertAcquiredInfo(acquired[j]);
frame.data.acquiredInfo = ac.first;
frame.data.vendorCode = ac.second;
frame.stage = (i == 0 && j == 0) ? EnrollmentStage::FIRST_FRAME_RECEIVED
: (i == progress.size() - 2 && j == N - 1)
? EnrollmentStage::ENROLLMENT_FINISHED
: EnrollmentStage::WAITING_FOR_CENTERING;
cb->onEnrollmentFrame(frame);
}
if (!IS_TRUE(succeeds)) { // end and failed
LOG(ERROR) << "Fail: requested by caller: " << parts[i];
if (left == 0 && !IS_TRUE(parts[2])) { // end and failed
LOG(ERROR) << "Fail: requested by caller: " << nextEnroll;
FaceHalProperties::next_enrollment({});
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);
} else { // progress and update props if last time
LOG(INFO) << "onEnroll: " << enrollmentId << " left: " << left;
if (left == 0) {
auto enrollments = FaceHalProperties::enrollments();
enrollments.emplace_back(enrollmentId);
FaceHalProperties::enrollments(enrollments);
FaceHalProperties::next_enrollment({});
// change authenticatorId after new enrollment
auto id = FaceHalProperties::authenticator_id().value_or(0);
auto newId = id + 1;
FaceHalProperties::authenticator_id(newId);
LOG(INFO) << "Enrolled: " << enrollmentId;
}
cb->onEnrollmentProgress(enrollmentId, left);
}
}
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*/,

View file

@ -1,30 +1,35 @@
# Face Virtual HAL (VHAL)
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.
Note: The virtual face HAL feature development will be done in phases. Refer to this doc often for
the latest supported features
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. Note: The virtual face HAL feature development will be done in
phases. Refer to this doc often for the latest supported features
## Supported Devices
The face virtual hal is automatically built in in all debug builds (userdebug and eng) for the latest pixel devices and CF.
The instructions in this doc applies to all
The face virtual hal is automatically built in in all debug builds (userdebug<br/>
and eng) for the latest pixel devices and CF. The instructions in this doc<br/>
applies to all
## Enabling Face Virtual HAL
On pixel devicse (non-CF), by default (after manufacture reset), Face VHAL is not enabled. Therefore real Face HAL is used.
Face VHAL enabling is gated by the following two AND conditions:
1. The Face VHAL feature flag (as part of Trunk-development strategy) must be tured until the flags life-cycle ends.
2. The Face VHAL must be enabled via sysprop
See adb commands below
On pixel devicse (non-CF), by default (after manufacture reset), Face VHAL is <br/>
not enabled. Therefore real Face HAL is used. Face VHAL enabling is gated by the<br/>
following two AND conditions:<br/>
1. The Face VHAL feature flag (as part ofTrunk-development strategy) must be<br/>
turned on until the flags life-cycle ends.
2. The Face VHAL must be enabled via sysprop.
##Getting Stared
See the adb commands below
A basic use case for a successful authentication via Face VHAL is given as an exmple below.
## Getting Stared
A basic use case for a successful authentication via Face VHAL is given as an
exmple below.
### Enabling VHAL
```shell
$ adb root
$ adb shell device_config put biometrics_framework com.android.server.biometrics.face_vhal_feature true
@ -35,6 +40,7 @@ $ adb reboot
```
### Direct Enrollment
```shell
$ adb shell locksettings set-pin 0000
$ adb shell setprop persist.vendor.face.virtual.enrollments 1
@ -42,22 +48,47 @@ $ adb shell cmd face syncadb shell cmd face sync
```
## Authenticating
To authenticate successfully, the captured (hit) must match the enrollment id set above. To trigger
authentication failure, set the hit id to a different value.
To authenticate successfully, the captured (hit) must match the enrollment id<br/>
set above. To trigger authentication failure, set the hit id to a different value.
```shell
$ adb shell setprop vendor.face.virtual.operation_authenticate_duration 800
$ adb shell setprop vendor.face.virtual.enrollment_hit 1
```
Refer to face.sysprop for full supported features of authentication, such as error and acquiredInfo insertion
### AcquiredInfo
AcquiredInfo codes can be sent during authentication by specifying the sysprop.<br/>
The codes is sent in sequence and in the interval of operation_authentication_duration/numberOfAcquiredInfoCode
```shell
$ adb shell setprop vendor.face.virtual.operation_authenticate_acquired 6,9,1013
```
Refer to [AcquiredInfo.aidl](https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/biometrics/face/aidl/android/hardware/biometrics/face/AcquiredInfo.aidl) for full face acquiredInfo codes.
Note: For vendor specific acquired info, acquiredInfo = 1000 + vendorCode.
### Error Insertion
Error can be inserted during authentction by specifying the authenticate_error sysprop.
```shell
$ adb shell setprop vendor.face.virtual.operation_authenticate_error 4
```
Refer to [Error.aidl](https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/biometrics/face/aidl/android/hardware/biometrics/face/Error.aidl) for full face error codes
## Enrollment via Setup
## Enrollment via Settings
Enrollment process is specified by sysprop `next_enrollment` in the following format
```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
$ walk thru the manual enrollment process by following screen instructions
Format: <id>:<progress_ms-[acquiredInfo,...],...:<success>
----:-----------------------------------:---------
| | |--->sucess (true/false)
| |--> progress_step(s)
|
|-->enrollment_id
# If you would like to get rid of the enrollment, run the follwoing command
$ adb shell setprop persist.vendor.face.virtual.enrollments \"\"
E.g.
$ adb shell setprop vendor.face.virtual.next_enrollment 1:6000-[21,8,1,1108,1,10,1113,1,1118,1124]:true
```
If next_enrollment prop is not set, the following default value is used:<br/>
&nbsp;&nbsp;defaultNextEnrollment="1:1000-[21,7,1,1103],1500-[1108,1],2000-[1113,1],2500-[1118,1]:true"<br/>
Note: Enrollment data and configuration can be supported upon request in case of needs

View file

@ -45,6 +45,7 @@ class TestSessionCallback : public BnSessionCallback {
};
::ndk::ScopedAStatus onEnrollmentProgress(int32_t enrollmentId, int32_t remaining) override {
if (remaining == 0) mLastEnrolled = enrollmentId;
mRemaining = remaining;
return ndk::ScopedAStatus::ok();
};
@ -128,6 +129,7 @@ class TestSessionCallback : public BnSessionCallback {
bool mAuthenticatorIdInvalidated = false;
bool mLockoutPermanent = false;
int mInteractionDetectedCount = 0;
int mRemaining = -1;
};
class FakeFaceEngineTest : public ::testing::Test {
@ -193,7 +195,7 @@ TEST_F(FakeFaceEngineTest, AuthenticatorIdInvalidate) {
}
TEST_F(FakeFaceEngineTest, Enroll) {
FaceHalProperties::next_enrollment("1,0:30:true,1:0:true,2:0:true,3:0:true,4:0:true");
FaceHalProperties::next_enrollment("1,0:1000-[21,5,6,7,1],1100-[1118,1108,1]:true");
keymaster::HardwareAuthToken hat{.mac = {2, 4}};
mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
mCancel.get_future());
@ -201,10 +203,11 @@ TEST_F(FakeFaceEngineTest, Enroll) {
ASSERT_EQ(1, FaceHalProperties::enrollments().size());
ASSERT_EQ(1, FaceHalProperties::enrollments()[0].value());
ASSERT_EQ(1, mCallback->mLastEnrolled);
ASSERT_EQ(0, mCallback->mRemaining);
}
TEST_F(FakeFaceEngineTest, EnrollFails) {
FaceHalProperties::next_enrollment("1,0:30:true,1:0:true,2:0:true,3:0:true,4:0:false");
FaceHalProperties::next_enrollment("1,0:1000-[21,5,6,7,1],1100-[1118,1108,1]:false");
keymaster::HardwareAuthToken hat{.mac = {2, 4}};
mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
mCancel.get_future());
@ -213,7 +216,7 @@ TEST_F(FakeFaceEngineTest, EnrollFails) {
}
TEST_F(FakeFaceEngineTest, EnrollCancel) {
FaceHalProperties::next_enrollment("1,0:30:true,1:0:true,2:0:true,3:0:true,4:0:false");
FaceHalProperties::next_enrollment("1:2000-[21,8,9],300:false");
keymaster::HardwareAuthToken hat{.mac = {2, 4}};
mCancel.set_value();
mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
@ -221,7 +224,7 @@ TEST_F(FakeFaceEngineTest, EnrollCancel) {
ASSERT_EQ(Error::CANCELED, mCallback->mError);
ASSERT_EQ(-1, mCallback->mLastEnrolled);
ASSERT_EQ(0, FaceHalProperties::enrollments().size());
ASSERT_FALSE(FaceHalProperties::next_enrollment().has_value());
ASSERT_TRUE(FaceHalProperties::next_enrollment().has_value());
}
TEST_F(FakeFaceEngineTest, Authenticate) {