Refactor biometric virtual HAL config/control
for additional ways besides system property
Bug: 326227403
Test: atest android.hardware.biometrics.common.ConfigTest
Change-Id: Id0aa4961cc732c23f5da140eca81470316834b70
diff --git a/biometrics/common/config/Android.bp b/biometrics/common/config/Android.bp
new file mode 100644
index 0000000..d38ffe8
--- /dev/null
+++ b/biometrics/common/config/Android.bp
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2024 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.
+//
+
+cc_library {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ name: "android.hardware.biometrics.common.config",
+ export_include_dirs: ["include"],
+ vendor: true,
+ srcs: [
+ "Config.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ ],
+}
+
+cc_test_host {
+ name: "android.hardware.biometrics.common.ConfigTest",
+ local_include_dirs: ["include"],
+ srcs: [
+ "tests/ConfigTest.cpp",
+ "Config.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "liblog",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/biometrics/common/config/Config.cpp b/biometrics/common/config/Config.cpp
new file mode 100644
index 0000000..01ae864
--- /dev/null
+++ b/biometrics/common/config/Config.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#define LOG_TAG "VirtualHalConfig"
+
+#include "config/Config.h"
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include "../../util/include/util/Util.h"
+
+using ::android::base::ParseInt;
+
+namespace aidl::android::hardware::biometrics {
+
+Config::Config() : mSource(Config::ConfigSourceType::SOURCE_SYSPROP) {}
+
+ConfigValue Config::parseBool(const std::string& value) {
+ OptBool res;
+ if (value == "true")
+ res.emplace(true);
+ else if (value == "false")
+ res.emplace(false);
+ else
+ LOG(ERROR) << "ERROR: invalid bool " << value;
+ return res;
+}
+
+ConfigValue Config::parseString(const std::string& value) {
+ OptString res;
+ if (!value.empty()) res.emplace(value);
+ return res;
+}
+
+ConfigValue Config::parseInt32(const std::string& value) {
+ OptInt32 res;
+ if (!value.empty()) {
+ std::int32_t val;
+ if (ParseInt(value, &val)) res.emplace(val);
+ }
+ return res;
+}
+
+ConfigValue Config::parseInt64(const std::string& value) {
+ OptInt64 res;
+ if (!value.empty()) {
+ std::int64_t val = std::strtoull(value.c_str(), nullptr, 10);
+ if (val != 0LL or (val == 0LL && value == "0")) {
+ res.emplace(val);
+ }
+ }
+ return res;
+}
+
+ConfigValue Config::parseIntVec(const std::string& value) {
+ OptIntVec res;
+ for (auto& i : Util::parseIntSequence(value)) {
+ res.push_back(i);
+ }
+ return res;
+}
+
+void Config::init() {
+ LOG(INFO) << "calling init()";
+ int len = 0;
+ Config::Data* pd = getConfigData(&len);
+ for (int i = 0; i < len; i++) {
+ LOG(INFO) << "init():" << pd->name;
+ pd->value = (this->*(pd->parser))(pd->defaultValue);
+ setConfig(pd->name, *pd);
+ ++pd;
+ }
+}
+
+bool Config::setParam(const std::string& name, const std::string& value) {
+ auto it = mMap.find(name);
+ if (it == mMap.end()) {
+ LOG(ERROR) << "ERROR: setParam unknown config name " << name;
+ return false;
+ }
+ LOG(INFO) << "setParam name=" << name << "=" << value;
+
+ it->second.value = (this->*(it->second.parser))(value);
+
+ mSource = ConfigSourceType::SOURCE_AIDL;
+
+ return true;
+}
+
+ConfigValue Config::getInternal(const std::string& name) {
+ ConfigValue res;
+
+ auto data = mMap[name];
+ switch (mSource) {
+ case ConfigSourceType::SOURCE_SYSPROP:
+ res = data.getter();
+ break;
+ case ConfigSourceType::SOURCE_AIDL:
+ res = data.value;
+ break;
+ case ConfigSourceType::SOURCE_FILE:
+ LOG(WARNING) << "Unsupported";
+ break;
+ default:
+ LOG(ERROR) << " wrong srouce type " << (int)mSource;
+ break;
+ }
+
+ return res;
+}
+
+ConfigValue Config::getDefault(const std::string& name) {
+ return mMap[name].value;
+}
+
+bool Config::setInternal(const std::string& name, const ConfigValue& val) {
+ bool res = false;
+ auto data = mMap[name];
+
+ switch (mSource) {
+ case ConfigSourceType::SOURCE_SYSPROP:
+ res = data.setter(val);
+ break;
+ case ConfigSourceType::SOURCE_AIDL:
+ data.value = val;
+ res = true;
+ break;
+ case ConfigSourceType::SOURCE_FILE:
+ LOG(WARNING) << "Unsupported";
+ break;
+ default:
+ LOG(ERROR) << " wrong srouce type " << (int)mSource;
+ break;
+ }
+
+ return res;
+}
+} // namespace aidl::android::hardware::biometrics
diff --git a/biometrics/common/config/include/config/Config.h b/biometrics/common/config/include/config/Config.h
new file mode 100644
index 0000000..864e164
--- /dev/null
+++ b/biometrics/common/config/include/config/Config.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 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 <cstdint>
+#include <iostream>
+#include <map>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <variant>
+#include <vector>
+
+namespace aidl::android::hardware::biometrics {
+
+using OptBool = std::optional<bool>;
+using OptInt32 = std::optional<std::int32_t>;
+using OptInt64 = std::optional<std::int64_t>;
+using OptString = std::optional<std::string>;
+using OptIntVec = std::vector<std::optional<std::int32_t>>;
+
+using ConfigValue = std::variant<OptBool, OptInt32, OptInt64, OptString, OptIntVec>;
+
+class Config {
+ public:
+ struct Data {
+ std::string name;
+ ConfigValue (*getter)();
+ bool (*setter)(const ConfigValue&);
+ ConfigValue (Config::*parser)(const std::string&);
+ std::string defaultValue;
+ ConfigValue value;
+ };
+ enum class ConfigSourceType { SOURCE_SYSPROP, SOURCE_AIDL, SOURCE_FILE };
+
+ public:
+ Config();
+ virtual ~Config() = default;
+
+ template <typename T>
+ T get(const std::string& name) {
+ CHECK(mMap.count(name) > 0) << " biometric/config get invalid name: " << name;
+ std::optional<T> optval = std::get<std::optional<T>>(getInternal(name));
+ if (!optval) optval = std::get<std::optional<T>>(getDefault(name));
+ if (optval) return optval.value();
+ return T();
+ }
+ template <typename T>
+ bool set(const std::string& name, const T& val) {
+ CHECK(mMap.count(name) > 0) << " biometric/config set invalid name: " << name;
+ std::optional<T> aval(val);
+ ConfigValue cval(aval);
+ return setInternal(name, cval);
+ }
+ template <typename T>
+ T getopt(const std::string& name) {
+ CHECK(mMap.count(name) > 0) << " biometric/config get invalid name: " << name;
+ return std::get<T>(getInternal(name));
+ }
+ template <typename T>
+ bool setopt(const std::string& name, const T& val) {
+ CHECK(mMap.count(name) > 0) << " biometric/config set invalid name: " << name;
+ ConfigValue cval(val);
+ return setInternal(name, cval);
+ }
+
+ void init();
+
+ virtual Config::Data* getConfigData(int* size) = 0;
+ bool setParam(const std::string& name, const std::string& value);
+
+ ConfigValue parseBool(const std::string& value);
+ ConfigValue parseString(const std::string& name);
+ ConfigValue parseInt32(const std::string& value);
+ ConfigValue parseInt64(const std::string& value);
+ ConfigValue parseIntVec(const std::string& value);
+
+ protected:
+ void setConfig(const std::string& name, const Config::Data& value) { mMap[name] = value; }
+
+ private:
+ ConfigValue getInternal(const std::string& name);
+ bool setInternal(const std::string& name, const ConfigValue& val);
+ ConfigValue getDefault(const std::string& name);
+
+ Config::ConfigSourceType mSource;
+ std::map<std::string, Config::Data> mMap;
+};
+
+} // namespace aidl::android::hardware::biometrics
diff --git a/biometrics/common/config/tests/ConfigTest.cpp b/biometrics/common/config/tests/ConfigTest.cpp
new file mode 100644
index 0000000..d922040
--- /dev/null
+++ b/biometrics/common/config/tests/ConfigTest.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2024 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 <gtest/gtest.h>
+
+#include "config/Config.h"
+
+#define LOG_TAG "ConfigTest"
+#include <android-base/logging.h>
+
+// using namespace ::testing::Eq;
+using namespace testing;
+
+#define SP_DEFAULT_astring "astringSP"
+#define SP_DEFAULT_aint32 32
+#define SP_DEFAULT_aint64 64
+#define SP_DEFAULT_abool false
+#define SP_DEFAULT_avector \
+ { 1, 2, 3 }
+namespace aidl::android::hardware::biometrics {
+namespace TestHalProperties {
+OptString val_astring = SP_DEFAULT_astring;
+OptInt32 val_aint32 = SP_DEFAULT_aint32;
+OptInt64 val_aint64 = SP_DEFAULT_aint64;
+OptBool val_abool = SP_DEFAULT_abool;
+OptIntVec val_avector = SP_DEFAULT_avector;
+
+OptString astring() {
+ return val_astring;
+}
+bool astring(const OptString& v) {
+ val_astring = v;
+ return true;
+}
+OptInt32 aint32() {
+ return val_aint32;
+}
+bool aint32(const OptInt32& v) {
+ val_aint32 = v;
+ return true;
+}
+OptInt64 aint64() {
+ return val_aint64;
+}
+bool aint64(const OptInt64& v) {
+ val_aint64 = v;
+ return true;
+}
+OptBool abool() {
+ return val_abool;
+}
+bool abool(const OptBool& v) {
+ val_abool = v;
+ return true;
+}
+OptIntVec avector() {
+ return val_avector;
+}
+bool avector(const OptIntVec& v) {
+ val_avector = v;
+ return true;
+}
+} // namespace TestHalProperties
+using namespace TestHalProperties;
+#define AIDL_DEFAULT_astring "astringAIDL"
+#define AIDL_DEFAULT_aint32 "320"
+#define AIDL_DEFAULT_aint64 "640"
+#define AIDL_DEFAULT_abool "true"
+#define AIDL_DEFAULT_avector "10,20,30"
+#define CREATE_GETTER_SETTER_WRAPPER(_NAME_, _T_) \
+ ConfigValue _NAME_##Getter() { \
+ return TestHalProperties::_NAME_(); \
+ } \
+ bool _NAME_##Setter(const ConfigValue& v) { \
+ return TestHalProperties::_NAME_(std::get<_T_>(v)); \
+ }
+CREATE_GETTER_SETTER_WRAPPER(astring, OptString)
+CREATE_GETTER_SETTER_WRAPPER(aint32, OptInt32)
+CREATE_GETTER_SETTER_WRAPPER(aint64, OptInt64)
+CREATE_GETTER_SETTER_WRAPPER(abool, OptBool)
+CREATE_GETTER_SETTER_WRAPPER(avector, std::vector<OptInt32>)
+
+// Name,Getter, Setter, Parser and default value
+#define NGS(_NAME_) #_NAME_, _NAME_##Getter, _NAME_##Setter
+static Config::Data configData[] = {
+ {NGS(astring), &Config::parseString, AIDL_DEFAULT_astring},
+ {NGS(aint32), &Config::parseInt32, AIDL_DEFAULT_aint32},
+ {NGS(aint64), &Config::parseInt64, AIDL_DEFAULT_aint64},
+ {NGS(abool), &Config::parseBool, AIDL_DEFAULT_abool},
+ {NGS(avector), &Config::parseIntVec, AIDL_DEFAULT_avector},
+};
+
+class TestConfig : public Config {
+ Config::Data* getConfigData(int* size) {
+ *size = sizeof(configData) / sizeof(configData[0]);
+ return configData;
+ }
+};
+
+class ConfigTest : public ::testing::Test {
+ protected:
+ void SetUp() override { cfg.init(); }
+ void TearDown() override {}
+
+ void switch2aidl() { cfg.setParam("astring", "astring"); }
+
+ TestConfig cfg;
+};
+
+TEST_F(ConfigTest, parseInt32) {
+ std::int32_t defval = 5678;
+ struct {
+ std::string strval;
+ std::int32_t expval;
+ } values[] = {
+ {"1234", 1234},
+ {"0", 0},
+ {"", defval},
+ {"xyz", defval},
+ };
+ for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
+ ASSERT_EQ((std::get<OptInt32>(cfg.parseInt32(values[i].strval))).value_or(defval),
+ values[i].expval);
+ }
+}
+
+TEST_F(ConfigTest, parseInt64) {
+ std::int64_t defval = 5678;
+ struct {
+ std::string strval;
+ std::int64_t expval;
+ } values[] = {
+ {"1234", 1234}, {"12345678909876", 12345678909876}, {"0", 0}, {"", defval},
+ {"xyz", defval},
+ };
+ for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
+ ASSERT_EQ((std::get<OptInt64>(cfg.parseInt64(values[i].strval))).value_or(defval),
+ values[i].expval);
+ }
+}
+
+TEST_F(ConfigTest, parseBool) {
+ bool defval = true;
+ struct {
+ std::string strval;
+ bool expval;
+ } values[] = {
+ {"false", false},
+ {"true", true},
+ {"", defval},
+ {"xyz", defval},
+ };
+ for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
+ ASSERT_EQ((std::get<OptBool>(cfg.parseBool(values[i].strval))).value_or(defval),
+ values[i].expval);
+ }
+}
+
+TEST_F(ConfigTest, parseIntVec) {
+ std::vector<std::optional<int>> defval = {};
+ struct {
+ std::string strval;
+ std::vector<std::optional<int>> expval;
+ } values[] = {
+ {"1", {1}}, {"1,2,3", {1, 2, 3}}, {"1,2,b", defval}, {"", defval}, {"xyz", defval},
+ };
+ for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
+ ASSERT_EQ(std::get<OptIntVec>(cfg.parseIntVec(values[i].strval)), values[i].expval);
+ }
+}
+
+TEST_F(ConfigTest, getters_sp) {
+ ASSERT_EQ(cfg.get<std::string>("astring"), val_astring);
+ ASSERT_EQ(cfg.get<std::int32_t>("aint32"), val_aint32);
+ ASSERT_EQ(cfg.get<std::int64_t>("aint64"), val_aint64);
+ ASSERT_EQ(cfg.get<bool>("abool"), val_abool);
+ OptIntVec exp{val_avector};
+ EXPECT_EQ(cfg.getopt<OptIntVec>("avector"), exp);
+}
+
+TEST_F(ConfigTest, setters_sp) {
+ std::string val_astring_new("astringNew");
+ ASSERT_TRUE(cfg.set<std::string>("astring", val_astring_new));
+ ASSERT_EQ(cfg.get<std::string>("astring"), val_astring_new);
+
+ std::int32_t val_aint32_new = val_aint32.value() + 100;
+ ASSERT_TRUE(cfg.set<std::int32_t>("aint32", val_aint32_new));
+ ASSERT_EQ(cfg.get<std::int32_t>("aint32"), val_aint32_new);
+
+ std::int64_t val_aint64_new = val_aint64.value() + 200;
+ ASSERT_TRUE(cfg.set<std::int64_t>("aint64", val_aint64_new));
+ ASSERT_EQ(cfg.get<std::int64_t>("aint64"), val_aint64_new);
+
+ bool val_abool_new = !val_abool.value();
+ ASSERT_TRUE(cfg.set<bool>("abool", val_abool_new));
+ ASSERT_EQ(cfg.get<bool>("abool"), val_abool_new);
+
+ OptIntVec val_avector_new{100, 200};
+ ASSERT_TRUE(cfg.setopt<OptIntVec>("avector", val_avector_new));
+ EXPECT_EQ(cfg.getopt<OptIntVec>("avector"), val_avector_new);
+}
+
+TEST_F(ConfigTest, setters_sp_null) {
+ val_astring = std::nullopt;
+ ASSERT_EQ(cfg.get<std::string>("astring"),
+ (std::get<OptString>(cfg.parseString(AIDL_DEFAULT_astring))).value());
+}
+
+TEST_F(ConfigTest, getters_aidl) {
+ cfg.setParam("astring", "astringAIDL");
+ ASSERT_EQ(cfg.get<std::string>("astring"),
+ (std::get<OptString>(cfg.parseString(AIDL_DEFAULT_astring))).value());
+ ASSERT_EQ(cfg.get<std::int32_t>("aint32"),
+ (std::get<OptInt32>(cfg.parseInt32(AIDL_DEFAULT_aint32))).value());
+ ASSERT_EQ(cfg.get<std::int64_t>("aint64"),
+ (std::get<OptInt64>(cfg.parseInt64(AIDL_DEFAULT_aint64))).value());
+ ASSERT_EQ(cfg.get<bool>("abool"),
+ (std::get<OptBool>(cfg.parseBool(AIDL_DEFAULT_abool))).value());
+ OptIntVec exp{std::get<OptIntVec>(cfg.parseIntVec(AIDL_DEFAULT_avector))};
+ EXPECT_EQ(cfg.getopt<OptIntVec>("avector"), exp);
+}
+
+TEST_F(ConfigTest, setters_aidl) {
+ std::string val_astring_new("astringNewAidl");
+ ASSERT_TRUE(cfg.set<std::string>("astring", val_astring_new));
+ ASSERT_EQ(cfg.get<std::string>("astring"), val_astring_new);
+
+ std::int32_t val_aint32_new = val_aint32.value() + 1000;
+ ASSERT_TRUE(cfg.set<std::int32_t>("aint32", val_aint32_new));
+ ASSERT_EQ(cfg.get<std::int32_t>("aint32"), val_aint32_new);
+
+ std::int64_t val_aint64_new = val_aint64.value() + 2000;
+ ASSERT_TRUE(cfg.set<std::int64_t>("aint64", val_aint64_new));
+ ASSERT_EQ(cfg.get<std::int64_t>("aint64"), val_aint64_new);
+
+ bool val_abool_new = !val_abool.value();
+ ASSERT_TRUE(cfg.set<bool>("abool", val_abool_new));
+ ASSERT_EQ(cfg.get<bool>("abool"), val_abool_new);
+
+ OptIntVec val_avector_new{1000, 2000};
+ ASSERT_TRUE(cfg.setopt<OptIntVec>("avector", val_avector_new));
+ EXPECT_EQ(cfg.getopt<OptIntVec>("avector"), val_avector_new);
+}
+
+TEST_F(ConfigTest, setParam) {
+ ASSERT_TRUE(cfg.setParam("aint32", "789"));
+ ASSERT_EQ(cfg.get<std::int32_t>("aint32"), 789);
+ ASSERT_TRUE(cfg.setParam("avector", "7,8,9,10"));
+ OptIntVec val_avector_new{7, 8, 9, 10};
+ EXPECT_EQ(cfg.getopt<OptIntVec>("avector"), val_avector_new);
+ ASSERT_FALSE(cfg.setParam("unknown", "any"));
+}
+} // namespace aidl::android::hardware::biometrics
diff --git a/biometrics/common/util/include/util/Util.h b/biometrics/common/util/include/util/Util.h
index efd66bc..078669d 100644
--- a/biometrics/common/util/include/util/Util.h
+++ b/biometrics/common/util/include/util/Util.h
@@ -80,7 +80,9 @@
if (ParseInt(seq, &val)) {
res.push_back(val);
} else {
- LOG(WARNING) << "Invalid int sequence:" + str + " seq:" + seq;
+ if (!str.empty()) {
+ LOG(WARNING) << "Invalid int sequence:" + str + " seq:" + seq;
+ }
res.clear();
break;
}