Face Virtual HAL enrollment basic support via SUW
Bug: 294254230
Test: Manually perform face enrollment via Settings
Change-Id: I30f6ffc3cde615b9cab55ef060623464a7799100
diff --git a/biometrics/face/aidl/default/FakeFaceEngine.cpp b/biometrics/face/aidl/default/FakeFaceEngine.cpp
index 578231d..dc524bb 100644
--- a/biometrics/face/aidl/default/FakeFaceEngine.cpp
+++ b/biometrics/face/aidl/default/FakeFaceEngine.cpp
@@ -53,15 +53,6 @@
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 @@
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*/,
diff --git a/biometrics/face/aidl/default/README.md b/biometrics/face/aidl/default/README.md
index 516a7aa..9225258 100644
--- a/biometrics/face/aidl/default/README.md
+++ b/biometrics/face/aidl/default/README.md
@@ -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 @@
```
### Direct Enrollment
+
```shell
$ adb shell locksettings set-pin 0000
$ adb shell setprop persist.vendor.face.virtual.enrollments 1
@@ -42,22 +48,47 @@
```
## 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/>
+ 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
+
diff --git a/biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp b/biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp
index 6897dc4..69c9bf4 100644
--- a/biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp
+++ b/biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp
@@ -45,6 +45,7 @@
};
::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 @@
bool mAuthenticatorIdInvalidated = false;
bool mLockoutPermanent = false;
int mInteractionDetectedCount = 0;
+ int mRemaining = -1;
};
class FakeFaceEngineTest : public ::testing::Test {
@@ -193,7 +195,7 @@
}
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 @@
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, 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 @@
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) {