blob: 76c77e1c9eace66f12db103e55f33e73a9909eb7 [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.
use {
crate::{
consumer_controls_binding, keyboard_binding, light_sensor_binding, metrics, mouse_binding,
touch_binding,
},
anyhow::{format_err, Error},
async_trait::async_trait,
async_utils::hanging_get::client::HangingGetStream,
fidl::endpoints::Proxy,
fidl_fuchsia_input_report as fidl_input_report,
fidl_fuchsia_input_report::{InputDeviceMarker, InputReport},
fidl_fuchsia_io as fio, fuchsia_async as fasync,
fuchsia_inspect::health::Reporter,
fuchsia_inspect::{
ExponentialHistogramParams, HistogramProperty as _, NumericProperty, Property,
},
fuchsia_trace as ftrace, fuchsia_zircon as zx,
futures::{
channel::mpsc::{UnboundedReceiver, UnboundedSender},
stream::StreamExt,
},
metrics_registry::*,
std::path::PathBuf,
};
pub use input_device_constants::InputDeviceType;
/// The path to the input-report directory.
pub static INPUT_REPORT_PATH: &str = "/dev/class/input-report";
const LATENCY_HISTOGRAM_PROPERTIES: ExponentialHistogramParams<i64> = ExponentialHistogramParams {
floor: 0,
initial_step: 1,
step_multiplier: 10,
// Seven buckets allows us to report
// * < 0 msec (added automatically by Inspect)
// * 0-1 msec
// * 1-10 msec
// * 10-100 msec
// * 100-1000 msec
// * 1-10 sec
// * 10-100 sec
// * 100-1000 sec
// * >1000 sec (added automatically by Inspect)
buckets: 7,
};
/// An [`InputDeviceStatus`] is tied to an [`InputDeviceBinding`] and provides properties
/// detailing its Inspect status.
pub struct InputDeviceStatus {
/// Function for getting the current timestamp. Enables unit testing
/// of the latency histogram.
now: Box<dyn Fn() -> zx::Time>,
/// A node that contains the state below.
_node: fuchsia_inspect::Node,
/// The total number of reports received by the device driver.
reports_received_count: fuchsia_inspect::UintProperty,
/// The number of reports received by the device driver that did
/// not get converted into InputEvents processed by InputPipeline.
reports_filtered_count: fuchsia_inspect::UintProperty,
/// The total number of events generated from received
/// InputReports that were sent to InputPipeline.
events_generated: fuchsia_inspect::UintProperty,
/// The event time the last received InputReport was generated.
last_received_timestamp_ns: fuchsia_inspect::UintProperty,
/// The event time the last InputEvent was generated.
last_generated_timestamp_ns: fuchsia_inspect::UintProperty,
// This node records the health status of the `InputDevice`.
pub health_node: fuchsia_inspect::health::Node,
/// Histogram of latency from the driver timestamp for an `InputReport` until
/// the time at which the report was seen by the respective binding. Reported
/// in milliseconds, because values less than 1 msec aren't especially
/// interesting.
driver_to_binding_latency_ms: fuchsia_inspect::IntExponentialHistogramProperty,
}
impl InputDeviceStatus {
pub fn new(device_node: fuchsia_inspect::Node) -> Self {
Self::new_internal(device_node, Box::new(zx::Time::get_monotonic))
}
fn new_internal(device_node: fuchsia_inspect::Node, now: Box<dyn Fn() -> zx::Time>) -> Self {
let mut health_node = fuchsia_inspect::health::Node::new(&device_node);
health_node.set_starting_up();
let reports_received_count = device_node.create_uint("reports_received_count", 0);
let reports_filtered_count = device_node.create_uint("reports_filtered_count", 0);
let events_generated = device_node.create_uint("events_generated", 0);
let last_received_timestamp_ns = device_node.create_uint("last_received_timestamp_ns", 0);
let last_generated_timestamp_ns = device_node.create_uint("last_generated_timestamp_ns", 0);
let driver_to_binding_latency_ms = device_node.create_int_exponential_histogram(
"driver_to_binding_latency_ms",
LATENCY_HISTOGRAM_PROPERTIES,
);
Self {
now,
_node: device_node,
reports_received_count,
reports_filtered_count,
events_generated,
last_received_timestamp_ns,
last_generated_timestamp_ns,
health_node,
driver_to_binding_latency_ms,
}
}
pub fn count_received_report(&self, report: &InputReport) {
self.reports_received_count.add(1);
match report.event_time {
Some(event_time) => {
self.driver_to_binding_latency_ms
.insert(((self.now)() - zx::Time::from_nanos(event_time)).into_millis());
self.last_received_timestamp_ns.set(event_time.try_into().unwrap());
}
None => (),
}
}
pub fn count_filtered_report(&self) {
self.reports_filtered_count.add(1);
}
pub fn count_generated_event(&self, event: InputEvent) {
self.events_generated.add(1);
self.last_generated_timestamp_ns.set(event.event_time.into_nanos().try_into().unwrap());
}
}
/// An [`InputEvent`] holds information about an input event and the device that produced the event.
#[derive(Clone, Debug, PartialEq)]
pub struct InputEvent {
/// The `device_event` contains the device-specific input event information.
pub device_event: InputDeviceEvent,
/// The `device_descriptor` contains static information about the device that generated the
/// input event.
pub device_descriptor: InputDeviceDescriptor,
/// The time in nanoseconds when the event was first recorded.
pub event_time: zx::Time,
/// The handled state of the event.
pub handled: Handled,
pub trace_id: Option<ftrace::Id>,
}
/// An [`UnhandledInputEvent`] is like an [`InputEvent`], except that the data represents an
/// event that has not been handled.
/// * Event producers must not use this type to carry data for an event that was already
/// handled.
/// * Event consumers should assume that the event has not been handled.
#[derive(Clone, Debug, PartialEq)]
pub struct UnhandledInputEvent {
/// The `device_event` contains the device-specific input event information.
pub device_event: InputDeviceEvent,
/// The `device_descriptor` contains static information about the device that generated the
/// input event.
pub device_descriptor: InputDeviceDescriptor,
/// The time in nanoseconds when the event was first recorded.
pub event_time: zx::Time,
pub trace_id: Option<ftrace::Id>,
}
/// An [`InputDeviceEvent`] represents an input event from an input device.
///
/// [`InputDeviceEvent`]s contain more context than the raw [`InputReport`] they are parsed from.
/// For example, [`KeyboardEvent`] contains all the pressed keys, as well as the key's
/// phase (pressed, released, etc.).
///
/// Each [`InputDeviceBinding`] generates the type of [`InputDeviceEvent`]s that are appropriate
/// for their device.
#[derive(Clone, Debug, PartialEq)]
pub enum InputDeviceEvent {
Keyboard(keyboard_binding::KeyboardEvent),
LightSensor(light_sensor_binding::LightSensorEvent),
ConsumerControls(consumer_controls_binding::ConsumerControlsEvent),
Mouse(mouse_binding::MouseEvent),
TouchScreen(touch_binding::TouchScreenEvent),
Touchpad(touch_binding::TouchpadEvent),
#[cfg(test)]
Fake,
}
/// An [`InputDescriptor`] describes the ranges of values a particular input device can generate.
///
/// For example, a [`InputDescriptor::Keyboard`] contains the keys available on the keyboard,
/// and a [`InputDescriptor::Touch`] contains the maximum number of touch contacts and the
/// range of x- and y-values each contact can take on.
///
/// The descriptor is sent alongside [`InputDeviceEvent`]s so clients can, for example, convert a
/// touch coordinate to a display coordinate. The descriptor is not expected to change for the
/// lifetime of a device binding.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum InputDeviceDescriptor {
Keyboard(keyboard_binding::KeyboardDeviceDescriptor),
LightSensor(light_sensor_binding::LightSensorDeviceDescriptor),
ConsumerControls(consumer_controls_binding::ConsumerControlsDeviceDescriptor),
Mouse(mouse_binding::MouseDeviceDescriptor),
TouchScreen(touch_binding::TouchScreenDeviceDescriptor),
Touchpad(touch_binding::TouchpadDeviceDescriptor),
#[cfg(test)]
Fake,
}
impl From<keyboard_binding::KeyboardDeviceDescriptor> for InputDeviceDescriptor {
fn from(b: keyboard_binding::KeyboardDeviceDescriptor) -> Self {
InputDeviceDescriptor::Keyboard(b)
}
}
// Whether the event is consumed by an [`InputHandler`].
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Handled {
// The event has been handled.
Yes,
// The event has not been handled.
No,
}
/// An [`InputDeviceBinding`] represents a binding to an input device (e.g., a mouse).
///
/// [`InputDeviceBinding`]s expose information about the bound device. For example, a
/// [`MouseBinding`] exposes the ranges of possible x and y values the device can generate.
///
/// An [`InputPipeline`] manages [`InputDeviceBinding`]s and holds the receiving end of a channel
/// that an [`InputDeviceBinding`]s send [`InputEvent`]s over.
/// ```
#[async_trait]
pub trait InputDeviceBinding: Send {
/// Returns information about the input device.
fn get_device_descriptor(&self) -> InputDeviceDescriptor;
/// Returns the input event stream's sender.
fn input_event_sender(&self) -> UnboundedSender<InputEvent>;
}
/// Initializes the input report stream for the device bound to `device_proxy`.
///
/// Spawns a future which awaits input reports from the device and forwards them to
/// clients via `event_sender`.
///
/// # Parameters
/// - `device_proxy`: The device proxy which is used to get input reports.
/// - `device_descriptor`: The descriptor of the device bound to `device_proxy`.
/// - `event_sender`: The channel to send InputEvents to.
/// - `metrics_logger`: The metrics logger.
/// - `process_reports`: A function that generates InputEvent(s) from an InputReport and the
/// InputReport that precedes it. Each type of input device defines how it
/// processes InputReports.
///
pub fn initialize_report_stream<InputDeviceProcessReportsFn>(
device_proxy: fidl_input_report::InputDeviceProxy,
device_descriptor: InputDeviceDescriptor,
mut event_sender: UnboundedSender<InputEvent>,
inspect_status: InputDeviceStatus,
metrics_logger: metrics::MetricsLogger,
mut process_reports: InputDeviceProcessReportsFn,
) where
InputDeviceProcessReportsFn: 'static
+ Send
+ FnMut(
InputReport,
Option<InputReport>,
&InputDeviceDescriptor,
&mut UnboundedSender<InputEvent>,
&InputDeviceStatus,
&metrics::MetricsLogger,
) -> (Option<InputReport>, Option<UnboundedReceiver<InputEvent>>),
{
fasync::Task::local(async move {
let mut previous_report: Option<InputReport> = None;
let (report_reader, server_end) = match fidl::endpoints::create_proxy() {
Ok(res) => res,
Err(e) => {
metrics_logger.log_error(
InputPipelineErrorMetricDimensionEvent::InputDeviceCreateInputReportProxyFailed,
std::format!("error creating InputReport proxy: {:?}", &e),
);
return; // TODO(https://fxbug.dev/42131965): signal error
}
};
let result = device_proxy.get_input_reports_reader(server_end);
if result.is_err() {
metrics_logger.log_error(
InputPipelineErrorMetricDimensionEvent::InputDeviceGetInputReportsReaderError,
std::format!("error on GetInputReportsReader: {:?}", &result),
);
return; // TODO(https://fxbug.dev/42131965): signal error
}
let mut report_stream = HangingGetStream::new(
report_reader,
fidl_input_report::InputReportsReaderProxy::read_input_reports,
);
loop {
match report_stream.next().await {
Some(Ok(Ok(input_reports))) => {
fuchsia_trace::duration!(c"input", c"input-device-process-reports");
let mut inspect_receiver: Option<UnboundedReceiver<InputEvent>>;
for report in input_reports {
(previous_report, inspect_receiver) = process_reports(
report,
previous_report,
&device_descriptor,
&mut event_sender,
&inspect_status,
&metrics_logger,
);
// If a report generates multiple events asynchronously, we send them over a mpsc channel
// to inspect_receiver. We update the event count on inspect_status here since we cannot
// pass a reference to inspect_status to an async task in process_reports().
match inspect_receiver {
Some(mut receiver) => {
while let Some(event) = receiver.next().await {
inspect_status.count_generated_event(event);
}
}
None => (),
};
}
}
Some(Ok(Err(_service_error))) => break,
Some(Err(_fidl_error)) => break,
None => break,
}
}
// TODO(https://fxbug.dev/42131965): Add signaling for when this loop exits, since it means the device
// binding is no longer functional.
tracing::warn!("initialize_report_stream exited - device binding no longer works");
})
.detach();
}
/// Returns true if the device type of `input_device` matches `device_type`.
///
/// # Parameters
/// - `input_device`: The InputDevice to check the type of.
/// - `device_type`: The type of the device to compare to.
pub async fn is_device_type(
device_descriptor: &fidl_input_report::DeviceDescriptor,
device_type: InputDeviceType,
) -> bool {
// Return if the device type matches the desired `device_type`.
match device_type {
InputDeviceType::ConsumerControls => device_descriptor.consumer_control.is_some(),
InputDeviceType::Mouse => device_descriptor.mouse.is_some(),
InputDeviceType::Touch => device_descriptor.touch.is_some(),
InputDeviceType::Keyboard => device_descriptor.keyboard.is_some(),
InputDeviceType::LightSensor => device_descriptor.sensor.is_some(),
}
}
/// Returns a new [`InputDeviceBinding`] of the given device type.
///
/// # Parameters
/// - `device_type`: The type of the input device.
/// - `device_proxy`: The device proxy which is used to get input reports.
/// - `device_id`: The id of the connected input device.
/// - `input_event_sender`: The channel to send generated InputEvents to.
pub async fn get_device_binding(
device_type: InputDeviceType,
device_proxy: fidl_input_report::InputDeviceProxy,
device_id: u32,
input_event_sender: UnboundedSender<InputEvent>,
device_node: fuchsia_inspect::Node,
metrics_logger: metrics::MetricsLogger,
) -> Result<Box<dyn InputDeviceBinding>, Error> {
match device_type {
InputDeviceType::ConsumerControls => {
let binding = consumer_controls_binding::ConsumerControlsBinding::new(
device_proxy,
device_id,
input_event_sender,
device_node,
metrics_logger,
)
.await?;
Ok(Box::new(binding))
}
InputDeviceType::Mouse => {
let binding = mouse_binding::MouseBinding::new(
device_proxy,
device_id,
input_event_sender,
device_node,
metrics_logger,
)
.await?;
Ok(Box::new(binding))
}
InputDeviceType::Touch => {
let binding = touch_binding::TouchBinding::new(
device_proxy,
device_id,
input_event_sender,
device_node,
metrics_logger,
)
.await?;
Ok(Box::new(binding))
}
InputDeviceType::Keyboard => {
let binding = keyboard_binding::KeyboardBinding::new(
device_proxy,
device_id,
input_event_sender,
device_node,
metrics_logger,
)
.await?;
Ok(Box::new(binding))
}
InputDeviceType::LightSensor => {
let binding = light_sensor_binding::LightSensorBinding::new(
device_proxy,
device_id,
input_event_sender,
device_node,
metrics_logger,
)
.await?;
Ok(Box::new(binding))
}
}
}
/// Returns a proxy to the InputDevice in `entry_path` if it exists.
///
/// # Parameters
/// - `dir_proxy`: The directory containing InputDevice connections.
/// - `entry_path`: The directory entry that contains an InputDevice.
///
/// # Errors
/// If there is an error connecting to the InputDevice in `entry_path`.
pub fn get_device_from_dir_entry_path(
dir_proxy: &fio::DirectoryProxy,
entry_path: &PathBuf,
) -> Result<fidl_input_report::InputDeviceProxy, Error> {
let input_device_path = entry_path.to_str();
if input_device_path.is_none() {
return Err(format_err!("Failed to get entry path as a string."));
}
let (input_device, server) = fidl::endpoints::create_proxy::<InputDeviceMarker>()?;
fdio::service_connect_at(
dir_proxy.as_channel().as_ref(),
input_device_path.unwrap(),
server.into_channel(),
)
.expect("Failed to connect to InputDevice.");
Ok(input_device)
}
/// Returns the event time if it exists, otherwise returns the current time.
///
/// # Parameters
/// - `event_time`: The event time from an InputReport.
pub fn event_time_or_now(event_time: Option<i64>) -> zx::Time {
match event_time {
Some(time) => zx::Time::from_nanos(time),
None => zx::Time::get_monotonic(),
}
}
impl std::convert::From<UnhandledInputEvent> for InputEvent {
fn from(event: UnhandledInputEvent) -> Self {
Self {
device_event: event.device_event,
device_descriptor: event.device_descriptor,
event_time: event.event_time,
handled: Handled::No,
trace_id: event.trace_id,
}
}
}
// Fallible conversion from an InputEvent to an UnhandledInputEvent.
//
// Useful to adapt various functions in the [`testing_utilities`] module
// to work with tests for [`UnhandledInputHandler`]s.
//
// Production code however, should probably just match on the [`InputEvent`].
#[cfg(test)]
impl std::convert::TryFrom<InputEvent> for UnhandledInputEvent {
type Error = anyhow::Error;
fn try_from(event: InputEvent) -> Result<UnhandledInputEvent, Self::Error> {
match event.handled {
Handled::Yes => {
Err(format_err!("Attempted to treat a handled InputEvent as unhandled"))
}
Handled::No => Ok(UnhandledInputEvent {
device_event: event.device_event,
device_descriptor: event.device_descriptor,
event_time: event.event_time,
trace_id: event.trace_id,
}),
}
}
}
impl InputEvent {
/// Marks the event as handled, if `predicate` is `true`.
/// Otherwise, leaves the event unchanged.
pub(crate) fn into_handled_if(self, predicate: bool) -> Self {
if predicate {
Self { handled: Handled::Yes, ..self }
} else {
self
}
}
/// Marks the event as handled.
pub(crate) fn into_handled(self) -> Self {
Self { handled: Handled::Yes, ..self }
}
/// Returns the same event, with modified event time.
pub fn into_with_event_time(self, event_time: zx::Time) -> Self {
Self { event_time, ..self }
}
/// Returns the same event, with modified device descriptor.
#[cfg(test)]
pub fn into_with_device_descriptor(self, device_descriptor: InputDeviceDescriptor) -> Self {
Self { device_descriptor, ..self }
}
/// Returns true if this event is marked as handled.
pub fn is_handled(&self) -> bool {
self.handled == Handled::Yes
}
// Returns event type as string.
pub fn get_event_type(&self) -> &'static str {
match self.device_event {
InputDeviceEvent::Keyboard(_) => "keyboard_event",
InputDeviceEvent::LightSensor(_) => "light_sensor_event",
InputDeviceEvent::ConsumerControls(_) => "consumer_controls_event",
InputDeviceEvent::Mouse(_) => "mouse_event",
InputDeviceEvent::TouchScreen(_) => "touch_screen_event",
InputDeviceEvent::Touchpad(_) => "touchpad_event",
#[cfg(test)]
InputDeviceEvent::Fake => "fake_event",
}
}
pub fn record_inspect(&self, node: &fuchsia_inspect::Node) {
node.record_int("event_time", self.event_time.into_nanos());
match &self.device_event {
InputDeviceEvent::LightSensor(e) => e.record_inspect(node),
InputDeviceEvent::ConsumerControls(e) => e.record_inspect(node),
InputDeviceEvent::Mouse(e) => e.record_inspect(node),
InputDeviceEvent::TouchScreen(e) => e.record_inspect(node),
InputDeviceEvent::Touchpad(e) => e.record_inspect(node),
// No-op for KeyboardEvent, since we don't want to potentially record sensitive information to Inspect.
InputDeviceEvent::Keyboard(_) => (),
#[cfg(test)] // No-op for Fake InputDeviceEvent.
InputDeviceEvent::Fake => (),
}
}
}
#[cfg(test)]
mod tests {
use {
super::*, assert_matches::assert_matches, diagnostics_assertions::AnyProperty,
fidl::endpoints::spawn_stream_handler, fuchsia_zircon as zx, pretty_assertions::assert_eq,
std::convert::TryFrom as _, test_case::test_case,
};
#[test]
fn max_event_time() {
let event_time = event_time_or_now(Some(i64::MAX));
assert_eq!(event_time, zx::Time::INFINITE);
}
#[test]
fn min_event_time() {
let event_time = event_time_or_now(Some(std::i64::MIN));
assert_eq!(event_time, zx::Time::INFINITE_PAST);
}
#[fasync::run_singlethreaded(test)]
async fn input_device_status_initialized_with_correct_properties() {
let inspector = fuchsia_inspect::Inspector::default();
let input_pipeline_node = inspector.root().create_child("input_pipeline");
let input_devices_node = input_pipeline_node.create_child("input_devices");
let device_node = input_devices_node.create_child("001_keyboard");
let _input_device_status = InputDeviceStatus::new(device_node);
diagnostics_assertions::assert_data_tree!(inspector, root: {
input_pipeline: {
input_devices: {
"001_keyboard": {
reports_received_count: 0u64,
reports_filtered_count: 0u64,
events_generated: 0u64,
last_received_timestamp_ns: 0u64,
last_generated_timestamp_ns: 0u64,
"fuchsia.inspect.Health": {
status: "STARTING_UP",
// Timestamp value is unpredictable and not relevant in this context,
// so we only assert that the property is present.
start_timestamp_nanos: AnyProperty
},
driver_to_binding_latency_ms: diagnostics_assertions::HistogramAssertion::exponential(super::LATENCY_HISTOGRAM_PROPERTIES),
}
}
}
});
}
#[test_case(i64::MIN; "min value")]
#[test_case(-1; "negative value")]
#[test_case(0; "zero")]
#[test_case(1; "positive value")]
#[test_case(i64::MAX; "max value")]
#[fuchsia::test(allow_stalls = false)]
async fn input_device_status_updates_latency_histogram_on_count_received_report(
latency_nsec: i64,
) {
let mut expected_histogram = diagnostics_assertions::HistogramAssertion::exponential(
super::LATENCY_HISTOGRAM_PROPERTIES,
);
let inspector = fuchsia_inspect::Inspector::default();
let input_device_status = InputDeviceStatus::new_internal(
inspector.root().clone_weak(),
Box::new(move || zx::Time::from_nanos(latency_nsec)),
);
input_device_status
.count_received_report(&InputReport { event_time: Some(0), ..InputReport::default() });
expected_histogram.insert_values([latency_nsec / 1000 / 1000]);
diagnostics_assertions::assert_data_tree!(inspector, root: contains {
driver_to_binding_latency_ms: expected_histogram,
});
}
// Tests that is_device_type() returns true for InputDeviceType::ConsumerControls when a
// consumer controls device exists.
#[fasync::run_singlethreaded(test)]
async fn consumer_controls_input_device_exists() {
let input_device_proxy: fidl_input_report::InputDeviceProxy =
spawn_stream_handler(move |input_device_request| async move {
match input_device_request {
fidl_input_report::InputDeviceRequest::GetDescriptor { responder } => {
let _ = responder.send(&fidl_input_report::DeviceDescriptor {
device_info: None,
mouse: None,
sensor: None,
touch: None,
keyboard: None,
consumer_control: Some(fidl_input_report::ConsumerControlDescriptor {
input: Some(fidl_input_report::ConsumerControlInputDescriptor {
buttons: Some(vec![
fidl_input_report::ConsumerControlButton::VolumeUp,
fidl_input_report::ConsumerControlButton::VolumeDown,
]),
..Default::default()
}),
..Default::default()
}),
..Default::default()
});
}
_ => panic!("InputDevice handler received an unexpected request"),
}
})
.unwrap();
assert!(
is_device_type(
&input_device_proxy
.get_descriptor()
.await
.expect("Failed to get device descriptor"),
InputDeviceType::ConsumerControls
)
.await
);
}
// Tests that is_device_type() returns true for InputDeviceType::Mouse when a mouse exists.
#[fasync::run_singlethreaded(test)]
async fn mouse_input_device_exists() {
let input_device_proxy: fidl_input_report::InputDeviceProxy =
spawn_stream_handler(move |input_device_request| async move {
match input_device_request {
fidl_input_report::InputDeviceRequest::GetDescriptor { responder } => {
let _ = responder.send(&fidl_input_report::DeviceDescriptor {
device_info: None,
mouse: Some(fidl_input_report::MouseDescriptor {
input: Some(fidl_input_report::MouseInputDescriptor {
movement_x: None,
movement_y: None,
position_x: None,
position_y: None,
scroll_v: None,
scroll_h: None,
buttons: None,
..Default::default()
}),
..Default::default()
}),
sensor: None,
touch: None,
keyboard: None,
consumer_control: None,
..Default::default()
});
}
_ => panic!("InputDevice handler received an unexpected request"),
}
})
.unwrap();
assert!(
is_device_type(
&input_device_proxy
.get_descriptor()
.await
.expect("Failed to get device descriptor"),
InputDeviceType::Mouse
)
.await
);
}
// Tests that is_device_type() returns true for InputDeviceType::Mouse when a mouse doesn't
// exist.
#[fasync::run_singlethreaded(test)]
async fn mouse_input_device_doesnt_exist() {
let input_device_proxy: fidl_input_report::InputDeviceProxy =
spawn_stream_handler(move |input_device_request| async move {
match input_device_request {
fidl_input_report::InputDeviceRequest::GetDescriptor { responder } => {
let _ = responder.send(&fidl_input_report::DeviceDescriptor {
device_info: None,
mouse: None,
sensor: None,
touch: None,
keyboard: None,
consumer_control: None,
..Default::default()
});
}
_ => panic!("InputDevice handler received an unexpected request"),
}
})
.unwrap();
assert!(
!is_device_type(
&input_device_proxy
.get_descriptor()
.await
.expect("Failed to get device descriptor"),
InputDeviceType::Mouse
)
.await
);
}
// Tests that is_device_type() returns true for InputDeviceType::Touch when a touchscreen
// exists.
#[fasync::run_singlethreaded(test)]
async fn touch_input_device_exists() {
let input_device_proxy: fidl_input_report::InputDeviceProxy =
spawn_stream_handler(move |input_device_request| async move {
match input_device_request {
fidl_input_report::InputDeviceRequest::GetDescriptor { responder } => {
let _ = responder.send(&fidl_input_report::DeviceDescriptor {
device_info: None,
mouse: None,
sensor: None,
touch: Some(fidl_input_report::TouchDescriptor {
input: Some(fidl_input_report::TouchInputDescriptor {
contacts: None,
max_contacts: None,
touch_type: None,
buttons: None,
..Default::default()
}),
..Default::default()
}),
keyboard: None,
consumer_control: None,
..Default::default()
});
}
_ => panic!("InputDevice handler received an unexpected request"),
}
})
.unwrap();
assert!(
is_device_type(
&input_device_proxy
.get_descriptor()
.await
.expect("Failed to get device descriptor"),
InputDeviceType::Touch
)
.await
);
}
// Tests that is_device_type() returns true for InputDeviceType::Touch when a touchscreen
// exists.
#[fasync::run_singlethreaded(test)]
async fn touch_input_device_doesnt_exist() {
let input_device_proxy: fidl_input_report::InputDeviceProxy =
spawn_stream_handler(move |input_device_request| async move {
match input_device_request {
fidl_input_report::InputDeviceRequest::GetDescriptor { responder } => {
let _ = responder.send(&fidl_input_report::DeviceDescriptor {
device_info: None,
mouse: None,
sensor: None,
touch: None,
keyboard: None,
consumer_control: None,
..Default::default()
});
}
_ => panic!("InputDevice handler received an unexpected request"),
}
})
.unwrap();
assert!(
!is_device_type(
&input_device_proxy
.get_descriptor()
.await
.expect("Failed to get device descriptor"),
InputDeviceType::Touch
)
.await
);
}
// Tests that is_device_type() returns true for InputDeviceType::Keyboard when a keyboard
// exists.
#[fasync::run_singlethreaded(test)]
async fn keyboard_input_device_exists() {
let input_device_proxy: fidl_input_report::InputDeviceProxy =
spawn_stream_handler(move |input_device_request| async move {
match input_device_request {
fidl_input_report::InputDeviceRequest::GetDescriptor { responder } => {
let _ = responder.send(&fidl_input_report::DeviceDescriptor {
device_info: None,
mouse: None,
sensor: None,
touch: None,
keyboard: Some(fidl_input_report::KeyboardDescriptor {
input: Some(fidl_input_report::KeyboardInputDescriptor {
keys3: None,
..Default::default()
}),
output: None,
..Default::default()
}),
consumer_control: None,
..Default::default()
});
}
_ => panic!("InputDevice handler received an unexpected request"),
}
})
.unwrap();
assert!(
is_device_type(
&input_device_proxy
.get_descriptor()
.await
.expect("Failed to get device descriptor"),
InputDeviceType::Keyboard
)
.await
);
}
// Tests that is_device_type() returns true for InputDeviceType::Keyboard when a keyboard
// exists.
#[fasync::run_singlethreaded(test)]
async fn keyboard_input_device_doesnt_exist() {
let input_device_proxy: fidl_input_report::InputDeviceProxy =
spawn_stream_handler(move |input_device_request| async move {
match input_device_request {
fidl_input_report::InputDeviceRequest::GetDescriptor { responder } => {
let _ = responder.send(&fidl_input_report::DeviceDescriptor {
device_info: None,
mouse: None,
sensor: None,
touch: None,
keyboard: None,
consumer_control: None,
..Default::default()
});
}
_ => panic!("InputDevice handler received an unexpected request"),
}
})
.unwrap();
assert!(
!is_device_type(
&input_device_proxy
.get_descriptor()
.await
.expect("Failed to get device descriptor"),
InputDeviceType::Keyboard
)
.await
);
}
// Tests that is_device_type() returns true for every input device type that exists.
#[fasync::run_singlethreaded(test)]
async fn no_input_device_match() {
let input_device_proxy: fidl_input_report::InputDeviceProxy =
spawn_stream_handler(move |input_device_request| async move {
match input_device_request {
fidl_input_report::InputDeviceRequest::GetDescriptor { responder } => {
let _ = responder.send(&fidl_input_report::DeviceDescriptor {
device_info: None,
mouse: Some(fidl_input_report::MouseDescriptor {
input: Some(fidl_input_report::MouseInputDescriptor {
movement_x: None,
movement_y: None,
position_x: None,
position_y: None,
scroll_v: None,
scroll_h: None,
buttons: None,
..Default::default()
}),
..Default::default()
}),
sensor: None,
touch: Some(fidl_input_report::TouchDescriptor {
input: Some(fidl_input_report::TouchInputDescriptor {
contacts: None,
max_contacts: None,
touch_type: None,
buttons: None,
..Default::default()
}),
..Default::default()
}),
keyboard: Some(fidl_input_report::KeyboardDescriptor {
input: Some(fidl_input_report::KeyboardInputDescriptor {
keys3: None,
..Default::default()
}),
output: None,
..Default::default()
}),
consumer_control: Some(fidl_input_report::ConsumerControlDescriptor {
input: Some(fidl_input_report::ConsumerControlInputDescriptor {
buttons: Some(vec![
fidl_input_report::ConsumerControlButton::VolumeUp,
fidl_input_report::ConsumerControlButton::VolumeDown,
]),
..Default::default()
}),
..Default::default()
}),
..Default::default()
});
}
_ => panic!("InputDevice handler received an unexpected request"),
}
})
.unwrap();
let device_descriptor =
&input_device_proxy.get_descriptor().await.expect("Failed to get device descriptor");
assert!(is_device_type(&device_descriptor, InputDeviceType::ConsumerControls).await);
assert!(is_device_type(&device_descriptor, InputDeviceType::Mouse).await);
assert!(is_device_type(&device_descriptor, InputDeviceType::Touch).await);
assert!(is_device_type(&device_descriptor, InputDeviceType::Keyboard).await);
}
#[fuchsia::test]
fn unhandled_to_generic_conversion_sets_handled_flag_to_no() {
assert_eq!(
InputEvent::from(UnhandledInputEvent {
device_event: InputDeviceEvent::Fake,
device_descriptor: InputDeviceDescriptor::Fake,
event_time: zx::Time::from_nanos(1),
trace_id: None,
})
.handled,
Handled::No
);
}
#[fuchsia::test]
fn unhandled_to_generic_conversion_preserves_fields() {
const EVENT_TIME: zx::Time = zx::Time::from_nanos(42);
let expected_trace_id: Option<ftrace::Id> = Some(1234.into());
assert_matches!(
InputEvent::from(UnhandledInputEvent {
device_event: InputDeviceEvent::Fake,
device_descriptor: InputDeviceDescriptor::Fake,
event_time: EVENT_TIME,
trace_id: expected_trace_id,
}),
InputEvent {
device_event: InputDeviceEvent::Fake,
device_descriptor: InputDeviceDescriptor::Fake,
event_time: EVENT_TIME,
handled: _,
trace_id
} if trace_id == expected_trace_id
);
}
#[fuchsia::test]
fn generic_to_unhandled_conversion_fails_for_handled_events() {
assert_matches!(
UnhandledInputEvent::try_from(InputEvent {
device_event: InputDeviceEvent::Fake,
device_descriptor: InputDeviceDescriptor::Fake,
event_time: zx::Time::from_nanos(1),
handled: Handled::Yes,
trace_id: None,
}),
Err(_)
)
}
#[fuchsia::test]
fn generic_to_unhandled_conversion_preserves_fields_for_unhandled_events() {
const EVENT_TIME: zx::Time = zx::Time::from_nanos(42);
let expected_trace_id: Option<ftrace::Id> = Some(1234.into());
assert_matches!(
UnhandledInputEvent::try_from(InputEvent {
device_event: InputDeviceEvent::Fake,
device_descriptor: InputDeviceDescriptor::Fake,
event_time: EVENT_TIME,
handled: Handled::No,
trace_id: expected_trace_id,
}),
Ok(UnhandledInputEvent {
device_event: InputDeviceEvent::Fake,
device_descriptor: InputDeviceDescriptor::Fake,
event_time: EVENT_TIME,
trace_id
}) if trace_id == expected_trace_id
)
}
#[test_case(Handled::No; "initially not handled")]
#[test_case(Handled::Yes; "initially handled")]
fn into_handled_if_yields_handled_yes_on_true(initially_handled: Handled) {
let event = InputEvent {
device_event: InputDeviceEvent::Fake,
device_descriptor: InputDeviceDescriptor::Fake,
event_time: zx::Time::from_nanos(1),
handled: initially_handled,
trace_id: None,
};
pretty_assertions::assert_eq!(event.into_handled_if(true).handled, Handled::Yes);
}
#[test_case(Handled::No; "initially not handled")]
#[test_case(Handled::Yes; "initially handled")]
fn into_handled_if_leaves_handled_unchanged_on_false(initially_handled: Handled) {
let event = InputEvent {
device_event: InputDeviceEvent::Fake,
device_descriptor: InputDeviceDescriptor::Fake,
event_time: zx::Time::from_nanos(1),
handled: initially_handled.clone(),
trace_id: None,
};
pretty_assertions::assert_eq!(event.into_handled_if(false).handled, initially_handled);
}
#[test_case(Handled::No; "initially not handled")]
#[test_case(Handled::Yes; "initially handled")]
fn into_handled_yields_handled_yes(initially_handled: Handled) {
let event = InputEvent {
device_event: InputDeviceEvent::Fake,
device_descriptor: InputDeviceDescriptor::Fake,
event_time: zx::Time::from_nanos(1),
handled: initially_handled,
trace_id: None,
};
pretty_assertions::assert_eq!(event.into_handled().handled, Handled::Yes);
}
}