Loading biometrics/common/util/include/util/Util.h +84 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,9 @@ #include <thread> #include <vector> #include <android-base/parseint.h> using ::android::base::ParseInt; namespace aidl::android::hardware::biometrics { #define SLEEP_MS(x) \ Loading Loading @@ -64,6 +67,87 @@ class Util { std::sregex_token_iterator()); return parts; } // Returns a vector of integers for the string separated by comma, // Empty vector is returned if there is any parsing error static std::vector<int32_t> parseIntSequence(const std::string& str, const std::string& sep = ",") { std::vector<std::string> seqs = Util::split(str, sep); std::vector<int32_t> res; for (const auto& seq : seqs) { int32_t val; if (ParseInt(seq, &val)) { res.push_back(val); } else { LOG(WARNING) << "Invalid int sequence:" + str + " seq:" + seq; res.clear(); break; } } return res; } // Parses a single enrollment stage string in the format of // enroll_stage_spec: <duration>[-acquiredInfos] // duration: integerInMs // acquiredInfos: [info1,info2,...] // // Returns false if there is parsing error // static bool parseEnrollmentCaptureSingle(const std::string& str, std::vector<std::vector<int32_t>>& res) { std::vector<int32_t> defaultAcquiredInfo = {1}; bool aborted = true; do { std::smatch sms; // Parses strings like "1000-[5,1]" or "500" std::regex ex("((\\d+)(-\\[([\\d|,]+)\\])?)"); if (!regex_match(str.cbegin(), str.cend(), sms, ex)) break; int32_t duration; if (!ParseInt(sms.str(2), &duration)) break; res.push_back({duration}); if (!sms.str(4).empty()) { auto acqv = parseIntSequence(sms.str(4)); if (acqv.empty()) break; res.push_back(acqv); } else res.push_back(defaultAcquiredInfo); aborted = false; } while (0); return !aborted; } // Parses enrollment string consisting of one or more stages in the formst of // <enroll_stage_spec>[,enroll_stage_spec,...] // Empty vector is returned in case of parsing error static std::vector<std::vector<int32_t>> parseEnrollmentCapture(const std::string& str) { std::vector<std::vector<int32_t>> res; std::string s(str); s.erase(std::remove_if(s.begin(), s.end(), ::isspace), s.end()); bool aborted = false; std::smatch sms; // Parses strings like "1000-[5,1],500,800-[6,5,1]" // -------------- ----- --------------- // into parts: A B C while (regex_search(s, sms, std::regex("^(,)?(\\d+(-\\[[\\d|,]+\\])?)"))) { if (!parseEnrollmentCaptureSingle(sms.str(2), res)) { aborted = true; break; } s = sms.suffix(); } if (aborted || s.length() != 0) { res.clear(); LOG(ERROR) << "Failed to parse enrollment captures:" + str; } return res; } }; } // namespace aidl::android::hardware::biometrics biometrics/face/aidl/default/FakeFaceEngine.cpp +110 −39 Original line number Diff line number Diff line Loading @@ -136,32 +136,52 @@ void FakeFaceEngine::authenticateImpl(ISessionCallback* cb, int64_t /*operationI 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 id = FaceHalProperties::enrollment_hit().value_or(0); auto enrolls = FaceHalProperties::enrollments(); auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end(); 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)); auto vec2str = [](std::vector<AcquiredInfo> va) { std::stringstream ss; bool isFirst = true; for (auto ac : va) { if (!isFirst) ss << ","; ss << std::to_string((int8_t)ac); isFirst = false; } return ss.str(); }; // default behavior mimic face sensor in U int64_t defaultAuthDuration = 500; std::string defaultAcquiredInfo = vec2str({AcquiredInfo::START, AcquiredInfo::FIRST_FRAME_RECEIVED}); if (!isEnrolled) { std::vector<AcquiredInfo> v; for (int i = 0; i < 56; i++) v.push_back(AcquiredInfo::NOT_DETECTED); defaultAcquiredInfo += "," + vec2str(v); defaultAuthDuration = 2100; } else { defaultAcquiredInfo += "," + vec2str({AcquiredInfo::TOO_BRIGHT, AcquiredInfo::TOO_BRIGHT, AcquiredInfo::TOO_BRIGHT, AcquiredInfo::TOO_BRIGHT, AcquiredInfo::GOOD, AcquiredInfo::GOOD}); } if (FaceHalProperties::operation_authenticate_fails().value_or(false)) { LOG(ERROR) << "Fail: operation_authenticate_fails"; cb->onError(Error::VENDOR, 0 /* vendorError */); int64_t now = Util::getSystemNanoTime(); int64_t duration = FaceHalProperties::operation_authenticate_duration().value_or(defaultAuthDuration); auto acquired = FaceHalProperties::operation_authenticate_acquired().value_or(defaultAcquiredInfo); auto acquiredInfos = Util::parseIntSequence(acquired); int N = acquiredInfos.size(); if (N == 0) { LOG(ERROR) << "Fail to parse authentiate acquired info: " + acquired; cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */); return; } int i = 0; do { if (FaceHalProperties::lockout().value_or(false)) { LOG(ERROR) << "Fail: lockout"; cb->onLockoutPermanent(); Loading @@ -169,23 +189,74 @@ void FakeFaceEngine::authenticateImpl(ISessionCallback* cb, int64_t /*operationI return; } if (FaceHalProperties::operation_authenticate_fails().value_or(false)) { LOG(ERROR) << "Fail: operation_authenticate_fails"; cb->onAuthenticationFailed(); return; } auto err = FaceHalProperties::operation_authenticate_error().value_or(0); if (err != 0) { LOG(ERROR) << "Fail: operation_authenticate_error"; auto ec = convertError(err); cb->onError(ec.first, ec.second); return; /* simply terminating current operation for any user inserted error, revisit if tests need*/ } 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"); if (i < N) { auto ac = convertAcquiredInfo(acquiredInfos[i]); AuthenticationFrame frame; frame.data.acquiredInfo = ac.first; frame.data.vendorCode = ac.second; cb->onAuthenticationFrame(frame); LOG(INFO) << "AcquiredInfo:" << i << ": (" << (int)ac.first << "," << (int)ac.second << ")"; i++; } SLEEP_MS(duration / N); } while (!Util::hasElapsed(now, duration)); if (id > 0 && isEnrolled) { cb->onAuthenticationSucceeded(id, {} /* hat */); return; } else { LOG(ERROR) << "Fail: face not enrolled"; cb->onAuthenticationFailed(); cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */); cb->onError(Error::TIMEOUT, 0 /* vendorError*/); return; } } cb->onAuthenticationSucceeded(id, {} /* hat */); std::pair<AcquiredInfo, int32_t> FakeFaceEngine::convertAcquiredInfo(int32_t code) { std::pair<AcquiredInfo, int32_t> res; if (code > FACE_ACQUIRED_VENDOR_BASE) { res.first = AcquiredInfo::VENDOR; res.second = code - FACE_ACQUIRED_VENDOR_BASE; } else { res.first = (AcquiredInfo)code; res.second = 0; } return res; } std::pair<Error, int32_t> FakeFaceEngine::convertError(int32_t code) { std::pair<Error, int32_t> res; if (code > FACE_ERROR_VENDOR_BASE) { res.first = Error::VENDOR; res.second = code - FACE_ERROR_VENDOR_BASE; } else { res.first = (Error)code; res.second = 0; } return res; } void FakeFaceEngine::detectInteractionImpl(ISessionCallback* cb, const std::future<void>& cancel) { Loading biometrics/face/aidl/default/FakeFaceEngine.h +6 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,12 @@ class FakeFaceEngine { void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/); std::mt19937 mRandom; private: static constexpr int32_t FACE_ACQUIRED_VENDOR_BASE = 1000; static constexpr int32_t FACE_ERROR_VENDOR_BASE = 1000; std::pair<AcquiredInfo, int32_t> convertAcquiredInfo(int32_t code); std::pair<Error, int32_t> convertError(int32_t code); }; } // namespace aidl::android::hardware::biometrics::face biometrics/face/aidl/default/README.md +2 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ On pixel devicse (non-CF), by default (after manufacture reset), Face VHAL is no 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 ##Getting Stared Loading Loading @@ -47,8 +48,7 @@ authentication failure, set the hit id to a different value. $ adb shell setprop vendor.face.virtual.operation_authenticate_duration 800 $ adb shell setprop vendor.face.virtual.enrollment_hit 1 ``` Note: At the initial phase of the Face VHAL development, Face-on-camera simulation is not supported, hence the authentication immediately occurrs as soon as the authentication request arriving at VHAL. Refer to face.sysprop for full supported features of authentication, such as error and acquiredInfo insertion ## Enrollment via Setup Loading biometrics/face/aidl/default/face.sysprop +19 −0 Original line number Diff line number Diff line Loading @@ -157,3 +157,22 @@ prop { access: ReadWrite api_name: "operation_authenticate_duration" } # insert error for authenticate operations prop { prop_name: "vendor.face.virtual.operation_authenticate_error" type: Integer scope: Internal access: ReadWrite api_name: "operation_authenticate_error" } # acquired info during authentication in format of sequence prop { prop_name: "vendor.face.virtual.operation_authenticate_acquired" type: String scope: Internal access: ReadWrite api_name: "operation_authenticate_acquired" } Loading
biometrics/common/util/include/util/Util.h +84 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,9 @@ #include <thread> #include <vector> #include <android-base/parseint.h> using ::android::base::ParseInt; namespace aidl::android::hardware::biometrics { #define SLEEP_MS(x) \ Loading Loading @@ -64,6 +67,87 @@ class Util { std::sregex_token_iterator()); return parts; } // Returns a vector of integers for the string separated by comma, // Empty vector is returned if there is any parsing error static std::vector<int32_t> parseIntSequence(const std::string& str, const std::string& sep = ",") { std::vector<std::string> seqs = Util::split(str, sep); std::vector<int32_t> res; for (const auto& seq : seqs) { int32_t val; if (ParseInt(seq, &val)) { res.push_back(val); } else { LOG(WARNING) << "Invalid int sequence:" + str + " seq:" + seq; res.clear(); break; } } return res; } // Parses a single enrollment stage string in the format of // enroll_stage_spec: <duration>[-acquiredInfos] // duration: integerInMs // acquiredInfos: [info1,info2,...] // // Returns false if there is parsing error // static bool parseEnrollmentCaptureSingle(const std::string& str, std::vector<std::vector<int32_t>>& res) { std::vector<int32_t> defaultAcquiredInfo = {1}; bool aborted = true; do { std::smatch sms; // Parses strings like "1000-[5,1]" or "500" std::regex ex("((\\d+)(-\\[([\\d|,]+)\\])?)"); if (!regex_match(str.cbegin(), str.cend(), sms, ex)) break; int32_t duration; if (!ParseInt(sms.str(2), &duration)) break; res.push_back({duration}); if (!sms.str(4).empty()) { auto acqv = parseIntSequence(sms.str(4)); if (acqv.empty()) break; res.push_back(acqv); } else res.push_back(defaultAcquiredInfo); aborted = false; } while (0); return !aborted; } // Parses enrollment string consisting of one or more stages in the formst of // <enroll_stage_spec>[,enroll_stage_spec,...] // Empty vector is returned in case of parsing error static std::vector<std::vector<int32_t>> parseEnrollmentCapture(const std::string& str) { std::vector<std::vector<int32_t>> res; std::string s(str); s.erase(std::remove_if(s.begin(), s.end(), ::isspace), s.end()); bool aborted = false; std::smatch sms; // Parses strings like "1000-[5,1],500,800-[6,5,1]" // -------------- ----- --------------- // into parts: A B C while (regex_search(s, sms, std::regex("^(,)?(\\d+(-\\[[\\d|,]+\\])?)"))) { if (!parseEnrollmentCaptureSingle(sms.str(2), res)) { aborted = true; break; } s = sms.suffix(); } if (aborted || s.length() != 0) { res.clear(); LOG(ERROR) << "Failed to parse enrollment captures:" + str; } return res; } }; } // namespace aidl::android::hardware::biometrics
biometrics/face/aidl/default/FakeFaceEngine.cpp +110 −39 Original line number Diff line number Diff line Loading @@ -136,32 +136,52 @@ void FakeFaceEngine::authenticateImpl(ISessionCallback* cb, int64_t /*operationI 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 id = FaceHalProperties::enrollment_hit().value_or(0); auto enrolls = FaceHalProperties::enrollments(); auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end(); 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)); auto vec2str = [](std::vector<AcquiredInfo> va) { std::stringstream ss; bool isFirst = true; for (auto ac : va) { if (!isFirst) ss << ","; ss << std::to_string((int8_t)ac); isFirst = false; } return ss.str(); }; // default behavior mimic face sensor in U int64_t defaultAuthDuration = 500; std::string defaultAcquiredInfo = vec2str({AcquiredInfo::START, AcquiredInfo::FIRST_FRAME_RECEIVED}); if (!isEnrolled) { std::vector<AcquiredInfo> v; for (int i = 0; i < 56; i++) v.push_back(AcquiredInfo::NOT_DETECTED); defaultAcquiredInfo += "," + vec2str(v); defaultAuthDuration = 2100; } else { defaultAcquiredInfo += "," + vec2str({AcquiredInfo::TOO_BRIGHT, AcquiredInfo::TOO_BRIGHT, AcquiredInfo::TOO_BRIGHT, AcquiredInfo::TOO_BRIGHT, AcquiredInfo::GOOD, AcquiredInfo::GOOD}); } if (FaceHalProperties::operation_authenticate_fails().value_or(false)) { LOG(ERROR) << "Fail: operation_authenticate_fails"; cb->onError(Error::VENDOR, 0 /* vendorError */); int64_t now = Util::getSystemNanoTime(); int64_t duration = FaceHalProperties::operation_authenticate_duration().value_or(defaultAuthDuration); auto acquired = FaceHalProperties::operation_authenticate_acquired().value_or(defaultAcquiredInfo); auto acquiredInfos = Util::parseIntSequence(acquired); int N = acquiredInfos.size(); if (N == 0) { LOG(ERROR) << "Fail to parse authentiate acquired info: " + acquired; cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */); return; } int i = 0; do { if (FaceHalProperties::lockout().value_or(false)) { LOG(ERROR) << "Fail: lockout"; cb->onLockoutPermanent(); Loading @@ -169,23 +189,74 @@ void FakeFaceEngine::authenticateImpl(ISessionCallback* cb, int64_t /*operationI return; } if (FaceHalProperties::operation_authenticate_fails().value_or(false)) { LOG(ERROR) << "Fail: operation_authenticate_fails"; cb->onAuthenticationFailed(); return; } auto err = FaceHalProperties::operation_authenticate_error().value_or(0); if (err != 0) { LOG(ERROR) << "Fail: operation_authenticate_error"; auto ec = convertError(err); cb->onError(ec.first, ec.second); return; /* simply terminating current operation for any user inserted error, revisit if tests need*/ } 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"); if (i < N) { auto ac = convertAcquiredInfo(acquiredInfos[i]); AuthenticationFrame frame; frame.data.acquiredInfo = ac.first; frame.data.vendorCode = ac.second; cb->onAuthenticationFrame(frame); LOG(INFO) << "AcquiredInfo:" << i << ": (" << (int)ac.first << "," << (int)ac.second << ")"; i++; } SLEEP_MS(duration / N); } while (!Util::hasElapsed(now, duration)); if (id > 0 && isEnrolled) { cb->onAuthenticationSucceeded(id, {} /* hat */); return; } else { LOG(ERROR) << "Fail: face not enrolled"; cb->onAuthenticationFailed(); cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */); cb->onError(Error::TIMEOUT, 0 /* vendorError*/); return; } } cb->onAuthenticationSucceeded(id, {} /* hat */); std::pair<AcquiredInfo, int32_t> FakeFaceEngine::convertAcquiredInfo(int32_t code) { std::pair<AcquiredInfo, int32_t> res; if (code > FACE_ACQUIRED_VENDOR_BASE) { res.first = AcquiredInfo::VENDOR; res.second = code - FACE_ACQUIRED_VENDOR_BASE; } else { res.first = (AcquiredInfo)code; res.second = 0; } return res; } std::pair<Error, int32_t> FakeFaceEngine::convertError(int32_t code) { std::pair<Error, int32_t> res; if (code > FACE_ERROR_VENDOR_BASE) { res.first = Error::VENDOR; res.second = code - FACE_ERROR_VENDOR_BASE; } else { res.first = (Error)code; res.second = 0; } return res; } void FakeFaceEngine::detectInteractionImpl(ISessionCallback* cb, const std::future<void>& cancel) { Loading
biometrics/face/aidl/default/FakeFaceEngine.h +6 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,12 @@ class FakeFaceEngine { void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/); std::mt19937 mRandom; private: static constexpr int32_t FACE_ACQUIRED_VENDOR_BASE = 1000; static constexpr int32_t FACE_ERROR_VENDOR_BASE = 1000; std::pair<AcquiredInfo, int32_t> convertAcquiredInfo(int32_t code); std::pair<Error, int32_t> convertError(int32_t code); }; } // namespace aidl::android::hardware::biometrics::face
biometrics/face/aidl/default/README.md +2 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ On pixel devicse (non-CF), by default (after manufacture reset), Face VHAL is no 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 ##Getting Stared Loading Loading @@ -47,8 +48,7 @@ authentication failure, set the hit id to a different value. $ adb shell setprop vendor.face.virtual.operation_authenticate_duration 800 $ adb shell setprop vendor.face.virtual.enrollment_hit 1 ``` Note: At the initial phase of the Face VHAL development, Face-on-camera simulation is not supported, hence the authentication immediately occurrs as soon as the authentication request arriving at VHAL. Refer to face.sysprop for full supported features of authentication, such as error and acquiredInfo insertion ## Enrollment via Setup Loading
biometrics/face/aidl/default/face.sysprop +19 −0 Original line number Diff line number Diff line Loading @@ -157,3 +157,22 @@ prop { access: ReadWrite api_name: "operation_authenticate_duration" } # insert error for authenticate operations prop { prop_name: "vendor.face.virtual.operation_authenticate_error" type: Integer scope: Internal access: ReadWrite api_name: "operation_authenticate_error" } # acquired info during authentication in format of sequence prop { prop_name: "vendor.face.virtual.operation_authenticate_acquired" type: String scope: Internal access: ReadWrite api_name: "operation_authenticate_acquired" }