| // 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/ui/input2/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fake_ddk/fake_ddk.h> |
| #include <lib/fidl-async/cpp/bind.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/eventpair.h> |
| #include <unistd.h> |
| #include <zircon/syscalls.h> |
| |
| #include <ddk/metadata/buttons.h> |
| #include <ddktl/protocol/hiddevice.h> |
| #include <hid/ambient-light.h> |
| #include <hid/boot.h> |
| #include <hid/buttons.h> |
| #include <hid/paradise.h> |
| #include <hid/usages.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "input-report.h" |
| |
| namespace hid_input_report_dev { |
| |
| struct ProtocolDeviceOps { |
| const zx_protocol_device_t* ops; |
| void* ctx; |
| }; |
| |
| // Create our own Fake Ddk Bind class. We want to save the last device arguments that |
| // have been seen, so the test can get ahold of the instance device and test |
| // Reads and Writes on it. |
| class Binder : public fake_ddk::Bind { |
| public: |
| zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args, |
| zx_device_t** out) override { |
| zx_status_t status; |
| |
| if (args && args->ops) { |
| if (args->ops->message) { |
| fake_ddk::FidlMessenger messenger; |
| fidl_clients_.emplace_back(std::make_unique<fake_ddk::FidlMessenger>()); |
| if ((status = fidl_clients_[fidl_clients_.size() - 1]->SetMessageOp( |
| args->ctx, args->ops->message)) < 0) { |
| return status; |
| } |
| } |
| } |
| |
| *out = fake_ddk::kFakeDevice; |
| add_called_ = true; |
| |
| last_ops_.ctx = args->ctx; |
| last_ops_.ops = args->ops; |
| |
| return ZX_OK; |
| } |
| |
| ProtocolDeviceOps GetLastDeviceOps() { return last_ops_; } |
| zx::channel& GetLastFidlClient() { return fidl_clients_[fidl_clients_.size() - 1]->local(); } |
| |
| private: |
| std::vector<std::unique_ptr<fake_ddk::FidlMessenger>> fidl_clients_; |
| ProtocolDeviceOps last_ops_; |
| }; |
| |
| 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() {} |
| |
| 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) { |
| listener_.ops->receive_report(listener_.ctx, report.data(), report.size(), |
| zx_clock_get_monotonic()); |
| } |
| |
| 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_); |
| device_ = new InputReport(fake_ddk::kFakeParent, client_); |
| // Each test is responsible for calling |device_->Bind()|. |
| } |
| |
| void TearDown() override { |
| device_->DdkAsyncRemove(); |
| EXPECT_TRUE(ddk_.Ok()); |
| |
| // This should delete the object, which means this test should not leak. |
| device_->DdkRelease(); |
| } |
| |
| protected: |
| Binder ddk_; |
| FakeHidDevice fake_hid_; |
| InputReport* 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_F(HidDevTest, InstanceLifetimeTest) { |
| 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()); |
| |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| 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()); |
| |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| fuchsia_input_report::InputDevice::ResultOf::GetDescriptor result = sync_client.GetDescriptor(); |
| ASSERT_OK(result.status()); |
| |
| auto& desc = result.Unwrap()->descriptor; |
| ASSERT_TRUE(desc.has_mouse()); |
| ASSERT_TRUE(desc.mouse().has_input()); |
| fuchsia_input_report::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); |
| |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| 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()); |
| |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| fuchsia_input_report::InputDevice::ResultOf::GetDescriptor result = sync_client.GetDescriptor(); |
| ASSERT_OK(result.status()); |
| |
| hid_device_info_t info; |
| fake_hid_.HidDeviceGetHidDeviceInfo(&info); |
| |
| auto& desc = result->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); |
| |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| TEST_F(HidDevTest, GetReportTest) { |
| std::vector<uint8_t> boot_mouse(boot_mouse_desc, boot_mouse_desc + sizeof(boot_mouse_desc)); |
| fake_hid_.SetReportDesc(boot_mouse); |
| |
| device_->Bind(); |
| |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| |
| // Spoof send a report. |
| std::vector<uint8_t> sent_report = {0xFF, 0x50, 0x70}; |
| fake_hid_.SendReport(sent_report); |
| |
| // Get the report. |
| fuchsia_input_report::InputDevice::ResultOf::GetReports result = sync_client.GetReports(); |
| ASSERT_OK(result.status()); |
| auto& reports = result.Unwrap()->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()); |
| fidl::VectorView<uint8_t> pressed_buttons = std::move(mouse.pressed_buttons()); |
| for (size_t i = 0; i < pressed_buttons.count(); i++) { |
| ASSERT_EQ(i + 1, pressed_buttons[i]); |
| } |
| |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| 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(); |
| |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| |
| // Get an InputReportsReader. |
| llcpp::fuchsia::input::report::InputReportsReader::SyncClient reader; |
| { |
| zx::channel token_server, token_client; |
| ASSERT_OK(zx::channel::create(0, &token_server, &token_client)); |
| auto result = sync_client.GetInputReportsReader(std::move(token_server)); |
| ASSERT_OK(result.status()); |
| reader = llcpp::fuchsia::input::report::InputReportsReader::SyncClient(std::move(token_client)); |
| } |
| |
| // 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->result.is_err()); |
| auto& reports = result->result.response().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]); |
| } |
| |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| TEST_F(HidDevTest, ReadInputReportsSecondClientFails) { |
| std::vector<uint8_t> boot_mouse(boot_mouse_desc, boot_mouse_desc + sizeof(boot_mouse_desc)); |
| fake_hid_.SetReportDesc(boot_mouse); |
| |
| device_->Bind(); |
| |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| |
| // Get an InputReportsReader. |
| llcpp::fuchsia::input::report::InputReportsReader::SyncClient reader; |
| { |
| zx::channel token_server, token_client; |
| ASSERT_OK(zx::channel::create(0, &token_server, &token_client)); |
| auto result = sync_client.GetInputReportsReader(std::move(token_server)); |
| ASSERT_OK(result.status()); |
| reader = llcpp::fuchsia::input::report::InputReportsReader::SyncClient(std::move(token_client)); |
| } |
| |
| // Try and get a second Reader which will fail since we have the first. |
| llcpp::fuchsia::input::report::InputReportsReader::SyncClient failed_reader; |
| { |
| zx::channel token_server, token_client; |
| ASSERT_OK(zx::channel::create(0, &token_server, &token_client)); |
| auto result = sync_client.GetInputReportsReader(std::move(token_server)); |
| ASSERT_OK(result.status()); |
| failed_reader = |
| llcpp::fuchsia::input::report::InputReportsReader::SyncClient(std::move(token_client)); |
| } |
| auto result = failed_reader.ReadInputReports(); |
| ASSERT_EQ(result.status(), ZX_ERR_PEER_CLOSED); |
| |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| 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(); |
| |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| |
| // Get an InputReportsReader. |
| |
| async::Loop loop = async::Loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fidl::Client<llcpp::fuchsia::input::report::InputReportsReader> reader; |
| { |
| zx::channel token_server, token_client; |
| ASSERT_OK(zx::channel::create(0, &token_server, &token_client)); |
| auto result = sync_client.GetInputReportsReader(std::move(token_server)); |
| ASSERT_OK(result.status()); |
| ASSERT_OK(reader.Bind(std::move(token_client), loop.dispatcher())); |
| } |
| |
| // Read the report. This will hang until a report is sent. |
| auto status = reader->ReadInputReports( |
| [&](::llcpp::fuchsia::input::report::InputReportsReader_ReadInputReports_Result result) { |
| ASSERT_FALSE(result.is_err()); |
| auto& reports = result.response().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(); |
| }); |
| ASSERT_OK(status.status()); |
| loop.RunUntilIdle(); |
| |
| // Send the report. |
| std::vector<uint8_t> sent_report = {0xFF, 0x50, 0x70}; |
| fake_hid_.SendReport(sent_report); |
| |
| loop.Run(); |
| |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| 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(); |
| |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| |
| // Get the report descriptor. |
| fuchsia_input_report::InputDevice::ResultOf::GetDescriptor result = sync_client.GetDescriptor(); |
| ASSERT_OK(result.status()); |
| fuchsia_input_report::DeviceDescriptor& desc = result->descriptor; |
| ASSERT_TRUE(desc.has_sensor()); |
| ASSERT_TRUE(desc.sensor().has_input()); |
| fuchsia_input_report::SensorInputDescriptor& sensor_desc = desc.sensor().input(); |
| ASSERT_TRUE(sensor_desc.has_values()); |
| ASSERT_EQ(4, sensor_desc.values().count()); |
| |
| ASSERT_EQ(sensor_desc.values()[0].type, fuchsia_input_report::SensorType::LIGHT_ILLUMINANCE); |
| ASSERT_EQ(sensor_desc.values()[0].axis.unit.type, fuchsia_input_report::UnitType::NONE); |
| |
| ASSERT_EQ(sensor_desc.values()[1].type, fuchsia_input_report::SensorType::LIGHT_RED); |
| ASSERT_EQ(sensor_desc.values()[1].axis.unit.type, fuchsia_input_report::UnitType::NONE); |
| |
| ASSERT_EQ(sensor_desc.values()[2].type, fuchsia_input_report::SensorType::LIGHT_BLUE); |
| ASSERT_EQ(sensor_desc.values()[2].axis.unit.type, fuchsia_input_report::UnitType::NONE); |
| |
| ASSERT_EQ(sensor_desc.values()[3].type, fuchsia_input_report::SensorType::LIGHT_GREEN); |
| ASSERT_EQ(sensor_desc.values()[3].axis.unit.type, fuchsia_input_report::UnitType::NONE); |
| |
| // 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. |
| fuchsia_input_report::InputDevice::ResultOf::GetReports report_result = sync_client.GetReports(); |
| ASSERT_OK(result.status()); |
| |
| fidl::VectorView<fuchsia_input_report::InputReport>& reports = report_result->reports; |
| ASSERT_EQ(1, reports.count()); |
| |
| ASSERT_TRUE(reports[0].has_sensor()); |
| fuchsia_input_report::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]); |
| |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| 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(); |
| |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| |
| // 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. |
| fuchsia_input_report::InputDevice::ResultOf::GetReports result = sync_client.GetReports(); |
| ASSERT_OK(result.status()); |
| auto& reports = result.Unwrap()->reports; |
| |
| ASSERT_EQ(1, reports.count()); |
| |
| auto& report = reports[0]; |
| auto& touch = report.touch(); |
| ASSERT_TRUE(touch.has_contacts()); |
| ASSERT_EQ(1, touch.contacts().count()); |
| 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()); |
| |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| 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(); |
| |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| fuchsia_input_report::InputDevice::ResultOf::GetDescriptor result = sync_client.GetDescriptor(); |
| ASSERT_OK(result.status()); |
| ASSERT_TRUE(result->descriptor.has_touch()); |
| ASSERT_TRUE(result->descriptor.touch().has_input()); |
| fuchsia_input_report::TouchInputDescriptor& touch = result->descriptor.touch().input(); |
| |
| ASSERT_EQ(fuchsia_input_report::TouchType::TOUCHPAD, touch.touch_type()); |
| |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| 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(); |
| |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| // 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. |
| fuchsia_input_report::InputDevice::ResultOf::GetReports result = sync_client.GetReports(); |
| ASSERT_OK(result.status()); |
| auto& reports = result->reports; |
| |
| ASSERT_EQ(1, reports.count()); |
| |
| auto& report = reports[0]; |
| auto& keyboard = report.keyboard(); |
| ASSERT_TRUE(keyboard.has_pressed_keys()); |
| ASSERT_EQ(3, keyboard.pressed_keys().count()); |
| EXPECT_EQ(llcpp::fuchsia::ui::input2::Key::A, keyboard.pressed_keys()[0]); |
| EXPECT_EQ(llcpp::fuchsia::ui::input2::Key::UP, keyboard.pressed_keys()[1]); |
| EXPECT_EQ(llcpp::fuchsia::ui::input2::Key::B, keyboard.pressed_keys()[2]); |
| |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| 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(); |
| // Open an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| // Make an output report. |
| std::array<hid_input_report::fuchsia_input_report::LedType, 2> led_array; |
| led_array[0] = hid_input_report::fuchsia_input_report::LedType::NUM_LOCK; |
| led_array[1] = hid_input_report::fuchsia_input_report::LedType::SCROLL_LOCK; |
| auto led_view = fidl::unowned_vec(led_array); |
| auto keyboard_builder = |
| hid_input_report::fuchsia_input_report::KeyboardOutputReport::UnownedBuilder(); |
| keyboard_builder.set_enabled_leds(fidl::unowned_ptr(&led_view)); |
| hid_input_report::fuchsia_input_report::KeyboardOutputReport fidl_keyboard = |
| keyboard_builder.build(); |
| auto builder = hid_input_report::fuchsia_input_report::OutputReport::UnownedBuilder(); |
| builder.set_keyboard(fidl::unowned_ptr(&fidl_keyboard)); |
| // Send the report. |
| fuchsia_input_report::InputDevice::ResultOf::SendOutputReport response = |
| sync_client.SendOutputReport(builder.build()); |
| ASSERT_OK(response.status()); |
| ASSERT_FALSE(response->result.is_err()); |
| // 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); |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| 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 DdkOpen(). |
| { |
| 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 an instance device. |
| zx_device_t* open_dev; |
| ASSERT_OK(device_->DdkOpen(&open_dev, 0)); |
| // Opening the device created an instance device to be created, and we can |
| // get its arguments here. |
| ProtocolDeviceOps dev_ops = ddk_.GetLastDeviceOps(); |
| |
| auto sync_client = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| |
| // Get the report descriptor. |
| fuchsia_input_report::InputDevice::ResultOf::GetDescriptor result = sync_client.GetDescriptor(); |
| ASSERT_OK(result.status()); |
| fuchsia_input_report::DeviceDescriptor& desc = result->descriptor; |
| ASSERT_TRUE(desc.has_consumer_control()); |
| ASSERT_TRUE(desc.consumer_control().has_input()); |
| fuchsia_input_report::ConsumerControlInputDescriptor& consumer_control_desc = |
| desc.consumer_control().input(); |
| ASSERT_TRUE(consumer_control_desc.has_buttons()); |
| ASSERT_EQ(4, consumer_control_desc.buttons().count()); |
| |
| ASSERT_EQ(consumer_control_desc.buttons()[0], |
| llcpp::fuchsia::input::report::ConsumerControlButton::VOLUME_UP); |
| ASSERT_EQ(consumer_control_desc.buttons()[1], |
| llcpp::fuchsia::input::report::ConsumerControlButton::VOLUME_DOWN); |
| ASSERT_EQ(consumer_control_desc.buttons()[2], |
| llcpp::fuchsia::input::report::ConsumerControlButton::REBOOT); |
| ASSERT_EQ(consumer_control_desc.buttons()[3], |
| llcpp::fuchsia::input::report::ConsumerControlButton::MIC_MUTE); |
| |
| // 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 reports. |
| fuchsia_input_report::InputDevice::ResultOf::GetReports report_result = sync_client.GetReports(); |
| ASSERT_OK(report_result.status()); |
| |
| fidl::VectorView<fuchsia_input_report::InputReport>& reports = report_result->reports; |
| ASSERT_EQ(2, reports.count()); |
| |
| // Check the initial report. |
| { |
| ASSERT_TRUE(reports[0].has_consumer_control()); |
| fuchsia_input_report::ConsumerControlInputReport& 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()); |
| fuchsia_input_report::ConsumerControlInputReport& report = reports[1].consumer_control(); |
| EXPECT_TRUE(report.has_pressed_buttons()); |
| EXPECT_EQ(3, report.pressed_buttons().count()); |
| |
| EXPECT_EQ(report.pressed_buttons()[0], |
| llcpp::fuchsia::input::report::ConsumerControlButton::VOLUME_UP); |
| EXPECT_EQ(report.pressed_buttons()[1], |
| llcpp::fuchsia::input::report::ConsumerControlButton::REBOOT); |
| EXPECT_EQ(report.pressed_buttons()[2], |
| llcpp::fuchsia::input::report::ConsumerControlButton::MIC_MUTE); |
| } |
| |
| // Close the instance device. |
| dev_ops.ops->close(dev_ops.ctx, 0); |
| } |
| |
| 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 DdkOpen(). |
| { |
| 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 (client a). |
| zx_device_t* dev_a; |
| ASSERT_OK(device_->DdkOpen(&dev_a, 0)); |
| ProtocolDeviceOps ops_a = ddk_.GetLastDeviceOps(); |
| auto client_a = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| |
| // Open the device (client b). |
| zx_device_t* dev_b; |
| ASSERT_OK(device_->DdkOpen(&dev_b, 0)); |
| ProtocolDeviceOps ops_b = ddk_.GetLastDeviceOps(); |
| auto client_b = |
| fuchsia_input_report::InputDevice::SyncClient(std::move(ddk_.GetLastFidlClient())); |
| |
| // Get and check report a. |
| { |
| fuchsia_input_report::InputDevice::ResultOf::GetReports reports_a = client_a.GetReports(); |
| ASSERT_OK(reports_a.status()); |
| ASSERT_EQ(1, reports_a->reports.count()); |
| |
| ASSERT_TRUE(reports_a->reports[0].has_consumer_control()); |
| fuchsia_input_report::ConsumerControlInputReport& report = |
| reports_a->reports[0].consumer_control(); |
| EXPECT_TRUE(report.has_pressed_buttons()); |
| EXPECT_EQ(0, report.pressed_buttons().count()); |
| } |
| |
| // Get and check report b. |
| { |
| fuchsia_input_report::InputDevice::ResultOf::GetReports reports_b = client_b.GetReports(); |
| ASSERT_OK(reports_b.status()); |
| ASSERT_EQ(1, reports_b->reports.count()); |
| |
| ASSERT_TRUE(reports_b->reports[0].has_consumer_control()); |
| fuchsia_input_report::ConsumerControlInputReport& report = |
| reports_b->reports[0].consumer_control(); |
| EXPECT_TRUE(report.has_pressed_buttons()); |
| EXPECT_EQ(0, report.pressed_buttons().count()); |
| } |
| |
| // Close the devices. |
| ops_a.ops->close(ops_a.ctx, 0); |
| ops_b.ops->close(ops_b.ctx, 0); |
| } |
| |
| } // namespace hid_input_report_dev |