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/>
+&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
+
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) {