blob: 91d9f6401943313a968dd00280ebac6c358a73d9 [file] [log] [blame]
// Copyright 2024 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::InputFile;
use crate::input_event_relay::{DeviceId, OpenedFiles};
use fuchsia_inspect::{NumericProperty, Property};
use starnix_core::device::kobject::DeviceMetadata;
use starnix_core::device::{DeviceMode, DeviceOps};
use starnix_core::task::CurrentTask;
use starnix_core::vfs::{FileOps, FsString, NamespaceNode};
#[cfg(test)]
use starnix_sync::Unlocked;
use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Mutex};
use starnix_uapi::device_type::{DeviceType, INPUT_MAJOR};
use starnix_uapi::errors::Errno;
use starnix_uapi::open_flags::OpenFlags;
use starnix_uapi::{BUS_VIRTUAL, input_id};
use std::sync::Arc;
// Add a fuchsia-specific vendor ID. 0xfc1a is currently not allocated
// to any vendor in the USB spec.
//
// May not be zero, see below.
const FUCHSIA_VENDOR_ID: u16 = 0xfc1a;
// May not be zero, see below.
const FUCHSIA_TOUCH_PRODUCT_ID: u16 = 0x2;
// May not be zero, see below.
const FUCHSIA_KEYBOARD_PRODUCT_ID: u16 = 0x1;
// May not be zero, see below.
const FUCHSIA_MOUSE_PRODUCT_ID: u16 = 0x3;
// Touch, keyboard, and mouse input IDs should be distinct.
// Per https://www.linuxjournal.com/article/6429, the bus type should be populated with a
// sensible value, but other fields may not be.
//
// While this may be the case for Linux itself, Android is not so relaxed.
// Devices with apparently-invalid vendor or product IDs don't get extra
// device configuration. So we must make a minimum effort to present
// sensibly-looking product and vendor IDs. Zero version only means that
// version-specific config files will not be applied.
//
// For background, see:
//
// * Allowable file locations:
// https://source.android.com/docs/core/interaction/input/input-device-configuration-files#location
// * Android configuration selection code:
// https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:frameworks/native/libs/input/InputDevice.cpp;l=60;drc=285211e60bff87fc5a9c9b4105a4b4ccb7edffaf
const TOUCH_INPUT_ID: input_id = input_id {
bustype: BUS_VIRTUAL as u16,
// Make sure that vendor ID and product ID at least seem plausible. See
// above for details.
vendor: FUCHSIA_VENDOR_ID,
product: FUCHSIA_TOUCH_PRODUCT_ID,
// Version is OK to be zero, but config files named `Product_yyyy_Vendor_zzzz_Version_ttt.*`
// will not work.
version: 0,
};
const KEYBOARD_INPUT_ID: input_id = input_id {
bustype: BUS_VIRTUAL as u16,
// Make sure that vendor ID and product ID at least seem plausible. See
// above for details.
vendor: FUCHSIA_VENDOR_ID,
product: FUCHSIA_KEYBOARD_PRODUCT_ID,
version: 1,
};
const MOUSE_INPUT_ID: input_id = input_id {
bustype: BUS_VIRTUAL as u16,
// Make sure that vendor ID and product ID at least seem plausible. See
// above for details.
vendor: FUCHSIA_VENDOR_ID,
product: FUCHSIA_MOUSE_PRODUCT_ID,
version: 1,
};
#[derive(Clone)]
enum InputDeviceType {
// A touch device, containing (display width, display height).
Touch(i32, i32),
// A keyboard device.
Keyboard,
// A mouse device.
Mouse,
}
/// An [`InputDeviceStatus`] is tied to an [`InputDeviceBinding`] and provides properties
/// detailing its Inspect status.
/// We expect all (non-timestamp) properties' counts to equal the sum of that property from all
/// files opened on that device. So for example, if a device had 3 separate input files opened, we
/// would expect it's `total_fidl_events_received_count` to equal the sum of
/// `fidl_events_received_count` from all 3 files and so forth.
pub struct InputDeviceStatus {
/// A node that contains the state below.
pub node: fuchsia_inspect::Node,
/// Hold onto inspect nodes for files opened on this device, so that when these files are
/// closed, their inspect data is maintained.
pub file_nodes: Mutex<Vec<fuchsia_inspect::Node>>,
/// The number of FIDL events received by this device from Fuchsia input system.
///
/// We expect:
/// total_fidl_events_received_count = total_fidl_events_ignored_count +
/// total_fidl_events_unexpected_count +
/// total_fidl_events_converted_count
/// otherwise starnix ignored events unexpectedly.
///
/// total_fidl_events_unexpected_count should be 0, if not it hints issues from upstream of
/// ui stack.
pub total_fidl_events_received_count: fuchsia_inspect::UintProperty,
/// The number of FIDL events ignored by this device when attempting conversion to this
/// module’s representation of a TouchEvent.
pub total_fidl_events_ignored_count: fuchsia_inspect::UintProperty,
/// The unexpected number of FIDL events reached to this module should be filtered out
/// earlier in the UI stack.
/// It maybe unexpected format or unexpected order.
pub total_fidl_events_unexpected_count: fuchsia_inspect::UintProperty,
/// The number of FIDL events converted by this device to this module’s representation of
/// TouchEvent.
pub total_fidl_events_converted_count: fuchsia_inspect::UintProperty,
/// The number of uapi::input_events generated by this device from TouchEvents.
pub total_uapi_events_generated_count: fuchsia_inspect::UintProperty,
/// The event time of the last generated uapi::input_event by one of this device's InputFiles.
pub last_generated_uapi_event_timestamp_ns: fuchsia_inspect::IntProperty,
}
impl InputDeviceStatus {
fn new(node: fuchsia_inspect::Node) -> Self {
let total_fidl_events_received_count =
node.create_uint("total_fidl_events_received_count", 0);
let total_fidl_events_ignored_count =
node.create_uint("total_fidl_events_ignored_count", 0);
let total_fidl_events_unexpected_count =
node.create_uint("total_fidl_events_unexpected_count", 0);
let total_fidl_events_converted_count =
node.create_uint("total_fidl_events_converted_count", 0);
let total_uapi_events_generated_count =
node.create_uint("total_uapi_events_generated_count", 0);
let last_generated_uapi_event_timestamp_ns =
node.create_int("last_generated_uapi_event_timestamp_ns", 0);
Self {
node,
file_nodes: Mutex::new(vec![]),
total_fidl_events_received_count,
total_fidl_events_ignored_count,
total_fidl_events_unexpected_count,
total_fidl_events_converted_count,
total_uapi_events_generated_count,
last_generated_uapi_event_timestamp_ns,
}
}
pub fn count_total_received_events(&self, count: u64) {
self.total_fidl_events_received_count.add(count);
}
pub fn count_total_ignored_events(&self, count: u64) {
self.total_fidl_events_ignored_count.add(count);
}
pub fn count_total_unexpected_events(&self, count: u64) {
self.total_fidl_events_unexpected_count.add(count);
}
pub fn count_total_converted_events(&self, count: u64) {
self.total_fidl_events_converted_count.add(count);
}
pub fn count_total_generated_events(&self, count: u64, event_time_ns: i64) {
self.total_uapi_events_generated_count.add(count);
self.last_generated_uapi_event_timestamp_ns.set(event_time_ns);
}
}
#[derive(Clone)]
pub struct InputDevice {
device_type: InputDeviceType,
pub open_files: OpenedFiles,
pub inspect_status: Arc<InputDeviceStatus>,
}
impl InputDevice {
pub fn new_touch(
display_width: i32,
display_height: i32,
inspect_node: &fuchsia_inspect::Node,
) -> Self {
let node = inspect_node.create_child("touch_device");
InputDevice {
device_type: InputDeviceType::Touch(display_width, display_height),
open_files: Default::default(),
inspect_status: Arc::new(InputDeviceStatus::new(node)),
}
}
pub fn new_keyboard(inspect_node: &fuchsia_inspect::Node) -> Self {
let node = inspect_node.create_child("keyboard_device");
InputDevice {
device_type: InputDeviceType::Keyboard,
open_files: Default::default(),
inspect_status: Arc::new(InputDeviceStatus::new(node)),
}
}
pub fn new_mouse(inspect_node: &fuchsia_inspect::Node) -> Self {
let node = inspect_node.create_child("mouse_device");
InputDevice {
device_type: InputDeviceType::Mouse,
open_files: Default::default(),
inspect_status: Arc::new(InputDeviceStatus::new(node)),
}
}
pub fn register<L>(
self,
locked: &mut Locked<L>,
system_task: &CurrentTask,
device_id: DeviceId,
) -> Result<(), Errno>
where
L: LockEqualOrBefore<FileOpsCore>,
{
let kernel = system_task.kernel();
let registry = &kernel.device_registry;
let input_class = registry.objects.input_class();
registry.register_device(
locked,
system_task,
FsString::from(format!("event{}", device_id)).as_ref(),
DeviceMetadata::new(
format!("input/event{}", device_id).into(),
DeviceType::new(INPUT_MAJOR, device_id),
DeviceMode::Char,
),
input_class,
self,
)?;
Ok(())
}
pub fn open_internal(&self) -> Box<dyn FileOps> {
let input_file = match self.device_type {
InputDeviceType::Touch(display_width, display_height) => {
let mut file_nodes = self.inspect_status.file_nodes.lock();
let child_node = self
.inspect_status
.node
.create_child(format!("touch_file_{}", file_nodes.len()));
let file = Arc::new(InputFile::new_touch(
TOUCH_INPUT_ID,
display_width,
display_height,
Some(&child_node),
));
file_nodes.push(child_node);
file
}
InputDeviceType::Keyboard => {
let mut file_nodes = self.inspect_status.file_nodes.lock();
let child_node = self
.inspect_status
.node
.create_child(format!("keyboard_file_{}", file_nodes.len()));
let file = Arc::new(InputFile::new_keyboard(KEYBOARD_INPUT_ID, Some(&child_node)));
file_nodes.push(child_node);
file
}
InputDeviceType::Mouse => {
let mut file_nodes = self.inspect_status.file_nodes.lock();
let child_node = self
.inspect_status
.node
.create_child(format!("mouse_file_{}", file_nodes.len()));
let file = Arc::new(InputFile::new_mouse(MOUSE_INPUT_ID, Some(&child_node)));
file_nodes.push(child_node);
file
}
};
self.open_files.lock().push(Arc::downgrade(&input_file));
Box::new(input_file)
}
#[allow(clippy::unwrap_in_result, reason = "Force clippy rule in Starnix")]
#[cfg(test)]
pub fn open_test(
&self,
locked: &mut Locked<Unlocked>,
current_task: &CurrentTask,
) -> Result<starnix_core::vfs::FileHandle, Errno> {
let input_file = self.open_internal();
let file_object = starnix_core::vfs::FileObject::new(
current_task,
input_file,
current_task
.lookup_path_from_root(locked, ".".into())
.expect("failed to get namespace node for root"),
OpenFlags::empty(),
)
.expect("FileObject::new failed");
Ok(file_object)
}
}
impl DeviceOps for InputDevice {
fn open(
&self,
_locked: &mut Locked<FileOpsCore>,
_current_task: &CurrentTask,
_id: DeviceType,
_node: &NamespaceNode,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
let input_file = self.open_internal();
Ok(input_file)
}
}
#[cfg(test)]
mod test {
#![allow(clippy::unused_unit)] // for compatibility with `test_case`
use super::*;
use crate::input_event_relay::{self, EventProxyMode};
use anyhow::anyhow;
use assert_matches::assert_matches;
use diagnostics_assertions::{AnyProperty, assert_data_tree};
use fidl_fuchsia_ui_input::MediaButtonsEvent;
use fuipointer::{
EventPhase, TouchEvent, TouchInteractionId, TouchPointerSample, TouchResponse,
TouchSourceMarker, TouchSourceRequest,
};
use futures::StreamExt as _;
use pretty_assertions::assert_eq;
use starnix_core::task::{EventHandler, Waiter};
use starnix_core::testing::create_kernel_task_and_unlocked;
use starnix_core::vfs::FileHandle;
use starnix_core::vfs::buffers::VecOutputBuffer;
use starnix_types::time::timeval_from_time;
use starnix_uapi::errors::EAGAIN;
use starnix_uapi::uapi;
use starnix_uapi::vfs::FdEvents;
use test_case::test_case;
use test_util::assert_near;
use zerocopy::FromBytes as _;
use {
fidl_fuchsia_ui_input3 as fuiinput, fidl_fuchsia_ui_pointer as fuipointer,
fidl_fuchsia_ui_policy as fuipolicy,
};
const INPUT_EVENT_SIZE: usize = std::mem::size_of::<uapi::input_event>();
async fn start_touch_input(
locked: &mut Locked<Unlocked>,
current_task: &CurrentTask,
) -> (InputDevice, FileHandle, fuipointer::TouchSourceRequestStream) {
let inspector = fuchsia_inspect::Inspector::default();
start_touch_input_inspect_and_dimensions(locked, current_task, 700, 1200, &inspector).await
}
async fn start_touch_input_inspect(
locked: &mut Locked<Unlocked>,
current_task: &CurrentTask,
inspector: &fuchsia_inspect::Inspector,
) -> (InputDevice, FileHandle, fuipointer::TouchSourceRequestStream) {
start_touch_input_inspect_and_dimensions(locked, current_task, 700, 1200, &inspector).await
}
async fn init_keyboard_listener(
keyboard_stream: &mut fuiinput::KeyboardRequestStream,
) -> fuiinput::KeyboardListenerProxy {
let keyboard_listener = match keyboard_stream.next().await {
Some(Ok(fuiinput::KeyboardRequest::AddListener {
view_ref: _,
listener,
responder,
})) => {
let _ = responder.send();
listener.into_proxy()
}
_ => {
panic!("Failed to get event");
}
};
keyboard_listener
}
async fn init_button_listeners(
device_listener_stream: &mut fuipolicy::DeviceListenerRegistryRequestStream,
) -> (fuipolicy::MediaButtonsListenerProxy, fuipolicy::TouchButtonsListenerProxy) {
let media_buttons_listener = match device_listener_stream.next().await {
Some(Ok(fuipolicy::DeviceListenerRegistryRequest::RegisterListener {
listener,
responder,
})) => {
let _ = responder.send();
listener.into_proxy()
}
_ => {
panic!("Failed to get event");
}
};
let touch_buttons_listener = match device_listener_stream.next().await {
Some(Ok(fuipolicy::DeviceListenerRegistryRequest::RegisterTouchButtonsListener {
listener,
responder,
})) => {
let _ = responder.send();
listener.into_proxy()
}
_ => {
panic!("Failed to get event");
}
};
(media_buttons_listener, touch_buttons_listener)
}
async fn start_touch_input_inspect_and_dimensions(
locked: &mut Locked<Unlocked>,
current_task: &CurrentTask,
x_max: i32,
y_max: i32,
inspector: &fuchsia_inspect::Inspector,
) -> (InputDevice, FileHandle, fuipointer::TouchSourceRequestStream) {
let input_device = InputDevice::new_touch(x_max, y_max, inspector.root());
let input_file =
input_device.open_test(locked, current_task).expect("Failed to create input file");
let (touch_source_client_end, touch_source_stream) =
fidl::endpoints::create_request_stream::<TouchSourceMarker>();
let (mouse_source_client_end, _mouse_source_stream) =
fidl::endpoints::create_request_stream::<fuipointer::MouseSourceMarker>();
let (keyboard_proxy, mut keyboard_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fuiinput::KeyboardMarker>();
let view_ref_pair =
fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
let (device_registry_proxy, mut device_listener_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
);
let (relay, _relay_handle) = input_event_relay::new_input_relay();
relay.start_relays(
&current_task.kernel(),
EventProxyMode::None,
touch_source_client_end,
keyboard_proxy,
mouse_source_client_end,
view_ref_pair.view_ref,
device_registry_proxy,
input_device.open_files.clone(),
Default::default(),
Default::default(),
Some(input_device.inspect_status.clone()),
None,
None,
);
let _ = init_keyboard_listener(&mut keyboard_stream).await;
let _ = init_button_listeners(&mut device_listener_stream).await;
(input_device, input_file, touch_source_stream)
}
async fn start_keyboard_input(
locked: &mut Locked<Unlocked>,
current_task: &CurrentTask,
) -> (InputDevice, FileHandle, fuiinput::KeyboardListenerProxy) {
let inspector = fuchsia_inspect::Inspector::default();
let input_device = InputDevice::new_keyboard(inspector.root());
let input_file =
input_device.open_test(locked, current_task).expect("Failed to create input file");
let (keyboard_proxy, mut keyboard_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fuiinput::KeyboardMarker>();
let view_ref_pair =
fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
let (device_registry_proxy, mut device_listener_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
);
let (touch_source_client_end, _touch_source_stream) =
fidl::endpoints::create_request_stream::<TouchSourceMarker>();
let (mouse_source_client_end, _mouse_source_stream) =
fidl::endpoints::create_request_stream::<fuipointer::MouseSourceMarker>();
let (relay, _relay_handle) = input_event_relay::new_input_relay();
relay.start_relays(
current_task.kernel(),
EventProxyMode::None,
touch_source_client_end,
keyboard_proxy,
mouse_source_client_end,
view_ref_pair.view_ref,
device_registry_proxy,
Default::default(),
input_device.open_files.clone(),
Default::default(),
None,
Some(input_device.inspect_status.clone()),
None,
);
let keyboad_listener = init_keyboard_listener(&mut keyboard_stream).await;
let _ = init_button_listeners(&mut device_listener_stream).await;
(input_device, input_file, keyboad_listener)
}
async fn start_button_input(
locked: &mut Locked<Unlocked>,
current_task: &CurrentTask,
) -> (InputDevice, FileHandle, fuipolicy::MediaButtonsListenerProxy) {
let inspector = fuchsia_inspect::Inspector::default();
start_button_input_inspect(locked, current_task, &inspector).await
}
async fn start_button_input_inspect(
locked: &mut Locked<Unlocked>,
current_task: &CurrentTask,
inspector: &fuchsia_inspect::Inspector,
) -> (InputDevice, FileHandle, fuipolicy::MediaButtonsListenerProxy) {
let input_device = InputDevice::new_keyboard(inspector.root());
let input_file =
input_device.open_test(locked, current_task).expect("Failed to create input file");
let (device_registry_proxy, mut device_listener_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
);
let (touch_source_client_end, _touch_source_stream) =
fidl::endpoints::create_request_stream::<TouchSourceMarker>();
let (mouse_source_client_end, _mouse_source_stream) =
fidl::endpoints::create_request_stream::<fuipointer::MouseSourceMarker>();
let (keyboard_proxy, mut keyboard_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fuiinput::KeyboardMarker>();
let view_ref_pair =
fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
let (relay, _relay_handle) = input_event_relay::new_input_relay();
relay.start_relays(
current_task.kernel(),
EventProxyMode::None,
touch_source_client_end,
keyboard_proxy,
mouse_source_client_end,
view_ref_pair.view_ref,
device_registry_proxy,
Default::default(),
input_device.open_files.clone(),
Default::default(),
None,
Some(input_device.inspect_status.clone()),
None,
);
let _ = init_keyboard_listener(&mut keyboard_stream).await;
let (button_listener, _) = init_button_listeners(&mut device_listener_stream).await;
(input_device, input_file, button_listener)
}
async fn start_mouse_input(
locked: &mut Locked<Unlocked>,
current_task: &CurrentTask,
) -> (InputDevice, FileHandle, fuipointer::MouseSourceRequestStream) {
let inspector = fuchsia_inspect::Inspector::default();
start_mouse_input_inspect(locked, current_task, &inspector).await
}
async fn start_mouse_input_inspect(
locked: &mut Locked<Unlocked>,
current_task: &CurrentTask,
inspector: &fuchsia_inspect::Inspector,
) -> (InputDevice, FileHandle, fuipointer::MouseSourceRequestStream) {
let input_device = InputDevice::new_mouse(inspector.root());
let input_file =
input_device.open_test(locked, current_task).expect("Failed to create input file");
let (touch_source_client_end, _touch_source_stream) =
fidl::endpoints::create_request_stream::<TouchSourceMarker>();
let (mouse_source_client_end, mouse_source_stream) =
fidl::endpoints::create_request_stream::<fuipointer::MouseSourceMarker>();
let (keyboard_proxy, mut keyboard_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fuiinput::KeyboardMarker>();
let view_ref_pair =
fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
let (device_registry_proxy, mut device_listener_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
);
let (relay, _relay_handle) = input_event_relay::new_input_relay();
relay.start_relays(
&current_task.kernel(),
EventProxyMode::None,
touch_source_client_end,
keyboard_proxy,
mouse_source_client_end,
view_ref_pair.view_ref,
device_registry_proxy,
Default::default(),
Default::default(),
input_device.open_files.clone(),
None,
None,
Some(input_device.inspect_status.clone()),
);
let _ = init_keyboard_listener(&mut keyboard_stream).await;
let _ = init_button_listeners(&mut device_listener_stream).await;
(input_device, input_file, mouse_source_stream)
}
fn make_touch_event(pointer_id: u32) -> fuipointer::TouchEvent {
// Default to `Change`, because that has the fewest side effects.
make_touch_event_with_phase(EventPhase::Change, pointer_id)
}
fn make_touch_event_with_phase(phase: EventPhase, pointer_id: u32) -> fuipointer::TouchEvent {
make_touch_event_with_coords_phase(0.0, 0.0, phase, pointer_id)
}
fn make_touch_event_with_coords_phase(
x: f32,
y: f32,
phase: EventPhase,
pointer_id: u32,
) -> fuipointer::TouchEvent {
make_touch_event_with_coords_phase_timestamp(x, y, phase, pointer_id, 0)
}
fn make_touch_event_with_coords(x: f32, y: f32, pointer_id: u32) -> fuipointer::TouchEvent {
make_touch_event_with_coords_phase(x, y, EventPhase::Change, pointer_id)
}
fn make_touch_event_with_coords_phase_timestamp(
x: f32,
y: f32,
phase: EventPhase,
pointer_id: u32,
time_nanos: i64,
) -> fuipointer::TouchEvent {
make_touch_event_with_coords_phase_timestamp_device_id(
x, y, phase, pointer_id, time_nanos, 0,
)
}
fn make_empty_touch_event() -> fuipointer::TouchEvent {
TouchEvent {
pointer_sample: Some(TouchPointerSample {
interaction: Some(TouchInteractionId {
pointer_id: 0,
device_id: 0,
interaction_id: 0,
}),
..Default::default()
}),
..Default::default()
}
}
fn make_touch_event_with_coords_phase_timestamp_device_id(
x: f32,
y: f32,
phase: EventPhase,
pointer_id: u32,
time_nanos: i64,
device_id: u32,
) -> fuipointer::TouchEvent {
TouchEvent {
timestamp: Some(time_nanos),
pointer_sample: Some(TouchPointerSample {
position_in_viewport: Some([x, y]),
// Default to `Change`, because that has the fewest side effects.
phase: Some(phase),
interaction: Some(TouchInteractionId { pointer_id, device_id, interaction_id: 0 }),
..Default::default()
}),
..Default::default()
}
}
fn make_mouse_wheel_event(ticks: i64) -> fuipointer::MouseEvent {
make_mouse_wheel_event_with_timestamp(ticks, 0)
}
fn make_mouse_wheel_event_with_timestamp(ticks: i64, timestamp: i64) -> fuipointer::MouseEvent {
fuipointer::MouseEvent {
timestamp: Some(timestamp),
pointer_sample: Some(fuipointer::MousePointerSample {
device_id: Some(0),
scroll_v: Some(ticks),
..Default::default()
}),
..Default::default()
}
}
fn read_uapi_events<L>(
locked: &mut Locked<L>,
file: &FileHandle,
current_task: &CurrentTask,
) -> Vec<uapi::input_event>
where
L: LockEqualOrBefore<FileOpsCore>,
{
std::iter::from_fn(|| {
let locked = locked.cast_locked::<FileOpsCore>();
let mut event_bytes = VecOutputBuffer::new(INPUT_EVENT_SIZE);
match file.read(locked, current_task, &mut event_bytes) {
Ok(INPUT_EVENT_SIZE) => Some(
uapi::input_event::read_from_bytes(Vec::from(event_bytes).as_slice())
.map_err(|_| anyhow!("failed to read input_event from buffer")),
),
Ok(other_size) => {
Some(Err(anyhow!("got {} bytes (expected {})", other_size, INPUT_EVENT_SIZE)))
}
Err(Errno { code: EAGAIN, .. }) => None,
Err(other_error) => Some(Err(anyhow!("read failed: {:?}", other_error))),
}
})
.enumerate()
.map(|(i, read_res)| match read_res {
Ok(event) => event,
Err(e) => panic!("unexpected result {:?} on iteration {}", e, i),
})
.collect()
}
// Waits for a `Watch()` request to arrive on `request_stream`, and responds with
// `touch_event`. Returns the arguments to the `Watch()` call.
async fn answer_next_touch_watch_request(
request_stream: &mut fuipointer::TouchSourceRequestStream,
touch_events: Vec<TouchEvent>,
) -> Vec<TouchResponse> {
match request_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responses, responder })) => {
responder.send(&touch_events).expect("failure sending Watch reply");
responses
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
}
// Waits for a `Watch()` request to arrive on `request_stream`, and responds with
// `mouse_events`.
async fn answer_next_mouse_watch_request(
request_stream: &mut fuipointer::MouseSourceRequestStream,
mouse_events: Vec<fuipointer::MouseEvent>,
) {
match request_stream.next().await {
Some(Ok(fuipointer::MouseSourceRequest::Watch { responder })) => {
responder.send(&mouse_events).expect("failure sending Watch reply");
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
}
#[::fuchsia::test()]
async fn initial_watch_request_has_empty_responses_arg() {
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
// Set up resources.
let (_input_device, _input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
// Verify that the watch request has empty `responses`.
assert_matches!(
touch_source_stream.next().await,
Some(Ok(TouchSourceRequest::Watch { responses, .. }))
=> assert_eq!(responses.as_slice(), [])
);
}
#[::fuchsia::test]
async fn later_watch_requests_have_responses_arg_matching_earlier_watch_replies() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, _input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
// Reply to first `Watch` with two `TouchEvent`s.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responder, .. })) => responder
.send(&vec![TouchEvent::default(); 2])
.expect("failure sending Watch reply"),
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
// Verify second `Watch` has two elements in `responses`.
// Then reply with five `TouchEvent`s.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responses, responder })) => {
assert_matches!(responses.as_slice(), [_, _]);
responder
.send(&vec![TouchEvent::default(); 5])
.expect("failure sending Watch reply")
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
// Verify third `Watch` has five elements in `responses`.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responses, .. })) => {
assert_matches!(responses.as_slice(), [_, _, _, _, _]);
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
}
#[::fuchsia::test]
async fn notifies_polling_waiters_of_new_data() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
let waiter1 = Waiter::new();
let waiter2 = Waiter::new();
// Ask `input_file` to notify waiters when data is available to read.
[&waiter1, &waiter2].iter().for_each(|waiter| {
input_file.wait_async(
locked,
&current_task,
waiter,
FdEvents::POLLIN,
EventHandler::None,
);
});
assert_matches!(
waiter1.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
Err(_)
);
assert_matches!(
waiter2.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
Err(_)
);
// Reply to first `Watch` request.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Add, 1)],
)
.await;
answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)]).await;
// `InputFile` should be done processing the first reply, since it has sent its second
// request. And, as part of processing the first reply, `InputFile` should have notified
// the interested waiters.
assert_eq!(waiter1.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO), Ok(()));
assert_eq!(waiter2.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO), Ok(()));
}
#[::fuchsia::test]
async fn notifies_blocked_waiter_of_new_data() {
// Set up resources.
let (kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
let waiter = Waiter::new();
// Ask `input_file` to notify `waiter` when data is available to read.
input_file.wait_async(locked, &current_task, &waiter, FdEvents::POLLIN, EventHandler::None);
let mut waiter_thread = kernel
.kthreads
.spawner()
.spawn_and_get_result(move |locked, task| waiter.wait(locked, &task));
assert!(futures::poll!(&mut waiter_thread).is_pending());
// Reply to first `Watch` request.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Add, 1)],
)
.await;
// Wait for another `Watch`.
//
// TODO(https://fxbug.dev/42075452): Without this, `relay_thread` gets stuck `await`-ing
// the reply to its first request. Figure out why that happens, and remove this second
// reply.
answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)]).await;
}
#[::fuchsia::test]
async fn does_not_notify_polling_waiters_without_new_data() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
let waiter1 = Waiter::new();
let waiter2 = Waiter::new();
// Ask `input_file` to notify waiters when data is available to read.
[&waiter1, &waiter2].iter().for_each(|waiter| {
input_file.wait_async(
locked,
&current_task,
waiter,
FdEvents::POLLIN,
EventHandler::None,
);
});
assert_matches!(
waiter1.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
Err(_)
);
assert_matches!(
waiter2.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
Err(_)
);
// Reply to first `Watch` request with an empty set of events.
answer_next_touch_watch_request(&mut touch_source_stream, vec![]).await;
// `InputFile` should be done processing the first reply. Since there
// were no touch_events given, `InputFile` should not have notified the
// interested waiters.
assert_matches!(
waiter1.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
Err(_)
);
assert_matches!(
waiter2.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
Err(_)
);
}
// Note: a user program may also want to be woken if events were already ready at the
// time that the program called `epoll_wait()`. However, there's no test for that case
// in this module, because:
//
// 1. Not all programs will want to be woken in such a case. In particular, some programs
// use "edge-triggered" mode instead of "level-tiggered" mode. For details on the
// two modes, see https://man7.org/linux/man-pages/man7/epoll.7.html.
// 2. For programs using "level-triggered" mode, the relevant behavior is implemented in
// the `epoll` module, and verified by `epoll::tests::test_epoll_ready_then_wait()`.
//
// See also: the documentation for `FileOps::wait_async()`.
#[::fuchsia::test]
async fn honors_wait_cancellation() {
// Set up input resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
let waiter1 = Waiter::new();
let waiter2 = Waiter::new();
// Ask `input_file` to notify `waiter` when data is available to read.
let waitkeys = [&waiter1, &waiter2]
.iter()
.map(|waiter| {
input_file
.wait_async(locked, &current_task, waiter, FdEvents::POLLIN, EventHandler::None)
.expect("wait_async")
})
.collect::<Vec<_>>();
// Cancel wait for `waiter1`.
waitkeys.into_iter().next().expect("failed to get first waitkey").cancel();
// Reply to first `Watch` request.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Add, 1)],
)
.await;
// Wait for another `Watch`.
answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)]).await;
// `InputFile` should be done processing the first reply, since it has sent its second
// request. And, as part of processing the first reply, `InputFile` should have notified
// the interested waiters.
assert_matches!(
waiter1.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
Err(_)
);
assert_eq!(waiter2.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO), Ok(()));
}
#[::fuchsia::test]
async fn query_events() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
// Check initial expectation.
assert_eq!(
input_file.query_events(locked, &current_task).expect("query_events"),
FdEvents::empty(),
"events should be empty before data arrives"
);
// Reply to first `Watch` request.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Add, 1)],
)
.await;
// Wait for another `Watch`.
answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)]).await;
// Check post-watch expectation.
assert_eq!(
input_file.query_events(locked, &current_task).expect("query_events"),
FdEvents::POLLIN | FdEvents::POLLRDNORM,
"events should be POLLIN after data arrives"
);
}
fn make_uapi_input_event(ty: u32, code: u32, value: i32) -> uapi::input_event {
make_uapi_input_event_with_timestamp(ty, code, value, 0)
}
fn make_uapi_input_event_with_timestamp(
ty: u32,
code: u32,
value: i32,
time_nanos: i64,
) -> uapi::input_event {
uapi::input_event {
time: timeval_from_time(zx::MonotonicInstant::from_nanos(time_nanos)),
type_: ty as u16,
code: code as u16,
value,
}
}
#[::fuchsia::test]
async fn touch_event_ignored() {
// Set up resources.
let inspector = fuchsia_inspect::Inspector::default();
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input_inspect(locked, &current_task, &inspector).await;
// Touch add for pointer 1. This should be counted as a received event and a converted
// event. It should also yield 6 generated events.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Add, 1)],
)
.await;
// Wait for another `Watch` to ensure input_file done processing the first reply.
// Use an empty `TouchEvent`, to minimize the chance that this event creates unexpected
// `uapi::input_event`s. This should be counted as a received event and an ignored event.
answer_next_touch_watch_request(&mut touch_source_stream, vec![make_empty_touch_event()])
.await;
// Consume all of the `uapi::input_event`s that are available.
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events.len(), 6);
// Reply to `Watch` request of empty event. This should be counted as a received event and
// an ignored event.
answer_next_touch_watch_request(&mut touch_source_stream, vec![make_empty_touch_event()])
.await;
// Wait for another `Watch`.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responses, .. })) => {
assert_matches!(responses.as_slice(), [_])
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events, vec![]);
assert_data_tree!(inspector, root: {
touch_device: {
total_fidl_events_received_count: 3u64,
total_fidl_events_ignored_count: 2u64,
total_fidl_events_unexpected_count: 0u64,
total_fidl_events_converted_count: 1u64,
total_uapi_events_generated_count: 6u64,
last_generated_uapi_event_timestamp_ns: 0i64,
touch_file_0: {
fidl_events_received_count: 3u64,
fidl_events_ignored_count: 2u64,
fidl_events_unexpected_count: 0u64,
fidl_events_converted_count: 1u64,
uapi_events_generated_count: 6u64,
uapi_events_read_count: 6u64,
last_generated_uapi_event_timestamp_ns: 0i64,
last_read_uapi_event_timestamp_ns: 0i64,
},
}
});
}
#[test_case(make_touch_event_with_phase(EventPhase::Add, 1); "touch add for pointer already added")]
#[test_case(make_touch_event_with_phase(EventPhase::Change, 2); "touch change for pointer not added")]
#[test_case(make_touch_event_with_phase(EventPhase::Remove, 2); "touch remove for pointer not added")]
#[test_case(make_touch_event_with_phase(EventPhase::Cancel, 1); "touch cancel")]
#[::fuchsia::test]
async fn touch_event_unexpected(event: TouchEvent) {
// Set up resources.
let inspector = fuchsia_inspect::Inspector::default();
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input_inspect(locked, &current_task, &inspector).await;
// Touch add for pointer 1. This should be counted as a received event and a converted
// event. It should also yield 6 generated events.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Add, 1)],
)
.await;
// Wait for another `Watch` to ensure input_file done processing the first reply.
// Use an empty `TouchEvent`, to minimize the chance that this event creates unexpected
// `uapi::input_event`s. This should be counted as a received event and an ignored event.
answer_next_touch_watch_request(&mut touch_source_stream, vec![make_empty_touch_event()])
.await;
// Consume all of the `uapi::input_event`s that are available.
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events.len(), 6);
// Reply to `Watch` request of given event. This should be counted as a received event and
// an unexpected event.
answer_next_touch_watch_request(&mut touch_source_stream, vec![event]).await;
// Wait for another `Watch`.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responses, .. })) => {
assert_matches!(responses.as_slice(), [_])
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events, vec![]);
assert_data_tree!(inspector, root: {
touch_device: {
total_fidl_events_received_count: 3u64,
total_fidl_events_ignored_count: 1u64,
total_fidl_events_unexpected_count: 1u64,
total_fidl_events_converted_count: 1u64,
total_uapi_events_generated_count: 6u64,
last_generated_uapi_event_timestamp_ns: 0i64,
touch_file_0: {
fidl_events_received_count: 3u64,
fidl_events_ignored_count: 1u64,
fidl_events_unexpected_count: 1u64,
fidl_events_converted_count: 1u64,
uapi_events_generated_count: 6u64,
uapi_events_read_count: 6u64,
last_generated_uapi_event_timestamp_ns: 0i64,
last_read_uapi_event_timestamp_ns: 0i64,
},
}
});
}
#[::fuchsia::test]
async fn translates_touch_add() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
// Touch add for pointer 1.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Add, 1)],
)
.await;
// Wait for another `Watch` to ensure input_file done processing the first reply.
// Use an empty `TouchEvent`, to minimize the chance that this event
// creates unexpected `uapi::input_event`s.
answer_next_touch_watch_request(&mut touch_source_stream, vec![make_empty_touch_event()])
.await;
// Consume all of the `uapi::input_event`s that are available.
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(
events,
vec![
make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 1),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 1),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 0),
make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
]
);
}
#[::fuchsia::test]
async fn translates_touch_change() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
// Touch add for pointer 1.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Add, 1)],
)
.await;
// Wait for another `Watch` to ensure input_file done processing the first reply.
// Use an empty `TouchEvent`, to minimize the chance that this event
// creates unexpected `uapi::input_event`s.
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
// Consume all of the `uapi::input_event`s that are available.
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events.len(), 6);
// Reply to touch change.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_coords(10.0, 20.0, 1)],
)
.await;
// Wait for another `Watch`.
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(
events,
vec![
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 10),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 20),
make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
]
);
}
#[::fuchsia::test]
async fn translates_touch_remove() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
// Touch add for pointer 1.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Add, 1)],
)
.await;
// Wait for another `Watch` to ensure input_file done processing the first reply.
// Use an empty `TouchEvent`, to minimize the chance that this event
// creates unexpected `uapi::input_event`s.
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
// Consume all of the `uapi::input_event`s that are available.
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events.len(), 6);
// Reply to touch change.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Remove, 1)],
)
.await;
// Wait for another `Watch`.
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(
events,
vec![
make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
]
);
}
#[::fuchsia::test]
async fn multi_touch_event_sequence() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
// Touch add for pointer 1.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Add, 1)],
)
.await;
// Wait for another `Watch`.
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events.len(), 6);
// Touch add for pointer 2.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![
make_touch_event_with_coords(10.0, 20.0, 1),
make_touch_event_with_phase(EventPhase::Add, 2),
],
)
.await;
// Wait for another `Watch`.
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(
events,
vec![
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 10),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 20),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 2),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 0),
make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
]
);
// Both pointers move.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![
make_touch_event_with_coords(11.0, 21.0, 1),
make_touch_event_with_coords(101.0, 201.0, 2),
],
)
.await;
// Wait for another `Watch`.
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(
events,
vec![
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 11),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 21),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 101),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 201),
make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
]
);
// Pointer 1 up.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![
make_touch_event_with_phase(EventPhase::Remove, 1),
make_touch_event_with_coords(101.0, 201.0, 2),
],
)
.await;
// Wait for another `Watch`.
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(
events,
vec![
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 101),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 201),
make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
]
);
// Pointer 2 up.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase(EventPhase::Remove, 2)],
)
.await;
// Wait for another `Watch`.
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(
events,
vec![
make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
]
);
}
#[::fuchsia::test]
async fn multi_event_sequence_unsorted_in_one_watch() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
// Touch add for pointer 1.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![
make_touch_event_with_coords_phase_timestamp(
10.0,
20.0,
EventPhase::Change,
1,
100,
),
make_touch_event_with_coords_phase_timestamp(0.0, 0.0, EventPhase::Add, 1, 1),
],
)
.await;
// Wait for another `Watch`.
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(
events,
vec![
make_uapi_input_event_with_timestamp(uapi::EV_KEY, uapi::BTN_TOUCH, 1, 1),
make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0, 1),
make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 1, 1),
make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 0, 1),
make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 0, 1),
make_uapi_input_event_with_timestamp(uapi::EV_SYN, uapi::SYN_REPORT, 0, 1),
make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0, 100),
make_uapi_input_event_with_timestamp(
uapi::EV_ABS,
uapi::ABS_MT_POSITION_X,
10,
100
),
make_uapi_input_event_with_timestamp(
uapi::EV_ABS,
uapi::ABS_MT_POSITION_Y,
20,
100
),
make_uapi_input_event_with_timestamp(uapi::EV_SYN, uapi::SYN_REPORT, 0, 100),
]
);
}
#[test_case((0.0, 0.0); "origin")]
#[test_case((100.7, 200.7); "above midpoint")]
#[test_case((100.3, 200.3); "below midpoint")]
#[test_case((100.5, 200.5); "midpoint")]
#[::fuchsia::test]
async fn sends_acceptable_coordinates((x, y): (f32, f32)) {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
// Touch add.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_coords_phase(x, y, EventPhase::Add, 1)],
)
.await;
// Wait for another `Watch`.
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
let events = read_uapi_events(locked, &input_file, &current_task);
// Check that the reported positions are within the acceptable error. The acceptable
// error is chosen to allow either rounding or truncation.
const ACCEPTABLE_ERROR: f32 = 1.0;
let actual_x = events
.iter()
.find(|event| {
event.type_ == uapi::EV_ABS as u16 && event.code == uapi::ABS_MT_POSITION_X as u16
})
.unwrap_or_else(|| panic!("did not find `ABS_X` event in {:?}", events))
.value;
let actual_y = events
.iter()
.find(|event| {
event.type_ == uapi::EV_ABS as u16 && event.code == uapi::ABS_MT_POSITION_Y as u16
})
.unwrap_or_else(|| panic!("did not find `ABS_Y` event in {:?}", events))
.value;
assert_near!(x, actual_x as f32, ACCEPTABLE_ERROR);
assert_near!(y, actual_y as f32, ACCEPTABLE_ERROR);
}
// Per the FIDL documentation for `TouchSource::Watch()`:
//
// > non-sample events should return an empty |TouchResponse| table to the
// > server
#[test_case(
make_touch_event_with_phase(EventPhase::Add, 2)
=> matches Some(TouchResponse { response_type: Some(_), ..});
"event_with_sample_yields_some_response_type")]
#[test_case(
TouchEvent::default() => matches Some(TouchResponse { response_type: None, ..});
"event_without_sample_yields_no_response_type")]
#[::fuchsia::test]
async fn sends_appropriate_reply_to_touch_source_server(
event: TouchEvent,
) -> Option<TouchResponse> {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, _input_file, mut touch_source_stream) =
start_touch_input(locked, &current_task).await;
// Reply to first `Watch` request.
answer_next_touch_watch_request(&mut touch_source_stream, vec![event]).await;
// Get response to `event`.
let responses =
answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
.await;
// Return the value for `test_case` to match on.
responses.get(0).cloned()
}
#[test_case(fidl_fuchsia_input::Key::Escape, uapi::KEY_POWER; "Esc maps to Power")]
#[test_case(fidl_fuchsia_input::Key::A, uapi::KEY_A; "A maps to A")]
#[::fuchsia::test]
async fn sends_keyboard_events(fkey: fidl_fuchsia_input::Key, lkey: u32) {
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_keyboard_device, keyboard_file, keyboard_listener) =
start_keyboard_input(locked, &current_task).await;
let key_event = fuiinput::KeyEvent {
timestamp: Some(0),
type_: Some(fuiinput::KeyEventType::Pressed),
key: Some(fkey),
..Default::default()
};
let _ = keyboard_listener.on_key_event(&key_event).await;
std::mem::drop(keyboard_listener); // Close Zircon channel.
let events = read_uapi_events(locked, &keyboard_file, &current_task);
assert_eq!(events.len(), 2);
assert_eq!(events[0].code, lkey as u16);
}
#[::fuchsia::test]
async fn skips_unknown_keyboard_events() {
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_keyboard_device, keyboard_file, keyboard_listener) =
start_keyboard_input(locked, &current_task).await;
let key_event = fuiinput::KeyEvent {
timestamp: Some(0),
type_: Some(fuiinput::KeyEventType::Pressed),
key: Some(fidl_fuchsia_input::Key::AcRefresh),
..Default::default()
};
let _ = keyboard_listener.on_key_event(&key_event).await;
std::mem::drop(keyboard_listener); // Close Zircon channel.
let events = read_uapi_events(locked, &keyboard_file, &current_task);
assert_eq!(events.len(), 0);
}
#[::fuchsia::test]
async fn sends_power_button_events() {
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, buttons_listener) =
start_button_input(locked, &current_task).await;
let power_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(true),
function: Some(false),
..Default::default()
};
let _ = buttons_listener.on_event(&power_event).await;
std::mem::drop(buttons_listener); // Close Zircon channel.
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events.len(), 2);
assert_eq!(events[0].code, uapi::KEY_POWER as u16);
assert_eq!(events[0].value, 1);
}
#[::fuchsia::test]
async fn sends_function_button_events() {
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, buttons_listener) =
start_button_input(locked, &current_task).await;
let function_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(false),
function: Some(true),
..Default::default()
};
let _ = buttons_listener.on_event(&function_event).await;
std::mem::drop(buttons_listener); // Close Zircon channel.
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events.len(), 2);
assert_eq!(events[0].code, uapi::KEY_VOLUMEDOWN as u16);
assert_eq!(events[0].value, 1);
}
#[::fuchsia::test]
async fn sends_overlapping_button_events() {
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, buttons_listener) =
start_button_input(locked, &current_task).await;
let power_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(true),
function: Some(false),
..Default::default()
};
let function_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(true),
function: Some(true),
..Default::default()
};
let function_release_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(true),
function: Some(false),
..Default::default()
};
let power_release_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(false),
function: Some(false),
..Default::default()
};
let _ = buttons_listener.on_event(&power_event).await;
let _ = buttons_listener.on_event(&function_event).await;
let _ = buttons_listener.on_event(&function_release_event).await;
let _ = buttons_listener.on_event(&power_release_event).await;
std::mem::drop(buttons_listener); // Close Zircon channel.
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events.len(), 8);
assert_eq!(events[0].code, uapi::KEY_POWER as u16);
assert_eq!(events[0].value, 1);
assert_eq!(events[2].code, uapi::KEY_VOLUMEDOWN as u16);
assert_eq!(events[2].value, 1);
assert_eq!(events[4].code, uapi::KEY_VOLUMEDOWN as u16);
assert_eq!(events[4].value, 0);
assert_eq!(events[6].code, uapi::KEY_POWER as u16);
assert_eq!(events[6].value, 0);
}
#[::fuchsia::test]
async fn sends_simultaneous_button_events() {
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, buttons_listener) =
start_button_input(locked, &current_task).await;
let power_and_function_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(true),
function: Some(true),
..Default::default()
};
let _ = buttons_listener.on_event(&power_and_function_event).await;
std::mem::drop(buttons_listener); // Close Zircon channel.
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events.len(), 4);
assert_eq!(events[0].code, uapi::KEY_POWER as u16);
assert_eq!(events[0].value, 1);
assert_eq!(events[2].code, uapi::KEY_VOLUMEDOWN as u16);
assert_eq!(events[2].value, 1);
}
#[test_case(1; "Scroll up")]
#[test_case(-1; "Scroll down")]
#[::fuchsia::test]
async fn sends_mouse_wheel_events(ticks: i64) {
let time = 100;
let uapi_event = uapi::input_event {
time: timeval_from_time(zx::MonotonicInstant::from_nanos(time)),
type_: uapi::EV_REL as u16,
code: uapi::REL_WHEEL as u16,
value: ticks as i32,
};
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_mouse_device, mouse_file, mut mouse_stream) =
start_mouse_input(locked, &current_task).await;
answer_next_mouse_watch_request(
&mut mouse_stream,
vec![make_mouse_wheel_event_with_timestamp(ticks, time)],
)
.await;
// Wait for another `Watch` to ensure mouse_file is done processing the other replies.
// Use an empty vec, to ensure no unexpected `uapi::input_event`s are created.
answer_next_mouse_watch_request(&mut mouse_stream, vec![]).await;
let events = read_uapi_events(locked, &mouse_file, &current_task);
assert_eq!(events.len(), 2);
assert_eq!(events[0], uapi_event);
}
#[::fuchsia::test]
async fn ignore_mouse_non_wheel_events() {
let mouse_move_event = fuipointer::MouseEvent {
timestamp: Some(0),
pointer_sample: Some(fuipointer::MousePointerSample {
device_id: Some(0),
position_in_viewport: Some([50.0, 50.0]),
..Default::default()
}),
..Default::default()
};
let mouse_click_event = fuipointer::MouseEvent {
timestamp: Some(0),
pointer_sample: Some(fuipointer::MousePointerSample {
device_id: Some(0),
pressed_buttons: Some(vec![1]),
..Default::default()
}),
..Default::default()
};
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_mouse_device, mouse_file, mut mouse_stream) =
start_mouse_input(locked, &current_task).await;
// Expect mouse relay to discard MouseEvents without vertical scroll.
answer_next_mouse_watch_request(&mut mouse_stream, vec![mouse_move_event]).await;
answer_next_mouse_watch_request(&mut mouse_stream, vec![mouse_click_event]).await;
// Wait for another `Watch` to ensure mouse_file is done processing the other replies.
// Use an empty vec, to ensure no unexpected `uapi::input_event`s are created.
answer_next_mouse_watch_request(&mut mouse_stream, vec![]).await;
let events = read_uapi_events(locked, &mouse_file, &current_task);
assert_eq!(events.len(), 0);
}
#[::fuchsia::test]
async fn touch_input_initialized_with_inspect_node() {
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let inspector = fuchsia_inspect::Inspector::default();
let touch_device = InputDevice::new_touch(
1200, /* screen width */
720, /* screen height */
&inspector.root(),
);
let _file_obj = touch_device.open_test(locked, &current_task);
assert_data_tree!(inspector, root: {
touch_device: {
total_fidl_events_received_count: 0u64,
total_fidl_events_ignored_count: 0u64,
total_fidl_events_unexpected_count: 0u64,
total_fidl_events_converted_count: 0u64,
total_uapi_events_generated_count: 0u64,
last_generated_uapi_event_timestamp_ns: 0i64,
touch_file_0: {
fidl_events_received_count: 0u64,
fidl_events_ignored_count: 0u64,
fidl_events_unexpected_count: 0u64,
fidl_events_converted_count: 0u64,
uapi_events_generated_count: 0u64,
uapi_events_read_count: 0u64,
last_generated_uapi_event_timestamp_ns: 0i64,
last_read_uapi_event_timestamp_ns: 0i64,
}
}
});
}
#[::fuchsia::test]
async fn touch_relay_updates_touch_inspect_status() {
let inspector = fuchsia_inspect::Inspector::default();
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut touch_source_stream) =
start_touch_input_inspect(locked, &current_task, &inspector).await;
// Send 2 TouchEvents to proxy that should be counted as `received` by InputFile
// A TouchEvent::default() has no pointer sample so these events should be discarded.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responder, .. })) => responder
.send(&vec![make_empty_touch_event(); 2])
.expect("failure sending Watch reply"),
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
// Send 5 TouchEvents with pointer sample to proxy, these should be received and converted
// Add/Remove events generate 5 uapi events each. Change events generate 3 uapi events each.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responses, responder })) => {
assert_matches!(responses.as_slice(), [_, _]);
responder
.send(&vec![
make_touch_event_with_coords_phase_timestamp(
0.0,
0.0,
EventPhase::Add,
1,
1000,
),
make_touch_event_with_coords_phase_timestamp(
0.0,
0.0,
EventPhase::Change,
1,
2000,
),
make_touch_event_with_coords_phase_timestamp(
0.0,
0.0,
EventPhase::Change,
1,
3000,
),
make_touch_event_with_coords_phase_timestamp(
0.0,
0.0,
EventPhase::Change,
1,
4000,
),
make_touch_event_with_coords_phase_timestamp(
0.0,
0.0,
EventPhase::Remove,
1,
5000,
),
])
.expect("failure sending Watch reply");
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
// Wait for next `Watch` call and verify it has five elements in `responses`.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responses, .. })) => {
assert_matches!(responses.as_slice(), [_, _, _, _, _])
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
let _events = read_uapi_events(locked, &input_file, &current_task);
assert_data_tree!(inspector, root: {
touch_device: {
total_fidl_events_received_count: 7u64,
total_fidl_events_ignored_count: 2u64,
total_fidl_events_unexpected_count: 0u64,
total_fidl_events_converted_count: 5u64,
total_uapi_events_generated_count: 22u64,
last_generated_uapi_event_timestamp_ns: 5000i64,
touch_file_0: {
fidl_events_received_count: 7u64,
fidl_events_ignored_count: 2u64,
fidl_events_unexpected_count: 0u64,
fidl_events_converted_count: 5u64,
uapi_events_generated_count: 22u64,
uapi_events_read_count: 22u64,
last_generated_uapi_event_timestamp_ns: 5000i64,
last_read_uapi_event_timestamp_ns: 5000i64,
},
}
});
}
#[::fuchsia::test]
async fn new_file_updates_inspect_status() {
let inspector = fuchsia_inspect::Inspector::default();
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let input_device = InputDevice::new_touch(700, 700, inspector.root());
let input_file_0 =
input_device.open_test(locked, &current_task).expect("Failed to create input file");
let (touch_source_client_end, mut touch_source_stream) =
fidl::endpoints::create_request_stream::<TouchSourceMarker>();
let (mouse_source_client_end, _mouse_source_stream) =
fidl::endpoints::create_request_stream::<fuipointer::MouseSourceMarker>();
let (keyboard_proxy, mut keyboard_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fuiinput::KeyboardMarker>();
let view_ref_pair =
fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
let (device_registry_proxy, mut device_listener_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
);
let (relay, relay_handle) = input_event_relay::new_input_relay();
relay.start_relays(
&current_task.kernel(),
EventProxyMode::None,
touch_source_client_end,
keyboard_proxy,
mouse_source_client_end,
view_ref_pair.view_ref,
device_registry_proxy,
input_device.open_files.clone(),
Default::default(),
Default::default(),
Some(input_device.inspect_status.clone()),
None,
None,
);
let _ = init_keyboard_listener(&mut keyboard_stream).await;
let _ = init_button_listeners(&mut device_listener_stream).await;
relay_handle.add_touch_device(
0,
input_device.open_files.clone(),
Some(input_device.inspect_status.clone()),
);
// Send 2 TouchEvents to proxy that should be counted as `received` by InputFile
// A TouchEvent::default() has no pointer sample so these events should be discarded.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responder, .. })) => responder
.send(&vec![make_empty_touch_event(); 2])
.expect("failure sending Watch reply"),
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
// Wait for next `Watch` call and verify it has two elements in `responses`.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responses, responder })) => {
assert_matches!(responses.as_slice(), [_, _]);
responder.send(&vec![]).expect("failure sending Watch reply");
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
// Verify file node & properties remain in inspect tree when file is closed
input_device.open_files.lock().clear();
drop(input_file_0);
// Open new file which should receive input_device's subsequent events
let input_file_1 =
input_device.open_test(locked, &current_task).expect("Failed to create input file");
// Send 5 TouchEvents with pointer sample to proxy, these should be received and converted
// Add/Remove events generate 5 uapi events each. Change events generate 3 uapi events each.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responder, .. })) => {
responder
.send(&vec![
make_touch_event_with_coords_phase_timestamp(
0.0,
0.0,
EventPhase::Add,
1,
1000,
),
make_touch_event_with_coords_phase_timestamp(
0.0,
0.0,
EventPhase::Change,
1,
2000,
),
make_touch_event_with_coords_phase_timestamp(
0.0,
0.0,
EventPhase::Change,
1,
3000,
),
make_touch_event_with_coords_phase_timestamp(
0.0,
0.0,
EventPhase::Change,
1,
4000,
),
make_touch_event_with_coords_phase_timestamp(
0.0,
0.0,
EventPhase::Remove,
1,
5000,
),
])
.expect("failure sending Watch reply");
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
// Wait for next `Watch` call and verify it has five elements in `responses`.
match touch_source_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responses, .. })) => {
assert_matches!(responses.as_slice(), [_, _, _, _, _])
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
let _events = read_uapi_events(locked, &input_file_1, &current_task);
// Verify file node & properties remain in inspect tree when file is closed
input_device.open_files.lock().clear();
drop(input_file_1);
assert_data_tree!(inspector, root: {
touch_device: {
total_fidl_events_received_count: 7u64,
total_fidl_events_ignored_count: 2u64,
total_fidl_events_unexpected_count: 0u64,
total_fidl_events_converted_count: 5u64,
total_uapi_events_generated_count: 22u64,
last_generated_uapi_event_timestamp_ns: 5000i64,
touch_file_0: {
fidl_events_received_count: 2u64,
fidl_events_ignored_count: 2u64,
fidl_events_unexpected_count: 0u64,
fidl_events_converted_count: 0u64,
uapi_events_generated_count: 0u64,
uapi_events_read_count: 0u64,
last_generated_uapi_event_timestamp_ns: 0i64,
last_read_uapi_event_timestamp_ns: 0i64,
},
touch_file_1: {
fidl_events_received_count: 5u64,
fidl_events_ignored_count: 0u64,
fidl_events_unexpected_count: 0u64,
fidl_events_converted_count: 5u64,
uapi_events_generated_count: 22u64,
uapi_events_read_count: 22u64,
last_generated_uapi_event_timestamp_ns: 5000i64,
last_read_uapi_event_timestamp_ns: 5000i64,
},
}
});
}
#[::fuchsia::test]
async fn keyboard_input_initialized_with_inspect_node() {
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let inspector = fuchsia_inspect::Inspector::default();
let keyboard_device = InputDevice::new_keyboard(&inspector.root());
let _file_obj = keyboard_device.open_test(locked, &current_task);
assert_data_tree!(inspector, root: {
keyboard_device: {
total_fidl_events_received_count: 0u64,
total_fidl_events_ignored_count: 0u64,
total_fidl_events_unexpected_count: 0u64,
total_fidl_events_converted_count: 0u64,
total_uapi_events_generated_count: 0u64,
last_generated_uapi_event_timestamp_ns: 0i64,
keyboard_file_0: {
fidl_events_received_count: 0u64,
fidl_events_ignored_count: 0u64,
fidl_events_unexpected_count: 0u64,
fidl_events_converted_count: 0u64,
uapi_events_generated_count: 0u64,
uapi_events_read_count: 0u64,
last_generated_uapi_event_timestamp_ns: 0i64,
last_read_uapi_event_timestamp_ns: 0i64,
}
}
});
}
#[::fuchsia::test]
async fn button_relay_updates_keyboard_inspect_status() {
let inspector = fuchsia_inspect::Inspector::default();
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, buttons_listener) =
start_button_input_inspect(locked, &current_task, &inspector).await;
// Each of these events should count toward received and converted.
// They also generate 2 uapi events each.
let power_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(true),
function: Some(false),
..Default::default()
};
let power_release_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(false),
function: Some(false),
..Default::default()
};
let _ = buttons_listener.on_event(&power_event).await;
let _ = buttons_listener.on_event(&power_release_event).await;
let events = read_uapi_events(locked, &input_file, &current_task);
assert_eq!(events.len(), 4);
assert_eq!(events[0].code, uapi::KEY_POWER as u16);
assert_eq!(events[0].value, 1);
assert_eq!(events[2].code, uapi::KEY_POWER as u16);
assert_eq!(events[2].value, 0);
let _events = read_uapi_events(locked, &input_file, &current_task);
assert_data_tree!(inspector, root: {
keyboard_device: {
total_fidl_events_received_count: 2u64,
total_fidl_events_ignored_count: 0u64,
total_fidl_events_unexpected_count: 0u64,
total_fidl_events_converted_count: 2u64,
total_uapi_events_generated_count: 4u64,
// Button events perform a realtime clockread, so any value will do.
last_generated_uapi_event_timestamp_ns: AnyProperty,
keyboard_file_0: {
fidl_events_received_count: 2u64,
fidl_events_ignored_count: 0u64,
fidl_events_unexpected_count: 0u64,
fidl_events_converted_count: 2u64,
uapi_events_generated_count: 4u64,
uapi_events_read_count: 4u64,
// Button events perform a realtime clockread, so any value will do.
last_generated_uapi_event_timestamp_ns: AnyProperty,
last_read_uapi_event_timestamp_ns: AnyProperty,
},
}
});
}
#[::fuchsia::test]
async fn mouse_input_initialized_with_inspect_node() {
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let inspector = fuchsia_inspect::Inspector::default();
let mouse_device = InputDevice::new_mouse(&inspector.root());
let _file_obj = mouse_device.open_test(locked, &current_task);
assert_data_tree!(inspector, root: {
mouse_device: {
total_fidl_events_received_count: 0u64,
total_fidl_events_ignored_count: 0u64,
total_fidl_events_unexpected_count: 0u64,
total_fidl_events_converted_count: 0u64,
total_uapi_events_generated_count: 0u64,
last_generated_uapi_event_timestamp_ns: 0i64,
mouse_file_0: {
fidl_events_received_count: 0u64,
fidl_events_ignored_count: 0u64,
fidl_events_unexpected_count: 0u64,
fidl_events_converted_count: 0u64,
uapi_events_generated_count: 0u64,
uapi_events_read_count: 0u64,
last_generated_uapi_event_timestamp_ns: 0i64,
last_read_uapi_event_timestamp_ns: 0i64,
}
}
});
}
#[::fuchsia::test]
async fn mouse_relay_updates_mouse_inspect_status() {
let inspector = fuchsia_inspect::Inspector::default();
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (_input_device, input_file, mut mouse_source_stream) =
start_mouse_input_inspect(locked, &current_task, &inspector).await;
let mouse_move_event = fuipointer::MouseEvent {
timestamp: Some(0),
pointer_sample: Some(fuipointer::MousePointerSample {
device_id: Some(0),
position_in_viewport: Some([50.0, 50.0]),
scroll_v: Some(0),
..Default::default()
}),
..Default::default()
};
let mouse_click_event = fuipointer::MouseEvent {
timestamp: Some(0),
pointer_sample: Some(fuipointer::MousePointerSample {
device_id: Some(0),
scroll_v: Some(0),
pressed_buttons: Some(vec![1]),
..Default::default()
}),
..Default::default()
};
// Send 2 non-wheel MouseEvents to proxy that should be counted as `received` by InputFile
// These events have no scroll_v delta in the pointer sample so they should be ignored.
answer_next_mouse_watch_request(
&mut mouse_source_stream,
vec![mouse_move_event, mouse_click_event],
)
.await;
// Send 5 MouseEvents with non-zero scroll_v delta to proxy, these should be received and
// converted to 1 uapi event each, with an extra sync event to signify end of the batch.
answer_next_mouse_watch_request(
&mut mouse_source_stream,
vec![make_mouse_wheel_event(1); 5],
)
.await;
// Send a final mouse wheel event and ensure the inspect tree reflects it's timestamp under
// last_generated_uapi_event_timestamp_ns and last_read_uapi_event_timestamp_ns.
answer_next_mouse_watch_request(
&mut mouse_source_stream,
vec![make_mouse_wheel_event_with_timestamp(-1, 5000)],
)
.await;
// Wait for another `Watch` to ensure mouse_file is done processing the other replies.
// Use an empty vec, to ensure no unexpected `uapi::input_event`s are created.
answer_next_mouse_watch_request(&mut mouse_source_stream, vec![]).await;
let _events = read_uapi_events(locked, &input_file, &current_task);
assert_data_tree!(inspector, root: {
mouse_device: {
total_fidl_events_received_count: 8u64,
total_fidl_events_ignored_count: 2u64,
total_fidl_events_unexpected_count: 0u64,
total_fidl_events_converted_count: 6u64,
total_uapi_events_generated_count: 8u64,
last_generated_uapi_event_timestamp_ns: 5000i64,
mouse_file_0: {
fidl_events_received_count: 8u64,
fidl_events_ignored_count: 2u64,
fidl_events_unexpected_count: 0u64,
fidl_events_converted_count: 6u64,
uapi_events_generated_count: 8u64,
uapi_events_read_count: 8u64,
last_generated_uapi_event_timestamp_ns: 5000i64,
last_read_uapi_event_timestamp_ns: 5000i64,
},
}
});
}
}