| // 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 "button_checker.h" | |
| #include <fcntl.h> | |
| #include <lib/fdio/fdio.h> | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <lib/zx/channel.h> | |
| #include <unistd.h> | |
| #include <cstring> | |
| #include <filesystem> | |
| #include <iostream> | |
| #include <string> | |
| #include <fbl/unique_fd.h> | |
| constexpr auto kDevicePath = "/dev/class/input"; | |
| constexpr auto kTag = "button_checker"; | |
| std::unique_ptr<ButtonChecker> ButtonChecker::Create() { | |
| auto checker = std::make_unique<ButtonChecker>(); | |
| std::error_code ec{}; | |
| auto it = std::filesystem::directory_iterator(kDevicePath, ec); | |
| if (ec) { | |
| FX_LOGST(ERROR, kTag) << "Unable to open " << kDevicePath << ": " << ec.message(); | |
| return nullptr; | |
| } | |
| for (const auto& entry : it) { | |
| auto device = BindDevice(entry.path()); | |
| hid::ReportField mute_field{}; | |
| if (device && !GetMuteFieldForDevice(device, &mute_field)) { | |
| checker->devices_.push_back(std::make_pair(std::move(device), std::move(mute_field))); | |
| } | |
| } | |
| if (checker->devices_.size() == 0) { | |
| FX_LOGST(WARNING, kTag) << "Zero devices were bound from " << kDevicePath; | |
| return nullptr; | |
| } | |
| return checker; | |
| } | |
| ButtonChecker::ButtonState ButtonChecker::GetMuteState() { | |
| auto state = ButtonState::UNKNOWN; | |
| for (auto& device : devices_) { | |
| // Get the report for the mute field. | |
| zx_status_t status_return = ZX_OK; | |
| std::vector<uint8_t> report; | |
| zx_status_t status = device.first->GetReport(fuchsia::hardware::input::ReportType::INPUT, | |
| device.second.report_id, &status_return, &report); | |
| if (status != ZX_OK) { | |
| FX_PLOGST(ERROR, kTag, status); | |
| return ButtonState::UNKNOWN; | |
| } | |
| if (status_return != ZX_OK) { | |
| FX_PLOGST(ERROR, kTag, status_return); | |
| return ButtonState::UNKNOWN; | |
| } | |
| // Extract the value from the report. | |
| double field_value = 0.0; | |
| if (!hid::ExtractAsUnit(report.data(), report.size(), device.second.attr, &field_value)) { | |
| FX_LOGST(ERROR, kTag) << "Failed to extract HID field value"; | |
| return ButtonState::UNKNOWN; | |
| } | |
| ButtonState state_for_device = field_value > 0 ? ButtonState::DOWN : ButtonState::UP; | |
| // Make sure that devices don't have conflicting states. | |
| if (state != ButtonState::UNKNOWN && state != state_for_device) { | |
| FX_LOGST(ERROR, kTag) << "Conflicting states reported by different devices"; | |
| return ButtonState::UNKNOWN; | |
| } | |
| state = state_for_device; | |
| } | |
| return state; | |
| } | |
| fuchsia::hardware::input::DeviceSyncPtr ButtonChecker::BindDevice(const std::string& path) { | |
| // Open the device. | |
| int result = open(path.c_str(), O_RDONLY); | |
| if (result < 0) { | |
| FX_LOGST(ERROR, kTag) << "Error accessing " << path; | |
| return nullptr; | |
| } | |
| fbl::unique_fd fd(result); | |
| // Get a channel to its services. | |
| zx::channel channel; | |
| zx_status_t status = fdio_get_service_handle(fd.get(), channel.reset_and_get_address()); | |
| if (status != ZX_OK) { | |
| FX_PLOGST(ERROR, kTag, status) << "Error getting channel from device " << path; | |
| return nullptr; | |
| } | |
| // Bind to the channel. | |
| fuchsia::hardware::input::DeviceSyncPtr device; | |
| device.Bind(std::move(channel)); | |
| return device; | |
| } | |
| bool ButtonChecker::GetMuteFieldForDevice(fuchsia::hardware::input::DeviceSyncPtr& device, | |
| hid::ReportField* mute_field_out) { | |
| FX_CHECK(mute_field_out); | |
| auto kMuteButtonUsage = | |
| hid::USAGE(hid::usage::Page::kTelephony, hid::usage::Telephony::kPhoneMute); | |
| // Get the report descriptor. | |
| std::vector<uint8_t> desc; | |
| zx_status_t status = device->GetReportDesc(&desc); | |
| if (status != ZX_OK) { | |
| FX_PLOGST(ERROR, kTag, status); | |
| return true; | |
| } | |
| // Parse the descriptor. | |
| hid::DeviceDescriptor* device_descriptor_ptr = nullptr; | |
| auto parse_result = hid::ParseReportDescriptor(desc.data(), desc.size(), &device_descriptor_ptr); | |
| if (parse_result != hid::kParseOk) { | |
| FX_LOGST(ERROR, kTag) << "HID Parse Failure: " << parse_result; | |
| return true; | |
| } | |
| std::unique_ptr<hid::DeviceDescriptor, decltype(&hid::FreeDeviceDescriptor)> device_descriptor( | |
| device_descriptor_ptr, &hid::FreeDeviceDescriptor); | |
| // Loop over the descriptor reports and their fields. | |
| bool found_mute_field = false; | |
| for (size_t report_index = 0; report_index < device_descriptor->rep_count; ++report_index) { | |
| const auto& report_descriptor = device_descriptor->report[report_index]; | |
| for (size_t field_index = 0; field_index < report_descriptor.input_count; ++field_index) { | |
| const auto& input_field = report_descriptor.input_fields[field_index]; | |
| // If the report desc is for the mute button, save out the field metadata. | |
| if (input_field.attr.usage == kMuteButtonUsage) { | |
| if (found_mute_field && memcmp(mute_field_out, &input_field, sizeof(input_field)) != 0) { | |
| FX_LOGST(ERROR, kTag) << "Multiple mute fields found in same device"; | |
| return true; | |
| } | |
| *mute_field_out = input_field; | |
| found_mute_field = true; | |
| } | |
| } | |
| } | |
| return !found_mute_field; | |
| } | |
| bool VerifyDeviceUnmuted(bool consider_unknown_as_unmuted) { | |
| auto state = ButtonChecker::ButtonState::UNKNOWN; | |
| auto checker = ButtonChecker::Create(); | |
| if (checker) { | |
| state = checker->GetMuteState(); | |
| } | |
| if (state == ButtonChecker::ButtonState::UP) { | |
| return true; | |
| } | |
| if (state == ButtonChecker::ButtonState::UNKNOWN) { | |
| std::cerr << "**************************************************\n" | |
| "* WARNING: DEVICE MUTE STATE UNKNOWN. CAMERA MAY *\n" | |
| "* NOT OPERATE AND TESTS MAY BE SKIPPED! *\n" | |
| "**************************************************\n"; | |
| std::cerr.flush(); | |
| return consider_unknown_as_unmuted; | |
| } | |
| FX_DCHECK(state == ButtonChecker::ButtonState::DOWN); | |
| std::cerr << "**********************************************\n" | |
| "* WARNING: DEVICE IS MUTED. CAMERA WILL NOT *\n" | |
| "* OPERATE AND TESTS MAY BE SKIPPED! *\n" | |
| "**********************************************\n"; | |
| std::cerr.flush(); | |
| return false; | |
| } |