Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bdc179bf authored by Ilya Matyukhin's avatar Ilya Matyukhin Committed by Android (Google) Code Review
Browse files

Merge "Implemented VTS tests for BiometricsFace.hal"

parents fe783f08 1a34e407
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
//
// 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"],
}
+412 −0
Original line number Diff line number Diff line
/*
 * 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;
}