blob: 327b61f2a42d9c9e996933588d533310075ab3e6 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "buttons.h"
#include <fidl/fuchsia.hardware.gpio/cpp/wire_test_base.h>
#include <fidl/fuchsia.power.system/cpp/test_base.h>
#include <lib/ddk/metadata.h>
#include <lib/driver/compat/cpp/device_server.h>
#include <lib/driver/fake-platform-device/cpp/fake-pdev.h>
#include <lib/driver/testing/cpp/driver_test.h>
#include <set>
#include <gtest/gtest.h>
#include "src/devices/gpio/testing/fake-gpio/fake-gpio.h"
#include "src/lib/testing/predicates/status.h"
namespace {
const std::vector<fuchsia_buttons::GpioButtonConfig> kButtonsDirect = {{{
.type = fuchsia_buttons::GpioButtonType::WithDirect({}),
.gpio_a_index = 0,
.id = fuchsia_buttons::GpioButtonId::kVolumeUp,
}}};
const std::vector<fuchsia_buttons::GpioConfig> kGpiosDirect = {
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}), .flags = {{}}}},
};
const std::vector<fuchsia_buttons::GpioConfig> kGpiosWakeable = {
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}),
.flags = fuchsia_buttons::GpioFlag::kWakeVector}},
};
const std::vector<fuchsia_buttons::GpioButtonConfig> kButtonsMultiple = {
{{
.type = fuchsia_buttons::GpioButtonType::WithDirect({}),
.gpio_a_index = 0,
.id = fuchsia_buttons::GpioButtonId::kVolumeUp,
}},
{{
.type = fuchsia_buttons::GpioButtonType::WithDirect({}),
.gpio_a_index = 1,
.id = fuchsia_buttons::GpioButtonId::kMicMute,
}},
{{
.type = fuchsia_buttons::GpioButtonType::WithDirect({}),
.gpio_a_index = 2,
.id = fuchsia_buttons::GpioButtonId::kCamMute,
}},
};
const std::vector<fuchsia_buttons::GpioConfig> kGpiosMultiple = {
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}), .flags = {{}}}},
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}), .flags = {{}}}},
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}), .flags = {{}}}},
};
const std::vector<fuchsia_buttons::GpioConfig> kGpiosMultipleOnePolled = {
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}), .flags = {{}}}},
{{.type = fuchsia_buttons::GpioType::WithPoll({{.period = zx::msec(20).get()}}),
.flags = {{}}}},
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}), .flags = {{}}}},
};
const std::vector<fuchsia_buttons::GpioButtonConfig> kButtonsMatrix = {
{{
.type = fuchsia_buttons::GpioButtonType::WithMatrix({{
.gpio_b_index = 2,
}}),
.gpio_a_index = 0,
.id = fuchsia_buttons::GpioButtonId::kVolumeUp,
}},
{{
.type = fuchsia_buttons::GpioButtonType::WithMatrix({{
.gpio_b_index = 2,
}}),
.gpio_a_index = 1,
.id = fuchsia_buttons::GpioButtonId::kKeyA,
}},
{{
.type = fuchsia_buttons::GpioButtonType::WithMatrix({{
.gpio_b_index = 3,
}}),
.gpio_a_index = 0,
.id = fuchsia_buttons::GpioButtonId::kKeyM,
}},
{{
.type = fuchsia_buttons::GpioButtonType::WithMatrix({{
.gpio_b_index = 3,
}}),
.gpio_a_index = 1,
.id = fuchsia_buttons::GpioButtonId::kPlayPause,
}},
};
const std::vector<fuchsia_buttons::GpioConfig> kGpiosMatrix = {
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}), .flags = {{}}}},
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}), .flags = {{}}}},
{{.type = fuchsia_buttons::GpioType::WithMatrixOutput({{.output_value = 0}}), .flags = {{}}}},
{{.type = fuchsia_buttons::GpioType::WithMatrixOutput({{.output_value = 0}}), .flags = {{}}}},
};
const std::vector<fuchsia_buttons::GpioButtonConfig> kButtonsDuplicate = {
{{
.type = fuchsia_buttons::GpioButtonType::WithDirect({}),
.gpio_a_index = 0,
.id = fuchsia_buttons::GpioButtonId::kVolumeUp,
}},
{{
.type = fuchsia_buttons::GpioButtonType::WithDirect({}),
.gpio_a_index = 1,
.id = fuchsia_buttons::GpioButtonId::kVolumeDown,
}},
{{
.type = fuchsia_buttons::GpioButtonType::WithDirect({}),
.gpio_a_index = 2,
.id = fuchsia_buttons::GpioButtonId::kFdr,
}},
};
const std::vector<fuchsia_buttons::GpioConfig> kGpiosDuplicate = {
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}), .flags = {{}}}},
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}), .flags = {{}}}},
{{.type = fuchsia_buttons::GpioType::WithInterrupt({}), .flags = {{}}}},
};
} // namespace
namespace buttons {
static constexpr size_t kMaxGpioServers = 4;
enum MetadataVersion : uint8_t {
kMetadataSingleButtonDirect = 0,
kMetadataWakeable,
kMetadataMultiple,
kMetadataDuplicate,
kMetadataMatrix,
kMetadataPolled,
};
class LocalFakeGpio : public fake_gpio::FakeGpio {
public:
LocalFakeGpio() = default;
void SetExpectedInterruptOptions(fuchsia_hardware_gpio::InterruptOptions options) {
expected_interrupt_options_ = options;
}
void SetExpectedInterruptMode(fuchsia_hardware_gpio::InterruptMode mode) {
expected_interrupt_mode_ = mode;
}
private:
void ConfigureInterrupt(ConfigureInterruptRequestView request,
ConfigureInterruptCompleter::Sync& completer) override {
ASSERT_TRUE(request->config.has_mode());
if (check_interrupt_mode_) {
EXPECT_EQ(request->config.mode(), expected_interrupt_mode_);
}
FakeGpio::ConfigureInterrupt(request, completer);
}
void GetInterrupt(GetInterruptRequestView request,
GetInterruptCompleter::Sync& completer) override {
EXPECT_EQ(request->options, expected_interrupt_options_);
check_interrupt_mode_ = false;
FakeGpio::GetInterrupt(request, completer);
}
fuchsia_hardware_gpio::InterruptOptions expected_interrupt_options_;
fuchsia_hardware_gpio::InterruptMode expected_interrupt_mode_ =
fuchsia_hardware_gpio::InterruptMode::kEdgeHigh;
bool check_interrupt_mode_ = true;
};
class FakeSystemActivityGovernor
: public fidl::testing::TestBase<fuchsia_power_system::ActivityGovernor> {
public:
fidl::ProtocolHandler<fuchsia_power_system::ActivityGovernor> CreateHandler() {
return bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure);
}
zx::eventpair AcquireWakeLease() {
has_wake_lease_been_taken_ = true;
zx::eventpair wake_lease_remote, wake_lease_local;
zx::eventpair::create(0, &wake_lease_local, &wake_lease_remote);
wake_leases_.push_back(std::move(wake_lease_local));
return wake_lease_remote;
}
void AcquireWakeLease(AcquireWakeLeaseRequest& request,
AcquireWakeLeaseCompleter::Sync& completer) override {
completer.Reply(fit::ok(AcquireWakeLease()));
}
void TakeWakeLease(TakeWakeLeaseRequest& request,
TakeWakeLeaseCompleter::Sync& completer) override {
completer.Reply(AcquireWakeLease());
}
void RegisterSuspendBlocker(RegisterSuspendBlockerRequest& request,
RegisterSuspendBlockerCompleter::Sync& completer) override {
suspend_blocker_client_.Bind(std::move(request.suspend_blocker().value()),
fdf::Dispatcher::GetCurrent()->async_dispatcher());
suspend_blocker_client_->BeforeSuspend().Then([this](auto unused) { suspend_started_ = true; });
// Use a fake lease token. There's no need to keep our handle alive.
zx::eventpair lease_token, peer;
zx::eventpair::create(0, &lease_token, &peer);
fuchsia_power_system::ActivityGovernorRegisterSuspendBlockerResponse response;
response.token() = std::move(lease_token);
completer.Reply(fit::ok(std::move(response)));
}
void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override {
FDF_LOG(ERROR, "unexpected call to %s", name.c_str());
}
size_t HasWakeLeaseBeenTaken() const { return has_wake_lease_been_taken_; }
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_system::ActivityGovernor> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
private:
std::vector<zx::eventpair> wake_leases_;
bool suspend_started_ = false;
fidl::Client<fuchsia_power_system::SuspendBlocker> suspend_blocker_client_;
fidl::ServerBindingGroup<fuchsia_power_system::ActivityGovernor> bindings_;
bool has_wake_lease_been_taken_ = false;
};
class ButtonsTestEnvironment : public fdf_testing::Environment {
public:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override {
// Serve fake GPIO servers.
EXPECT_LE(gpios_.size(), kMaxGpioServers);
for (size_t i = 0; i < gpios_.size(); ++i) {
EXPECT_OK(zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL,
&fake_gpio_interrupts_[i]));
zx::interrupt interrupt;
EXPECT_OK(fake_gpio_interrupts_[i].duplicate(ZX_RIGHT_SAME_RIGHTS, &interrupt));
fake_gpio_servers_[i].SetInterrupt(zx::ok(std::move(interrupt)));
auto gpio_handler = fake_gpio_servers_[i].CreateInstanceHandler();
auto result = to_driver_vfs.AddService<fuchsia_hardware_gpio::Service>(
std::move(gpio_handler), buttons_names_[i].c_str());
if (result.is_error()) {
return result;
}
fake_gpio_servers_[i].SetDefaultReadResponse(zx::ok(uint8_t{0u}));
}
{
zx::result result = to_driver_vfs.AddService<fuchsia_hardware_platform_device::Service>(
pdev_.GetInstanceHandler(fdf::Dispatcher::GetCurrent()->async_dispatcher()), "pdev");
if (result.is_error()) {
return result.take_error();
}
}
// Serve fake sag.
if (serve_sag_) {
return to_driver_vfs.component().AddUnmanagedProtocol<fuchsia_power_system::ActivityGovernor>(
fake_sag_.CreateHandler());
}
return zx::ok();
}
void Init(MetadataVersion metadata_version, bool serve_sag) {
serve_sag_ = serve_sag;
// Serve metadata.
std::vector<fuchsia_buttons::GpioButtonConfig> buttons;
switch (metadata_version) {
case kMetadataSingleButtonDirect: {
buttons_names_ = {"volume-up"};
buttons = kButtonsDirect;
gpios_ = std::span(kGpiosDirect);
} break;
case kMetadataWakeable: {
buttons_names_ = {"volume-up"};
buttons = kButtonsDirect;
gpios_ = std::span(kGpiosWakeable);
} break;
case kMetadataMultiple: {
buttons_names_ = {"volume-up", "mic-privacy", "cam-mute"};
buttons = kButtonsMultiple;
gpios_ = std::span(kGpiosMultiple);
} break;
case kMetadataDuplicate: {
buttons_names_ = {"volume-up", "volume-down", "volume-both"};
buttons = kButtonsDuplicate;
gpios_ = std::span(kGpiosDuplicate);
} break;
case kMetadataMatrix: {
buttons_names_ = {"volume-up", "key-a", "key-m", "play-pause"};
buttons = kButtonsMatrix;
gpios_ = std::span(kGpiosMatrix);
} break;
case kMetadataPolled: {
buttons_names_ = {"volume-up", "mic-privacy", "cam-mute"};
buttons = kButtonsMultiple;
gpios_ = std::span(kGpiosMultipleOnePolled);
} break;
default:
ASSERT_TRUE(0);
}
std::vector<fuchsia_buttons::GpioConfig> gpios(gpios_.begin(), gpios_.end());
const fuchsia_buttons::GpioButtonsMetadata metadata{
{.buttons = buttons, .gpios = std::move(gpios)}};
ASSERT_OK(
pdev_.AddFidlMetadata(fuchsia_buttons::GpioButtonsMetadata::kSerializableName, metadata));
}
void SetGpioReadResponse(size_t gpio_index, uint8_t read_data) {
fake_gpio_servers_[gpio_index].PushReadResponse(zx::ok(read_data));
}
void SetDefaultGpioReadResponse(size_t gpio_index, uint8_t read_data) {
fake_gpio_servers_[gpio_index].SetDefaultReadResponse(zx::ok(read_data));
}
void SetExpectedInterruptOptions(fuchsia_hardware_gpio::InterruptOptions options) {
fake_gpio_servers_[0].SetExpectedInterruptOptions(options);
}
void SetExpectedInterruptMode(fuchsia_hardware_gpio::InterruptMode mode) {
fake_gpio_servers_[0].SetExpectedInterruptMode(mode);
}
zx::interrupt fake_gpio_interrupts_[kMaxGpioServers];
LocalFakeGpio fake_gpio_servers_[kMaxGpioServers]{};
FakeSystemActivityGovernor fake_sag_;
private:
std::vector<std::string> buttons_names_;
// Must be of static lifetime.
cpp20::span<const fuchsia_buttons::GpioConfig> gpios_;
fdf_fake::FakePDev pdev_;
bool serve_sag_;
};
class ButtonsTestConfig final {
public:
using DriverType = buttons::Buttons;
using EnvironmentType = ButtonsTestEnvironment;
};
class ButtonsTest : public ::testing::Test {
public:
void TearDown() override {
zx::result<> result = driver_test().StopDriver();
ASSERT_EQ(ZX_OK, result.status_value());
}
zx::result<> Init(MetadataVersion metadata_version, bool suspend_enabled, bool serve_sag = true) {
driver_test().RunInEnvironmentTypeContext(
[metadata_version, serve_sag](ButtonsTestEnvironment& env) {
env.Init(metadata_version, serve_sag);
});
return driver_test().StartDriverWithCustomStartArgs([&](fdf::DriverStartArgs& start_args) {
buttons_config::Config fake_config;
fake_config.suspend_enabled() = suspend_enabled;
start_args.config(fake_config.ToVmo());
});
}
fidl::ClientEnd<fuchsia_input_report::InputDevice> GetClient() {
// Connect to InputDevice.
zx::result connect_result =
driver_test().ConnectThroughDevfs<fuchsia_input_report::InputDevice>("buttons");
EXPECT_EQ(ZX_OK, connect_result.status_value());
return std::move(connect_result.value());
}
fdf_testing::BackgroundDriverTest<ButtonsTestConfig>& driver_test() { return driver_test_; }
void DrainInitialReport(fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>& reader) {
auto result = reader->ReadInputReports();
ASSERT_OK(result.status());
ASSERT_FALSE(result.value().is_error());
auto& reports = result.value().value()->reports;
ASSERT_EQ(1U, reports.size());
auto report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
}
fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> GetReader() {
auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create();
fidl::ClientEnd<fuchsia_input_report::InputDevice> client = GetClient();
auto result = fidl::WireCall(client)->GetInputReportsReader(std::move(endpoints.server));
ZX_ASSERT(result.ok());
ZX_ASSERT(fidl::WireCall(client)->GetDescriptor().ok());
auto reader =
fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(endpoints.client));
ZX_ASSERT(reader.is_valid());
DrainInitialReport(reader);
return reader;
}
private:
fdf_testing::BackgroundDriverTest<ButtonsTestConfig> driver_test_;
};
class ParameterizedButtonsTest : public ButtonsTest, public ::testing::WithParamInterface<bool> {};
INSTANTIATE_TEST_SUITE_P(ParameterizedButtonsTest, ParameterizedButtonsTest,
::testing::Values(true, false));
TEST_P(ParameterizedButtonsTest, DirectButtonInit) {
auto result = Init(kMetadataSingleButtonDirect, /* suspend_enabled */ GetParam());
ASSERT_TRUE(result.is_ok());
}
TEST_P(ParameterizedButtonsTest, DirectButtonPush) {
auto result = Init(kMetadataSingleButtonDirect, /* suspend_enabled */ GetParam());
ASSERT_TRUE(result.is_ok());
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
}
TEST_P(ParameterizedButtonsTest, DirectButtonPushReleaseReport) {
bool suspend_enabled = GetParam();
auto result = Init(kMetadataSingleButtonDirect, suspend_enabled);
ASSERT_TRUE(result.is_ok());
auto reader = GetReader();
// Push.
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetDefaultGpioReadResponse(0, 1);
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 1U);
EXPECT_EQ(consumer_control.pressed_buttons()[0],
fuchsia_input_report::wire::ConsumerControlButton::kVolumeUp);
driver_test().RunInEnvironmentTypeContext([suspend_enabled](ButtonsTestEnvironment& env) {
EXPECT_EQ(env.fake_sag_.HasWakeLeaseBeenTaken(), suspend_enabled);
});
}
// Release.
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetDefaultGpioReadResponse(0, 0);
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 0U);
driver_test().RunInEnvironmentTypeContext([suspend_enabled](ButtonsTestEnvironment& env) {
EXPECT_EQ(env.fake_sag_.HasWakeLeaseBeenTaken(), suspend_enabled);
});
}
}
TEST_P(ParameterizedButtonsTest, DirectButtonPushReleaseReportWithoutPower) {
bool suspend_enabled = GetParam();
auto result = Init(kMetadataSingleButtonDirect, suspend_enabled, /* serve_sag= */ false);
ASSERT_TRUE(result.is_ok());
auto reader = GetReader();
// Push.
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetDefaultGpioReadResponse(0, 1);
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 1U);
EXPECT_EQ(consumer_control.pressed_buttons()[0],
fuchsia_input_report::wire::ConsumerControlButton::kVolumeUp);
}
// Release.
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetDefaultGpioReadResponse(0, 0);
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 0U);
}
}
TEST_P(ParameterizedButtonsTest, DirectButtonPushReleasePush) {
auto result = Init(kMetadataSingleButtonDirect, /* suspend_enabled */ GetParam());
ASSERT_TRUE(result.is_ok());
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetGpioReadResponse(0, 0);
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
env.SetGpioReadResponse(0, 1);
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
env.SetGpioReadResponse(0, 0);
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
}
TEST_P(ParameterizedButtonsTest, DirectButtonFlaky) {
auto init_result = Init(kMetadataSingleButtonDirect, /* suspend_enabled */ GetParam());
ASSERT_TRUE(init_result.is_ok());
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetGpioReadResponse(0, 1);
env.SetGpioReadResponse(0, 0);
env.SetDefaultGpioReadResponse(0, 1); // Stabilizes.
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
auto reader = GetReader();
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 1U);
EXPECT_EQ(consumer_control.pressed_buttons()[0],
fuchsia_input_report::wire::ConsumerControlButton::kVolumeUp);
}
TEST_P(ParameterizedButtonsTest, MatrixButtonInit) {
auto result = Init(kMetadataMatrix, /* suspend_enabled */ GetParam());
ASSERT_TRUE(result.is_ok());
}
TEST_P(ParameterizedButtonsTest, MatrixButtonPush) {
auto init_result = Init(kMetadataMatrix, /* suspend_enabled */ GetParam());
ASSERT_TRUE(init_result.is_ok());
auto reader = GetReader();
// Initial reads.
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetGpioReadResponse(0, 0);
env.SetGpioReadResponse(0, 0);
env.SetGpioReadResponse(1, 0);
env.SetGpioReadResponse(1, 0);
env.SetGpioReadResponse(0, 1); // Read row. Matrix Scan for 0.
env.SetGpioReadResponse(0, 0); // Read row. Matrix Scan for 2.
env.SetGpioReadResponse(1, 0); // Read row. Matrix Scan for 1.
env.SetGpioReadResponse(1, 0); // Read row. Matrix Scan for 3.
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 1U);
EXPECT_EQ(consumer_control.pressed_buttons()[0],
fuchsia_input_report::wire::ConsumerControlButton::kVolumeUp);
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
auto gpio_2_states = env.fake_gpio_servers_[2].GetStateLog();
const fuchsia_buttons::GpioConfig& gpio_2 = kGpiosMatrix[2];
ASSERT_TRUE(gpio_2.type().has_value());
ASSERT_TRUE(gpio_2.type()->matrix_output().has_value());
const fuchsia_buttons::MatrixOutputGpio& gpio_2_matrix = gpio_2.type()->matrix_output().value();
ASSERT_TRUE(gpio_2_matrix.output_value().has_value());
const uint8_t gpio_2_matrix_output_value = gpio_2_matrix.output_value().value();
ASSERT_GE(gpio_2_states.size(), 4U);
ASSERT_EQ(fake_gpio::ReadSubState{},
(gpio_2_states.end() - 4)->sub_state); // Float column.
ASSERT_EQ(fake_gpio::WriteSubState{.value = gpio_2_matrix_output_value},
(gpio_2_states.end() - 3)->sub_state); // Restore column.
ASSERT_EQ(fake_gpio::ReadSubState{},
(gpio_2_states.end() - 2)->sub_state); // Float column.
ASSERT_EQ(fake_gpio::WriteSubState{.value = gpio_2_matrix_output_value},
(gpio_2_states.end() - 1)->sub_state); // Restore column.
auto gpio_3_states = env.fake_gpio_servers_[3].GetStateLog();
const fuchsia_buttons::GpioConfig& gpio_3 = kGpiosMatrix[2];
ASSERT_TRUE(gpio_3.type().has_value());
ASSERT_TRUE(gpio_3.type()->matrix_output().has_value());
const fuchsia_buttons::MatrixOutputGpio& gpio_3_matrix = gpio_3.type()->matrix_output().value();
ASSERT_TRUE(gpio_3_matrix.output_value().has_value());
const uint8_t gpio_3_matrix_output_value = gpio_3_matrix.output_value().value();
ASSERT_GE(gpio_3_states.size(), 4U);
ASSERT_EQ(fake_gpio::ReadSubState{},
(gpio_3_states.end() - 4)->sub_state); // Float column.
ASSERT_EQ(fake_gpio::WriteSubState{.value = gpio_3_matrix_output_value},
(gpio_3_states.end() - 3)->sub_state); // Restore column.
ASSERT_EQ(fake_gpio::ReadSubState{},
(gpio_3_states.end() - 2)->sub_state); // Float column.
ASSERT_EQ(fake_gpio::WriteSubState{.value = gpio_3_matrix_output_value},
(gpio_3_states.end() - 1)->sub_state); // Restore column.
});
}
TEST_P(ParameterizedButtonsTest, DuplicateReports) {
auto result = Init(kMetadataDuplicate, /* suspend_enabled */ GetParam());
ASSERT_TRUE(result.is_ok());
auto reader = GetReader();
// Holding FDR (VOL_UP and VOL_DOWN), then release VOL_UP, should only get one report
// for the FDR and one report for the VOL_UP. When FDR is released, there is no
// new report generated since the reported values do not change.
// Push FDR (both VOL_UP and VOL_DOWN).
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetGpioReadResponse(2, 1);
env.SetGpioReadResponse(2, 1);
// Report.
env.SetGpioReadResponse(0, 1);
env.SetGpioReadResponse(1, 1);
env.SetGpioReadResponse(2, 1);
// Release VOL_UP.
env.SetGpioReadResponse(0, 0);
env.SetGpioReadResponse(0, 0);
// Report.
env.SetGpioReadResponse(0, 0);
env.SetGpioReadResponse(1, 1);
env.SetGpioReadResponse(2, 0);
// Release FDR (both VOL_UP and VOL_DOWN).
env.SetGpioReadResponse(2, 0);
env.SetGpioReadResponse(2, 0);
// Report (same as before).
env.SetGpioReadResponse(0, 0);
env.SetGpioReadResponse(1, 1);
env.SetGpioReadResponse(2, 0);
env.fake_gpio_interrupts_[2].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 3U);
std::set<fuchsia_input_report::wire::ConsumerControlButton> pressed_buttons;
for (const auto& button : consumer_control.pressed_buttons()) {
pressed_buttons.insert(button);
}
const std::set<fuchsia_input_report::wire::ConsumerControlButton> expected_buttons = {
fuchsia_input_report::wire::ConsumerControlButton::kVolumeUp,
fuchsia_input_report::wire::ConsumerControlButton::kVolumeDown,
fuchsia_input_report::wire::ConsumerControlButton::kFactoryReset};
EXPECT_EQ(expected_buttons, pressed_buttons);
}
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 1U);
EXPECT_EQ(consumer_control.pressed_buttons()[0],
fuchsia_input_report::ConsumerControlButton::kVolumeDown);
}
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.fake_gpio_interrupts_[2].trigger(0, zx::clock::get_boot());
});
}
TEST_P(ParameterizedButtonsTest, CamMute) {
auto result = Init(kMetadataMultiple, /* suspend_enabled */ GetParam());
ASSERT_TRUE(result.is_ok());
auto reader = GetReader();
// Push camera mute.
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetGpioReadResponse(2, 1);
env.SetGpioReadResponse(2, 1);
// Report.
env.SetGpioReadResponse(0, 0);
env.SetGpioReadResponse(1, 0);
env.SetGpioReadResponse(2, 1);
env.fake_gpio_interrupts_[2].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 1U);
EXPECT_EQ(consumer_control.pressed_buttons()[0],
fuchsia_input_report::wire::ConsumerControlButton::kCameraDisable);
}
// Release camera mute.
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetGpioReadResponse(2, 0);
env.SetGpioReadResponse(2, 0);
// Report.
env.SetGpioReadResponse(0, 0);
env.SetGpioReadResponse(1, 0);
env.SetGpioReadResponse(2, 0);
env.fake_gpio_interrupts_[2].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 0U);
}
}
TEST_P(ParameterizedButtonsTest, DirectButtonWakeable) {
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetExpectedInterruptOptions(fuchsia_hardware_gpio::InterruptOptions::kWakeable);
env.SetExpectedInterruptMode(fuchsia_hardware_gpio::InterruptMode::kEdgeHigh);
});
auto result = Init(kMetadataWakeable, /* suspend_enabled */ GetParam());
ASSERT_TRUE(result.is_ok());
auto reader = GetReader();
// Push.
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetDefaultGpioReadResponse(0, 1);
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 1U);
EXPECT_EQ(consumer_control.pressed_buttons()[0],
fuchsia_input_report::wire::ConsumerControlButton::kVolumeUp);
}
// Release.
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetDefaultGpioReadResponse(0, 0);
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 0U);
}
}
TEST_P(ParameterizedButtonsTest, PollOneButton) {
auto result = Init(kMetadataPolled, /* suspend_enabled */ GetParam());
ASSERT_TRUE(result.is_ok());
auto reader = GetReader();
// All gpios_ must have a default read value if polling is being used, as they are all ready
// every poll period.
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetDefaultGpioReadResponse(0, 1);
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 1U);
EXPECT_EQ(consumer_control.pressed_buttons()[0],
fuchsia_input_report::wire::ConsumerControlButton::kVolumeUp);
}
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetDefaultGpioReadResponse(1, 1);
env.fake_gpio_interrupts_[0].trigger(0, zx::clock::get_boot());
env.fake_gpio_interrupts_[1].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 2U);
std::set<fuchsia_input_report::wire::ConsumerControlButton> pressed_buttons;
for (const auto& button : consumer_control.pressed_buttons()) {
pressed_buttons.insert(button);
}
const std::set<fuchsia_input_report::wire::ConsumerControlButton> expected_buttons = {
fuchsia_input_report::wire::ConsumerControlButton::kVolumeUp,
fuchsia_input_report::wire::ConsumerControlButton::kMicMute};
EXPECT_EQ(expected_buttons, pressed_buttons);
}
driver_test().RunInEnvironmentTypeContext([](ButtonsTestEnvironment& env) {
env.SetDefaultGpioReadResponse(0, 0);
env.fake_gpio_interrupts_[1].trigger(0, zx::clock::get_boot());
});
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 1U);
EXPECT_EQ(consumer_control.pressed_buttons()[0],
fuchsia_input_report::wire::ConsumerControlButton::kMicMute);
}
driver_test().RunInEnvironmentTypeContext(
[](ButtonsTestEnvironment& env) { env.SetDefaultGpioReadResponse(1, 0); });
{
auto result = reader->ReadInputReports();
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
auto& reports = result->value()->reports;
ASSERT_EQ(reports.size(), 1U);
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_consumer_control());
auto& consumer_control = report.consumer_control();
ASSERT_TRUE(consumer_control.has_pressed_buttons());
ASSERT_EQ(consumer_control.pressed_buttons().size(), 0U);
}
}
} // namespace buttons