Merge "Implemented VTS tests for BiometricsFace.hal"
diff --git a/biometrics/face/1.0/vts/functional/Android.bp b/biometrics/face/1.0/vts/functional/Android.bp
new file mode 100644
index 0000000..fa68c4e
--- /dev/null
+++ b/biometrics/face/1.0/vts/functional/Android.bp
@@ -0,0 +1,24 @@
+//
+// Copyright (C) 2019 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_test {
+ name: "VtsHalBiometricsFaceV1_0TargetTest",
+ defaults: ["VtsHalTargetTestDefaults"],
+ srcs: ["VtsHalBiometricsFaceV1_0TargetTest.cpp"],
+ static_libs: ["android.hardware.biometrics.face@1.0"],
+ test_suites: ["general-tests"],
+}
+
diff --git a/biometrics/face/1.0/vts/functional/VtsHalBiometricsFaceV1_0TargetTest.cpp b/biometrics/face/1.0/vts/functional/VtsHalBiometricsFaceV1_0TargetTest.cpp
new file mode 100644
index 0000000..f496bbe
--- /dev/null
+++ b/biometrics/face/1.0/vts/functional/VtsHalBiometricsFaceV1_0TargetTest.cpp
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2019 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 "face_hidl_test"
+
+#include <VtsHalHidlTargetTestBase.h>
+#include <VtsHalHidlTargetTestEnvBase.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/hardware/biometrics/face/1.0/IBiometricsFace.h>
+#include <android/hardware/biometrics/face/1.0/IBiometricsFaceClientCallback.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/HidlTransportSupport.h>
+#include <utils/Condition.h>
+
+#include <cinttypes>
+#include <cstdint>
+#include <future>
+#include <utility>
+
+using android::Condition;
+using android::Mutex;
+using android::sp;
+using android::base::GetUintProperty;
+using android::hardware::hidl_vec;
+using android::hardware::Return;
+using android::hardware::biometrics::face::V1_0::FaceAcquiredInfo;
+using android::hardware::biometrics::face::V1_0::FaceError;
+using android::hardware::biometrics::face::V1_0::Feature;
+using android::hardware::biometrics::face::V1_0::IBiometricsFace;
+using android::hardware::biometrics::face::V1_0::IBiometricsFaceClientCallback;
+using android::hardware::biometrics::face::V1_0::OptionalUint64;
+using android::hardware::biometrics::face::V1_0::Status;
+
+namespace {
+
+const uint32_t kTimeout = 3;
+const std::chrono::seconds kTimeoutInSeconds = std::chrono::seconds(kTimeout);
+const uint32_t kUserId = 99;
+const uint32_t kFaceId = 5;
+const char kTmpDir[] = "/data/system/users/0/facedata";
+const int kIterations = 1000;
+
+const auto kAssertCallbackIsSet = [](const OptionalUint64& res) {
+ ASSERT_EQ(Status::OK, res.status);
+ // Makes sure the "deviceId" represented by "res.value" is not 0.
+ // 0 would mean the HIDL is not available.
+ ASSERT_NE(0UL, res.value);
+};
+
+// Wait for a callback to occur (signaled by the given future) up to the
+// provided timeout. If the future is invalid or the callback does not come
+// within the given time, returns false.
+template <class ReturnType>
+bool waitForCallback(std::future<ReturnType> future,
+ std::chrono::milliseconds timeout = kTimeoutInSeconds) {
+ auto expiration = std::chrono::system_clock::now() + timeout;
+ EXPECT_TRUE(future.valid());
+ if (future.valid()) {
+ std::future_status status = future.wait_until(expiration);
+ EXPECT_NE(std::future_status::timeout, status) << "Timed out waiting for callback";
+ if (status == std::future_status::ready) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Base callback implementation that just logs all callbacks by default
+class FaceCallbackBase : public IBiometricsFaceClientCallback {
+ public:
+ Return<void> onEnrollResult(uint64_t, uint32_t, int32_t, uint32_t) override {
+ ALOGD("Enroll callback called.");
+ return Return<void>();
+ }
+
+ Return<void> onAuthenticated(uint64_t, uint32_t, int32_t, const hidl_vec<uint8_t>&) override {
+ ALOGD("Authenticated callback called.");
+ return Return<void>();
+ }
+
+ Return<void> onAcquired(uint64_t, int32_t, FaceAcquiredInfo, int32_t) override {
+ ALOGD("Acquired callback called.");
+ return Return<void>();
+ }
+
+ Return<void> onError(uint64_t, int32_t, FaceError, int32_t) override {
+ ALOGD("Error callback called.");
+ EXPECT_TRUE(false); // fail any test that triggers an error
+ return Return<void>();
+ }
+
+ Return<void> onRemoved(uint64_t, uint32_t, int32_t, uint32_t) override {
+ ALOGD("Removed callback called.");
+ return Return<void>();
+ }
+
+ Return<void> onEnumerate(uint64_t, const hidl_vec<uint32_t>&, int32_t /* userId */) override {
+ ALOGD("Enumerate callback called.");
+ return Return<void>();
+ }
+
+ Return<void> onLockoutChanged(uint64_t) override {
+ ALOGD("LockoutChanged callback called.");
+ return Return<void>();
+ }
+};
+
+class EnumerateCallback : public FaceCallbackBase {
+ public:
+ Return<void> onEnumerate(uint64_t, const hidl_vec<uint32_t>&, int32_t) override {
+ promise.set_value();
+ return Return<void>();
+ }
+
+ std::promise<void> promise;
+};
+
+class ErrorCallback : public FaceCallbackBase {
+ public:
+ ErrorCallback(bool filterErrors = false, FaceError errorType = FaceError::HW_UNAVAILABLE)
+ : filterErrors(filterErrors), errorType(errorType), hasError(false) {}
+
+ Return<void> onError(uint64_t, int32_t, FaceError error, int32_t) override {
+ if ((filterErrors && errorType == error) || !filterErrors) {
+ hasError = true;
+ this->error = error;
+ promise.set_value();
+ }
+ return Return<void>();
+ }
+
+ bool filterErrors;
+ FaceError errorType;
+ bool hasError;
+ FaceError error;
+ std::promise<void> promise;
+};
+
+class RemoveCallback : public FaceCallbackBase {
+ public:
+ explicit RemoveCallback(int32_t userId) : removeUserId(userId) {}
+
+ Return<void> onRemoved(uint64_t, uint32_t, int32_t userId, uint32_t remaining) override {
+ EXPECT_EQ(removeUserId, userId);
+ promise.set_value();
+ if (remaining == 0UL) {
+ promise.set_value();
+ }
+ return Return<void>();
+ }
+
+ int32_t removeUserId;
+ std::promise<void> promise;
+};
+
+// Test environment for Face HIDL HAL.
+class FaceHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase {
+ public:
+ // get the test environment singleton
+ static FaceHidlEnvironment* Instance() {
+ static FaceHidlEnvironment* instance = new FaceHidlEnvironment;
+ return instance;
+ }
+
+ void registerTestServices() override { registerTestService<IBiometricsFace>(); }
+};
+
+class FaceHidlTest : public ::testing::VtsHalHidlTargetTestBase {
+ public:
+ void SetUp() override {
+ mService = ::testing::VtsHalHidlTargetTestBase::getService<IBiometricsFace>(
+ FaceHidlEnvironment::Instance()->getServiceName<IBiometricsFace>());
+ ASSERT_FALSE(mService == nullptr);
+ Return<Status> res = mService->setActiveUser(kUserId, kTmpDir);
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+ }
+
+ void TearDown() override {}
+
+ sp<IBiometricsFace> mService;
+};
+
+// The service should be reachable.
+TEST_F(FaceHidlTest, ConnectTest) {
+ sp<FaceCallbackBase> cb = new FaceCallbackBase();
+ mService->setCallback(cb, kAssertCallbackIsSet);
+}
+
+// Starting the service with null callback should succeed.
+TEST_F(FaceHidlTest, ConnectNullTest) {
+ mService->setCallback(nullptr, kAssertCallbackIsSet);
+}
+
+// generateChallenge should always return a unique, cryptographically secure,
+// non-zero number.
+TEST_F(FaceHidlTest, GenerateChallengeTest) {
+ std::map<uint64_t, int> m;
+ for (int i = 0; i < kIterations; ++i) {
+ mService->generateChallenge(kTimeout, [&m](const OptionalUint64& res) {
+ ASSERT_EQ(Status::OK, res.status);
+ EXPECT_NE(0UL, res.value);
+ m[res.value]++;
+ EXPECT_EQ(1UL, m[res.value]);
+ });
+ }
+}
+
+// enroll with an invalid (all zeroes) HAT should fail.
+TEST_F(FaceHidlTest, EnrollZeroHatTest) {
+ sp<ErrorCallback> cb = new ErrorCallback();
+ mService->setCallback(cb, kAssertCallbackIsSet);
+
+ hidl_vec<uint8_t> token(69);
+ for (size_t i = 0; i < 69; i++) {
+ token[i] = 0;
+ }
+
+ Return<Status> res = mService->enroll(token, kTimeout, {});
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+
+ // At least one call to onError should occur
+ ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
+ ASSERT_TRUE(cb->hasError);
+}
+
+// enroll with an invalid HAT should fail.
+TEST_F(FaceHidlTest, EnrollGarbageHatTest) {
+ sp<ErrorCallback> cb = new ErrorCallback();
+ mService->setCallback(cb, kAssertCallbackIsSet);
+
+ // Filling HAT with invalid data
+ hidl_vec<uint8_t> token(69);
+ for (size_t i = 0; i < 69; ++i) {
+ token[i] = i;
+ }
+
+ Return<Status> res = mService->enroll(token, kTimeout, {});
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+
+ // At least one call to onError should occur
+ ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
+ ASSERT_TRUE(cb->hasError);
+}
+
+// setFeature with an invalid (all zeros) HAT should fail.
+TEST_F(FaceHidlTest, SetFeatureZeroHatTest) {
+ sp<ErrorCallback> cb = new ErrorCallback();
+ mService->setCallback(cb, kAssertCallbackIsSet);
+
+ hidl_vec<uint8_t> token(69);
+ for (size_t i = 0; i < 69; i++) {
+ token[i] = 0;
+ }
+
+ Return<Status> res = mService->setFeature(Feature::REQUIRE_DIVERSITY, false, token);
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+
+ // At least one call to onError should occur
+ ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
+ ASSERT_TRUE(cb->hasError);
+}
+
+// setFeature with an invalid HAT should fail.
+TEST_F(FaceHidlTest, SetFeatureGarbageHatTest) {
+ sp<ErrorCallback> cb = new ErrorCallback();
+ mService->setCallback(cb, kAssertCallbackIsSet);
+
+ // Filling HAT with invalid data
+ hidl_vec<uint8_t> token(69);
+ for (size_t i = 0; i < 69; ++i) {
+ token[i] = i;
+ }
+
+ Return<Status> res = mService->setFeature(Feature::REQUIRE_DIVERSITY, false, token);
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+
+ // At least one call to onError should occur
+ ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
+ ASSERT_TRUE(cb->hasError);
+}
+
+// getFeature by default should return true for REQUIRE_ATTENTION.
+TEST_F(FaceHidlTest, GetFeatureRequireAttentionTest) {
+ Return<bool> res = mService->getFeature(Feature::REQUIRE_ATTENTION);
+ ASSERT_EQ(true, static_cast<bool>(res));
+}
+
+// getFeature by default should return true for REQUIRE_DIVERSITY.
+TEST_F(FaceHidlTest, GetFeatureRequireDiversityTest) {
+ Return<bool> res = mService->getFeature(Feature::REQUIRE_DIVERSITY);
+ ASSERT_EQ(true, static_cast<bool>(res));
+}
+
+// revokeChallenge should always return within the timeout
+TEST_F(FaceHidlTest, RevokeChallengeTest) {
+ sp<FaceCallbackBase> cb = new FaceCallbackBase();
+ mService->setCallback(cb, kAssertCallbackIsSet);
+
+ auto start = std::chrono::system_clock::now();
+ mService->revokeChallenge();
+ auto elapsed = std::chrono::system_clock::now() - start;
+ ASSERT_GE(kTimeoutInSeconds, elapsed);
+}
+
+// The call to getAuthenticatorId should succeed.
+TEST_F(FaceHidlTest, GetAuthenticatorIdTest) {
+ mService->getAuthenticatorId(
+ [](const OptionalUint64& res) { ASSERT_EQ(Status::OK, res.status); });
+}
+
+// The call to enumerate should succeed.
+TEST_F(FaceHidlTest, EnumerateTest) {
+ sp<EnumerateCallback> cb = new EnumerateCallback();
+ mService->setCallback(cb, kAssertCallbackIsSet);
+ Return<Status> res = mService->enumerate();
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+ ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
+}
+
+// The call to remove should succeed for any faceId
+TEST_F(FaceHidlTest, RemoveFaceTest) {
+ sp<ErrorCallback> cb = new ErrorCallback();
+ mService->setCallback(cb, kAssertCallbackIsSet);
+
+ // Remove a face
+ Return<Status> res = mService->remove(kFaceId);
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+}
+
+// Remove should accept 0 to delete all faces
+TEST_F(FaceHidlTest, RemoveAllFacesTest) {
+ sp<ErrorCallback> cb = new ErrorCallback();
+ mService->setCallback(cb, kAssertCallbackIsSet);
+
+ // Remove all faces
+ Return<Status> res = mService->remove(0);
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+}
+
+// Active user should successfully set to a writable location.
+TEST_F(FaceHidlTest, SetActiveUserTest) {
+ // Create an active user
+ Return<Status> res = mService->setActiveUser(2, kTmpDir);
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+
+ // Reset active user
+ res = mService->setActiveUser(kUserId, kTmpDir);
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+}
+
+// Active user should fail to set to an unwritable location.
+TEST_F(FaceHidlTest, SetActiveUserUnwritableTest) {
+ // Create an active user to an unwritable location (device root dir)
+ Return<Status> res = mService->setActiveUser(3, "/");
+ ASSERT_NE(Status::OK, static_cast<Status>(res));
+
+ // Reset active user
+ res = mService->setActiveUser(kUserId, kTmpDir);
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+}
+
+// Active user should fail to set to a null location.
+TEST_F(FaceHidlTest, SetActiveUserNullTest) {
+ // Create an active user to a null location.
+ Return<Status> res = mService->setActiveUser(4, nullptr);
+ ASSERT_NE(Status::OK, static_cast<Status>(res));
+
+ // Reset active user
+ res = mService->setActiveUser(kUserId, kTmpDir);
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+}
+
+// Cancel should always return CANCELED from any starting state including
+// the IDLE state.
+TEST_F(FaceHidlTest, CancelTest) {
+ sp<ErrorCallback> cb = new ErrorCallback(true, FaceError::CANCELED);
+ mService->setCallback(cb, kAssertCallbackIsSet);
+
+ Return<Status> res = mService->cancel();
+ // check that we were able to make an IPC request successfully
+ ASSERT_EQ(Status::OK, static_cast<Status>(res));
+
+ // make sure callback was invoked within kTimeoutInSeconds
+ ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
+ // check error should be CANCELED
+ ASSERT_EQ(FaceError::CANCELED, cb->error);
+}
+
+} // anonymous namespace
+
+int main(int argc, char** argv) {
+ ::testing::AddGlobalTestEnvironment(FaceHidlEnvironment::Instance());
+ ::testing::InitGoogleTest(&argc, argv);
+ FaceHidlEnvironment::Instance()->init(&argc, argv);
+ int status = RUN_ALL_TESTS();
+ LOG(INFO) << "Test result = " << status;
+ return status;
+}