Skip to main content

starnix_modules_input/
input_device.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::InputFile;
6use crate::input_event_relay::OpenedFiles;
7use futures::FutureExt;
8use starnix_core::device::kobject::{Device, DeviceMetadata};
9use starnix_core::device::{DeviceMode, DeviceOps};
10use starnix_core::fs::sysfs::build_device_directory;
11use starnix_core::task::{CurrentTask, Kernel};
12use starnix_core::vfs::pseudo::simple_file::BytesFile;
13use starnix_core::vfs::{FileOps, NamespaceNode};
14use starnix_sync::{InputDeviceFileNodesLock, InputDeviceInfoLock, LockDepMutex};
15use starnix_uapi::device_id::{DeviceId as StarnixDeviceId, INPUT_MAJOR};
16use starnix_uapi::errors::Errno;
17use starnix_uapi::file_mode::mode;
18use starnix_uapi::input_id;
19use starnix_uapi::open_flags::OpenFlags;
20use std::sync::Arc;
21use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
22
23#[derive(Clone)]
24enum InputDeviceId {
25    // A touch device, containing (display width, display height).
26    Touch(i32, i32),
27
28    // A keyboard device.
29    Keyboard,
30
31    // A mouse device.
32    Mouse,
33}
34
35/// An [`InputDeviceStatus`] is tied to an [`InputDeviceBinding`] and provides properties
36/// detailing its Inspect status.
37/// We expect all (non-timestamp) properties' counts to equal the sum of that property from all
38/// files opened on that device. So for example, if a device had 3 separate input files opened, we
39/// would expect it's `total_fidl_events_received_count` to equal the sum of
40/// `fidl_events_received_count` from all 3 files and so forth.
41pub struct InputDeviceStatus {
42    /// A node that contains the state below.
43    pub node: fuchsia_inspect::Node,
44
45    /// Hold onto inspect nodes for files opened on this device, so that when these files are
46    /// closed, their inspect data is maintained.
47    pub file_nodes: LockDepMutex<Vec<fuchsia_inspect::Node>, InputDeviceFileNodesLock>,
48
49    /// The number of FIDL events received by this device from Fuchsia input system.
50    ///
51    /// We expect:
52    /// total_fidl_events_received_count = total_fidl_events_ignored_count +
53    ///                                    total_fidl_events_unexpected_count +
54    ///                                    total_fidl_events_converted_count
55    /// otherwise starnix ignored events unexpectedly.
56    ///
57    /// total_fidl_events_unexpected_count should be 0, if not it hints issues from upstream of
58    /// ui stack.
59    pub total_fidl_events_received_count: AtomicU64,
60
61    /// The number of FIDL events ignored by this device when attempting conversion to this
62    /// module’s representation of a TouchEvent.
63    pub total_fidl_events_ignored_count: AtomicU64,
64
65    /// The unexpected number of FIDL events reached to this module should be filtered out
66    /// earlier in the UI stack.
67    /// It maybe unexpected format or unexpected order.
68    pub total_fidl_events_unexpected_count: AtomicU64,
69
70    /// The number of FIDL events converted by this device to this module’s representation of
71    /// TouchEvent.
72    pub total_fidl_events_converted_count: AtomicU64,
73
74    /// The number of uapi::input_events generated by this device from TouchEvents.
75    pub total_uapi_events_generated_count: AtomicU64,
76
77    /// The event time of the last generated uapi::input_event by one of this device's InputFiles.
78    pub last_generated_uapi_event_timestamp_ns: AtomicI64,
79
80    /// The number of events that entered with wake leases.
81    pub total_events_with_wake_lease_count: AtomicU64,
82
83    /// The number of active incoming wake leases.
84    pub active_wake_leases_count: AtomicU64,
85}
86
87impl InputDeviceStatus {
88    pub fn new(node: fuchsia_inspect::Node) -> Arc<Self> {
89        let status = Arc::new(Self {
90            node,
91            file_nodes: Default::default(),
92            total_fidl_events_received_count: AtomicU64::new(0),
93            total_fidl_events_ignored_count: AtomicU64::new(0),
94            total_fidl_events_unexpected_count: AtomicU64::new(0),
95            total_fidl_events_converted_count: AtomicU64::new(0),
96            total_uapi_events_generated_count: AtomicU64::new(0),
97            last_generated_uapi_event_timestamp_ns: AtomicI64::new(0),
98            total_events_with_wake_lease_count: AtomicU64::new(0),
99            active_wake_leases_count: AtomicU64::new(0),
100        });
101
102        let weak_status = Arc::downgrade(&status);
103        status.node.record_lazy_values("status", move || {
104            let status = weak_status.upgrade();
105            async move {
106                let inspector = fuchsia_inspect::Inspector::default();
107                if let Some(status) = status {
108                    let root = inspector.root();
109                    root.record_uint(
110                        "total_fidl_events_received_count",
111                        status.total_fidl_events_received_count.load(Ordering::Relaxed),
112                    );
113                    root.record_uint(
114                        "total_fidl_events_ignored_count",
115                        status.total_fidl_events_ignored_count.load(Ordering::Relaxed),
116                    );
117                    root.record_uint(
118                        "total_fidl_events_unexpected_count",
119                        status.total_fidl_events_unexpected_count.load(Ordering::Relaxed),
120                    );
121                    root.record_uint(
122                        "total_fidl_events_converted_count",
123                        status.total_fidl_events_converted_count.load(Ordering::Relaxed),
124                    );
125                    root.record_uint(
126                        "total_uapi_events_generated_count",
127                        status.total_uapi_events_generated_count.load(Ordering::Relaxed),
128                    );
129                    root.record_int(
130                        "last_generated_uapi_event_timestamp_ns",
131                        status.last_generated_uapi_event_timestamp_ns.load(Ordering::Relaxed),
132                    );
133                    root.record_uint(
134                        "total_events_with_wake_lease_count",
135                        status.total_events_with_wake_lease_count.load(Ordering::Relaxed),
136                    );
137                    root.record_uint(
138                        "active_wake_leases_count",
139                        status.active_wake_leases_count.load(Ordering::Relaxed),
140                    );
141                }
142                Ok(inspector)
143            }
144            .boxed()
145        });
146
147        status
148    }
149
150    pub fn count_total_received_events(&self, count: u64) {
151        self.total_fidl_events_received_count.fetch_add(count, Ordering::Relaxed);
152    }
153
154    pub fn count_events_with_wake_lease(&self, count: u64) {
155        self.total_events_with_wake_lease_count.fetch_add(count, Ordering::Relaxed);
156    }
157
158    pub fn increment_active_wake_leases(&self, count: u64) {
159        self.active_wake_leases_count.fetch_add(count, Ordering::Relaxed);
160    }
161
162    pub fn decrement_active_wake_leases(&self, count: u64) {
163        self.active_wake_leases_count.fetch_sub(count, Ordering::Relaxed);
164    }
165
166    pub fn count_total_ignored_events(&self, count: u64) {
167        self.total_fidl_events_ignored_count.fetch_add(count, Ordering::Relaxed);
168    }
169
170    pub fn count_total_unexpected_events(&self, count: u64) {
171        self.total_fidl_events_unexpected_count.fetch_add(count, Ordering::Relaxed);
172    }
173
174    pub fn count_total_converted_events(&self, count: u64) {
175        self.total_fidl_events_converted_count.fetch_add(count, Ordering::Relaxed);
176    }
177
178    pub fn count_total_generated_events(&self, count: u64, event_time_ns: i64) {
179        self.total_uapi_events_generated_count.fetch_add(count, Ordering::Relaxed);
180        self.last_generated_uapi_event_timestamp_ns.store(event_time_ns, Ordering::Relaxed);
181    }
182}
183
184#[derive(Clone, Debug, PartialEq)]
185pub struct InputDeviceInfo {
186    pub input_id: input_id,
187    pub name: String,
188}
189
190pub type InputDeviceInfoHandle = Arc<LockDepMutex<InputDeviceInfo, InputDeviceInfoLock>>;
191
192impl InputDeviceInfo {
193    pub fn new(input_id: input_id, name: String) -> InputDeviceInfoHandle {
194        Arc::new(LockDepMutex::new(Self { input_id, name }))
195    }
196}
197
198#[derive(Clone)]
199pub struct InputDevice {
200    device_type: InputDeviceId,
201
202    pub open_files: OpenedFiles,
203
204    pub inspect_status: Arc<InputDeviceStatus>,
205
206    pub info: InputDeviceInfoHandle,
207}
208
209impl InputDevice {
210    pub fn new_touch(
211        display_width: i32,
212        display_height: i32,
213        info: InputDeviceInfoHandle,
214        node_name: &str,
215        inspect_node: &fuchsia_inspect::Node,
216    ) -> Self {
217        let node = inspect_node.create_child(node_name);
218        InputDevice {
219            device_type: InputDeviceId::Touch(display_width, display_height),
220            open_files: Default::default(),
221            inspect_status: InputDeviceStatus::new(node),
222            info,
223        }
224    }
225
226    pub fn new_keyboard(
227        info: InputDeviceInfoHandle,
228        node_name: &str,
229        inspect_node: &fuchsia_inspect::Node,
230    ) -> Self {
231        let node = inspect_node.create_child(node_name);
232        InputDevice {
233            device_type: InputDeviceId::Keyboard,
234            open_files: Default::default(),
235            inspect_status: InputDeviceStatus::new(node),
236            info,
237        }
238    }
239
240    pub fn new_mouse(
241        info: InputDeviceInfoHandle,
242        node_name: &str,
243        inspect_node: &fuchsia_inspect::Node,
244    ) -> Self {
245        let node = inspect_node.create_child(node_name);
246        InputDevice {
247            device_type: InputDeviceId::Mouse,
248            open_files: Default::default(),
249            inspect_status: InputDeviceStatus::new(node),
250            info,
251        }
252    }
253
254    /// Registers the input device, and its event interface, with the kernel's device registry.
255    ///
256    /// Linux exposes each input device as an `inputN` device of the `input` class, with the
257    /// `eventN` character device for its event interface nested beneath it, e.g.:
258    ///
259    /// ```text
260    /// /sys/devices/virtual/input/input0/
261    /// ├── name
262    /// ├── uevent
263    /// ├── id/{bustype,vendor,product,version}
264    /// └── event0/
265    ///     ├── dev
266    ///     └── uevent
267    /// ```
268    ///
269    /// Since both devices belong to the `input` class, `event{minor}` is created directly within
270    /// the `input{minor}` directory, without an intermediate class directory. Each is also linked
271    /// from `/sys/class/input`.
272    ///
273    /// Only the `eventN` device generates a uevent, since it is the only device with a `/dev`
274    /// node.
275    ///
276    /// Returns the `eventN` [`Device`], which should be passed to [`Self::unregister`] to remove
277    /// both devices.
278    pub fn register(self, kernel: &Kernel, minor: u32) -> Result<Device, Errno> {
279        let registry = &kernel.device_registry;
280        let input_class = registry.objects.input_class();
281        let info = self.info.clone();
282
283        let input_device = registry.add_numberless_device(
284            format!("input{minor}").as_str().into(),
285            /* parent = */ None,
286            input_class.clone(),
287            move |device, dir| {
288                build_device_directory(device, dir);
289                let name_info = info.clone();
290                dir.entry(
291                    "name",
292                    BytesFile::new_node(move || Ok(format!("{}\n", name_info.lock().name))),
293                    mode!(IFREG, 0o444),
294                );
295                dir.subdir("id", 0o755, |dir| {
296                    let fields: [(&str, fn(&input_id) -> u16); 4] = [
297                        ("bustype", |id| id.bustype),
298                        ("vendor", |id| id.vendor),
299                        ("product", |id| id.product),
300                        ("version", |id| id.version),
301                    ];
302                    for (attribute, selector) in fields {
303                        let attr_info = info.clone();
304                        dir.entry(
305                            attribute,
306                            BytesFile::new_node(move || {
307                                Ok(format!("{:04x}\n", selector(&attr_info.lock().input_id)))
308                            }),
309                            mode!(IFREG, 0o444),
310                        );
311                    }
312                });
313            },
314        );
315
316        registry.register_device_with_dir(
317            kernel,
318            format!("event{minor}").as_str().into(),
319            DeviceMetadata::new(
320                format!("input/event{minor}").into(),
321                StarnixDeviceId::new(INPUT_MAJOR, minor),
322                DeviceMode::Char,
323            ),
324            Some(input_device),
325            input_class,
326            build_device_directory,
327            self,
328        )
329    }
330
331    /// Unregisters an input device previously registered with [`Self::register`].
332    ///
333    /// `event_device` is the `eventN` [`Device`] returned by [`Self::register`]; its parent
334    /// `inputN` device is removed too.
335    pub fn unregister(current_task: &CurrentTask, event_device: Device) {
336        let registry = &current_task.kernel().device_registry;
337        let input_device = event_device.parent().cloned();
338        registry.remove_device(current_task, event_device);
339        if let Some(input_device) = input_device {
340            registry.remove_device(current_task, input_device);
341        }
342    }
343
344    pub fn open_internal(&self) -> Box<dyn FileOps> {
345        let input_file = match self.device_type {
346            InputDeviceId::Touch(display_width, display_height) => {
347                let mut file_nodes = self.inspect_status.file_nodes.lock();
348                let child_node = self
349                    .inspect_status
350                    .node
351                    .create_child(format!("touch_file_{}", file_nodes.len()));
352                let file = Arc::new(InputFile::new_touch(
353                    self.info.clone(),
354                    display_width,
355                    display_height,
356                    &child_node,
357                ));
358                file_nodes.push(child_node);
359                file
360            }
361            InputDeviceId::Keyboard => {
362                let mut file_nodes = self.inspect_status.file_nodes.lock();
363                let child_node = self
364                    .inspect_status
365                    .node
366                    .create_child(format!("keyboard_file_{}", file_nodes.len()));
367                let file = Arc::new(InputFile::new_keyboard(self.info.clone(), &child_node));
368                file_nodes.push(child_node);
369                file
370            }
371            InputDeviceId::Mouse => {
372                let mut file_nodes = self.inspect_status.file_nodes.lock();
373                let child_node = self
374                    .inspect_status
375                    .node
376                    .create_child(format!("mouse_file_{}", file_nodes.len()));
377                let file = Arc::new(InputFile::new_mouse(self.info.clone(), &child_node));
378                file_nodes.push(child_node);
379                file
380            }
381        };
382        input_file.init_inspect_status();
383        self.open_files.lock().on_file_opened(&input_file);
384        Box::new(crate::input_file::ArcInputFile(input_file))
385    }
386
387    #[cfg(test)]
388    pub fn open_test(
389        &self,
390        current_task: &CurrentTask,
391    ) -> Result<starnix_core::vfs::FileHandle, Errno> {
392        let input_file = self.open_internal();
393        let root_namespace_node = current_task
394            .lookup_path_from_root(".".into())
395            .expect("failed to get namespace node for root");
396
397        let file_object = starnix_core::vfs::FileObject::new(
398            current_task,
399            input_file,
400            root_namespace_node,
401            OpenFlags::empty(),
402        )
403        .expect("FileObject::new failed");
404        Ok(file_object)
405    }
406}
407
408impl DeviceOps for InputDevice {
409    fn open(
410        &self,
411        _current_task: &CurrentTask,
412        _id: StarnixDeviceId,
413        _node: &NamespaceNode,
414        _flags: OpenFlags,
415    ) -> Result<Box<dyn FileOps>, Errno> {
416        let input_file = self.open_internal();
417        Ok(input_file)
418    }
419}
420
421#[cfg(test)]
422mod test {
423    #![allow(clippy::unused_unit)] // for compatibility with `test_case`
424
425    use super::*;
426    use crate::input_event_relay::{
427        self, EventProxyMode, KEYBOARD_INPUT_ID, MOUSE_INPUT_ID, StartRelaysArgs, TOUCH_INPUT_ID,
428    };
429    use anyhow::anyhow;
430    use assert_matches::assert_matches;
431    use diagnostics_assertions::{AnyProperty, assert_data_tree};
432    use fidl::endpoints::RequestStream as _;
433    use fidl_fuchsia_ui_input as fuiinput;
434    use fidl_fuchsia_ui_input::MediaButtonsEvent;
435    use fidl_fuchsia_ui_input3 as fuiinput3;
436    use fidl_fuchsia_ui_pointer as fuipointer;
437    use fidl_fuchsia_ui_policy as fuipolicy;
438    use fuchsia_async::DurationExt;
439    use fuipointer::{
440        EventPhase, TouchEvent, TouchInteractionId, TouchPointerSample, TouchSourceV2Marker,
441    };
442    use futures::StreamExt as _;
443    use pretty_assertions::assert_eq;
444    use starnix_core::mm::{MemoryAccessor as _, MemoryAccessorExt as _};
445    use starnix_core::task::dynamic_thread_spawner::SpawnRequestBuilder;
446    use starnix_core::task::{EventHandler, Waiter};
447    use starnix_core::testing::{map_memory, spawn_kernel_and_run};
448    use starnix_core::vfs::FileHandle;
449    use starnix_core::vfs::buffers::VecOutputBuffer;
450    use starnix_syscalls::SUCCESS;
451    use starnix_types::time::timeval_from_time;
452    use starnix_uapi::errors::EAGAIN;
453    use starnix_uapi::uapi;
454    use starnix_uapi::user_address::UserAddress;
455    use starnix_uapi::vfs::FdEvents;
456
457    use test_case::test_case;
458    use test_util::assert_near;
459    use zerocopy::FromBytes as _;
460
461    const INPUT_EVENT_SIZE: usize = std::mem::size_of::<uapi::input_event>();
462    async fn start_touch_input(
463        current_task: &CurrentTask,
464    ) -> (InputDevice, FileHandle, fuipointer::TouchSourceV2RequestStream) {
465        let inspector = fuchsia_inspect::Inspector::default();
466        start_touch_input_inspect_and_dimensions(current_task, 700, 1200, &inspector).await
467    }
468
469    async fn start_touch_input_inspect(
470        current_task: &CurrentTask,
471        inspector: &fuchsia_inspect::Inspector,
472    ) -> (InputDevice, FileHandle, fuipointer::TouchSourceV2RequestStream) {
473        start_touch_input_inspect_and_dimensions(current_task, 700, 1200, &inspector).await
474    }
475
476    async fn init_keyboard_listener(
477        keyboard_stream: &mut fuiinput3::KeyboardRequestStream,
478    ) -> fuiinput3::KeyboardListenerProxy {
479        let keyboard_listener = match keyboard_stream.next().await {
480            Some(Ok(fuiinput3::KeyboardRequest::AddListener {
481                view_ref: _,
482                listener,
483                responder,
484            })) => {
485                let _ = responder.send();
486                listener.into_proxy()
487            }
488            _ => {
489                panic!("Failed to get event");
490            }
491        };
492
493        keyboard_listener
494    }
495
496    async fn init_button_listeners(
497        device_listener_stream: &mut fuipolicy::DeviceListenerRegistryRequestStream,
498    ) -> (fuipolicy::MediaButtonsListenerProxy, fuipolicy::TouchButtonsListenerProxy) {
499        let media_buttons_listener = match device_listener_stream.next().await {
500            Some(Ok(fuipolicy::DeviceListenerRegistryRequest::RegisterListener {
501                listener,
502                responder,
503            })) => {
504                let _ = responder.send();
505                listener.into_proxy()
506            }
507            _ => {
508                panic!("Failed to get event");
509            }
510        };
511
512        let touch_buttons_listener = match device_listener_stream.next().await {
513            Some(Ok(fuipolicy::DeviceListenerRegistryRequest::RegisterTouchButtonsListener {
514                listener,
515                responder,
516            })) => {
517                let _ = responder.send();
518                listener.into_proxy()
519            }
520            _ => {
521                panic!("Failed to get event");
522            }
523        };
524
525        (media_buttons_listener, touch_buttons_listener)
526    }
527
528    async fn init_device_listener(
529        device_listener_registry_stream: &mut fuiinput::DeviceListenerRegistryRequestStream,
530    ) -> (fuiinput::DeviceListenerProxy, fuiinput::DeviceIteratorRequestStream) {
531        let (device_iterator_client_end, device_iterator_stream) =
532            fidl::endpoints::create_request_stream::<fuiinput::DeviceIteratorMarker>();
533        let device_listener_proxy = match device_listener_registry_stream.next().await {
534            Some(Ok(fuiinput::DeviceListenerRegistryRequest::RegisterListener {
535                listener,
536                responder,
537            })) => {
538                let _ = responder.send(device_iterator_client_end);
539                listener.into_proxy()
540            }
541            _ => {
542                panic!("Failed to initialize device listener");
543            }
544        };
545        (device_listener_proxy, device_iterator_stream)
546    }
547
548    async fn drain_empty_device_iterator(
549        device_iterator_stream: &mut fuiinput::DeviceIteratorRequestStream,
550    ) {
551        match device_iterator_stream.next().await {
552            Some(Ok(fuiinput::DeviceIteratorRequest::GetNext { responder })) => {
553                responder.send(&[]).expect("failed to send empty batch");
554            }
555            _ => panic!("Failed to respond to existing devices iterator GetNext"),
556        }
557    }
558
559    async fn start_touch_input_inspect_and_dimensions(
560        current_task: &CurrentTask,
561        x_max: i32,
562        y_max: i32,
563        inspector: &fuchsia_inspect::Inspector,
564    ) -> (InputDevice, FileHandle, fuipointer::TouchSourceV2RequestStream) {
565        let input_device = InputDevice::new_touch(
566            x_max,
567            y_max,
568            InputDeviceInfo::new(TOUCH_INPUT_ID, "starnix_touch".to_string()),
569            "touch_device",
570            inspector.root(),
571        );
572        let input_file = input_device.open_test(current_task).expect("Failed to create input file");
573
574        let (touch_source_client_end, touch_source_stream) =
575            fidl::endpoints::create_request_stream::<TouchSourceV2Marker>();
576
577        let (mouse_source_client_end, _mouse_source_stream) =
578            fidl::endpoints::create_request_stream::<fuipointer::MouseSourceV2Marker>();
579
580        let (keyboard_proxy, mut keyboard_stream) =
581            fidl::endpoints::create_sync_proxy_and_stream::<fuiinput3::KeyboardMarker>();
582        let view_ref_pair =
583            fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
584
585        let (device_registry_proxy, mut device_listener_stream) =
586            fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
587            );
588
589        let (mut relay, _relay_handle) = input_event_relay::new_input_relay();
590        relay.add_touch_device(
591            input_event_relay::DEFAULT_TOUCH_DEVICE_ID,
592            input_device.open_files.clone(),
593            Some(input_device.inspect_status.clone()),
594        );
595        relay.start_relays(
596            &current_task.kernel(),
597            StartRelaysArgs {
598                event_proxy_mode: EventProxyMode::None,
599                touch_source_client_end,
600                keyboard_proxy,
601                mouse_source_client_end,
602                view_ref: view_ref_pair.view_ref,
603                registry_proxy: device_registry_proxy,
604                device_listener_registry: None,
605                display_width: x_max,
606                display_height: y_max,
607            },
608        );
609
610        let _ = init_keyboard_listener(&mut keyboard_stream).await;
611        let _ = init_button_listeners(&mut device_listener_stream).await;
612
613        (input_device, input_file, touch_source_stream)
614    }
615
616    async fn start_keyboard_input(
617        current_task: &CurrentTask,
618    ) -> (InputDevice, FileHandle, fuiinput3::KeyboardListenerProxy) {
619        let inspector = fuchsia_inspect::Inspector::default();
620        start_keyboard_input_inspect(current_task, &inspector).await
621    }
622
623    async fn start_keyboard_input_inspect(
624        current_task: &CurrentTask,
625        inspector: &fuchsia_inspect::Inspector,
626    ) -> (InputDevice, FileHandle, fuiinput3::KeyboardListenerProxy) {
627        let input_device = InputDevice::new_keyboard(
628            InputDeviceInfo::new(KEYBOARD_INPUT_ID, "starnix_buttons".to_string()),
629            "keyboard_device",
630            inspector.root(),
631        );
632        let input_file = input_device.open_test(current_task).expect("Failed to create input file");
633        let (keyboard_proxy, mut keyboard_stream) =
634            fidl::endpoints::create_sync_proxy_and_stream::<fuiinput3::KeyboardMarker>();
635        let view_ref_pair =
636            fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
637
638        let (device_registry_proxy, mut device_listener_stream) =
639            fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
640            );
641
642        let (touch_source_client_end, _touch_source_stream) =
643            fidl::endpoints::create_request_stream::<TouchSourceV2Marker>();
644
645        let (mouse_source_client_end, _mouse_source_stream) =
646            fidl::endpoints::create_request_stream::<fuipointer::MouseSourceV2Marker>();
647
648        let (mut relay, _relay_handle) = input_event_relay::new_input_relay();
649        relay.add_keyboard_device(
650            input_event_relay::DEFAULT_KEYBOARD_DEVICE_ID,
651            input_device.open_files.clone(),
652            Some(input_device.inspect_status.clone()),
653        );
654        relay.start_relays(
655            current_task.kernel(),
656            StartRelaysArgs {
657                event_proxy_mode: EventProxyMode::None,
658                touch_source_client_end,
659                keyboard_proxy,
660                mouse_source_client_end,
661                view_ref: view_ref_pair.view_ref,
662                registry_proxy: device_registry_proxy,
663                device_listener_registry: None,
664                display_width: 0,
665                display_height: 0,
666            },
667        );
668
669        let keyboard_listener = init_keyboard_listener(&mut keyboard_stream).await;
670        let _ = init_button_listeners(&mut device_listener_stream).await;
671
672        (input_device, input_file, keyboard_listener)
673    }
674
675    async fn start_button_input(
676        current_task: &CurrentTask,
677    ) -> (InputDevice, FileHandle, fuipolicy::MediaButtonsListenerProxy) {
678        let inspector = fuchsia_inspect::Inspector::default();
679        start_button_input_inspect(current_task, &inspector).await
680    }
681
682    async fn start_button_input_inspect(
683        current_task: &CurrentTask,
684        inspector: &fuchsia_inspect::Inspector,
685    ) -> (InputDevice, FileHandle, fuipolicy::MediaButtonsListenerProxy) {
686        let input_device = InputDevice::new_keyboard(
687            InputDeviceInfo::new(KEYBOARD_INPUT_ID, "starnix_buttons".to_string()),
688            "keyboard_device",
689            inspector.root(),
690        );
691        let input_file = input_device.open_test(current_task).expect("Failed to create input file");
692        let (device_registry_proxy, mut device_listener_stream) =
693            fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
694            );
695
696        let (touch_source_client_end, _touch_source_stream) =
697            fidl::endpoints::create_request_stream::<TouchSourceV2Marker>();
698        let (mouse_source_client_end, _mouse_source_stream) =
699            fidl::endpoints::create_request_stream::<fuipointer::MouseSourceV2Marker>();
700        let (keyboard_proxy, mut keyboard_stream) =
701            fidl::endpoints::create_sync_proxy_and_stream::<fuiinput3::KeyboardMarker>();
702        let view_ref_pair =
703            fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
704
705        let (mut relay, _relay_handle) = input_event_relay::new_input_relay();
706        relay.add_keyboard_device(
707            input_event_relay::DEFAULT_KEYBOARD_DEVICE_ID,
708            input_device.open_files.clone(),
709            Some(input_device.inspect_status.clone()),
710        );
711        relay.start_relays(
712            current_task.kernel(),
713            StartRelaysArgs {
714                event_proxy_mode: EventProxyMode::None,
715                touch_source_client_end,
716                keyboard_proxy,
717                mouse_source_client_end,
718                view_ref: view_ref_pair.view_ref,
719                registry_proxy: device_registry_proxy,
720                device_listener_registry: None,
721                display_width: 0,
722                display_height: 0,
723            },
724        );
725
726        let _ = init_keyboard_listener(&mut keyboard_stream).await;
727        let (button_listener, _) = init_button_listeners(&mut device_listener_stream).await;
728
729        (input_device, input_file, button_listener)
730    }
731
732    async fn start_mouse_input(
733        current_task: &CurrentTask,
734    ) -> (InputDevice, FileHandle, fuipointer::MouseSourceV2RequestStream) {
735        let inspector = fuchsia_inspect::Inspector::default();
736        start_mouse_input_inspect(current_task, &inspector).await
737    }
738
739    async fn start_mouse_input_inspect(
740        current_task: &CurrentTask,
741        inspector: &fuchsia_inspect::Inspector,
742    ) -> (InputDevice, FileHandle, fuipointer::MouseSourceV2RequestStream) {
743        let input_device = InputDevice::new_mouse(
744            InputDeviceInfo::new(MOUSE_INPUT_ID, "starnix_mouse".to_string()),
745            "mouse_device",
746            inspector.root(),
747        );
748        let input_file = input_device.open_test(current_task).expect("Failed to create input file");
749
750        let (touch_source_client_end, _touch_source_stream) =
751            fidl::endpoints::create_request_stream::<TouchSourceV2Marker>();
752
753        let (mouse_source_client_end, mouse_source_stream) =
754            fidl::endpoints::create_request_stream::<fuipointer::MouseSourceV2Marker>();
755
756        let (keyboard_proxy, mut keyboard_stream) =
757            fidl::endpoints::create_sync_proxy_and_stream::<fuiinput3::KeyboardMarker>();
758        let view_ref_pair =
759            fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
760
761        let (device_registry_proxy, mut device_listener_stream) =
762            fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
763            );
764
765        let (mut relay, _relay_handle) = input_event_relay::new_input_relay();
766        relay.add_mouse_device(
767            input_event_relay::DEFAULT_MOUSE_DEVICE_ID,
768            input_device.open_files.clone(),
769            Some(input_device.inspect_status.clone()),
770        );
771        relay.start_relays(
772            &current_task.kernel(),
773            StartRelaysArgs {
774                event_proxy_mode: EventProxyMode::None,
775                touch_source_client_end,
776                keyboard_proxy,
777                mouse_source_client_end,
778                view_ref: view_ref_pair.view_ref,
779                registry_proxy: device_registry_proxy,
780                device_listener_registry: None,
781                display_width: 0,
782                display_height: 0,
783            },
784        );
785
786        let _ = init_keyboard_listener(&mut keyboard_stream).await;
787        let _ = init_button_listeners(&mut device_listener_stream).await;
788
789        (input_device, input_file, mouse_source_stream)
790    }
791
792    fn make_touch_event(pointer_id: u32) -> fuipointer::TouchEvent {
793        // Default to `Change`, because that has the fewest side effects.
794        make_touch_event_with_phase(EventPhase::Change, pointer_id)
795    }
796
797    fn make_touch_event_with_phase(phase: EventPhase, pointer_id: u32) -> fuipointer::TouchEvent {
798        make_touch_event_with_coords_phase(0.0, 0.0, phase, pointer_id)
799    }
800
801    fn make_touch_event_with_coords_phase(
802        x: f32,
803        y: f32,
804        phase: EventPhase,
805        pointer_id: u32,
806    ) -> fuipointer::TouchEvent {
807        make_touch_event_with_coords_phase_timestamp(x, y, phase, pointer_id, 0)
808    }
809
810    fn make_touch_event_with_coords(x: f32, y: f32, pointer_id: u32) -> fuipointer::TouchEvent {
811        make_touch_event_with_coords_phase(x, y, EventPhase::Change, pointer_id)
812    }
813
814    fn make_touch_event_with_coords_phase_timestamp(
815        x: f32,
816        y: f32,
817        phase: EventPhase,
818        pointer_id: u32,
819        time_nanos: i64,
820    ) -> fuipointer::TouchEvent {
821        make_touch_event_with_coords_phase_timestamp_device_id(
822            x, y, phase, pointer_id, time_nanos, 0,
823        )
824    }
825
826    fn make_empty_touch_event() -> fuipointer::TouchEvent {
827        TouchEvent {
828            pointer_sample: Some(TouchPointerSample {
829                interaction: Some(TouchInteractionId {
830                    pointer_id: 0,
831                    device_id: 0,
832                    interaction_id: 0,
833                }),
834                ..Default::default()
835            }),
836            ..Default::default()
837        }
838    }
839
840    fn make_touch_event_with_coords_phase_timestamp_device_id(
841        x: f32,
842        y: f32,
843        phase: EventPhase,
844        pointer_id: u32,
845        time_nanos: i64,
846        device_id: u32,
847    ) -> fuipointer::TouchEvent {
848        TouchEvent {
849            timestamp: Some(time_nanos),
850            pointer_sample: Some(TouchPointerSample {
851                position_in_viewport: Some([x, y]),
852                // Default to `Change`, because that has the fewest side effects.
853                phase: Some(phase),
854                interaction: Some(TouchInteractionId { pointer_id, device_id, interaction_id: 0 }),
855                ..Default::default()
856            }),
857            ..Default::default()
858        }
859    }
860
861    fn make_mouse_wheel_event(ticks: i64) -> fuipointer::MouseEvent {
862        make_mouse_wheel_event_with_timestamp(ticks, 0)
863    }
864
865    fn make_mouse_wheel_event_with_timestamp(ticks: i64, timestamp: i64) -> fuipointer::MouseEvent {
866        fuipointer::MouseEvent {
867            timestamp: Some(timestamp),
868            pointer_sample: Some(fuipointer::MousePointerSample {
869                device_id: Some(input_event_relay::DEFAULT_MOUSE_DEVICE_ID),
870                scroll_v: Some(ticks),
871                ..Default::default()
872            }),
873            ..Default::default()
874        }
875    }
876
877    fn read_uapi_events(file: &FileHandle, current_task: &CurrentTask) -> Vec<uapi::input_event> {
878        std::iter::from_fn(|| {
879            let mut event_bytes = VecOutputBuffer::new(INPUT_EVENT_SIZE);
880            match file.read(current_task, &mut event_bytes) {
881                Ok(INPUT_EVENT_SIZE) => Some(
882                    uapi::input_event::read_from_bytes(Vec::from(event_bytes).as_slice())
883                        .map_err(|_| anyhow!("failed to read input_event from buffer")),
884                ),
885                Ok(other_size) => {
886                    Some(Err(anyhow!("got {} bytes (expected {})", other_size, INPUT_EVENT_SIZE)))
887                }
888                Err(Errno { code: EAGAIN, .. }) => None,
889                Err(other_error) => Some(Err(anyhow!("read failed: {:?}", other_error))),
890            }
891        })
892        .enumerate()
893        .map(|(i, read_res)| match read_res {
894            Ok(event) => event,
895            Err(e) => panic!("unexpected result {:?} on iteration {}", e, i),
896        })
897        .collect()
898    }
899
900    // Sends `touch_events` to the client stream and waits for `AcknowledgeEvents`.
901    async fn answer_next_touch_watch_request(
902        request_stream: &mut fuipointer::TouchSourceV2RequestStream,
903        touch_events: Vec<TouchEvent>,
904    ) {
905        let control_handle = request_stream.control_handle();
906        control_handle
907            .send_on_touch_events(touch_events, 1)
908            .expect("failure sending OnTouchEvents");
909        match request_stream.next().await {
910            Some(Ok(fuipointer::TouchSourceV2Request::AcknowledgeEvents { .. })) => {}
911            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
912        }
913    }
914
915    // Sends `mouse_events` to the client stream and waits for `AcknowledgeEvents`.
916    async fn answer_next_mouse_watch_request(
917        request_stream: &mut fuipointer::MouseSourceV2RequestStream,
918        mouse_events: Vec<fuipointer::MouseEvent>,
919    ) {
920        let control_handle = request_stream.control_handle();
921        control_handle
922            .send_on_mouse_events(mouse_events, 1)
923            .expect("failure sending OnMouseEvents");
924        match request_stream.next().await {
925            Some(Ok(fuipointer::MouseSourceV2Request::AcknowledgeEvents { .. })) => {}
926            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
927        }
928    }
929
930    #[::fuchsia::test]
931    async fn touch_source_v2_acknowledges_events_with_matching_stamp() {
932        spawn_kernel_and_run(async move |current_task| {
933            let (_input_device, _input_file, mut touch_source_stream) =
934                start_touch_input(&current_task).await;
935
936            let control_handle = touch_source_stream.control_handle();
937            control_handle
938                .send_on_touch_events(vec![make_empty_touch_event(), make_empty_touch_event()], 10)
939                .expect("failure sending OnTouchEvents");
940            match touch_source_stream.next().await {
941                Some(Ok(fuipointer::TouchSourceV2Request::AcknowledgeEvents {
942                    last_acknowledged_event_stamp,
943                    ..
944                })) => assert_eq!(last_acknowledged_event_stamp, 10),
945                unexpected => panic!("unexpected request {:?}", unexpected),
946            }
947
948            control_handle
949                .send_on_touch_events(
950                    vec![
951                        make_empty_touch_event(),
952                        make_empty_touch_event(),
953                        make_empty_touch_event(),
954                        make_empty_touch_event(),
955                        make_empty_touch_event(),
956                    ],
957                    20,
958                )
959                .expect("failure sending OnTouchEvents");
960            match touch_source_stream.next().await {
961                Some(Ok(fuipointer::TouchSourceV2Request::AcknowledgeEvents {
962                    last_acknowledged_event_stamp,
963                    ..
964                })) => assert_eq!(last_acknowledged_event_stamp, 20),
965                unexpected => panic!("unexpected request {:?}", unexpected),
966            }
967        })
968        .await;
969    }
970
971    #[::fuchsia::test]
972    async fn notifies_polling_waiters_of_new_data() {
973        spawn_kernel_and_run(async move |current_task| {
974            // Set up resources.
975
976            let (_input_device, input_file, mut touch_source_stream) =
977                start_touch_input(&current_task).await;
978            let waiter1 = Waiter::new();
979            let waiter2 = Waiter::new();
980
981            // Ask `input_file` to notify waiters when data is available to read.
982            [&waiter1, &waiter2].iter().for_each(|waiter| {
983                input_file.wait_async(&current_task, waiter, FdEvents::POLLIN, EventHandler::None);
984            });
985            assert_matches!(waiter1.wait_until(&current_task, zx::MonotonicInstant::ZERO), Err(_));
986            assert_matches!(waiter2.wait_until(&current_task, zx::MonotonicInstant::ZERO), Err(_));
987
988            // Reply to first `Watch` request.
989            answer_next_touch_watch_request(
990                &mut touch_source_stream,
991                vec![make_touch_event_with_phase(EventPhase::Add, 1)],
992            )
993            .await;
994            answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)])
995                .await;
996
997            // `InputFile` should be done processing the first reply, since it has sent its second
998            // request. And, as part of processing the first reply, `InputFile` should have notified
999            // the interested waiters.
1000            assert_eq!(waiter1.wait_until(&current_task, zx::MonotonicInstant::ZERO), Ok(()));
1001            assert_eq!(waiter2.wait_until(&current_task, zx::MonotonicInstant::ZERO), Ok(()));
1002        })
1003        .await;
1004    }
1005
1006    #[::fuchsia::test]
1007    async fn notifies_blocked_waiter_of_new_data() {
1008        spawn_kernel_and_run(async move |current_task| {
1009            // Set up resources.
1010            let kernel = current_task.kernel().clone();
1011            let (_input_device, input_file, mut touch_source_stream) =
1012                start_touch_input(&current_task).await;
1013            let waiter = Waiter::new();
1014
1015            // Ask `input_file` to notify `waiter` when data is available to read.
1016            input_file.wait_async(&current_task, &waiter, FdEvents::POLLIN, EventHandler::None);
1017
1018            let closure = move |task: &CurrentTask| waiter.wait(&task);
1019
1020            let (waiter_thread, req) = SpawnRequestBuilder::new()
1021                .with_debug_name("input-device-waiter")
1022                .with_sync_closure(closure)
1023                .build_with_async_result();
1024            kernel.kthreads.spawner().spawn_from_request(req);
1025
1026            let mut waiter_thread = Box::pin(waiter_thread);
1027            assert_matches!(futures::poll!(&mut waiter_thread), futures::task::Poll::Pending);
1028
1029            // Reply to first `Watch` request.
1030            answer_next_touch_watch_request(
1031                &mut touch_source_stream,
1032                vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1033            )
1034            .await;
1035
1036            // Wait for another `Watch`.
1037            //
1038            // TODO(https://fxbug.dev/42075452): Without this, `relay_thread` gets stuck `await`-ing
1039            // the reply to its first request. Figure out why that happens, and remove this second
1040            // reply.
1041            answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)])
1042                .await;
1043        })
1044        .await;
1045    }
1046
1047    #[::fuchsia::test]
1048    async fn does_not_notify_polling_waiters_without_new_data() {
1049        spawn_kernel_and_run(async move |current_task| {
1050            // Set up resources.
1051
1052            let (_input_device, input_file, mut touch_source_stream) =
1053                start_touch_input(&current_task).await;
1054            let waiter1 = Waiter::new();
1055            let waiter2 = Waiter::new();
1056
1057            // Ask `input_file` to notify waiters when data is available to read.
1058            [&waiter1, &waiter2].iter().for_each(|waiter| {
1059                input_file.wait_async(&current_task, waiter, FdEvents::POLLIN, EventHandler::None);
1060            });
1061            assert_matches!(waiter1.wait_until(&current_task, zx::MonotonicInstant::ZERO), Err(_));
1062            assert_matches!(waiter2.wait_until(&current_task, zx::MonotonicInstant::ZERO), Err(_));
1063
1064            // Reply to first `Watch` request with an empty set of events.
1065            answer_next_touch_watch_request(&mut touch_source_stream, vec![]).await;
1066
1067            // `InputFile` should be done processing the first reply. Since there
1068            // were no touch_events given, `InputFile` should not have notified the
1069            // interested waiters.
1070            assert_matches!(waiter1.wait_until(&current_task, zx::MonotonicInstant::ZERO), Err(_));
1071            assert_matches!(waiter2.wait_until(&current_task, zx::MonotonicInstant::ZERO), Err(_));
1072        })
1073        .await;
1074    }
1075
1076    // Note: a user program may also want to be woken if events were already ready at the
1077    // time that the program called `epoll_wait()`. However, there's no test for that case
1078    // in this module, because:
1079    //
1080    // 1. Not all programs will want to be woken in such a case. In particular, some programs
1081    //    use "edge-triggered" mode instead of "level-tiggered" mode. For details on the
1082    //    two modes, see https://man7.org/linux/man-pages/man7/epoll.7.html.
1083    // 2. For programs using "level-triggered" mode, the relevant behavior is implemented in
1084    //    the `epoll` module, and verified by `epoll::tests::test_epoll_ready_then_wait()`.
1085    //
1086    // See also: the documentation for `FileOps::wait_async()`.
1087
1088    #[::fuchsia::test]
1089    async fn honors_wait_cancellation() {
1090        spawn_kernel_and_run(async move |current_task| {
1091            // Set up input resources.
1092
1093            let (_input_device, input_file, mut touch_source_stream) =
1094                start_touch_input(&current_task).await;
1095            let waiter1 = Waiter::new();
1096            let waiter2 = Waiter::new();
1097
1098            // Ask `input_file` to notify `waiter` when data is available to read.
1099            let waitkeys = [&waiter1, &waiter2]
1100                .iter()
1101                .map(|waiter| {
1102                    input_file
1103                        .wait_async(&current_task, waiter, FdEvents::POLLIN, EventHandler::None)
1104                        .expect("wait_async")
1105                })
1106                .collect::<Vec<_>>();
1107
1108            // Cancel wait for `waiter1`.
1109            waitkeys.into_iter().next().expect("failed to get first waitkey").cancel();
1110
1111            // Reply to first `Watch` request.
1112            answer_next_touch_watch_request(
1113                &mut touch_source_stream,
1114                vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1115            )
1116            .await;
1117            // Wait for another `Watch`.
1118            answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)])
1119                .await;
1120
1121            // `InputFile` should be done processing the first reply, since it has sent its second
1122            // request. And, as part of processing the first reply, `InputFile` should have notified
1123            // the interested waiters.
1124            assert_matches!(waiter1.wait_until(&current_task, zx::MonotonicInstant::ZERO), Err(_));
1125            assert_eq!(waiter2.wait_until(&current_task, zx::MonotonicInstant::ZERO), Ok(()));
1126        })
1127        .await;
1128    }
1129
1130    #[::fuchsia::test]
1131    async fn query_events() {
1132        spawn_kernel_and_run(async move |current_task| {
1133            // Set up resources.
1134
1135            let (_input_device, input_file, mut touch_source_stream) =
1136                start_touch_input(&current_task).await;
1137
1138            // Check initial expectation.
1139            assert_eq!(
1140                input_file.query_events(&current_task).expect("query_events"),
1141                FdEvents::empty(),
1142                "events should be empty before data arrives"
1143            );
1144
1145            // Reply to first `Watch` request.
1146            answer_next_touch_watch_request(
1147                &mut touch_source_stream,
1148                vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1149            )
1150            .await;
1151
1152            // Wait for another `Watch`.
1153            answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)])
1154                .await;
1155
1156            // Check post-watch expectation.
1157            assert_eq!(
1158                input_file.query_events(&current_task).expect("query_events"),
1159                FdEvents::POLLIN | FdEvents::POLLRDNORM,
1160                "events should be POLLIN after data arrives"
1161            );
1162        })
1163        .await;
1164    }
1165
1166    fn make_uapi_input_event(ty: u32, code: u32, value: i32) -> uapi::input_event {
1167        make_uapi_input_event_with_timestamp(ty, code, value, 0)
1168    }
1169
1170    fn make_uapi_input_event_with_timestamp(
1171        ty: u32,
1172        code: u32,
1173        value: i32,
1174        time_nanos: i64,
1175    ) -> uapi::input_event {
1176        uapi::input_event {
1177            time: timeval_from_time(zx::MonotonicInstant::from_nanos(time_nanos)),
1178            type_: ty as u16,
1179            code: code as u16,
1180            value,
1181        }
1182    }
1183
1184    #[::fuchsia::test]
1185    async fn touch_event_ignored() {
1186        spawn_kernel_and_run(async move |current_task| {
1187            // Set up resources.
1188            let inspector = fuchsia_inspect::Inspector::default();
1189
1190            let (_input_device, input_file, mut touch_source_stream) =
1191                start_touch_input_inspect(&current_task, &inspector).await;
1192
1193            // Touch add for pointer 1. This should be counted as a received event and a converted
1194            // event. It should also yield 6 generated events.
1195            answer_next_touch_watch_request(
1196                &mut touch_source_stream,
1197                vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1198            )
1199            .await;
1200
1201            // Wait for another `Watch` to ensure input_file done processing the first reply.
1202            // Use an empty `TouchEvent`, to minimize the chance that this event creates unexpected
1203            // `uapi::input_event`s. This should be counted as a received event and an ignored event.
1204            answer_next_touch_watch_request(
1205                &mut touch_source_stream,
1206                vec![make_empty_touch_event()],
1207            )
1208            .await;
1209
1210            // Consume all of the `uapi::input_event`s that are available.
1211            let events = read_uapi_events(&input_file, &current_task);
1212
1213            assert_eq!(events.len(), 6);
1214
1215            // Reply to `Watch` request of empty event. This should be counted as a received event and
1216            // an ignored event.
1217            answer_next_touch_watch_request(
1218                &mut touch_source_stream,
1219                vec![make_empty_touch_event()],
1220            )
1221            .await;
1222
1223            let events = read_uapi_events(&input_file, &current_task);
1224            assert_eq!(events, vec![]);
1225            assert_data_tree!(inspector, root: {
1226                touch_device: {
1227                    active_wake_leases_count: 0u64,
1228                    total_events_with_wake_lease_count: 0u64,
1229                    total_fidl_events_received_count: 3u64,
1230                    total_fidl_events_ignored_count: 2u64,
1231                    total_fidl_events_unexpected_count: 0u64,
1232                    total_fidl_events_converted_count: 1u64,
1233                    total_uapi_events_generated_count: 6u64,
1234                    last_generated_uapi_event_timestamp_ns: 0i64,
1235                    touch_file_0: {
1236                        fidl_events_received_count: 3u64,
1237                        fidl_events_ignored_count: 2u64,
1238                        fidl_events_unexpected_count: 0u64,
1239                        fidl_events_converted_count: 1u64,
1240                        uapi_events_generated_count: 6u64,
1241                        uapi_events_read_count: 6u64,
1242                        fd_read_count: 8u64,
1243                        fd_notify_count: 1u64,
1244                        last_generated_uapi_event_timestamp_ns: 0i64,
1245                        last_read_uapi_event_timestamp_ns: 0i64,
1246                        opened_without_nonblock: AnyProperty,
1247                        open_timestamp_ns: AnyProperty,
1248                        closed: AnyProperty,
1249                        close_timestamp_ns: AnyProperty,
1250                    },
1251                }
1252            });
1253        })
1254        .await;
1255    }
1256
1257    #[test_case(make_touch_event_with_phase(EventPhase::Add, 1); "touch add for pointer already added")]
1258    #[test_case(make_touch_event_with_phase(EventPhase::Change, 2); "touch change for pointer not added")]
1259    #[test_case(make_touch_event_with_phase(EventPhase::Remove, 2); "touch remove for pointer not added")]
1260    #[test_case(make_touch_event_with_phase(EventPhase::Cancel, 1); "touch cancel")]
1261    #[::fuchsia::test]
1262    async fn touch_event_unexpected(event: TouchEvent) {
1263        spawn_kernel_and_run(async move |current_task| {
1264            // Set up resources.
1265            let inspector = fuchsia_inspect::Inspector::default();
1266
1267            let (_input_device, input_file, mut touch_source_stream) =
1268                start_touch_input_inspect(&current_task, &inspector).await;
1269
1270            // Touch add for pointer 1. This should be counted as a received event and a converted
1271            // event. It should also yield 6 generated events.
1272            answer_next_touch_watch_request(
1273                &mut touch_source_stream,
1274                vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1275            )
1276            .await;
1277
1278            // Wait for another `Watch` to ensure input_file done processing the first reply.
1279            // Use an empty `TouchEvent`, to minimize the chance that this event creates unexpected
1280            // `uapi::input_event`s. This should be counted as a received event and an ignored event.
1281            answer_next_touch_watch_request(
1282                &mut touch_source_stream,
1283                vec![make_empty_touch_event()],
1284            )
1285            .await;
1286
1287            // Consume all of the `uapi::input_event`s that are available.
1288            let events = read_uapi_events(&input_file, &current_task);
1289
1290            assert_eq!(events.len(), 6);
1291
1292            // Reply to `Watch` request of given event. This should be counted as a received event and
1293            // an unexpected event.
1294            answer_next_touch_watch_request(&mut touch_source_stream, vec![event]).await;
1295
1296            let events = read_uapi_events(&input_file, &current_task);
1297            assert_eq!(events, vec![]);
1298            assert_data_tree!(inspector, root: {
1299                touch_device: {
1300                    active_wake_leases_count: 0u64,
1301                    total_events_with_wake_lease_count: 0u64,
1302                    total_fidl_events_received_count: 3u64,
1303                    total_fidl_events_ignored_count: 1u64,
1304                    total_fidl_events_unexpected_count: 1u64,
1305                    total_fidl_events_converted_count: 1u64,
1306                    total_uapi_events_generated_count: 6u64,
1307                    last_generated_uapi_event_timestamp_ns: 0i64,
1308                    touch_file_0: {
1309                        fidl_events_received_count: 3u64,
1310                        fidl_events_ignored_count: 1u64,
1311                        fidl_events_unexpected_count: 1u64,
1312                        fidl_events_converted_count: 1u64,
1313                        uapi_events_generated_count: 6u64,
1314                        uapi_events_read_count: 6u64,
1315                        fd_read_count: 8u64,
1316                        fd_notify_count: 1u64,
1317                        last_generated_uapi_event_timestamp_ns: 0i64,
1318                        last_read_uapi_event_timestamp_ns: 0i64,
1319                        opened_without_nonblock: AnyProperty,
1320                        open_timestamp_ns: AnyProperty,
1321                        closed: AnyProperty,
1322                        close_timestamp_ns: AnyProperty,
1323                    },
1324                }
1325            });
1326        })
1327        .await;
1328    }
1329
1330    #[::fuchsia::test]
1331    async fn translates_touch_add() {
1332        spawn_kernel_and_run(async move |current_task| {
1333            // Set up resources.
1334
1335            let (_input_device, input_file, mut touch_source_stream) =
1336                start_touch_input(&current_task).await;
1337
1338            // Touch add for pointer 1.
1339            answer_next_touch_watch_request(
1340                &mut touch_source_stream,
1341                vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1342            )
1343            .await;
1344
1345            // Wait for another `Watch` to ensure input_file done processing the first reply.
1346            // Use an empty `TouchEvent`, to minimize the chance that this event
1347            // creates unexpected `uapi::input_event`s.
1348            answer_next_touch_watch_request(
1349                &mut touch_source_stream,
1350                vec![make_empty_touch_event()],
1351            )
1352            .await;
1353
1354            // Consume all of the `uapi::input_event`s that are available.
1355            let events = read_uapi_events(&input_file, &current_task);
1356
1357            assert_eq!(
1358                events,
1359                vec![
1360                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1361                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 1),
1362                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 0),
1363                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 0),
1364                    make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 1),
1365                    make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1366                ]
1367            );
1368        })
1369        .await;
1370    }
1371
1372    #[::fuchsia::test]
1373    async fn translates_touch_change() {
1374        spawn_kernel_and_run(async move |current_task| {
1375            // Set up resources.
1376
1377            let (_input_device, input_file, mut touch_source_stream) =
1378                start_touch_input(&current_task).await;
1379
1380            // Touch add for pointer 1.
1381            answer_next_touch_watch_request(
1382                &mut touch_source_stream,
1383                vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1384            )
1385            .await;
1386
1387            // Wait for another `Watch` to ensure input_file done processing the first reply.
1388            // Use an empty `TouchEvent`, to minimize the chance that this event
1389            // creates unexpected `uapi::input_event`s.
1390            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1391                .await;
1392
1393            // Consume all of the `uapi::input_event`s that are available.
1394            let events = read_uapi_events(&input_file, &current_task);
1395
1396            assert_eq!(events.len(), 6);
1397
1398            // Reply to touch change.
1399            answer_next_touch_watch_request(
1400                &mut touch_source_stream,
1401                vec![make_touch_event_with_coords(10.0, 20.0, 1)],
1402            )
1403            .await;
1404
1405            // Wait for another `Watch`.
1406            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1407                .await;
1408
1409            let events = read_uapi_events(&input_file, &current_task);
1410            assert_eq!(
1411                events,
1412                vec![
1413                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1414                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 10),
1415                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 20),
1416                    make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1417                ]
1418            );
1419        })
1420        .await;
1421    }
1422
1423    #[::fuchsia::test]
1424    async fn translates_touch_remove() {
1425        spawn_kernel_and_run(async move |current_task| {
1426            // Set up resources.
1427
1428            let (_input_device, input_file, mut touch_source_stream) =
1429                start_touch_input(&current_task).await;
1430
1431            // Touch add for pointer 1.
1432            answer_next_touch_watch_request(
1433                &mut touch_source_stream,
1434                vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1435            )
1436            .await;
1437
1438            // Wait for another `Watch` to ensure input_file done processing the first reply.
1439            // Use an empty `TouchEvent`, to minimize the chance that this event
1440            // creates unexpected `uapi::input_event`s.
1441            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1442                .await;
1443
1444            // Consume all of the `uapi::input_event`s that are available.
1445            let events = read_uapi_events(&input_file, &current_task);
1446
1447            assert_eq!(events.len(), 6);
1448
1449            // Reply to touch change.
1450            answer_next_touch_watch_request(
1451                &mut touch_source_stream,
1452                vec![make_touch_event_with_phase(EventPhase::Remove, 1)],
1453            )
1454            .await;
1455
1456            // Wait for another `Watch`.
1457            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1458                .await;
1459
1460            let events = read_uapi_events(&input_file, &current_task);
1461            assert_eq!(
1462                events,
1463                vec![
1464                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1465                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
1466                    make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 0),
1467                    make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1468                ]
1469            );
1470        })
1471        .await;
1472    }
1473
1474    #[::fuchsia::test]
1475    async fn multi_touch_event_sequence() {
1476        spawn_kernel_and_run(async move |current_task| {
1477            // Set up resources.
1478
1479            let (_input_device, input_file, mut touch_source_stream) =
1480                start_touch_input(&current_task).await;
1481
1482            // Touch add for pointer 1.
1483            answer_next_touch_watch_request(
1484                &mut touch_source_stream,
1485                vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1486            )
1487            .await;
1488
1489            // Wait for another `Watch`.
1490            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1491                .await;
1492            let events = read_uapi_events(&input_file, &current_task);
1493
1494            assert_eq!(events.len(), 6);
1495
1496            // Touch add for pointer 2.
1497            answer_next_touch_watch_request(
1498                &mut touch_source_stream,
1499                vec![
1500                    make_touch_event_with_coords(10.0, 20.0, 1),
1501                    make_touch_event_with_phase(EventPhase::Add, 2),
1502                ],
1503            )
1504            .await;
1505
1506            // Wait for another `Watch`.
1507            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1508                .await;
1509            let events = read_uapi_events(&input_file, &current_task);
1510
1511            assert_eq!(
1512                events,
1513                vec![
1514                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1515                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 10),
1516                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 20),
1517                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
1518                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 2),
1519                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 0),
1520                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 0),
1521                    make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1522                ]
1523            );
1524
1525            // Both pointers move.
1526            answer_next_touch_watch_request(
1527                &mut touch_source_stream,
1528                vec![
1529                    make_touch_event_with_coords(11.0, 21.0, 1),
1530                    make_touch_event_with_coords(101.0, 201.0, 2),
1531                ],
1532            )
1533            .await;
1534
1535            // Wait for another `Watch`.
1536            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1537                .await;
1538            let events = read_uapi_events(&input_file, &current_task);
1539
1540            assert_eq!(
1541                events,
1542                vec![
1543                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1544                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 11),
1545                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 21),
1546                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
1547                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 101),
1548                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 201),
1549                    make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1550                ]
1551            );
1552
1553            // Pointer 1 up.
1554            answer_next_touch_watch_request(
1555                &mut touch_source_stream,
1556                vec![
1557                    make_touch_event_with_phase(EventPhase::Remove, 1),
1558                    make_touch_event_with_coords(102.0, 202.0, 2),
1559                ],
1560            )
1561            .await;
1562
1563            // Wait for another `Watch`.
1564            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1565                .await;
1566            let events = read_uapi_events(&input_file, &current_task);
1567
1568            assert_eq!(
1569                events,
1570                vec![
1571                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1572                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
1573                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
1574                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 102),
1575                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 202),
1576                    make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1577                ]
1578            );
1579
1580            // Pointer 2 up.
1581            answer_next_touch_watch_request(
1582                &mut touch_source_stream,
1583                vec![make_touch_event_with_phase(EventPhase::Remove, 2)],
1584            )
1585            .await;
1586
1587            // Wait for another `Watch`.
1588            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1589                .await;
1590            let events = read_uapi_events(&input_file, &current_task);
1591
1592            assert_eq!(
1593                events,
1594                vec![
1595                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
1596                    make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
1597                    make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 0),
1598                    make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1599                ]
1600            );
1601        })
1602        .await;
1603    }
1604
1605    #[::fuchsia::test]
1606    async fn multi_event_sequence_unsorted_in_one_watch() {
1607        spawn_kernel_and_run(async move |current_task| {
1608            // Set up resources.
1609
1610            let (_input_device, input_file, mut touch_source_stream) =
1611                start_touch_input(&current_task).await;
1612
1613            // Touch add for pointer 1.
1614            answer_next_touch_watch_request(
1615                &mut touch_source_stream,
1616                vec![
1617                    make_touch_event_with_coords_phase_timestamp(
1618                        10.0,
1619                        20.0,
1620                        EventPhase::Change,
1621                        1,
1622                        100,
1623                    ),
1624                    make_touch_event_with_coords_phase_timestamp(0.0, 0.0, EventPhase::Add, 1, 1),
1625                ],
1626            )
1627            .await;
1628
1629            // Wait for another `Watch`.
1630            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1631                .await;
1632            let events = read_uapi_events(&input_file, &current_task);
1633
1634            assert_eq!(
1635                events,
1636                vec![
1637                    make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0, 1),
1638                    make_uapi_input_event_with_timestamp(
1639                        uapi::EV_ABS,
1640                        uapi::ABS_MT_TRACKING_ID,
1641                        1,
1642                        1
1643                    ),
1644                    make_uapi_input_event_with_timestamp(
1645                        uapi::EV_ABS,
1646                        uapi::ABS_MT_POSITION_X,
1647                        0,
1648                        1
1649                    ),
1650                    make_uapi_input_event_with_timestamp(
1651                        uapi::EV_ABS,
1652                        uapi::ABS_MT_POSITION_Y,
1653                        0,
1654                        1
1655                    ),
1656                    make_uapi_input_event_with_timestamp(uapi::EV_KEY, uapi::BTN_TOUCH, 1, 1),
1657                    make_uapi_input_event_with_timestamp(uapi::EV_SYN, uapi::SYN_REPORT, 0, 1),
1658                    make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0, 100),
1659                    make_uapi_input_event_with_timestamp(
1660                        uapi::EV_ABS,
1661                        uapi::ABS_MT_POSITION_X,
1662                        10,
1663                        100
1664                    ),
1665                    make_uapi_input_event_with_timestamp(
1666                        uapi::EV_ABS,
1667                        uapi::ABS_MT_POSITION_Y,
1668                        20,
1669                        100
1670                    ),
1671                    make_uapi_input_event_with_timestamp(uapi::EV_SYN, uapi::SYN_REPORT, 0, 100),
1672                ]
1673            );
1674        })
1675        .await;
1676    }
1677
1678    #[test_case((0.0, 0.0); "origin")]
1679    #[test_case((100.7, 200.7); "above midpoint")]
1680    #[test_case((100.3, 200.3); "below midpoint")]
1681    #[test_case((100.5, 200.5); "midpoint")]
1682    #[::fuchsia::test]
1683    async fn sends_acceptable_coordinates((x, y): (f32, f32)) {
1684        spawn_kernel_and_run(async move |current_task| {
1685            // Set up resources.
1686            let (_input_device, input_file, mut touch_source_stream) =
1687                start_touch_input(&current_task).await;
1688
1689            // Touch add.
1690            answer_next_touch_watch_request(
1691                &mut touch_source_stream,
1692                vec![make_touch_event_with_coords_phase(x, y, EventPhase::Add, 1)],
1693            )
1694            .await;
1695
1696            // Wait for another `Watch`.
1697            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1698                .await;
1699            let events = read_uapi_events(&input_file, &current_task);
1700
1701            // Check that the reported positions are within the acceptable error. The acceptable
1702            // error is chosen to allow either rounding or truncation.
1703            const ACCEPTABLE_ERROR: f32 = 1.0;
1704            let actual_x = events
1705                .iter()
1706                .find(|event| {
1707                    event.type_ == uapi::EV_ABS as u16
1708                        && event.code == uapi::ABS_MT_POSITION_X as u16
1709                })
1710                .unwrap_or_else(|| panic!("did not find `ABS_X` event in {:?}", events))
1711                .value;
1712            let actual_y = events
1713                .iter()
1714                .find(|event| {
1715                    event.type_ == uapi::EV_ABS as u16
1716                        && event.code == uapi::ABS_MT_POSITION_Y as u16
1717                })
1718                .unwrap_or_else(|| panic!("did not find `ABS_Y` event in {:?}", events))
1719                .value;
1720            assert_near!(x, actual_x as f32, ACCEPTABLE_ERROR);
1721            assert_near!(y, actual_y as f32, ACCEPTABLE_ERROR);
1722        })
1723        .await;
1724    }
1725
1726    #[::fuchsia::test]
1727    async fn sends_appropriate_ack_to_touch_source_server() {
1728        spawn_kernel_and_run(async move |current_task| {
1729            let (_input_device, _input_file, mut touch_source_stream) =
1730                start_touch_input(&current_task).await;
1731
1732            let control_handle = touch_source_stream.control_handle();
1733            control_handle
1734                .send_on_touch_events(vec![make_touch_event_with_phase(EventPhase::Add, 2)], 42)
1735                .expect("failure sending OnTouchEvents");
1736            match touch_source_stream.next().await {
1737                Some(Ok(fuipointer::TouchSourceV2Request::AcknowledgeEvents {
1738                    last_acknowledged_event_stamp,
1739                    ..
1740                })) => assert_eq!(last_acknowledged_event_stamp, 42),
1741                unexpected => panic!("unexpected request {:?}", unexpected),
1742            }
1743        })
1744        .await;
1745    }
1746
1747    #[test_case(fidl_fuchsia_input::Key::Escape, uapi::KEY_POWER; "Esc maps to Power")]
1748    #[test_case(fidl_fuchsia_input::Key::A, uapi::KEY_A; "A maps to A")]
1749    #[::fuchsia::test]
1750    async fn sends_keyboard_events(fkey: fidl_fuchsia_input::Key, lkey: u32) {
1751        spawn_kernel_and_run(async move |current_task| {
1752            let (_keyboard_device, keyboard_file, keyboard_listener) =
1753                start_keyboard_input(&current_task).await;
1754
1755            let key_event = fuiinput3::KeyEvent {
1756                timestamp: Some(0),
1757                type_: Some(fuiinput3::KeyEventType::Pressed),
1758                key: Some(fkey),
1759                ..Default::default()
1760            };
1761
1762            let _ = keyboard_listener.on_key_event(&key_event).await;
1763            std::mem::drop(keyboard_listener); // Close Zircon channel.
1764            let events = read_uapi_events(&keyboard_file, &current_task);
1765            assert_eq!(events.len(), 2);
1766            assert_eq!(events[0].code, lkey as u16);
1767        })
1768        .await;
1769    }
1770
1771    #[::fuchsia::test]
1772    async fn skips_unknown_keyboard_events() {
1773        spawn_kernel_and_run(async move |current_task| {
1774            let inspector = fuchsia_inspect::Inspector::default();
1775            let (_keyboard_device, keyboard_file, keyboard_listener) =
1776                start_keyboard_input_inspect(&current_task, &inspector).await;
1777
1778            let key_event = fuiinput3::KeyEvent {
1779                timestamp: Some(0),
1780                type_: Some(fuiinput3::KeyEventType::Pressed),
1781                key: Some(fidl_fuchsia_input::Key::AcRefresh),
1782                ..Default::default()
1783            };
1784
1785            let _ = keyboard_listener.on_key_event(&key_event).await;
1786            std::mem::drop(keyboard_listener); // Close Zircon channel.
1787            let events = read_uapi_events(&keyboard_file, &current_task);
1788            assert_eq!(events.len(), 0);
1789
1790            // The unknown key is counted as received-but-ignored, and must not be
1791            // counted as converted.
1792            assert_data_tree!(inspector, root: {
1793                keyboard_device: {
1794                    active_wake_leases_count: AnyProperty,
1795                    total_events_with_wake_lease_count: AnyProperty,
1796                    total_fidl_events_received_count: 1u64,
1797                    total_fidl_events_ignored_count: 1u64,
1798                    total_fidl_events_unexpected_count: 0u64,
1799                    total_fidl_events_converted_count: 0u64,
1800                    total_uapi_events_generated_count: 0u64,
1801                    last_generated_uapi_event_timestamp_ns: 0i64,
1802                    keyboard_file_0: {
1803                        fidl_events_received_count: 1u64,
1804                        fidl_events_ignored_count: 1u64,
1805                        fidl_events_unexpected_count: 0u64,
1806                        fidl_events_converted_count: 0u64,
1807                        uapi_events_generated_count: 0u64,
1808                        uapi_events_read_count: 0u64,
1809                        fd_read_count: AnyProperty,
1810                        fd_notify_count: AnyProperty,
1811                        last_generated_uapi_event_timestamp_ns: 0i64,
1812                        last_read_uapi_event_timestamp_ns: AnyProperty,
1813                        opened_without_nonblock: AnyProperty,
1814                        open_timestamp_ns: AnyProperty,
1815                        closed: AnyProperty,
1816                        close_timestamp_ns: AnyProperty,
1817                    }
1818                }
1819            });
1820        })
1821        .await;
1822    }
1823
1824    #[::fuchsia::test]
1825    async fn sends_power_button_events() {
1826        spawn_kernel_and_run(async move |current_task| {
1827            let (_input_device, input_file, buttons_listener) =
1828                start_button_input(&current_task).await;
1829
1830            let power_event = MediaButtonsEvent {
1831                volume: Some(0),
1832                mic_mute: Some(false),
1833                pause: Some(false),
1834                camera_disable: Some(false),
1835                power: Some(true),
1836                function: Some(false),
1837                ..Default::default()
1838            };
1839
1840            let _ = buttons_listener.on_event(power_event).await;
1841            std::mem::drop(buttons_listener); // Close Zircon channel.
1842
1843            let events = read_uapi_events(&input_file, &current_task);
1844            assert_eq!(events.len(), 2);
1845            assert_eq!(events[0].code, uapi::KEY_POWER as u16);
1846            assert_eq!(events[0].value, 1);
1847        })
1848        .await;
1849    }
1850
1851    #[::fuchsia::test]
1852    async fn sends_function_button_events() {
1853        spawn_kernel_and_run(async move |current_task| {
1854            let (_input_device, input_file, buttons_listener) =
1855                start_button_input(&current_task).await;
1856
1857            let function_event = MediaButtonsEvent {
1858                volume: Some(0),
1859                mic_mute: Some(false),
1860                pause: Some(false),
1861                camera_disable: Some(false),
1862                power: Some(false),
1863                function: Some(true),
1864                ..Default::default()
1865            };
1866
1867            let _ = buttons_listener.on_event(function_event).await;
1868            std::mem::drop(buttons_listener); // Close Zircon channel.
1869
1870            let events = read_uapi_events(&input_file, &current_task);
1871            assert_eq!(events.len(), 2);
1872            assert_eq!(events[0].code, uapi::KEY_VOLUMEDOWN as u16);
1873            assert_eq!(events[0].value, 1);
1874        })
1875        .await;
1876    }
1877
1878    #[::fuchsia::test]
1879    async fn sends_overlapping_button_events() {
1880        spawn_kernel_and_run(async move |current_task| {
1881            let (_input_device, input_file, buttons_listener) =
1882                start_button_input(&current_task).await;
1883
1884            let power_event = MediaButtonsEvent {
1885                volume: Some(0),
1886                mic_mute: Some(false),
1887                pause: Some(false),
1888                camera_disable: Some(false),
1889                power: Some(true),
1890                function: Some(false),
1891                ..Default::default()
1892            };
1893
1894            let function_event = MediaButtonsEvent {
1895                volume: Some(0),
1896                mic_mute: Some(false),
1897                pause: Some(false),
1898                camera_disable: Some(false),
1899                power: Some(true),
1900                function: Some(true),
1901                ..Default::default()
1902            };
1903
1904            let function_release_event = MediaButtonsEvent {
1905                volume: Some(0),
1906                mic_mute: Some(false),
1907                pause: Some(false),
1908                camera_disable: Some(false),
1909                power: Some(true),
1910                function: Some(false),
1911                ..Default::default()
1912            };
1913
1914            let power_release_event = MediaButtonsEvent {
1915                volume: Some(0),
1916                mic_mute: Some(false),
1917                pause: Some(false),
1918                camera_disable: Some(false),
1919                power: Some(false),
1920                function: Some(false),
1921                ..Default::default()
1922            };
1923
1924            let _ = buttons_listener.on_event(power_event).await;
1925            let _ = buttons_listener.on_event(function_event).await;
1926            let _ = buttons_listener.on_event(function_release_event).await;
1927            let _ = buttons_listener.on_event(power_release_event).await;
1928            std::mem::drop(buttons_listener); // Close Zircon channel.
1929
1930            let events = read_uapi_events(&input_file, &current_task);
1931            assert_eq!(events.len(), 8);
1932            assert_eq!(events[0].code, uapi::KEY_POWER as u16);
1933            assert_eq!(events[0].value, 1);
1934            assert_eq!(events[2].code, uapi::KEY_VOLUMEDOWN as u16);
1935            assert_eq!(events[2].value, 1);
1936            assert_eq!(events[4].code, uapi::KEY_VOLUMEDOWN as u16);
1937            assert_eq!(events[4].value, 0);
1938            assert_eq!(events[6].code, uapi::KEY_POWER as u16);
1939            assert_eq!(events[6].value, 0);
1940        })
1941        .await;
1942    }
1943
1944    #[::fuchsia::test]
1945    async fn sends_simultaneous_button_events() {
1946        spawn_kernel_and_run(async move |current_task| {
1947            let (_input_device, input_file, buttons_listener) =
1948                start_button_input(&current_task).await;
1949
1950            let power_and_function_event = MediaButtonsEvent {
1951                volume: Some(0),
1952                mic_mute: Some(false),
1953                pause: Some(false),
1954                camera_disable: Some(false),
1955                power: Some(true),
1956                function: Some(true),
1957                ..Default::default()
1958            };
1959
1960            let _ = buttons_listener.on_event(power_and_function_event).await;
1961            std::mem::drop(buttons_listener); // Close Zircon channel.
1962
1963            let events = read_uapi_events(&input_file, &current_task);
1964            assert_eq!(events.len(), 4);
1965            assert_eq!(events[0].code, uapi::KEY_POWER as u16);
1966            assert_eq!(events[0].value, 1);
1967            assert_eq!(events[2].code, uapi::KEY_VOLUMEDOWN as u16);
1968            assert_eq!(events[2].value, 1);
1969        })
1970        .await;
1971    }
1972
1973    #[::fuchsia::test]
1974    async fn sends_volume_up_button_events() {
1975        spawn_kernel_and_run(async move |current_task| {
1976            let (_input_device, input_file, buttons_listener) =
1977                start_button_input(&current_task).await;
1978
1979            let volume_up_event = MediaButtonsEvent {
1980                volume: Some(1),
1981                mic_mute: Some(false),
1982                pause: Some(false),
1983                camera_disable: Some(false),
1984                power: Some(false),
1985                function: Some(false),
1986                ..Default::default()
1987            };
1988
1989            let _ = buttons_listener.on_event(volume_up_event).await;
1990            std::mem::drop(buttons_listener); // Close Zircon channel.
1991
1992            let events = read_uapi_events(&input_file, &current_task);
1993            assert_eq!(events.len(), 2);
1994            assert_eq!(events[0].code, uapi::KEY_VOLUMEUP as u16);
1995            assert_eq!(events[0].value, 1);
1996        })
1997        .await;
1998    }
1999
2000    #[::fuchsia::test]
2001    async fn sends_volume_down_button_events() {
2002        spawn_kernel_and_run(async move |current_task| {
2003            let (_input_device, input_file, buttons_listener) =
2004                start_button_input(&current_task).await;
2005
2006            let volume_down_event = MediaButtonsEvent {
2007                volume: Some(-1),
2008                mic_mute: Some(false),
2009                pause: Some(false),
2010                camera_disable: Some(false),
2011                power: Some(false),
2012                function: Some(false),
2013                ..Default::default()
2014            };
2015
2016            let _ = buttons_listener.on_event(volume_down_event).await;
2017            std::mem::drop(buttons_listener); // Close Zircon channel.
2018
2019            let events = read_uapi_events(&input_file, &current_task);
2020            assert_eq!(events.len(), 2);
2021            assert_eq!(events[0].code, uapi::KEY_VOLUMEDOWN as u16);
2022            assert_eq!(events[0].value, 1);
2023        })
2024        .await;
2025    }
2026
2027    #[test_case(1; "Scroll up")]
2028    #[test_case(-1; "Scroll down")]
2029    #[::fuchsia::test]
2030    async fn sends_mouse_wheel_events(ticks: i64) {
2031        spawn_kernel_and_run(async move |current_task| {
2032            let time = 100;
2033            let uapi_event = uapi::input_event {
2034                time: timeval_from_time(zx::MonotonicInstant::from_nanos(time)),
2035                type_: uapi::EV_REL as u16,
2036                code: uapi::REL_WHEEL as u16,
2037                value: ticks as i32,
2038            };
2039
2040            let (_mouse_device, mouse_file, mut mouse_stream) =
2041                start_mouse_input(&current_task).await;
2042
2043            answer_next_mouse_watch_request(
2044                &mut mouse_stream,
2045                vec![make_mouse_wheel_event_with_timestamp(ticks, time)],
2046            )
2047            .await;
2048
2049            // Wait for another `Watch` to ensure mouse_file is done processing the other replies.
2050            // Use an empty vec, to ensure no unexpected `uapi::input_event`s are created.
2051            answer_next_mouse_watch_request(&mut mouse_stream, vec![]).await;
2052
2053            let events = read_uapi_events(&mouse_file, &current_task);
2054            assert_eq!(events.len(), 2);
2055            assert_eq!(events[0], uapi_event);
2056        })
2057        .await;
2058    }
2059
2060    #[::fuchsia::test]
2061    async fn handles_mouse_move_and_click_events() {
2062        spawn_kernel_and_run(async move |current_task| {
2063            let mouse_move_event = fuipointer::MouseEvent {
2064                timestamp: Some(0),
2065                pointer_sample: Some(fuipointer::MousePointerSample {
2066                    device_id: Some(input_event_relay::DEFAULT_MOUSE_DEVICE_ID),
2067                    relative_motion: Some([10.0, 20.0]),
2068                    ..Default::default()
2069                }),
2070                ..Default::default()
2071            };
2072            let mouse_click_event = fuipointer::MouseEvent {
2073                timestamp: Some(0),
2074                pointer_sample: Some(fuipointer::MousePointerSample {
2075                    device_id: Some(input_event_relay::DEFAULT_MOUSE_DEVICE_ID),
2076                    pressed_buttons: Some(vec![1]),
2077                    ..Default::default()
2078                }),
2079                ..Default::default()
2080            };
2081
2082            let (_mouse_device, mouse_file, mut mouse_stream) =
2083                start_mouse_input(&current_task).await;
2084
2085            // Expect mouse relay to handle MouseEvents with movement and buttons.
2086            answer_next_mouse_watch_request(&mut mouse_stream, vec![mouse_move_event]).await;
2087            answer_next_mouse_watch_request(&mut mouse_stream, vec![mouse_click_event]).await;
2088
2089            // Wait for another `Watch` to ensure mouse_file is done processing the other replies.
2090            // Use an empty vec, to ensure no unexpected `uapi::input_event`s are created.
2091            answer_next_mouse_watch_request(&mut mouse_stream, vec![]).await;
2092
2093            let events = read_uapi_events(&mouse_file, &current_task);
2094            assert!(!events.is_empty());
2095            assert!(events.iter().any(|e| e.type_ == uapi::EV_REL as u16
2096                && e.code == uapi::REL_X as u16
2097                && e.value == 10));
2098            assert!(events.iter().any(|e| e.type_ == uapi::EV_REL as u16
2099                && e.code == uapi::REL_Y as u16
2100                && e.value == 20));
2101            assert!(events.iter().any(|e| e.type_ == uapi::EV_KEY as u16
2102                && e.code == uapi::BTN_LEFT as u16
2103                && e.value == 1));
2104        })
2105        .await;
2106    }
2107
2108    #[::fuchsia::test]
2109    async fn touch_input_initialized_with_inspect_node() {
2110        spawn_kernel_and_run(async move |current_task| {
2111            let inspector = fuchsia_inspect::Inspector::default();
2112            let touch_device = InputDevice::new_touch(
2113                1200, /* screen width */
2114                720,  /* screen height */
2115                InputDeviceInfo::new(TOUCH_INPUT_ID, "starnix_touch".to_string()),
2116                "touch_device",
2117                &inspector.root(),
2118            );
2119            let _file_obj = touch_device.open_test(&current_task);
2120
2121            assert_data_tree!(inspector, root: {
2122                touch_device: {
2123                    active_wake_leases_count: 0u64,
2124                    total_events_with_wake_lease_count: 0u64,
2125                    total_fidl_events_received_count: 0u64,
2126                    total_fidl_events_ignored_count: 0u64,
2127                    total_fidl_events_unexpected_count: 0u64,
2128                    total_fidl_events_converted_count: 0u64,
2129                    total_uapi_events_generated_count: 0u64,
2130                    last_generated_uapi_event_timestamp_ns: 0i64,
2131                    touch_file_0: {
2132                        fidl_events_received_count: 0u64,
2133                        fidl_events_ignored_count: 0u64,
2134                        fidl_events_unexpected_count: 0u64,
2135                        fidl_events_converted_count: 0u64,
2136                        uapi_events_generated_count: 0u64,
2137                        uapi_events_read_count: 0u64,
2138                        fd_read_count: 0u64,
2139                        fd_notify_count: 0u64,
2140                        last_generated_uapi_event_timestamp_ns: 0i64,
2141                        last_read_uapi_event_timestamp_ns: 0i64,
2142                        opened_without_nonblock: AnyProperty,
2143                        open_timestamp_ns: AnyProperty,
2144                        closed: AnyProperty,
2145                        close_timestamp_ns: AnyProperty,
2146                    }
2147                }
2148            });
2149        })
2150        .await;
2151    }
2152
2153    #[::fuchsia::test]
2154    async fn touch_relay_updates_touch_inspect_status() {
2155        spawn_kernel_and_run(async move |current_task| {
2156            let inspector = fuchsia_inspect::Inspector::default();
2157
2158            let (_input_device, input_file, mut touch_source_stream) =
2159                start_touch_input_inspect(&current_task, &inspector).await;
2160
2161            // Send 2 TouchEvents to proxy that should be counted as `received` by InputFile
2162            // A TouchEvent::default() has no pointer sample so these events should be discarded.
2163            let control_handle = touch_source_stream.control_handle();
2164            control_handle
2165                .send_on_touch_events(vec![make_empty_touch_event(), make_empty_touch_event()], 2)
2166                .expect("failure sending OnTouchEvents");
2167            match touch_source_stream.next().await {
2168                Some(Ok(fuipointer::TouchSourceV2Request::AcknowledgeEvents {
2169                    last_acknowledged_event_stamp,
2170                    ..
2171                })) => assert_eq!(last_acknowledged_event_stamp, 2),
2172                unexpected_request => panic!("unexpected request {:?}", unexpected_request),
2173            }
2174
2175            // Send 5 TouchEvents with pointer sample to proxy, these should be received and converted
2176            // Add/Remove events generate 5 uapi events each. Change events generate 3 uapi events each.
2177            control_handle
2178                .send_on_touch_events(
2179                    vec![
2180                        make_touch_event_with_coords_phase_timestamp(
2181                            0.0,
2182                            0.0,
2183                            EventPhase::Add,
2184                            1,
2185                            1000,
2186                        ),
2187                        make_touch_event_with_coords_phase_timestamp(
2188                            1.0,
2189                            1.0,
2190                            EventPhase::Change,
2191                            1,
2192                            2000,
2193                        ),
2194                        make_touch_event_with_coords_phase_timestamp(
2195                            2.0,
2196                            2.0,
2197                            EventPhase::Change,
2198                            1,
2199                            3000,
2200                        ),
2201                        make_touch_event_with_coords_phase_timestamp(
2202                            3.0,
2203                            3.0,
2204                            EventPhase::Change,
2205                            1,
2206                            4000,
2207                        ),
2208                        make_touch_event_with_coords_phase_timestamp(
2209                            3.0,
2210                            3.0,
2211                            EventPhase::Remove,
2212                            1,
2213                            5000,
2214                        ),
2215                    ],
2216                    7,
2217                )
2218                .expect("failure sending OnTouchEvents");
2219            match touch_source_stream.next().await {
2220                Some(Ok(fuipointer::TouchSourceV2Request::AcknowledgeEvents {
2221                    last_acknowledged_event_stamp,
2222                    ..
2223                })) => assert_eq!(last_acknowledged_event_stamp, 7),
2224                unexpected_request => panic!("unexpected request {:?}", unexpected_request),
2225            }
2226
2227            let _events = read_uapi_events(&input_file, &current_task);
2228            assert_data_tree!(inspector, root: {
2229                touch_device: {
2230                    active_wake_leases_count: 0u64,
2231                    total_events_with_wake_lease_count: 0u64,
2232                    total_fidl_events_received_count: 7u64,
2233                    total_fidl_events_ignored_count: 2u64,
2234                    total_fidl_events_unexpected_count: 0u64,
2235                    total_fidl_events_converted_count: 5u64,
2236                    total_uapi_events_generated_count: 22u64,
2237                    last_generated_uapi_event_timestamp_ns: 5000i64,
2238                    touch_file_0: {
2239                        fidl_events_received_count: 7u64,
2240                        fidl_events_ignored_count: 2u64,
2241                        fidl_events_unexpected_count: 0u64,
2242                        fidl_events_converted_count: 5u64,
2243                        uapi_events_generated_count: 22u64,
2244                        uapi_events_read_count: 22u64,
2245                        fd_read_count: 23u64,
2246                        fd_notify_count: 1u64,
2247                        last_generated_uapi_event_timestamp_ns: 5000i64,
2248                        last_read_uapi_event_timestamp_ns: 5000i64,
2249                        opened_without_nonblock: AnyProperty,
2250                        open_timestamp_ns: AnyProperty,
2251                        closed: AnyProperty,
2252                        close_timestamp_ns: AnyProperty,
2253                    },
2254                }
2255            });
2256        })
2257        .await;
2258    }
2259
2260    #[::fuchsia::test]
2261    async fn new_file_updates_inspect_status() {
2262        spawn_kernel_and_run(async move |current_task| {
2263            let inspector = fuchsia_inspect::Inspector::default();
2264
2265            let input_device = InputDevice::new_touch(
2266                700,
2267                700,
2268                InputDeviceInfo::new(TOUCH_INPUT_ID, "starnix_touch".to_string()),
2269                "touch_device",
2270                inspector.root(),
2271            );
2272            let input_file_0 =
2273                input_device.open_test(&current_task).expect("Failed to create input file");
2274
2275            let (touch_source_client_end, mut touch_source_stream) =
2276                fidl::endpoints::create_request_stream::<TouchSourceV2Marker>();
2277            let (mouse_source_client_end, _mouse_source_stream) =
2278                fidl::endpoints::create_request_stream::<fuipointer::MouseSourceV2Marker>();
2279            let (keyboard_proxy, mut keyboard_stream) =
2280                fidl::endpoints::create_sync_proxy_and_stream::<fuiinput3::KeyboardMarker>();
2281            let view_ref_pair =
2282                fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
2283            let (device_registry_proxy, mut device_listener_stream) =
2284                fidl::endpoints::create_sync_proxy_and_stream::<
2285                    fuipolicy::DeviceListenerRegistryMarker,
2286                >();
2287
2288            let (mut relay, _relay_handle) = input_event_relay::new_input_relay();
2289            relay.add_touch_device(
2290                input_event_relay::DEFAULT_TOUCH_DEVICE_ID,
2291                input_device.open_files.clone(),
2292                Some(input_device.inspect_status.clone()),
2293            );
2294            relay.start_relays(
2295                &current_task.kernel(),
2296                StartRelaysArgs {
2297                    event_proxy_mode: EventProxyMode::None,
2298                    touch_source_client_end,
2299                    keyboard_proxy,
2300                    mouse_source_client_end,
2301                    view_ref: view_ref_pair.view_ref,
2302                    registry_proxy: device_registry_proxy,
2303                    device_listener_registry: None,
2304                    display_width: 700,
2305                    display_height: 700,
2306                },
2307            );
2308
2309            let _ = init_keyboard_listener(&mut keyboard_stream).await;
2310            let _ = init_button_listeners(&mut device_listener_stream).await;
2311
2312            // Send 2 TouchEvents to proxy that should be counted as `received` by InputFile
2313            // A TouchEvent::default() has no pointer sample so these events should be discarded.
2314            let control_handle = touch_source_stream.control_handle();
2315            control_handle
2316                .send_on_touch_events(vec![make_empty_touch_event(), make_empty_touch_event()], 2)
2317                .expect("failure sending OnTouchEvents");
2318            match touch_source_stream.next().await {
2319                Some(Ok(fuipointer::TouchSourceV2Request::AcknowledgeEvents {
2320                    last_acknowledged_event_stamp,
2321                    ..
2322                })) => assert_eq!(last_acknowledged_event_stamp, 2),
2323                unexpected_request => panic!("unexpected request {:?}", unexpected_request),
2324            }
2325
2326            // Verify file node & properties remain in inspect tree when file is closed
2327            input_device.open_files.lock().clear();
2328            drop(input_file_0);
2329
2330            // Open new file which should receive input_device's subsequent events
2331            let input_file_1 =
2332                input_device.open_test(&current_task).expect("Failed to create input file");
2333
2334            // Send 5 TouchEvents with pointer sample to proxy, these should be received and converted
2335            // Add/Remove events generate 5 uapi events each. Change events generate 3 uapi events each.
2336            control_handle
2337                .send_on_touch_events(
2338                    vec![
2339                        make_touch_event_with_coords_phase_timestamp(
2340                            0.0,
2341                            0.0,
2342                            EventPhase::Add,
2343                            1,
2344                            1000,
2345                        ),
2346                        make_touch_event_with_coords_phase_timestamp(
2347                            1.0,
2348                            1.0,
2349                            EventPhase::Change,
2350                            1,
2351                            2000,
2352                        ),
2353                        make_touch_event_with_coords_phase_timestamp(
2354                            2.0,
2355                            2.0,
2356                            EventPhase::Change,
2357                            1,
2358                            3000,
2359                        ),
2360                        make_touch_event_with_coords_phase_timestamp(
2361                            3.0,
2362                            3.0,
2363                            EventPhase::Change,
2364                            1,
2365                            4000,
2366                        ),
2367                        make_touch_event_with_coords_phase_timestamp(
2368                            3.0,
2369                            3.0,
2370                            EventPhase::Remove,
2371                            1,
2372                            5000,
2373                        ),
2374                    ],
2375                    7,
2376                )
2377                .expect("failure sending OnTouchEvents");
2378            match touch_source_stream.next().await {
2379                Some(Ok(fuipointer::TouchSourceV2Request::AcknowledgeEvents {
2380                    last_acknowledged_event_stamp,
2381                    ..
2382                })) => assert_eq!(last_acknowledged_event_stamp, 7),
2383                unexpected_request => panic!("unexpected request {:?}", unexpected_request),
2384            }
2385
2386            let _events = read_uapi_events(&input_file_1, &current_task);
2387
2388            // Verify file node & properties remain in inspect tree when file is closed
2389            input_device.open_files.lock().clear();
2390            drop(input_file_1);
2391
2392            assert_data_tree!(inspector, root: {
2393                touch_device: {
2394                    active_wake_leases_count: 0u64,
2395                    total_events_with_wake_lease_count: 0u64,
2396                    total_fidl_events_received_count: 7u64,
2397                    total_fidl_events_ignored_count: 2u64,
2398                    total_fidl_events_unexpected_count: 0u64,
2399                    total_fidl_events_converted_count: 5u64,
2400                    total_uapi_events_generated_count: 22u64,
2401                    last_generated_uapi_event_timestamp_ns: 5000i64,
2402                    touch_file_0: {
2403                        fidl_events_received_count: 2u64,
2404                        fidl_events_ignored_count: 2u64,
2405                        fidl_events_unexpected_count: 0u64,
2406                        fidl_events_converted_count: 0u64,
2407                        uapi_events_generated_count: 0u64,
2408                        uapi_events_read_count: 0u64,
2409                        fd_read_count: 0u64,
2410                        fd_notify_count: 0u64,
2411                        last_generated_uapi_event_timestamp_ns: 0i64,
2412                        last_read_uapi_event_timestamp_ns: 0i64,
2413                        opened_without_nonblock: AnyProperty,
2414                        open_timestamp_ns: AnyProperty,
2415                        closed: AnyProperty,
2416                        close_timestamp_ns: AnyProperty,
2417                    },
2418                    touch_file_1: {
2419                        fidl_events_received_count: 5u64,
2420                        fidl_events_ignored_count: 0u64,
2421                        fidl_events_unexpected_count: 0u64,
2422                        fidl_events_converted_count: 5u64,
2423                        uapi_events_generated_count: 22u64,
2424                        uapi_events_read_count: 22u64,
2425                        fd_read_count: 23u64,
2426                        fd_notify_count: 1u64,
2427                        last_generated_uapi_event_timestamp_ns: 5000i64,
2428                        last_read_uapi_event_timestamp_ns: 5000i64,
2429                        opened_without_nonblock: AnyProperty,
2430                        open_timestamp_ns: AnyProperty,
2431                        closed: AnyProperty,
2432                        close_timestamp_ns: AnyProperty,
2433                    },
2434                }
2435            });
2436        })
2437        .await;
2438    }
2439
2440    #[::fuchsia::test]
2441    async fn file_status_inspect_not_empty_after_close() {
2442        spawn_kernel_and_run(async move |current_task| {
2443            let inspector = fuchsia_inspect::Inspector::default();
2444
2445            let input_device = InputDevice::new_touch(
2446                700,
2447                700,
2448                InputDeviceInfo::new(TOUCH_INPUT_ID, "starnix_touch".to_string()),
2449                "touch_device",
2450                inspector.root(),
2451            );
2452            let file_handle =
2453                input_device.open_test(&current_task).expect("Failed to create input file");
2454
2455            let status = file_handle
2456                .downcast_file::<crate::input_file::ArcInputFile>()
2457                .unwrap()
2458                .0
2459                .inspect_status
2460                .clone()
2461                .expect("touch file must have status");
2462            status.count_received_events(5);
2463
2464            assert_data_tree!(inspector, root: {
2465                touch_device: {
2466                    active_wake_leases_count: AnyProperty,
2467                    last_generated_uapi_event_timestamp_ns: AnyProperty,
2468                    total_events_with_wake_lease_count: AnyProperty,
2469                    total_fidl_events_converted_count: AnyProperty,
2470                    total_fidl_events_ignored_count: AnyProperty,
2471                    total_fidl_events_received_count: AnyProperty,
2472                    total_fidl_events_unexpected_count: AnyProperty,
2473                    total_uapi_events_generated_count: AnyProperty,
2474                    touch_file_0: {
2475                        fidl_events_received_count: 5u64,
2476                        fd_notify_count: 0u64,
2477                        fd_read_count: 0u64,
2478                        fidl_events_converted_count: 0u64,
2479                        fidl_events_ignored_count: 0u64,
2480                        fidl_events_unexpected_count: 0u64,
2481                        last_generated_uapi_event_timestamp_ns: 0i64,
2482                        last_read_uapi_event_timestamp_ns: 0i64,
2483                        uapi_events_generated_count: 0u64,
2484                        uapi_events_read_count: 0u64,
2485                        opened_without_nonblock: true,
2486                        open_timestamp_ns: AnyProperty,
2487                        closed: false,
2488                        close_timestamp_ns: 0i64,
2489                    }
2490                }
2491            });
2492
2493            drop(status);
2494            input_device.open_files.lock().clear();
2495            drop(file_handle);
2496
2497            assert_data_tree!(inspector, root: {
2498                touch_device: {
2499                    active_wake_leases_count: AnyProperty,
2500                    last_generated_uapi_event_timestamp_ns: AnyProperty,
2501                    total_events_with_wake_lease_count: AnyProperty,
2502                    total_fidl_events_converted_count: AnyProperty,
2503                    total_fidl_events_ignored_count: AnyProperty,
2504                    total_fidl_events_received_count: AnyProperty,
2505                    total_fidl_events_unexpected_count: AnyProperty,
2506                    total_uapi_events_generated_count: AnyProperty,
2507                    touch_file_0: {
2508                        fidl_events_received_count: 5u64,
2509                        fd_notify_count: 0u64,
2510                        fd_read_count: 0u64,
2511                        fidl_events_converted_count: 0u64,
2512                        fidl_events_ignored_count: 0u64,
2513                        fidl_events_unexpected_count: 0u64,
2514                        last_generated_uapi_event_timestamp_ns: 0i64,
2515                        last_read_uapi_event_timestamp_ns: 0i64,
2516                        uapi_events_generated_count: 0u64,
2517                        uapi_events_read_count: 0u64,
2518                        opened_without_nonblock: true,
2519                        open_timestamp_ns: AnyProperty,
2520                        closed: AnyProperty,
2521                        close_timestamp_ns: AnyProperty,
2522                    }
2523                }
2524            });
2525        })
2526        .await;
2527    }
2528
2529    #[::fuchsia::test]
2530    async fn keyboard_input_initialized_with_inspect_node() {
2531        spawn_kernel_and_run(async move |current_task| {
2532            let inspector = fuchsia_inspect::Inspector::default();
2533            let keyboard_device = InputDevice::new_keyboard(
2534                InputDeviceInfo::new(KEYBOARD_INPUT_ID, "starnix_buttons".to_string()),
2535                "keyboard_device",
2536                &inspector.root(),
2537            );
2538            let _file_obj = keyboard_device.open_test(&current_task);
2539
2540            assert_data_tree!(inspector, root: {
2541                keyboard_device: {
2542                    active_wake_leases_count: 0u64,
2543                    total_events_with_wake_lease_count: 0u64,
2544                    total_fidl_events_received_count: 0u64,
2545                    total_fidl_events_ignored_count: 0u64,
2546                    total_fidl_events_unexpected_count: 0u64,
2547                    total_fidl_events_converted_count: 0u64,
2548                    total_uapi_events_generated_count: 0u64,
2549                    last_generated_uapi_event_timestamp_ns: 0i64,
2550                    keyboard_file_0: {
2551                        fidl_events_received_count: 0u64,
2552                        fidl_events_ignored_count: 0u64,
2553                        fidl_events_unexpected_count: 0u64,
2554                        fidl_events_converted_count: 0u64,
2555                        uapi_events_generated_count: 0u64,
2556                        uapi_events_read_count: 0u64,
2557                        fd_read_count: 0u64,
2558                        fd_notify_count: 0u64,
2559                        last_generated_uapi_event_timestamp_ns: 0i64,
2560                        last_read_uapi_event_timestamp_ns: 0i64,
2561                        opened_without_nonblock: AnyProperty,
2562                        open_timestamp_ns: AnyProperty,
2563                        closed: AnyProperty,
2564                        close_timestamp_ns: AnyProperty,
2565                    }
2566                }
2567            });
2568        })
2569        .await;
2570    }
2571
2572    #[::fuchsia::test]
2573    async fn button_relay_updates_keyboard_inspect_status() {
2574        spawn_kernel_and_run(async move |current_task| {
2575            let inspector = fuchsia_inspect::Inspector::default();
2576
2577            let (_input_device, input_file, buttons_listener) =
2578                start_button_input_inspect(&current_task, &inspector).await;
2579
2580            // Each of these events should count toward received and converted.
2581            // They also generate 2 uapi events each.
2582            let power_event = MediaButtonsEvent {
2583                volume: Some(0),
2584                mic_mute: Some(false),
2585                pause: Some(false),
2586                camera_disable: Some(false),
2587                power: Some(true),
2588                function: Some(false),
2589                ..Default::default()
2590            };
2591
2592            let power_release_event = MediaButtonsEvent {
2593                volume: Some(0),
2594                mic_mute: Some(false),
2595                pause: Some(false),
2596                camera_disable: Some(false),
2597                power: Some(false),
2598                function: Some(false),
2599                ..Default::default()
2600            };
2601
2602            let _ = buttons_listener.on_event(power_event).await;
2603            let _ = buttons_listener.on_event(power_release_event).await;
2604
2605            let events = read_uapi_events(&input_file, &current_task);
2606            assert_eq!(events.len(), 4);
2607            assert_eq!(events[0].code, uapi::KEY_POWER as u16);
2608            assert_eq!(events[0].value, 1);
2609            assert_eq!(events[2].code, uapi::KEY_POWER as u16);
2610            assert_eq!(events[2].value, 0);
2611
2612            let _events = read_uapi_events(&input_file, &current_task);
2613
2614            assert_data_tree!(inspector, root: {
2615                keyboard_device: {
2616                    active_wake_leases_count: 0u64,
2617                    total_events_with_wake_lease_count: 0u64,
2618                    total_fidl_events_received_count: 2u64,
2619                    total_fidl_events_ignored_count: 0u64,
2620                    total_fidl_events_unexpected_count: 0u64,
2621                    total_fidl_events_converted_count: 2u64,
2622                    total_uapi_events_generated_count: 4u64,
2623                    // Button events perform a realtime clockread, so any value will do.
2624                    last_generated_uapi_event_timestamp_ns: AnyProperty,
2625                    keyboard_file_0: {
2626                        fidl_events_received_count: 2u64,
2627                        fidl_events_ignored_count: 0u64,
2628                        fidl_events_unexpected_count: 0u64,
2629                        fidl_events_converted_count: 2u64,
2630                        uapi_events_generated_count: 4u64,
2631                        uapi_events_read_count: 4u64,
2632                        fd_read_count: 6u64,
2633                        fd_notify_count: 2u64,
2634                        // Button events perform a realtime clockread, so any value will do.
2635                        last_generated_uapi_event_timestamp_ns: AnyProperty,
2636                        last_read_uapi_event_timestamp_ns: AnyProperty,
2637                        opened_without_nonblock: AnyProperty,
2638                        open_timestamp_ns: AnyProperty,
2639                        closed: AnyProperty,
2640                        close_timestamp_ns: AnyProperty,
2641                    },
2642                }
2643            });
2644        })
2645        .await;
2646    }
2647
2648    #[::fuchsia::test]
2649    async fn mouse_input_initialized_with_inspect_node() {
2650        spawn_kernel_and_run(async move |current_task| {
2651            let inspector = fuchsia_inspect::Inspector::default();
2652            let mouse_device = InputDevice::new_mouse(
2653                InputDeviceInfo::new(MOUSE_INPUT_ID, "starnix_mouse".to_string()),
2654                "mouse_device",
2655                &inspector.root(),
2656            );
2657            let _file_obj = mouse_device.open_test(&current_task);
2658
2659            assert_data_tree!(inspector, root: {
2660                mouse_device: {
2661                    active_wake_leases_count: 0u64,
2662                    total_events_with_wake_lease_count: 0u64,
2663                    total_fidl_events_received_count: 0u64,
2664                    total_fidl_events_ignored_count: 0u64,
2665                    total_fidl_events_unexpected_count: 0u64,
2666                    total_fidl_events_converted_count: 0u64,
2667                    total_uapi_events_generated_count: 0u64,
2668                    last_generated_uapi_event_timestamp_ns: 0i64,
2669                    mouse_file_0: {
2670                        fidl_events_received_count: 0u64,
2671                        fidl_events_ignored_count: 0u64,
2672                        fidl_events_unexpected_count: 0u64,
2673                        fidl_events_converted_count: 0u64,
2674                        uapi_events_generated_count: 0u64,
2675                        uapi_events_read_count: 0u64,
2676                        fd_read_count: 0u64,
2677                        fd_notify_count: 0u64,
2678                        last_generated_uapi_event_timestamp_ns: 0i64,
2679                        last_read_uapi_event_timestamp_ns: 0i64,
2680                        opened_without_nonblock: AnyProperty,
2681                        open_timestamp_ns: AnyProperty,
2682                        closed: AnyProperty,
2683                        close_timestamp_ns: AnyProperty,
2684                    }
2685                }
2686            });
2687        })
2688        .await;
2689    }
2690
2691    #[::fuchsia::test]
2692    async fn mouse_relay_updates_mouse_inspect_status() {
2693        spawn_kernel_and_run(async move |current_task| {
2694            let inspector = fuchsia_inspect::Inspector::default();
2695
2696            let (_input_device, input_file, mut mouse_source_stream) =
2697                start_mouse_input_inspect(&current_task, &inspector).await;
2698
2699            let mouse_move_event = fuipointer::MouseEvent {
2700                timestamp: Some(0),
2701                pointer_sample: Some(fuipointer::MousePointerSample {
2702                    device_id: Some(input_event_relay::DEFAULT_MOUSE_DEVICE_ID),
2703                    position_in_viewport: Some([50.0, 50.0]),
2704                    scroll_v: Some(0),
2705                    ..Default::default()
2706                }),
2707                ..Default::default()
2708            };
2709            let mouse_click_event = fuipointer::MouseEvent {
2710                timestamp: Some(0),
2711                pointer_sample: Some(fuipointer::MousePointerSample {
2712                    device_id: Some(input_event_relay::DEFAULT_MOUSE_DEVICE_ID),
2713                    scroll_v: Some(0),
2714                    pressed_buttons: Some(vec![1]),
2715                    ..Default::default()
2716                }),
2717                ..Default::default()
2718            };
2719
2720            // Send 2 MouseEvents: one move event with no relative motion (ignored) and one click
2721            // event with button 1 pressed (converted).
2722            answer_next_mouse_watch_request(
2723                &mut mouse_source_stream,
2724                vec![mouse_move_event, mouse_click_event],
2725            )
2726            .await;
2727
2728            // Send 5 MouseEvents with non-zero scroll_v delta to proxy, these should be received and
2729            // converted to 1 uapi event each, with an extra sync event to signify end of the batch.
2730            answer_next_mouse_watch_request(
2731                &mut mouse_source_stream,
2732                (0..5).map(|_| make_mouse_wheel_event(1)).collect(),
2733            )
2734            .await;
2735
2736            // Send a final mouse wheel event and ensure the inspect tree reflects it's timestamp under
2737            // last_generated_uapi_event_timestamp_ns and last_read_uapi_event_timestamp_ns.
2738            answer_next_mouse_watch_request(
2739                &mut mouse_source_stream,
2740                vec![make_mouse_wheel_event_with_timestamp(-1, 5000)],
2741            )
2742            .await;
2743
2744            // Wait for another `Watch` to ensure mouse_file is done processing the other replies.
2745            // Use an empty vec, to ensure no unexpected `uapi::input_event`s are created.
2746            answer_next_mouse_watch_request(&mut mouse_source_stream, vec![]).await;
2747
2748            let _events = read_uapi_events(&input_file, &current_task);
2749            assert_data_tree!(inspector, root: {
2750                mouse_device: {
2751                    active_wake_leases_count: 0u64,
2752                    total_events_with_wake_lease_count: 0u64,
2753                    total_fidl_events_received_count: 8u64,
2754                    total_fidl_events_ignored_count: 1u64,
2755                    total_fidl_events_unexpected_count: 0u64,
2756                    total_fidl_events_converted_count: 7u64,
2757                    total_uapi_events_generated_count: 7u64,
2758                    last_generated_uapi_event_timestamp_ns: 5000i64,
2759                    mouse_file_0: {
2760                        fidl_events_received_count: 8u64,
2761                        fidl_events_ignored_count: 1u64,
2762                        fidl_events_unexpected_count: 0u64,
2763                        fidl_events_converted_count: 7u64,
2764                        uapi_events_generated_count: 7u64,
2765                        uapi_events_read_count: 7u64,
2766                        fd_read_count: 8u64,
2767                        fd_notify_count: 3u64,
2768                        last_generated_uapi_event_timestamp_ns: 5000i64,
2769                        last_read_uapi_event_timestamp_ns: 5000i64,
2770                        opened_without_nonblock: AnyProperty,
2771                        open_timestamp_ns: AnyProperty,
2772                        closed: AnyProperty,
2773                        close_timestamp_ns: AnyProperty,
2774                    },
2775                }
2776            });
2777        })
2778        .await;
2779    }
2780    #[::fuchsia::test]
2781    async fn eviocgname_zero_buffer_length_succeeds() {
2782        spawn_kernel_and_run(async move |current_task| {
2783            let inspector = fuchsia_inspect::Inspector::default();
2784            let touch_device = InputDevice::new_touch(
2785                700,
2786                1200,
2787                InputDeviceInfo::new(TOUCH_INPUT_ID, "starnix_touch".to_string()),
2788                "touch_device",
2789                &inspector.root(),
2790            );
2791            let file =
2792                touch_device.open_test(&current_task).expect("Failed to open touch input file");
2793
2794            let user_addr = starnix_core::testing::map_memory(
2795                &current_task,
2796                starnix_uapi::user_address::UserAddress::default(),
2797                4096,
2798            );
2799
2800            // EVIOCGNAME with 0 buffer length should return SUCCESS and not panic or write memory.
2801            let res = file.ioctl(&current_task, uapi::EVIOCGNAME_0, user_addr.into());
2802            assert_eq!(res, Ok(starnix_syscalls::SUCCESS));
2803
2804            // EVIOCGNAME with non-zero buffer length should succeed and return bytes written.
2805            let name_req = uapi::EVIOCGNAME_0 | (256 << 16);
2806            let res = file.ioctl(&current_task, name_req, user_addr.into());
2807            assert!(res.is_ok());
2808            let bytes_written = res.unwrap().value() as usize;
2809            assert!(bytes_written > 0);
2810        })
2811        .await;
2812    }
2813
2814    #[::fuchsia::test]
2815    async fn evdev_variable_length_ioctls_succeed() {
2816        spawn_kernel_and_run(async move |current_task| {
2817            let inspector = fuchsia_inspect::Inspector::default();
2818            let touch_device = InputDevice::new_touch(
2819                700,
2820                1200,
2821                InputDeviceInfo::new(TOUCH_INPUT_ID, "starnix_touch".to_string()),
2822                "touch_device",
2823                &inspector.root(),
2824            );
2825            let file =
2826                touch_device.open_test(&current_task).expect("Failed to open touch input file");
2827
2828            let user_addr = starnix_core::testing::map_memory(
2829                &current_task,
2830                starnix_uapi::user_address::UserAddress::default(),
2831                4096,
2832            );
2833
2834            // Test EVIOCGRAB succeeds.
2835            let res = file.ioctl(&current_task, uapi::EVIOCGRAB, 1u64.into());
2836            assert_eq!(res, Ok(starnix_syscalls::SUCCESS));
2837
2838            // Test EVIOCGPHYS with variable length.
2839            let phys_req = crate::input_file::EVIOCGPHYS_BASE | (256 << 16);
2840            let res = file.ioctl(&current_task, phys_req, user_addr.into());
2841            assert!(res.is_ok());
2842            let bytes_written = res.unwrap().value() as usize;
2843            assert!(bytes_written > 0);
2844
2845            // Test EVIOCGUNIQ returns ENOENT (synthetic devices have no serial/unique identifier).
2846            let uniq_req = crate::input_file::EVIOCGUNIQ_BASE | (256 << 16);
2847            let res = file.ioctl(&current_task, uniq_req, user_addr.into());
2848            assert_eq!(res, starnix_uapi::error!(ENOENT));
2849
2850            // Test EVIOCGPROP with variable length.
2851            let prop_req = crate::input_file::EVIOCGPROP_BASE | (64 << 16);
2852            let res = file.ioctl(&current_task, prop_req, user_addr.into());
2853            assert_eq!(res, Ok(starnix_syscalls::SUCCESS));
2854
2855            // Test EVIOCGBIT(0) with variable length.
2856            let bit0_req = crate::input_file::EVIOCGBIT_0_BASE | (64 << 16);
2857            let res = file.ioctl(&current_task, bit0_req, user_addr.into());
2858            assert_eq!(res, Ok(starnix_syscalls::SUCCESS));
2859
2860            // Test EVIOCGBIT(EV_SND) succeeds and returns zeros.
2861            let snd_req = crate::input_file::EVIOCGBIT_EV_SND_BASE | (64 << 16);
2862            let res = file.ioctl(&current_task, snd_req, user_addr.into());
2863            assert_eq!(res, Ok(starnix_syscalls::SUCCESS));
2864
2865            // Test invalid EVIOCGBIT(0x1f) returns EINVAL.
2866            let invalid_bit_req = (uapi::_IOC_READ << uapi::_IOC_DIRSHIFT)
2867                | ((b'E' as u32) << uapi::_IOC_TYPESHIFT)
2868                | (64 << 16)
2869                | (0x20 + 0x1f);
2870            let res = file.ioctl(&current_task, invalid_bit_req, user_addr.into());
2871            assert_eq!(res, starnix_uapi::error!(EINVAL));
2872
2873            // Test unrecognized evdev ioctl code with valid type/dir returns EINVAL via fallback.
2874            let unrecognized_evdev_req = (uapi::_IOC_READ << uapi::_IOC_DIRSHIFT)
2875                | ((b'E' as u32) << uapi::_IOC_TYPESHIFT)
2876                | 0xFF;
2877            let res = file.ioctl(&current_task, unrecognized_evdev_req, user_addr.into());
2878            assert_eq!(res, starnix_uapi::error!(EINVAL));
2879
2880            // Test invalid direction/type returns EINVAL.
2881            let invalid_dir_req = (uapi::_IOC_WRITE << uapi::_IOC_DIRSHIFT)
2882                | ((b'E' as u32) << uapi::_IOC_TYPESHIFT)
2883                | 0x20;
2884            let res = file.ioctl(&current_task, invalid_dir_req, user_addr.into());
2885            assert_eq!(res, starnix_uapi::error!(EINVAL));
2886        })
2887        .await;
2888    }
2889
2890    // The evdev current-state queries report what the device is doing right now,
2891    // as opposed to the EVIOCGBIT queries which report what it is capable of.
2892    // Starnix tracks none of this state and reports all-zeros, but these must not
2893    // fail: `xf86-input-evdev` issues all four during PreInit and treats an error
2894    // as fatal, so returning EINVAL here means X11 brings up no input devices.
2895    #[::fuchsia::test]
2896    async fn evdev_current_state_queries_succeed() {
2897        spawn_kernel_and_run(async move |current_task| {
2898            let inspector = fuchsia_inspect::Inspector::default();
2899            let touch_device = InputDevice::new_touch(
2900                700,
2901                1200,
2902                InputDeviceInfo::new(TOUCH_INPUT_ID, "starnix_touch".to_string()),
2903                "touch_device",
2904                &inspector.root(),
2905            );
2906            let file =
2907                touch_device.open_test(&current_task).expect("Failed to open touch input file");
2908
2909            let user_addr = starnix_core::testing::map_memory(
2910                &current_task,
2911                starnix_uapi::user_address::UserAddress::default(),
2912                4096,
2913            );
2914
2915            const BUF_LEN: usize = 96;
2916            for (name, base) in [
2917                ("EVIOCGKEY", crate::input_file::EVIOCGKEY_BASE),
2918                ("EVIOCGLED", crate::input_file::EVIOCGLED_BASE),
2919                ("EVIOCGSND", crate::input_file::EVIOCGSND_BASE),
2920                ("EVIOCGSW", crate::input_file::EVIOCGSW_BASE),
2921            ] {
2922                // Poison the buffer first, so that observing zeros afterwards
2923                // proves the ioctl wrote them rather than finding them.
2924                current_task
2925                    .write_memory(user_addr, &[0xAAu8; BUF_LEN])
2926                    .expect("failed to poison buffer");
2927
2928                let req = base | ((BUF_LEN as u32) << 16);
2929                let res = file.ioctl(&current_task, req, user_addr.into());
2930                assert_eq!(res, Ok(starnix_syscalls::SUCCESS), "{name} should succeed");
2931
2932                let read_back = current_task
2933                    .read_memory_to_vec(user_addr, BUF_LEN)
2934                    .expect("failed to read back buffer");
2935                assert_eq!(
2936                    read_back,
2937                    vec![0u8; BUF_LEN],
2938                    "{name} should report nothing currently active"
2939                );
2940            }
2941        })
2942        .await;
2943    }
2944    #[::fuchsia::test]
2945    async fn keyboard_input_file_ioctls() {
2946        spawn_kernel_and_run(async move |current_task| {
2947            let inspector = fuchsia_inspect::Inspector::default();
2948            let keyboard_device = InputDevice::new_keyboard(
2949                InputDeviceInfo::new(KEYBOARD_INPUT_ID, "starnix_buttons".to_string()),
2950                "keyboard_device",
2951                &inspector.root(),
2952            );
2953            let file = keyboard_device
2954                .open_test(&current_task)
2955                .expect("Failed to open keyboard input file");
2956
2957            let user_addr = map_memory(&current_task, UserAddress::default(), 4096);
2958
2959            // Test EVIOCGNAME
2960            let name_req = crate::input_file::EVIOCGNAME_BASE | (256 << 16);
2961            let res = file.ioctl(&current_task, name_req, user_addr.into());
2962            assert!(res.is_ok());
2963
2964            // Test EVIOCGPROP - should have INPUT_PROP_DIRECT, not INPUT_PROP_POINTER
2965            let prop_req = crate::input_file::EVIOCGPROP_BASE | (64 << 16);
2966            let res = file.ioctl(&current_task, prop_req, user_addr.into());
2967            assert_eq!(res, Ok(SUCCESS));
2968            let prop_buf = current_task.read_memory_to_vec(user_addr, 8).unwrap();
2969            let byte_idx = (uapi::INPUT_PROP_DIRECT / 8) as usize;
2970            let bit_idx = uapi::INPUT_PROP_DIRECT % 8;
2971            assert_ne!(prop_buf[byte_idx] & (1 << bit_idx), 0);
2972
2973            // Test EVIOCGBIT_EV_KEY - should have KEY_A, KEY_1, KEY_ENTER, KEY_POWER, etc.
2974            let key_req = crate::input_file::EVIOCGBIT_EV_KEY_BASE | (64 << 16);
2975            let res = file.ioctl(&current_task, key_req, user_addr.into());
2976            assert_eq!(res, Ok(SUCCESS));
2977            let key_buf = current_task.read_memory_to_vec(user_addr, 64).unwrap();
2978
2979            let check_key = |key_code: u32| {
2980                let byte = (key_code / 8) as usize;
2981                let bit = key_code % 8;
2982                assert_ne!(
2983                    key_buf[byte] & (1 << bit),
2984                    0,
2985                    "Keycode {} should be set in evdev bitmask",
2986                    key_code
2987                );
2988            };
2989            check_key(uapi::KEY_A);
2990            check_key(uapi::KEY_Z);
2991            check_key(uapi::KEY_1);
2992            check_key(uapi::KEY_ENTER);
2993            check_key(uapi::KEY_SPACE);
2994            check_key(uapi::KEY_ESC);
2995            check_key(uapi::KEY_POWER);
2996            check_key(uapi::KEY_VOLUMEUP);
2997            check_key(uapi::KEY_VOLUMEDOWN);
2998
2999            // Buttons that are not keyboard keys must NOT be advertised. `KEY_MAP` carries a
3000            // block of test-only keycodes (b/311425670) covering BTN_JOYSTICK..BTN_THUMBR; if
3001            // those leak into the capability bitmask, evdev clients classify this device as a
3002            // gamepad as well as a keyboard.
3003            let check_key_absent = |key_code: u32, name: &str| {
3004                let byte = (key_code / 8) as usize;
3005                let bit = key_code % 8;
3006                assert_eq!(
3007                    key_buf[byte] & (1 << bit),
3008                    0,
3009                    "{} ({}) should NOT be set in the keyboard evdev bitmask",
3010                    name,
3011                    key_code
3012                );
3013            };
3014            check_key_absent(uapi::BTN_LEFT, "BTN_LEFT");
3015            check_key_absent(uapi::BTN_TRIGGER, "BTN_TRIGGER");
3016            check_key_absent(uapi::BTN_SOUTH, "BTN_SOUTH");
3017            check_key_absent(uapi::BTN_THUMBR, "BTN_THUMBR");
3018        })
3019        .await;
3020    }
3021
3022    #[::fuchsia::test]
3023    async fn keyboard_events_update_inspect_counters() {
3024        spawn_kernel_and_run(async move |current_task| {
3025            let inspector = fuchsia_inspect::Inspector::default();
3026            let (_keyboard_device, keyboard_file, keyboard_listener) =
3027                start_keyboard_input_inspect(&current_task, &inspector).await;
3028
3029            let key_event = fuiinput3::KeyEvent {
3030                timestamp: Some(12345),
3031                type_: Some(fuiinput3::KeyEventType::Pressed),
3032                key: Some(fidl_fuchsia_input::Key::A),
3033                ..Default::default()
3034            };
3035
3036            let _ = keyboard_listener.on_key_event(&key_event).await;
3037            std::mem::drop(keyboard_listener);
3038
3039            // Read events to verify delivery
3040            let events = read_uapi_events(&keyboard_file, &current_task);
3041            assert_eq!(events.len(), 2);
3042
3043            // `converted` counts FIDL events, so one key event is 1 here even though it
3044            // produces 2 uapi events (the key plus a SYN). Keep this distinct from
3045            // `total_uapi_events_generated_count`.
3046            assert_data_tree!(inspector, root: {
3047                keyboard_device: {
3048                    active_wake_leases_count: AnyProperty,
3049                    total_events_with_wake_lease_count: AnyProperty,
3050                    total_fidl_events_received_count: 1u64,
3051                    total_fidl_events_ignored_count: 0u64,
3052                    total_fidl_events_unexpected_count: 0u64,
3053                    total_fidl_events_converted_count: 1u64,
3054                    total_uapi_events_generated_count: 2u64,
3055                    last_generated_uapi_event_timestamp_ns: 12345i64,
3056                    keyboard_file_0: {
3057                        fidl_events_received_count: 1u64,
3058                        fidl_events_ignored_count: 0u64,
3059                        fidl_events_unexpected_count: 0u64,
3060                        fidl_events_converted_count: 1u64,
3061                        uapi_events_generated_count: 2u64,
3062                        uapi_events_read_count: 2u64,
3063                        fd_read_count: AnyProperty,
3064                        fd_notify_count: AnyProperty,
3065                        last_generated_uapi_event_timestamp_ns: 12345i64,
3066                        last_read_uapi_event_timestamp_ns: AnyProperty,
3067                        opened_without_nonblock: AnyProperty,
3068                        open_timestamp_ns: AnyProperty,
3069                        closed: AnyProperty,
3070                        close_timestamp_ns: AnyProperty,
3071                    }
3072                }
3073            });
3074        })
3075        .await;
3076    }
3077
3078    async fn wait_for_device(kernel: &Kernel, devt: StarnixDeviceId, present: bool) {
3079        while kernel.device_registry.get_device(devt, DeviceMode::Char).is_ok() != present {
3080            fuchsia_async::Timer::new(
3081                fuchsia_async::MonotonicDuration::from_millis(10).after_now(),
3082            )
3083            .await;
3084        }
3085    }
3086
3087    async fn wait_for_device_listener_registered(
3088        inspector: &fuchsia_inspect::Inspector,
3089        expected: bool,
3090    ) {
3091        loop {
3092            let hierarchy = fuchsia_inspect::reader::read(inspector).await.unwrap();
3093            if let Some(relay_node) = hierarchy.get_child("input_events_relay") {
3094                if let Some(prop) = relay_node.get_property("device_listener_registered") {
3095                    if prop.boolean() == Some(expected) {
3096                        break;
3097                    }
3098                }
3099            }
3100            fuchsia_async::Timer::new(
3101                fuchsia_async::MonotonicDuration::from_millis(10).after_now(),
3102            )
3103            .await;
3104        }
3105    }
3106
3107    async fn wait_for_num_devices(inspector: &fuchsia_inspect::Inspector, expected: u64) {
3108        loop {
3109            let hierarchy = fuchsia_inspect::reader::read(inspector).await.unwrap();
3110            if let Some(relay_node) = hierarchy.get_child("input_events_relay") {
3111                if let Some(prop) = relay_node.get_property("num_devices") {
3112                    if prop.uint() == Some(expected) {
3113                        break;
3114                    }
3115                }
3116            }
3117            fuchsia_async::Timer::new(
3118                fuchsia_async::MonotonicDuration::from_millis(10).after_now(),
3119            )
3120            .await;
3121        }
3122    }
3123
3124    #[::fuchsia::test]
3125    async fn test_dynamic_device_registration() {
3126        spawn_kernel_and_run(async move |current_task| {
3127            let kernel = current_task.kernel();
3128
3129            // 1. Create endpoints for all standard protocols
3130            let (touch_source_client_end, _touch_source_stream) =
3131                fidl::endpoints::create_request_stream::<TouchSourceV2Marker>();
3132            let (mouse_source_client_end, _mouse_source_stream) =
3133                fidl::endpoints::create_request_stream::<fuipointer::MouseSourceV2Marker>();
3134            let (keyboard_proxy, mut keyboard_stream) =
3135                fidl::endpoints::create_sync_proxy_and_stream::<fuiinput3::KeyboardMarker>();
3136            let view_ref_pair =
3137                fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
3138            let (device_registry_proxy, mut device_listener_stream) =
3139                fidl::endpoints::create_sync_proxy_and_stream::<
3140                    fuipolicy::DeviceListenerRegistryMarker,
3141                >();
3142
3143            // 2. Create endpoints for the DeviceListenerRegistry protocol
3144            let (device_listener_registry_proxy, mut device_listener_registry_stream) =
3145                fidl::endpoints::create_sync_proxy_and_stream::<
3146                    fuiinput::DeviceListenerRegistryMarker,
3147                >();
3148
3149            let inspector = fuchsia_inspect::Inspector::default();
3150
3151            let (relay, relay_handle) = input_event_relay::new_input_relay();
3152            let relay = relay.with_inspect_node(inspector.root());
3153            relay.start_relays(
3154                &kernel,
3155                StartRelaysArgs {
3156                    event_proxy_mode: EventProxyMode::None,
3157                    touch_source_client_end,
3158                    keyboard_proxy,
3159                    mouse_source_client_end,
3160                    view_ref: view_ref_pair.view_ref,
3161                    registry_proxy: device_registry_proxy,
3162                    device_listener_registry: Some(device_listener_registry_proxy),
3163                    display_width: 700,
3164                    display_height: 700,
3165                },
3166            );
3167
3168            let _ = init_keyboard_listener(&mut keyboard_stream).await;
3169            let _ = init_button_listeners(&mut device_listener_stream).await;
3170            let (device_listener_proxy, mut device_iterator_stream) =
3171                init_device_listener(&mut device_listener_registry_stream).await;
3172            drain_empty_device_iterator(&mut device_iterator_stream).await;
3173
3174            // 4. Send an Action::Added event for a Touch device with pre-populated device_infos
3175            let device_id = 42;
3176            let prepopulated_input_id =
3177                uapi::input_id { bustype: 25, vendor: 26, product: 27, version: 28 };
3178            let prepopulated_name = "prepopulated_touch_device".to_string();
3179            relay_handle.update_uinput_device(
3180                device_id,
3181                prepopulated_input_id,
3182                prepopulated_name.clone(),
3183            );
3184
3185            let descriptor = fuiinput::DeviceDescriptor {
3186                touch: Some(fuiinput::TouchDescriptor::default()),
3187                device_information: Some(fuiinput::DeviceInformation {
3188                    product_name: Some("test_touch_device".to_string()),
3189                    ..Default::default()
3190                }),
3191                ..Default::default()
3192            };
3193            let event = fuiinput::DeviceEvent {
3194                action: Some(fuiinput::Action::Added),
3195                device_id: Some(device_id),
3196                descriptor: Some(descriptor.clone()),
3197                ..Default::default()
3198            };
3199
3200            device_listener_proxy
3201                .on_device_changed(&event)
3202                .expect("Failed to send dynamic device Added event");
3203
3204            // 5. Wait for the device to be registered in the kernel's device registry (minor 0).
3205            let touch_devt = StarnixDeviceId::new(INPUT_MAJOR, 0);
3206            wait_for_device(kernel, touch_devt, true).await;
3207
3208            // Verify that register_and_add_device reused the pre-populated device_infos.
3209            let info = relay_handle
3210                .device_infos
3211                .lock()
3212                .get(&device_id)
3213                .expect("device_info exists")
3214                .clone();
3215            assert_eq!(info.lock().input_id, prepopulated_input_id);
3216            assert_eq!(info.lock().name, prepopulated_name);
3217
3218            // Test ConsumerControl (standalone buttons) registration
3219            let buttons_device_id = 43;
3220            let descriptor_buttons = fuiinput::DeviceDescriptor {
3221                consumer_control: Some(fuiinput::ConsumerControlDescriptor::default()),
3222                device_information: Some(fuiinput::DeviceInformation {
3223                    product_name: Some("test_buttons_device".to_string()),
3224                    ..Default::default()
3225                }),
3226                ..Default::default()
3227            };
3228            let event_buttons = fuiinput::DeviceEvent {
3229                action: Some(fuiinput::Action::Added),
3230                device_id: Some(buttons_device_id),
3231                descriptor: Some(descriptor_buttons.clone()),
3232                ..Default::default()
3233            };
3234            device_listener_proxy
3235                .on_device_changed(&event_buttons)
3236                .expect("Failed to send dynamic ConsumerControl device Added event");
3237
3238            let buttons_devt = StarnixDeviceId::new(INPUT_MAJOR, 1);
3239            wait_for_device(kernel, buttons_devt, true).await;
3240
3241            // Test Keyboard dynamic registration
3242            let keyboard_device_id = 44;
3243            let descriptor_keyboard = fuiinput::DeviceDescriptor {
3244                keyboard: Some(fuiinput::KeyboardDescriptor::default()),
3245                device_information: Some(fuiinput::DeviceInformation {
3246                    product_name: Some("test_keyboard_device".to_string()),
3247                    ..Default::default()
3248                }),
3249                ..Default::default()
3250            };
3251            let event_keyboard = fuiinput::DeviceEvent {
3252                action: Some(fuiinput::Action::Added),
3253                device_id: Some(keyboard_device_id),
3254                descriptor: Some(descriptor_keyboard.clone()),
3255                ..Default::default()
3256            };
3257            device_listener_proxy
3258                .on_device_changed(&event_keyboard)
3259                .expect("Failed to send dynamic Keyboard device Added event");
3260
3261            let keyboard_devt = StarnixDeviceId::new(INPUT_MAJOR, 2);
3262            wait_for_device(kernel, keyboard_devt, true).await;
3263
3264            // Test Mouse dynamic registration
3265            let mouse_device_id = 45;
3266            let descriptor_mouse = fuiinput::DeviceDescriptor {
3267                mouse: Some(fuiinput::MouseDescriptor::default()),
3268                device_information: Some(fuiinput::DeviceInformation {
3269                    product_name: Some("test_mouse_device".to_string()),
3270                    ..Default::default()
3271                }),
3272                ..Default::default()
3273            };
3274            let event_mouse = fuiinput::DeviceEvent {
3275                action: Some(fuiinput::Action::Added),
3276                device_id: Some(mouse_device_id),
3277                descriptor: Some(descriptor_mouse.clone()),
3278                ..Default::default()
3279            };
3280            device_listener_proxy
3281                .on_device_changed(&event_mouse)
3282                .expect("Failed to send dynamic Mouse device Added event");
3283
3284            let mouse_devt = StarnixDeviceId::new(INPUT_MAJOR, 3);
3285            wait_for_device(kernel, mouse_devt, true).await;
3286
3287            wait_for_num_devices(&inspector, 4).await;
3288            assert_data_tree!(inspector, root: contains {
3289                input_events_relay: contains {
3290                    num_devices: 4u64,
3291                    device_listener_registered: true,
3292                }
3293            });
3294
3295            // 6. Send Action::Removed events
3296            let event_removed = fuiinput::DeviceEvent {
3297                action: Some(fuiinput::Action::Removed),
3298                device_id: Some(device_id),
3299                descriptor: Some(descriptor.clone()),
3300                ..Default::default()
3301            };
3302            device_listener_proxy
3303                .on_device_changed(&event_removed)
3304                .expect("Failed to send dynamic device Removed event");
3305
3306            // Wait for the touch device to be removed from the kernel's device registry
3307            wait_for_device(kernel, touch_devt, false).await;
3308            assert!(relay_handle.device_infos.lock().get(&device_id).is_none());
3309
3310            // Re-add a device to verify that the freed minor number (0) is recycled.
3311            let replacement_device_id = 46;
3312            let replacement_descriptor = fuiinput::DeviceDescriptor {
3313                touch: Some(fuiinput::TouchDescriptor::default()),
3314                device_information: Some(fuiinput::DeviceInformation {
3315                    product_name: Some("test_replacement_touch_device".to_string()),
3316                    ..Default::default()
3317                }),
3318                ..Default::default()
3319            };
3320            let event_readd = fuiinput::DeviceEvent {
3321                action: Some(fuiinput::Action::Added),
3322                device_id: Some(replacement_device_id),
3323                descriptor: Some(replacement_descriptor.clone()),
3324                ..Default::default()
3325            };
3326            device_listener_proxy
3327                .on_device_changed(&event_readd)
3328                .expect("Failed to send dynamic device Added event for replacement device");
3329
3330            // Verify minor 0 is recycled for the new device
3331            wait_for_device(kernel, touch_devt, true).await;
3332
3333            // Remove the replacement device
3334            let event_replacement_removed = fuiinput::DeviceEvent {
3335                action: Some(fuiinput::Action::Removed),
3336                device_id: Some(replacement_device_id),
3337                descriptor: Some(replacement_descriptor.clone()),
3338                ..Default::default()
3339            };
3340            device_listener_proxy
3341                .on_device_changed(&event_replacement_removed)
3342                .expect("Failed to send dynamic device Removed event for replacement device");
3343
3344            wait_for_device(kernel, touch_devt, false).await;
3345
3346            // Remove Keyboard
3347            let event_keyboard_removed = fuiinput::DeviceEvent {
3348                action: Some(fuiinput::Action::Removed),
3349                device_id: Some(keyboard_device_id),
3350                descriptor: Some(descriptor_keyboard.clone()),
3351                ..Default::default()
3352            };
3353            device_listener_proxy
3354                .on_device_changed(&event_keyboard_removed)
3355                .expect("Failed to send dynamic Keyboard device Removed event");
3356
3357            wait_for_device(kernel, keyboard_devt, false).await;
3358
3359            // Remove Mouse
3360            let event_mouse_removed = fuiinput::DeviceEvent {
3361                action: Some(fuiinput::Action::Removed),
3362                device_id: Some(mouse_device_id),
3363                descriptor: Some(descriptor_mouse.clone()),
3364                ..Default::default()
3365            };
3366            device_listener_proxy
3367                .on_device_changed(&event_mouse_removed)
3368                .expect("Failed to send dynamic Mouse device Removed event");
3369
3370            wait_for_device(kernel, mouse_devt, false).await;
3371
3372            wait_for_num_devices(&inspector, 1).await;
3373            assert_data_tree!(inspector, root: contains {
3374                input_events_relay: contains {
3375                    num_devices: 1u64,
3376                    device_listener_registered: true,
3377                }
3378            });
3379
3380            // Dropping the listener proxy closes the peer channel, which should cause the relay
3381            // to mark device_listener_registered as false.
3382            drop(device_listener_proxy);
3383            wait_for_device_listener_registered(&inspector, false).await;
3384        })
3385        .await;
3386    }
3387
3388    #[::fuchsia::test]
3389    async fn test_existing_devices_iterator() {
3390        spawn_kernel_and_run(async move |current_task| {
3391            let kernel = current_task.kernel();
3392
3393            // 1. Create endpoints for all standard protocols
3394            let (touch_source_client_end, _touch_source_stream) =
3395                fidl::endpoints::create_request_stream::<TouchSourceV2Marker>();
3396            let (mouse_source_client_end, _mouse_source_stream) =
3397                fidl::endpoints::create_request_stream::<fuipointer::MouseSourceV2Marker>();
3398            let (keyboard_proxy, mut keyboard_stream) =
3399                fidl::endpoints::create_sync_proxy_and_stream::<fuiinput3::KeyboardMarker>();
3400            let view_ref_pair =
3401                fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
3402            let (device_registry_proxy, mut device_listener_stream) =
3403                fidl::endpoints::create_sync_proxy_and_stream::<
3404                    fuipolicy::DeviceListenerRegistryMarker,
3405                >();
3406
3407            // 2. Create endpoints for DeviceListenerRegistry protocol
3408            let (device_listener_registry_proxy, mut device_listener_registry_stream) =
3409                fidl::endpoints::create_sync_proxy_and_stream::<
3410                    fuiinput::DeviceListenerRegistryMarker,
3411                >();
3412
3413            let inspector = fuchsia_inspect::Inspector::default();
3414
3415            // 3. Start the relays with existing_devices_iterator
3416            let (relay, _relay_handle) = input_event_relay::new_input_relay();
3417            let relay = relay.with_inspect_node(inspector.root());
3418            relay.start_relays(
3419                &kernel,
3420                StartRelaysArgs {
3421                    event_proxy_mode: EventProxyMode::None,
3422                    touch_source_client_end,
3423                    keyboard_proxy,
3424                    mouse_source_client_end,
3425                    view_ref: view_ref_pair.view_ref,
3426                    registry_proxy: device_registry_proxy,
3427                    device_listener_registry: Some(device_listener_registry_proxy),
3428                    display_width: 700,
3429                    display_height: 700,
3430                },
3431            );
3432
3433            // Respond to initial listener setup
3434            let _ = init_keyboard_listener(&mut keyboard_stream).await;
3435            let _ = init_button_listeners(&mut device_listener_stream).await;
3436            let (_device_listener_proxy, mut device_iterator_stream) =
3437                init_device_listener(&mut device_listener_registry_stream).await;
3438
3439            // 4. Serve the existing devices iterator:
3440            // First batch yields a touch device and a keyboard device with Action::Added.
3441            let touch_event = fuiinput::DeviceEvent {
3442                action: Some(fuiinput::Action::Added),
3443                device_id: Some(10),
3444                descriptor: Some(fuiinput::DeviceDescriptor {
3445                    touch: Some(fuiinput::TouchDescriptor::default()),
3446                    device_information: Some(fuiinput::DeviceInformation {
3447                        product_name: Some("existing_touch_device".to_string()),
3448                        ..Default::default()
3449                    }),
3450                    ..Default::default()
3451                }),
3452                ..Default::default()
3453            };
3454            let keyboard_event = fuiinput::DeviceEvent {
3455                action: Some(fuiinput::Action::Added),
3456                device_id: Some(11),
3457                descriptor: Some(fuiinput::DeviceDescriptor {
3458                    keyboard: Some(fuiinput::KeyboardDescriptor::default()),
3459                    device_information: Some(fuiinput::DeviceInformation {
3460                        product_name: Some("existing_keyboard_device".to_string()),
3461                        ..Default::default()
3462                    }),
3463                    ..Default::default()
3464                }),
3465                ..Default::default()
3466            };
3467
3468            match device_iterator_stream.next().await {
3469                Some(Ok(fuiinput::DeviceIteratorRequest::GetNext { responder })) => {
3470                    responder
3471                        .send(&[touch_event, keyboard_event])
3472                        .expect("failed to send first batch");
3473                }
3474                other => panic!("Unexpected request on DeviceIterator: {:?}", other),
3475            }
3476
3477            // Second batch yields an empty list to indicate completion.
3478            match device_iterator_stream.next().await {
3479                Some(Ok(fuiinput::DeviceIteratorRequest::GetNext { responder })) => {
3480                    responder.send(&[]).expect("failed to send empty batch");
3481                }
3482                other => panic!("Unexpected request on DeviceIterator: {:?}", other),
3483            }
3484
3485            // 5. Verify the devices are registered in kernel.device_registry at startup.
3486            let touch_devt = StarnixDeviceId::new(INPUT_MAJOR, 0);
3487            wait_for_device(kernel, touch_devt, true).await;
3488
3489            let keyboard_devt = StarnixDeviceId::new(INPUT_MAJOR, 1);
3490            wait_for_device(kernel, keyboard_devt, true).await;
3491
3492            wait_for_num_devices(&inspector, 2).await;
3493            assert_data_tree!(inspector, root: contains {
3494                input_events_relay: contains {
3495                    num_devices: 2u64,
3496                    device_listener_registered: true,
3497                }
3498            });
3499        })
3500        .await;
3501    }
3502
3503    #[::fuchsia::test]
3504    async fn test_dynamic_device_registration_failure() {
3505        spawn_kernel_and_run(async move |current_task| {
3506            let kernel = current_task.kernel();
3507
3508            // 1. Create endpoints for all standard protocols
3509            let (touch_source_client_end, _touch_source_stream) =
3510                fidl::endpoints::create_request_stream::<TouchSourceV2Marker>();
3511            let (mouse_source_client_end, _mouse_source_stream) =
3512                fidl::endpoints::create_request_stream::<fuipointer::MouseSourceV2Marker>();
3513            let (keyboard_proxy, mut keyboard_stream) =
3514                fidl::endpoints::create_sync_proxy_and_stream::<fuiinput3::KeyboardMarker>();
3515            let view_ref_pair =
3516                fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
3517            let (device_registry_proxy, mut device_listener_stream) =
3518                fidl::endpoints::create_sync_proxy_and_stream::<
3519                    fuipolicy::DeviceListenerRegistryMarker,
3520                >();
3521
3522            // 2. Create endpoints for DeviceListenerRegistry and immediately drop server
3523            // to simulate registration failure (peer closed).
3524            let (device_listener_registry_proxy, device_listener_registry_stream) =
3525                fidl::endpoints::create_sync_proxy_and_stream::<
3526                    fuiinput::DeviceListenerRegistryMarker,
3527                >();
3528            drop(device_listener_registry_stream);
3529
3530            let inspector = fuchsia_inspect::Inspector::default();
3531
3532            // 3. Start the relays
3533            let (relay, _relay_handle) = input_event_relay::new_input_relay();
3534            let relay = relay.with_inspect_node(inspector.root());
3535            relay.start_relays(
3536                &kernel,
3537                StartRelaysArgs {
3538                    event_proxy_mode: EventProxyMode::None,
3539                    touch_source_client_end,
3540                    keyboard_proxy,
3541                    mouse_source_client_end,
3542                    view_ref: view_ref_pair.view_ref,
3543                    registry_proxy: device_registry_proxy,
3544                    device_listener_registry: Some(device_listener_registry_proxy),
3545                    display_width: 700,
3546                    display_height: 700,
3547                },
3548            );
3549
3550            let keyboard_listener = init_keyboard_listener(&mut keyboard_stream).await;
3551            let _ = init_button_listeners(&mut device_listener_stream).await;
3552
3553            // Synchronize with the relay thread by awaiting a two-way key event.
3554            // Because keyboard events are only handled inside the event loop after registration
3555            // has finished, this guarantees registration has completed.
3556            let key_event = fuiinput3::KeyEvent {
3557                timestamp: Some(0),
3558                type_: Some(fuiinput3::KeyEventType::Pressed),
3559                key: Some(fidl_fuchsia_input::Key::A),
3560                ..Default::default()
3561            };
3562            let _ = keyboard_listener.on_key_event(&key_event).await;
3563
3564            wait_for_device_listener_registered(&inspector, false).await;
3565
3566            assert_data_tree!(inspector, root: contains {
3567                input_events_relay: contains {
3568                    num_devices: 0u64,
3569                    device_listener_registered: false,
3570                }
3571            });
3572        })
3573        .await;
3574    }
3575
3576    #[::fuchsia::test]
3577    async fn register_nests_event_device_under_input_device() {
3578        spawn_kernel_and_run(async move |current_task| {
3579            let kernel = current_task.kernel();
3580            let inspector = fuchsia_inspect::Inspector::default();
3581            let info = InputDeviceInfo::new(KEYBOARD_INPUT_ID, "starnix_buttons".to_string());
3582            let keyboard_device =
3583                InputDevice::new_keyboard(info.clone(), "keyboard_device", &inspector.root());
3584
3585            let event_device =
3586                keyboard_device.register(kernel, 7).expect("failed to register input device");
3587            assert_eq!(event_device.name(), "event7");
3588            assert_eq!(event_device.parent().map(|parent| parent.name()), Some("input7".into()));
3589
3590            let root = &kernel.device_registry.objects.root;
3591            for path in [
3592                "devices/virtual/input/input7/uevent",
3593                "devices/virtual/input/input7/event7/dev",
3594                "devices/virtual/input/input7/event7/uevent",
3595                "class/input/input7",
3596                "class/input/event7",
3597                "dev/char/13:7",
3598            ] {
3599                assert!(root.lookup(path.into()).is_some(), "missing {path}");
3600            }
3601
3602            let read_attr = |path: &str| -> String {
3603                let node = root.lookup(path.into()).unwrap_or_else(|| panic!("missing {path}"));
3604                let file_ops =
3605                    node.create_file_ops(&current_task, OpenFlags::RDONLY).expect("open attr");
3606                let file = starnix_core::testing::anon_test_file(
3607                    &current_task,
3608                    file_ops,
3609                    OpenFlags::RDONLY,
3610                );
3611                let mut buf = VecOutputBuffer::new(64);
3612                let bytes_read = file.read(&current_task, &mut buf).expect("read attr");
3613                String::from_utf8(Vec::from(&buf.data()[..bytes_read])).expect("utf8 attr")
3614            };
3615            assert_eq!(read_attr("devices/virtual/input/input7/name"), "starnix_buttons\n");
3616            assert_eq!(
3617                read_attr("devices/virtual/input/input7/id/bustype"),
3618                format!("{:04x}\n", KEYBOARD_INPUT_ID.bustype)
3619            );
3620            assert_eq!(
3621                read_attr("devices/virtual/input/input7/id/vendor"),
3622                format!("{:04x}\n", KEYBOARD_INPUT_ID.vendor)
3623            );
3624            assert_eq!(
3625                read_attr("devices/virtual/input/input7/id/product"),
3626                format!("{:04x}\n", KEYBOARD_INPUT_ID.product)
3627            );
3628            assert_eq!(
3629                read_attr("devices/virtual/input/input7/id/version"),
3630                format!("{:04x}\n", KEYBOARD_INPUT_ID.version)
3631            );
3632
3633            // Dynamic metadata updates (as performed by uinput) must be reflected in sysfs.
3634            *info.lock() = InputDeviceInfo {
3635                input_id: input_id { bustype: 0x0003, vendor: 0x1234, product: 0x5678, version: 9 },
3636                name: "custom_uinput".to_string(),
3637            };
3638            assert_eq!(read_attr("devices/virtual/input/input7/name"), "custom_uinput\n");
3639            assert_eq!(read_attr("devices/virtual/input/input7/id/bustype"), "0003\n");
3640            assert_eq!(read_attr("devices/virtual/input/input7/id/vendor"), "1234\n");
3641            assert_eq!(read_attr("devices/virtual/input/input7/id/product"), "5678\n");
3642            assert_eq!(read_attr("devices/virtual/input/input7/id/version"), "0009\n");
3643
3644            // Same-class children are created directly within their parent, without an
3645            // intermediate class directory.
3646            assert!(root.lookup("devices/virtual/input/input7/input".into()).is_none());
3647
3648            InputDevice::unregister(&current_task, event_device);
3649            for path in [
3650                "devices/virtual/input/input7",
3651                "class/input/input7",
3652                "class/input/event7",
3653                "dev/char/13:7",
3654            ] {
3655                assert!(root.lookup(path.into()).is_none(), "unexpected {path}");
3656            }
3657        })
3658        .await;
3659    }
3660}