| // 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 <fuchsia/hardware/hiddevice/cpp/banjo.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/hid/acer12.h> |
| #include <lib/hid/ambient-light.h> |
| #include <lib/hid/boot.h> |
| #include <lib/hid/buttons.h> |
| #include <lib/hid/gt92xx.h> |
| #include <lib/hid/paradise.h> |
| #include <lib/hid/usages.h> |
| #include <lib/inspect/testing/cpp/zxtest/inspect.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/eventpair.h> |
| #include <unistd.h> |
| #include <zircon/syscalls.h> |
| |
| #include <ddk/metadata/buttons.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "driver_v1.h" |
| #include "src/devices/testing/mock-ddk/mock-device.h" |
| |
| namespace hid_input_report_dev { |
| const uint8_t boot_mouse_desc[] = { |
| 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) |
| 0x09, 0x02, // Usage (Mouse) |
| 0xA1, 0x01, // Collection (Application) |
| 0x09, 0x01, // Usage (Pointer) |
| 0xA1, 0x00, // Collection (Physical) |
| 0x05, 0x09, // Usage Page (Button) |
| 0x19, 0x01, // Usage Minimum (0x01) |
| 0x29, 0x03, // Usage Maximum (0x03) |
| 0x15, 0x00, // Logical Minimum (0) |
| 0x25, 0x01, // Logical Maximum (1) |
| 0x95, 0x03, // Report Count (3) |
| 0x75, 0x01, // Report Size (1) |
| 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,No Null Position) |
| 0x95, 0x01, // Report Count (1) |
| 0x75, 0x05, // Report Size (5) |
| 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,No Null Position |
| 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) |
| 0x09, 0x30, // Usage (X) |
| 0x09, 0x31, // Usage (Y) |
| 0x15, 0x81, // Logical Minimum (-127) |
| 0x25, 0x7F, // Logical Maximum (127) |
| 0x75, 0x08, // Report Size (8) |
| 0x95, 0x02, // Report Count (2) |
| 0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,No Null Position) |
| 0xC0, // End Collection |
| 0xC0, // End Collection |
| }; |
| |
| class FakeHidDevice : public ddk::HidDeviceProtocol<FakeHidDevice> { |
| public: |
| FakeHidDevice() : proto_({&hid_device_protocol_ops_, this}) {} |
| |
| zx_status_t HidDeviceRegisterListener(const hid_report_listener_protocol_t* listener) { |
| listener_ = *listener; |
| return ZX_OK; |
| } |
| |
| void HidDeviceUnregisterListener() { listener_.reset(); } |
| |
| zx_status_t HidDeviceGetDescriptor(uint8_t* out_descriptor_list, size_t descriptor_count, |
| size_t* out_descriptor_actual) { |
| if (descriptor_count < report_desc_.size()) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| memcpy(out_descriptor_list, report_desc_.data(), report_desc_.size()); |
| *out_descriptor_actual = report_desc_.size(); |
| return ZX_OK; |
| } |
| |
| zx_status_t HidDeviceGetReport(hid_report_type_t rpt_type, uint8_t rpt_id, |
| uint8_t* out_report_list, size_t report_count, |
| size_t* out_report_actual) { |
| // If the client is Getting a report with a specific ID, check that it matches |
| // our saved report. |
| if ((rpt_id != 0) && (report_.size() > 0)) { |
| if (rpt_id != report_[0]) { |
| return ZX_ERR_WRONG_TYPE; |
| } |
| } |
| |
| if (report_count < report_.size()) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| memcpy(out_report_list, report_.data(), report_.size()); |
| *out_report_actual = report_.size(); |
| |
| return ZX_OK; |
| } |
| |
| void HidDeviceGetHidDeviceInfo(hid_device_info_t* out_info) { |
| out_info->vendor_id = 0xabc; |
| out_info->product_id = 123; |
| out_info->version = 5; |
| } |
| |
| zx_status_t HidDeviceSetReport(hid_report_type_t rpt_type, uint8_t rpt_id, |
| const uint8_t* report_list, size_t report_count) { |
| report_ = std::vector<uint8_t>(report_list, report_list + report_count); |
| return ZX_OK; |
| } |
| |
| void SetReportDesc(std::vector<uint8_t> report_desc) { report_desc_ = report_desc; } |
| |
| void SendReport(const std::vector<uint8_t>& report, zx_time_t timestamp = ZX_TIME_INFINITE) { |
| if (timestamp == ZX_TIME_INFINITE) { |
| timestamp = zx_clock_get_monotonic(); |
| } |
| if (listener_.has_value()) { |
| listener_->ops->receive_report(listener_->ctx, report.data(), report.size(), timestamp); |
| } |
| } |
| |
| std::optional<hid_report_listener_protocol_t> listener_; |
| hid_device_protocol_t proto_; |
| std::vector<uint8_t> report_desc_; |
| |
| std::vector<uint8_t> report_; |
| }; |
| |
| class HidDevTest : public zxtest::Test { |
| void SetUp() override { |
| client_ = ddk::HidDeviceProtocolClient(&fake_hid_.proto_); |
| fake_parent_ = MockDevice::FakeRootParent(); |
| |
| device_ = new InputReportDriver(fake_parent_.get(), client_); |
| fidl_loop_.StartThread("fidl-thread"); |
| // Each test is responsible for calling |device_->Bind()|. |
| } |
| |
| void TearDown() override { mock_ddk::ReleaseFlaggedDevices(fake_parent_.get()); } |
| |
| protected: |
| fidl::WireSyncClient<fuchsia_input_report::InputDevice> GetSyncClient() { |
| auto endpoints = fidl::Endpoints<fuchsia_input_report::InputDevice>::Create(); |
| fidl::BindServer(fidl_loop_.dispatcher(), std::move(endpoints.server), device_); |
| return fidl::WireSyncClient(std::move(endpoints.client)); |
| } |
| |
| async::Loop fidl_loop_ = async::Loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| std::shared_ptr<MockDevice> fake_parent_; |
| FakeHidDevice fake_hid_; |
| InputReportDriver* device_; |
| ddk::HidDeviceProtocolClient client_; |
| }; |
| |
| TEST_F(HidDevTest, HidLifetimeTest) { |
| std::vector<uint8_t> boot_mouse(boot_mouse_desc, boot_mouse_desc + sizeof(boot_mouse_desc)); |
| fake_hid_.SetReportDesc(boot_mouse); |
| |
| ASSERT_OK(device_->Bind()); |
| } |
| |
| TEST(HidDevTest, InputReportUnregisterTest) { |
| auto fake_parent = MockDevice::FakeRootParent(); |
| FakeHidDevice fake_hid_; |
| InputReportDriver* device_; |
| ddk::HidDeviceProtocolClient client_; |
| |
| client_ = ddk::HidDeviceProtocolClient(&fake_hid_.proto_); |
| device_ = new InputReportDriver(fake_parent.get(), client_); |
| |
| std::vector<uint8_t> boot_mouse(boot_mouse_desc, boot_mouse_desc + sizeof(boot_mouse_desc)); |
| fake_hid_.SetReportDesc(boot_mouse); |
| |
| ASSERT_OK(device_->Bind()); |
| |
| mock_ddk::ReleaseFlaggedDevices(fake_parent.get()); |
| fake_parent.reset(); |
| |
| // Make sure that the InputReport class has unregistered from the HID device. |
| ASSERT_FALSE(fake_hid_.listener_); |
| } |
| |
| TEST(HidDevTest, InputReportUnregisterTestBindFailed) { |
| auto fake_parent = MockDevice::FakeRootParent(); |
| FakeHidDevice fake_hid; |
| ddk::HidDeviceProtocolClient client; |
| |
| client = ddk::HidDeviceProtocolClient(&fake_hid.proto_); |
| auto device = std::make_unique<InputReportDriver>(fake_parent.get(), client); |
| |
| // We don't set a input report on `fake_hid` so the bind will fail. |
| ASSERT_EQ(device->Bind(), ZX_ERR_INTERNAL); |
| |
| // Make sure that the InputReport class is not registered to the HID device. |
| ASSERT_FALSE(fake_hid.listener_); |
| } |
| |
| TEST_F(HidDevTest, GetReportDescTest) { |
| std::vector<uint8_t> boot_mouse(boot_mouse_desc, boot_mouse_desc + sizeof(boot_mouse_desc)); |
| fake_hid_.SetReportDesc(boot_mouse); |
| |
| ASSERT_OK(device_->Bind()); |
| |
| auto sync_client = GetSyncClient(); |
| fidl::WireResult<fuchsia_input_report::InputDevice::GetDescriptor> result = |
| sync_client->GetDescriptor(); |
| ASSERT_OK(result.status()); |
| |
| auto& desc = result->descriptor; |
| ASSERT_TRUE(desc.has_mouse()); |
| ASSERT_TRUE(desc.mouse().has_input()); |
| fuchsia_input_report::wire::MouseInputDescriptor& mouse = desc.mouse().input(); |
| |
| ASSERT_TRUE(mouse.has_movement_x()); |
| ASSERT_EQ(-127, mouse.movement_x().range.min); |
| ASSERT_EQ(127, mouse.movement_x().range.max); |
| |
| ASSERT_TRUE(mouse.has_movement_y()); |
| ASSERT_EQ(-127, mouse.movement_y().range.min); |
| ASSERT_EQ(127, mouse.movement_y().range.max); |
| } |
| |
| TEST_F(HidDevTest, ReportDescInfoTest) { |
| std::vector<uint8_t> boot_mouse(boot_mouse_desc, boot_mouse_desc + sizeof(boot_mouse_desc)); |
| fake_hid_.SetReportDesc(boot_mouse); |
| |
| ASSERT_OK(device_->Bind()); |
| |
| auto sync_client = GetSyncClient(); |
| fidl::WireResult<fuchsia_input_report::InputDevice::GetDescriptor> result = |
| sync_client->GetDescriptor(); |
| ASSERT_OK(result.status()); |
| |
| hid_device_info_t info; |
| fake_hid_.HidDeviceGetHidDeviceInfo(&info); |
| |
| auto& desc = result.value().descriptor; |
| ASSERT_TRUE(desc.has_device_info()); |
| ASSERT_EQ(desc.device_info().vendor_id, info.vendor_id); |
| ASSERT_EQ(desc.device_info().product_id, info.product_id); |
| ASSERT_EQ(desc.device_info().version, info.version); |
| } |
| |
| TEST_F(HidDevTest, ReadInputReportsTest) { |
| std::vector<uint8_t> boot_mouse(boot_mouse_desc, boot_mouse_desc + sizeof(boot_mouse_desc)); |
| fake_hid_.SetReportDesc(boot_mouse); |
| |
| device_->Bind(); |
| |
| auto sync_client = GetSyncClient(); |
| |
| // Get an InputReportsReader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| |
| auto result = sync_client->GetInputReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader = |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(endpoints.client)); |
| ASSERT_OK(device_->input_report().WaitForNextReader(zx::duration::infinite())); |
| } |
| |
| // Spoof send a report. |
| std::vector<uint8_t> sent_report = {0xFF, 0x50, 0x70}; |
| fake_hid_.SendReport(sent_report); |
| |
| // Get the report. |
| auto result = reader->ReadInputReports(); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| auto& reports = result->value()->reports; |
| |
| ASSERT_EQ(1, reports.count()); |
| |
| auto& report = reports[0]; |
| ASSERT_TRUE(report.has_event_time()); |
| ASSERT_TRUE(report.has_mouse()); |
| auto& mouse = report.mouse(); |
| |
| ASSERT_TRUE(mouse.has_movement_x()); |
| ASSERT_EQ(0x50, mouse.movement_x()); |
| |
| ASSERT_TRUE(mouse.has_movement_y()); |
| ASSERT_EQ(0x70, mouse.movement_y()); |
| |
| ASSERT_TRUE(mouse.has_pressed_buttons()); |
| const fidl::VectorView<uint8_t>& pressed_buttons = mouse.pressed_buttons(); |
| for (size_t i = 0; i < pressed_buttons.count(); i++) { |
| ASSERT_EQ(i + 1, pressed_buttons[i]); |
| } |
| } |
| |
| TEST_F(HidDevTest, ReadInputReportsHangingGetTest) { |
| std::vector<uint8_t> boot_mouse(boot_mouse_desc, boot_mouse_desc + sizeof(boot_mouse_desc)); |
| fake_hid_.SetReportDesc(boot_mouse); |
| |
| device_->Bind(); |
| |
| auto sync_client = GetSyncClient(); |
| |
| // Get an InputReportsReader. |
| |
| async::Loop loop = async::Loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fidl::WireClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| |
| auto result = sync_client->GetInputReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader.Bind(std::move(endpoints.client), loop.dispatcher()); |
| ASSERT_OK(device_->input_report().WaitForNextReader(zx::duration::infinite())); |
| } |
| |
| // Read the report. This will hang until a report is sent. |
| reader->ReadInputReports().ThenExactlyOnce( |
| [&](fidl::WireUnownedResult<fuchsia_input_report::InputReportsReader::ReadInputReports>& |
| response) { |
| ASSERT_OK(response.status()); |
| ASSERT_FALSE(response->is_error()); |
| auto& reports = response->value()->reports; |
| ASSERT_EQ(1, reports.count()); |
| |
| auto& report = reports[0]; |
| ASSERT_TRUE(report.has_event_time()); |
| ASSERT_TRUE(report.has_mouse()); |
| auto& mouse = report.mouse(); |
| |
| ASSERT_TRUE(mouse.has_movement_x()); |
| ASSERT_EQ(0x50, mouse.movement_x()); |
| |
| ASSERT_TRUE(mouse.has_movement_y()); |
| ASSERT_EQ(0x70, mouse.movement_y()); |
| loop.Quit(); |
| }); |
| loop.RunUntilIdle(); |
| |
| // Send the report. |
| std::vector<uint8_t> sent_report = {0xFF, 0x50, 0x70}; |
| fake_hid_.SendReport(sent_report); |
| |
| loop.Run(); |
| } |
| |
| TEST_F(HidDevTest, CloseReaderWithOutstandingRead) { |
| std::vector<uint8_t> boot_mouse(boot_mouse_desc, boot_mouse_desc + sizeof(boot_mouse_desc)); |
| fake_hid_.SetReportDesc(boot_mouse); |
| |
| device_->Bind(); |
| |
| auto sync_client = GetSyncClient(); |
| |
| // Get an InputReportsReader. |
| |
| async::Loop loop = async::Loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fidl::WireClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| |
| auto result = sync_client->GetInputReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader.Bind(std::move(endpoints.client), loop.dispatcher()); |
| ASSERT_OK(device_->input_report().WaitForNextReader(zx::duration::infinite())); |
| } |
| |
| // Queue a report. |
| reader->ReadInputReports().ThenExactlyOnce( |
| [&](fidl::WireUnownedResult<fuchsia_input_report::InputReportsReader::ReadInputReports>& |
| result) { ASSERT_TRUE(result.is_canceled()); }); |
| loop.RunUntilIdle(); |
| |
| // Unbind the reader now that the report is waiting. |
| reader = {}; |
| } |
| |
| TEST_F(HidDevTest, SensorTest) { |
| const uint8_t* sensor_desc_ptr; |
| size_t sensor_desc_size = get_ambient_light_report_desc(&sensor_desc_ptr); |
| std::vector<uint8_t> sensor_desc_vector(sensor_desc_ptr, sensor_desc_ptr + sensor_desc_size); |
| fake_hid_.SetReportDesc(sensor_desc_vector); |
| |
| device_->Bind(); |
| |
| auto sync_client = GetSyncClient(); |
| |
| // Get the report descriptor. |
| fidl::WireResult<fuchsia_input_report::InputDevice::GetDescriptor> result = |
| sync_client->GetDescriptor(); |
| ASSERT_OK(result.status()); |
| fuchsia_input_report::wire::DeviceDescriptor& desc = result.value().descriptor; |
| ASSERT_TRUE(desc.has_sensor()); |
| ASSERT_TRUE(desc.sensor().has_input()); |
| ASSERT_EQ(desc.sensor().input().count(), 1); |
| fuchsia_input_report::wire::SensorInputDescriptor& sensor_desc = desc.sensor().input()[0]; |
| ASSERT_TRUE(sensor_desc.has_values()); |
| ASSERT_EQ(4, sensor_desc.values().count()); |
| |
| ASSERT_EQ(sensor_desc.values()[0].type, |
| fuchsia_input_report::wire::SensorType::kLightIlluminance); |
| ASSERT_EQ(sensor_desc.values()[0].axis.unit.type, fuchsia_input_report::wire::UnitType::kNone); |
| |
| ASSERT_EQ(sensor_desc.values()[1].type, fuchsia_input_report::wire::SensorType::kLightRed); |
| ASSERT_EQ(sensor_desc.values()[1].axis.unit.type, fuchsia_input_report::wire::UnitType::kNone); |
| |
| ASSERT_EQ(sensor_desc.values()[2].type, fuchsia_input_report::wire::SensorType::kLightBlue); |
| ASSERT_EQ(sensor_desc.values()[2].axis.unit.type, fuchsia_input_report::wire::UnitType::kNone); |
| |
| ASSERT_EQ(sensor_desc.values()[3].type, fuchsia_input_report::wire::SensorType::kLightGreen); |
| ASSERT_EQ(sensor_desc.values()[3].axis.unit.type, fuchsia_input_report::wire::UnitType::kNone); |
| |
| // Get an InputReportsReader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| |
| auto result = sync_client->GetInputReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader = |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(endpoints.client)); |
| ASSERT_OK(device_->input_report().WaitForNextReader(zx::duration::infinite())); |
| } |
| |
| // Create the report. |
| ambient_light_input_rpt_t report_data = {}; |
| // Values are arbitrarily chosen. |
| const int kIlluminanceTestVal = 10; |
| const int kRedTestVal = 101; |
| const int kBlueTestVal = 5; |
| const int kGreenTestVal = 3; |
| report_data.rpt_id = AMBIENT_LIGHT_RPT_ID_INPUT; |
| report_data.illuminance = kIlluminanceTestVal; |
| report_data.red = kRedTestVal; |
| report_data.blue = kBlueTestVal; |
| report_data.green = kGreenTestVal; |
| |
| std::vector<uint8_t> report_vector( |
| reinterpret_cast<uint8_t*>(&report_data), |
| reinterpret_cast<uint8_t*>(&report_data) + sizeof(report_data)); |
| fake_hid_.SendReport(report_vector); |
| |
| // Get the report. |
| auto report_result = reader->ReadInputReports(); |
| ASSERT_OK(report_result.status()); |
| |
| const fidl::VectorView<fuchsia_input_report::wire::InputReport>& reports = |
| report_result->value()->reports; |
| ASSERT_EQ(1, reports.count()); |
| |
| ASSERT_TRUE(reports[0].has_sensor()); |
| const fuchsia_input_report::wire::SensorInputReport& sensor_report = reports[0].sensor(); |
| EXPECT_TRUE(sensor_report.has_values()); |
| EXPECT_EQ(4, sensor_report.values().count()); |
| |
| // Check the report. |
| // These will always match the ordering in the descriptor. |
| EXPECT_EQ(kIlluminanceTestVal, sensor_report.values()[0]); |
| EXPECT_EQ(kRedTestVal, sensor_report.values()[1]); |
| EXPECT_EQ(kBlueTestVal, sensor_report.values()[2]); |
| EXPECT_EQ(kGreenTestVal, sensor_report.values()[3]); |
| } |
| |
| TEST_F(HidDevTest, GetTouchInputReportTest) { |
| size_t desc_len; |
| const uint8_t* report_desc = get_paradise_touch_report_desc(&desc_len); |
| std::vector<uint8_t> desc(report_desc, report_desc + desc_len); |
| fake_hid_.SetReportDesc(desc); |
| |
| device_->Bind(); |
| |
| auto sync_client = GetSyncClient(); |
| |
| // Get an InputReportsReader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| |
| auto result = sync_client->GetInputReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader = |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(endpoints.client)); |
| ASSERT_OK(device_->input_report().WaitForNextReader(zx::duration::infinite())); |
| } |
| |
| // Spoof send a report. |
| paradise_touch_t touch_report = {}; |
| touch_report.rpt_id = PARADISE_RPT_ID_TOUCH; |
| touch_report.contact_count = 1; |
| touch_report.fingers[0].flags = 0xFF; |
| touch_report.fingers[0].x = 100; |
| touch_report.fingers[0].y = 200; |
| touch_report.fingers[0].finger_id = 1; |
| |
| std::vector<uint8_t> sent_report = |
| std::vector<uint8_t>(reinterpret_cast<uint8_t*>(&touch_report), |
| reinterpret_cast<uint8_t*>(&touch_report) + sizeof(touch_report)); |
| fake_hid_.SendReport(sent_report); |
| |
| // Get the report. |
| auto report_result = reader->ReadInputReports(); |
| ASSERT_OK(report_result.status()); |
| |
| const fidl::VectorView<fuchsia_input_report::wire::InputReport>& reports = |
| report_result->value()->reports; |
| ASSERT_EQ(1, reports.count()); |
| |
| const auto& report = reports[0]; |
| const auto& touch = report.touch(); |
| ASSERT_TRUE(touch.has_contacts()); |
| ASSERT_EQ(1, touch.contacts().count()); |
| const auto& contact = touch.contacts()[0]; |
| |
| ASSERT_TRUE(contact.has_position_x()); |
| ASSERT_EQ(2500, contact.position_x()); |
| |
| ASSERT_TRUE(contact.has_position_y()); |
| ASSERT_EQ(5000, contact.position_y()); |
| } |
| |
| TEST_F(HidDevTest, GetTouchPadDescTest) { |
| size_t desc_len; |
| const uint8_t* report_desc = get_paradise_touchpad_v1_report_desc(&desc_len); |
| std::vector<uint8_t> desc(report_desc, report_desc + desc_len); |
| fake_hid_.SetReportDesc(desc); |
| |
| device_->Bind(); |
| |
| auto sync_client = GetSyncClient(); |
| fidl::WireResult<fuchsia_input_report::InputDevice::GetDescriptor> result = |
| sync_client->GetDescriptor(); |
| ASSERT_OK(result.status()); |
| ASSERT_TRUE(result.value().descriptor.has_touch()); |
| ASSERT_TRUE(result.value().descriptor.touch().has_input()); |
| fuchsia_input_report::wire::TouchInputDescriptor& touch = |
| result.value().descriptor.touch().input(); |
| |
| ASSERT_EQ(fuchsia_input_report::wire::TouchType::kTouchpad, touch.touch_type()); |
| } |
| |
| TEST_F(HidDevTest, KeyboardTest) { |
| size_t keyboard_descriptor_size; |
| const uint8_t* keyboard_descriptor = get_boot_kbd_report_desc(&keyboard_descriptor_size); |
| std::vector<uint8_t> desc(keyboard_descriptor, keyboard_descriptor + keyboard_descriptor_size); |
| |
| fake_hid_.SetReportDesc(desc); |
| device_->Bind(); |
| |
| auto sync_client = GetSyncClient(); |
| |
| // Get an InputReportsReader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| |
| auto result = sync_client->GetInputReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader = |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(endpoints.client)); |
| ASSERT_OK(device_->input_report().WaitForNextReader(zx::duration::infinite())); |
| } |
| |
| // Spoof send a report. |
| hid_boot_kbd_report keyboard_report = {}; |
| keyboard_report.usage[0] = HID_USAGE_KEY_A; |
| keyboard_report.usage[1] = HID_USAGE_KEY_UP; |
| keyboard_report.usage[2] = HID_USAGE_KEY_B; |
| |
| std::vector<uint8_t> sent_report( |
| reinterpret_cast<uint8_t*>(&keyboard_report), |
| reinterpret_cast<uint8_t*>(&keyboard_report) + sizeof(keyboard_report)); |
| fake_hid_.SendReport(sent_report); |
| |
| // Get the report. |
| auto report_result = reader->ReadInputReports(); |
| ASSERT_OK(report_result.status()); |
| |
| const fidl::VectorView<fuchsia_input_report::wire::InputReport>& reports = |
| report_result->value()->reports; |
| ASSERT_EQ(1, reports.count()); |
| |
| const auto& report = reports[0]; |
| const auto& keyboard = report.keyboard(); |
| ASSERT_EQ(3, keyboard.pressed_keys3().count()); |
| EXPECT_EQ(fuchsia_input::wire::Key::kA, keyboard.pressed_keys3()[0]); |
| EXPECT_EQ(fuchsia_input::wire::Key::kUp, keyboard.pressed_keys3()[1]); |
| EXPECT_EQ(fuchsia_input::wire::Key::kB, keyboard.pressed_keys3()[2]); |
| } |
| |
| TEST_F(HidDevTest, KeyboardOutputReportTest) { |
| size_t keyboard_descriptor_size; |
| const uint8_t* keyboard_descriptor = get_boot_kbd_report_desc(&keyboard_descriptor_size); |
| std::vector<uint8_t> desc(keyboard_descriptor, keyboard_descriptor + keyboard_descriptor_size); |
| fake_hid_.SetReportDesc(desc); |
| device_->Bind(); |
| |
| auto sync_client = GetSyncClient(); |
| // Make an output report. |
| fidl::Arena allocator; |
| fidl::VectorView<fuchsia_input_report::wire::LedType> led_view(allocator, 2); |
| led_view[0] = fuchsia_input_report::wire::LedType::kNumLock; |
| led_view[1] = fuchsia_input_report::wire::LedType::kScrollLock; |
| fuchsia_input_report::wire::KeyboardOutputReport fidl_keyboard(allocator); |
| fidl_keyboard.set_enabled_leds(allocator, std::move(led_view)); |
| |
| fuchsia_input_report::wire::OutputReport output_report(allocator); |
| output_report.set_keyboard(allocator, std::move(fidl_keyboard)); |
| // Send the report. |
| fidl::WireResult<fuchsia_input_report::InputDevice::SendOutputReport> response = |
| sync_client->SendOutputReport(std::move(output_report)); |
| ASSERT_OK(response.status()); |
| ASSERT_FALSE(response->is_error()); |
| // Get and check the hid output report. |
| uint8_t report; |
| size_t out_size; |
| ASSERT_OK( |
| fake_hid_.HidDeviceGetReport(HID_REPORT_TYPE_OUTPUT, 0, &report, sizeof(report), &out_size)); |
| ASSERT_EQ(out_size, sizeof(report)); |
| ASSERT_EQ(report, 0b101); |
| } |
| |
| TEST_F(HidDevTest, ConsumerControlTest) { |
| { |
| const uint8_t* descriptor; |
| size_t descriptor_size = get_buttons_report_desc(&descriptor); |
| std::vector<uint8_t> desc_vector(descriptor, descriptor + descriptor_size); |
| fake_hid_.SetReportDesc(desc_vector); |
| } |
| |
| // Create the initial report that will be queried on OpenSession. |
| { |
| struct buttons_input_rpt report = {}; |
| report.rpt_id = BUTTONS_RPT_ID_INPUT; |
| std::vector<uint8_t> report_vector(reinterpret_cast<uint8_t*>(&report), |
| reinterpret_cast<uint8_t*>(&report) + sizeof(report)); |
| fake_hid_.HidDeviceSetReport(HID_REPORT_TYPE_INPUT, BUTTONS_RPT_ID_INPUT, report_vector.data(), |
| report_vector.size()); |
| } |
| |
| device_->Bind(); |
| |
| auto sync_client = GetSyncClient(); |
| |
| // Get the report descriptor. |
| fidl::WireResult<fuchsia_input_report::InputDevice::GetDescriptor> result = |
| sync_client->GetDescriptor(); |
| ASSERT_OK(result.status()); |
| fuchsia_input_report::wire::DeviceDescriptor& desc = result.value().descriptor; |
| ASSERT_TRUE(desc.has_consumer_control()); |
| ASSERT_TRUE(desc.consumer_control().has_input()); |
| fuchsia_input_report::wire::ConsumerControlInputDescriptor& consumer_control_desc = |
| desc.consumer_control().input(); |
| ASSERT_TRUE(consumer_control_desc.has_buttons()); |
| ASSERT_EQ(5, consumer_control_desc.buttons().count()); |
| |
| ASSERT_EQ(consumer_control_desc.buttons()[0], |
| fuchsia_input_report::wire::ConsumerControlButton::kVolumeUp); |
| ASSERT_EQ(consumer_control_desc.buttons()[1], |
| fuchsia_input_report::wire::ConsumerControlButton::kVolumeDown); |
| ASSERT_EQ(consumer_control_desc.buttons()[2], |
| fuchsia_input_report::wire::ConsumerControlButton::kFactoryReset); |
| ASSERT_EQ(consumer_control_desc.buttons()[3], |
| fuchsia_input_report::wire::ConsumerControlButton::kCameraDisable); |
| ASSERT_EQ(consumer_control_desc.buttons()[4], |
| fuchsia_input_report::wire::ConsumerControlButton::kMicMute); |
| |
| // Get an InputReportsReader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| |
| auto result = sync_client->GetInputReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader = |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(endpoints.client)); |
| ASSERT_OK(device_->input_report().WaitForNextReader(zx::duration::infinite())); |
| } |
| |
| // Create another report. |
| { |
| struct buttons_input_rpt report = {}; |
| report.rpt_id = BUTTONS_RPT_ID_INPUT; |
| fill_button_in_report(BUTTONS_ID_VOLUME_UP, true, &report); |
| fill_button_in_report(BUTTONS_ID_FDR, true, &report); |
| fill_button_in_report(BUTTONS_ID_MIC_MUTE, true, &report); |
| |
| std::vector<uint8_t> report_vector(reinterpret_cast<uint8_t*>(&report), |
| reinterpret_cast<uint8_t*>(&report) + sizeof(report)); |
| fake_hid_.SendReport(report_vector); |
| } |
| |
| // Get the report. |
| auto report_result = reader->ReadInputReports(); |
| ASSERT_OK(report_result.status()); |
| |
| const fidl::VectorView<fuchsia_input_report::wire::InputReport>& reports = |
| report_result->value()->reports; |
| ASSERT_EQ(2, reports.count()); |
| |
| // Check the initial report. |
| { |
| ASSERT_TRUE(reports[0].has_consumer_control()); |
| const auto& report = reports[0].consumer_control(); |
| EXPECT_TRUE(report.has_pressed_buttons()); |
| EXPECT_EQ(0, report.pressed_buttons().count()); |
| } |
| |
| // Check the second report. |
| { |
| ASSERT_TRUE(reports[1].has_consumer_control()); |
| const auto& report = reports[1].consumer_control(); |
| EXPECT_TRUE(report.has_pressed_buttons()); |
| EXPECT_EQ(3, report.pressed_buttons().count()); |
| |
| EXPECT_EQ(report.pressed_buttons()[0], |
| fuchsia_input_report::wire::ConsumerControlButton::kVolumeUp); |
| EXPECT_EQ(report.pressed_buttons()[1], |
| fuchsia_input_report::wire::ConsumerControlButton::kFactoryReset); |
| EXPECT_EQ(report.pressed_buttons()[2], |
| fuchsia_input_report::wire::ConsumerControlButton::kMicMute); |
| } |
| } |
| |
| TEST_F(HidDevTest, ConsumerControlTwoClientsTest) { |
| { |
| const uint8_t* descriptor; |
| size_t descriptor_size = get_buttons_report_desc(&descriptor); |
| std::vector<uint8_t> desc_vector(descriptor, descriptor + descriptor_size); |
| fake_hid_.SetReportDesc(desc_vector); |
| } |
| |
| // Create the initial report that will be queried on OpenSession. |
| { |
| struct buttons_input_rpt report = {}; |
| report.rpt_id = BUTTONS_RPT_ID_INPUT; |
| std::vector<uint8_t> report_vector(reinterpret_cast<uint8_t*>(&report), |
| reinterpret_cast<uint8_t*>(&report) + sizeof(report)); |
| fake_hid_.HidDeviceSetReport(HID_REPORT_TYPE_INPUT, BUTTONS_RPT_ID_INPUT, report_vector.data(), |
| report_vector.size()); |
| } |
| |
| device_->Bind(); |
| |
| // Open the device. |
| auto client = GetSyncClient(); |
| |
| // Get an input reader and check reports. |
| { |
| // Get an InputReportsReader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| |
| auto result = client->GetInputReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader = fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>( |
| std::move(endpoints.client)); |
| } |
| |
| auto report_result = reader->ReadInputReports(); |
| ASSERT_OK(report_result.status()); |
| const fidl::VectorView<fuchsia_input_report::wire::InputReport>& reports = |
| report_result->value()->reports; |
| ASSERT_EQ(1, reports.count()); |
| |
| ASSERT_TRUE(reports[0].has_consumer_control()); |
| const auto& report = reports[0].consumer_control(); |
| EXPECT_TRUE(report.has_pressed_buttons()); |
| EXPECT_EQ(0, report.pressed_buttons().count()); |
| } |
| |
| { |
| // Get an InputReportsReader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| |
| auto result = client->GetInputReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader = fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>( |
| std::move(endpoints.client)); |
| ASSERT_OK(device_->input_report().WaitForNextReader(zx::duration::infinite())); |
| } |
| |
| auto report_result = reader->ReadInputReports(); |
| ASSERT_OK(report_result.status()); |
| const fidl::VectorView<fuchsia_input_report::wire::InputReport>& reports = |
| report_result->value()->reports; |
| ASSERT_EQ(1, reports.count()); |
| |
| ASSERT_TRUE(reports[0].has_consumer_control()); |
| const auto& report = reports[0].consumer_control(); |
| EXPECT_TRUE(report.has_pressed_buttons()); |
| EXPECT_EQ(0, report.pressed_buttons().count()); |
| } |
| } |
| |
| TEST_F(HidDevTest, TouchLatencyMeasurements) { |
| const uint8_t* report_desc; |
| const size_t desc_len = get_gt92xx_report_desc(&report_desc); |
| std::vector<uint8_t> desc(report_desc, report_desc + desc_len); |
| fake_hid_.SetReportDesc(desc); |
| |
| device_->Bind(); |
| |
| const zx::vmo& inspect_vmo = fake_parent_->GetLatestChild()->GetInspectVmo(); |
| ASSERT_TRUE(inspect_vmo.is_valid()); |
| |
| // Send five reports, and verify that the inspect stats make sense. |
| gt92xx_touch_t report = {}; |
| const uint8_t* report_ptr = reinterpret_cast<uint8_t*>(&report); |
| const std::vector<uint8_t> report_vector(report_ptr, report_ptr + sizeof(report)); |
| |
| // Add some additional computed latency to make the results more realistic. |
| const zx_time_t timestamp = zx_clock_get_monotonic() - ZX_MSEC(15); |
| |
| fake_hid_.SendReport(report_vector, timestamp); |
| fake_hid_.SendReport(report_vector, timestamp); |
| fake_hid_.SendReport(report_vector, timestamp); |
| fake_hid_.SendReport(report_vector, timestamp); |
| fake_hid_.SendReport(report_vector, timestamp - ZX_MSEC(5)); |
| |
| inspect::InspectTestHelper inspector; |
| inspector.ReadInspect(inspect_vmo); |
| |
| const inspect::Hierarchy* root = inspector.hierarchy().GetByPath({"hid-input-report-touch"}); |
| ASSERT_NOT_NULL(root); |
| |
| const auto* latency_histogram = |
| root->node().get_property<inspect::UintArrayValue>("latency_histogram_usecs"); |
| ASSERT_NOT_NULL(latency_histogram); |
| uint64_t latency_bucket_sum = 0; |
| for (const inspect::UintArrayValue::HistogramBucket& bucket : latency_histogram->GetBuckets()) { |
| latency_bucket_sum += bucket.count; |
| } |
| |
| EXPECT_EQ(latency_bucket_sum, 5); |
| |
| const auto* average_latency = |
| root->node().get_property<inspect::UintPropertyValue>("average_latency_usecs"); |
| ASSERT_NOT_NULL(average_latency); |
| |
| const auto* max_latency = |
| root->node().get_property<inspect::UintPropertyValue>("max_latency_usecs"); |
| ASSERT_NOT_NULL(max_latency); |
| |
| EXPECT_GE(max_latency->value(), average_latency->value()); |
| } |
| |
| TEST_F(HidDevTest, InspectDeviceTypes) { |
| size_t desc_len; |
| const uint8_t* report_desc = get_paradise_touch_report_desc(&desc_len); |
| std::vector<uint8_t> desc(report_desc, report_desc + desc_len); |
| fake_hid_.SetReportDesc(desc); |
| |
| device_->Bind(); |
| |
| const zx::vmo& inspect_vmo = fake_parent_->GetLatestChild()->GetInspectVmo(); |
| ASSERT_TRUE(inspect_vmo.is_valid()); |
| |
| inspect::InspectTestHelper inspector; |
| inspector.ReadInspect(inspect_vmo); |
| |
| const inspect::Hierarchy* root = |
| inspector.hierarchy().GetByPath({"hid-input-report-touch,touch,mouse"}); |
| ASSERT_NOT_NULL(root); |
| |
| const auto* device_types = |
| root->node().get_property<inspect::StringPropertyValue>("device_types"); |
| ASSERT_NOT_NULL(device_types); |
| |
| EXPECT_STREQ(device_types->value().c_str(), "touch,touch,mouse"); |
| } |
| |
| TEST_F(HidDevTest, GetInputReport) { |
| const uint8_t* sensor_desc_ptr; |
| size_t sensor_desc_size = get_ambient_light_report_desc(&sensor_desc_ptr); |
| std::vector<uint8_t> sensor_desc_vector(sensor_desc_ptr, sensor_desc_ptr + sensor_desc_size); |
| fake_hid_.SetReportDesc(sensor_desc_vector); |
| |
| device_->Bind(); |
| |
| auto sync_client = GetSyncClient(); |
| |
| ambient_light_input_rpt_t report_data = {}; |
| const int kIlluminanceTestVal = 10; |
| const int kRedTestVal = 101; |
| const int kBlueTestVal = 5; |
| const int kGreenTestVal = 3; |
| report_data.rpt_id = AMBIENT_LIGHT_RPT_ID_INPUT; |
| report_data.illuminance = kIlluminanceTestVal; |
| report_data.red = kRedTestVal; |
| report_data.blue = kBlueTestVal; |
| report_data.green = kGreenTestVal; |
| |
| std::vector<uint8_t> report_vector( |
| reinterpret_cast<uint8_t*>(&report_data), |
| reinterpret_cast<uint8_t*>(&report_data) + sizeof(report_data)); |
| |
| fake_hid_.HidDeviceSetReport(HID_REPORT_TYPE_INPUT, AMBIENT_LIGHT_RPT_ID_INPUT, |
| report_vector.data(), report_vector.size()); |
| |
| { |
| const auto report_result = |
| sync_client->GetInputReport(fuchsia_input_report::wire::DeviceType::kSensor); |
| ASSERT_TRUE(report_result.ok()); |
| |
| ASSERT_TRUE(report_result->is_ok()); |
| const auto& report = report_result->value()->report; |
| |
| ASSERT_TRUE(report.has_sensor()); |
| const fuchsia_input_report::wire::SensorInputReport& sensor_report = report.sensor(); |
| EXPECT_TRUE(sensor_report.has_values()); |
| EXPECT_EQ(4, sensor_report.values().count()); |
| |
| EXPECT_EQ(kIlluminanceTestVal, sensor_report.values()[0]); |
| EXPECT_EQ(kRedTestVal, sensor_report.values()[1]); |
| EXPECT_EQ(kBlueTestVal, sensor_report.values()[2]); |
| EXPECT_EQ(kGreenTestVal, sensor_report.values()[3]); |
| } |
| |
| { |
| // Requesting a different device type should fail. |
| const auto result = sync_client->GetInputReport(fuchsia_input_report::wire::DeviceType::kTouch); |
| ASSERT_TRUE(result.ok()); |
| EXPECT_TRUE(result->is_error()); |
| } |
| } |
| |
| TEST_F(HidDevTest, GetInputReportMultipleDevices) { |
| size_t touch_desc_size; |
| const uint8_t* touch_desc_ptr = get_acer12_touch_report_desc(&touch_desc_size); |
| std::vector<uint8_t> touch_desc_vector(touch_desc_ptr, touch_desc_ptr + touch_desc_size); |
| fake_hid_.SetReportDesc(touch_desc_vector); |
| |
| device_->Bind(); |
| |
| auto sync_client = GetSyncClient(); |
| |
| acer12_stylus_t report_data = {}; |
| report_data.rpt_id = ACER12_RPT_ID_STYLUS; |
| report_data.status = 0; |
| report_data.x = 1; |
| report_data.y = 2; |
| report_data.pressure = 3; |
| |
| std::vector<uint8_t> report_vector( |
| reinterpret_cast<uint8_t*>(&report_data), |
| reinterpret_cast<uint8_t*>(&report_data) + sizeof(report_data)); |
| |
| fake_hid_.HidDeviceSetReport(HID_REPORT_TYPE_INPUT, ACER12_RPT_ID_STYLUS, report_vector.data(), |
| report_vector.size()); |
| |
| // There are two devices with this type (finger and stylus), so this should fail. |
| const auto result = sync_client->GetInputReport(fuchsia_input_report::wire::DeviceType::kTouch); |
| ASSERT_TRUE(result.ok()); |
| EXPECT_TRUE(result->is_error()); |
| } |
| |
| } // namespace hid_input_report_dev |