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::{DeviceId, OpenedFiles};
7use futures::FutureExt;
8use starnix_core::device::kobject::DeviceMetadata;
9use starnix_core::device::{DeviceMode, DeviceOps};
10use starnix_core::task::CurrentTask;
11use starnix_core::vfs::{FileOps, FsString, NamespaceNode};
12#[cfg(test)]
13use starnix_sync::Unlocked;
14use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Mutex};
15use starnix_uapi::device_type::{DeviceType, INPUT_MAJOR};
16use starnix_uapi::errors::Errno;
17use starnix_uapi::open_flags::OpenFlags;
18use starnix_uapi::{BUS_VIRTUAL, input_id};
19use std::sync::Arc;
20use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
21
22// Add a fuchsia-specific vendor ID. 0xfc1a is currently not allocated
23// to any vendor in the USB spec.
24//
25// May not be zero, see below.
26const FUCHSIA_VENDOR_ID: u16 = 0xfc1a;
27
28// May not be zero, see below.
29const FUCHSIA_TOUCH_PRODUCT_ID: u16 = 0x2;
30
31// May not be zero, see below.
32const FUCHSIA_KEYBOARD_PRODUCT_ID: u16 = 0x1;
33
34// May not be zero, see below.
35const FUCHSIA_MOUSE_PRODUCT_ID: u16 = 0x3;
36
37// Touch, keyboard, and mouse input IDs should be distinct.
38// Per https://www.linuxjournal.com/article/6429, the bus type should be populated with a
39// sensible value, but other fields may not be.
40//
41// While this may be the case for Linux itself, Android is not so relaxed.
42// Devices with apparently-invalid vendor or product IDs don't get extra
43// device configuration.  So we must make a minimum effort to present
44// sensibly-looking product and vendor IDs.  Zero version only means that
45// version-specific config files will not be applied.
46//
47// For background, see:
48//
49// * Allowable file locations:
50//   https://source.android.com/docs/core/interaction/input/input-device-configuration-files#location
51// * Android configuration selection code:
52//   https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:frameworks/native/libs/input/InputDevice.cpp;l=60;drc=285211e60bff87fc5a9c9b4105a4b4ccb7edffaf
53const TOUCH_INPUT_ID: input_id = input_id {
54    bustype: BUS_VIRTUAL as u16,
55    // Make sure that vendor ID and product ID at least seem plausible.  See
56    // above for details.
57    vendor: FUCHSIA_VENDOR_ID,
58    product: FUCHSIA_TOUCH_PRODUCT_ID,
59    // Version is OK to be zero, but config files named `Product_yyyy_Vendor_zzzz_Version_ttt.*`
60    // will not work.
61    version: 0,
62};
63const KEYBOARD_INPUT_ID: input_id = input_id {
64    bustype: BUS_VIRTUAL as u16,
65    // Make sure that vendor ID and product ID at least seem plausible.  See
66    // above for details.
67    vendor: FUCHSIA_VENDOR_ID,
68    product: FUCHSIA_KEYBOARD_PRODUCT_ID,
69    version: 1,
70};
71
72const MOUSE_INPUT_ID: input_id = input_id {
73    bustype: BUS_VIRTUAL as u16,
74    // Make sure that vendor ID and product ID at least seem plausible.  See
75    // above for details.
76    vendor: FUCHSIA_VENDOR_ID,
77    product: FUCHSIA_MOUSE_PRODUCT_ID,
78    version: 1,
79};
80
81#[derive(Clone)]
82enum InputDeviceType {
83    // A touch device, containing (display width, display height).
84    Touch(i32, i32),
85
86    // A keyboard device.
87    Keyboard,
88
89    // A mouse device.
90    Mouse,
91}
92
93/// An [`InputDeviceStatus`] is tied to an [`InputDeviceBinding`] and provides properties
94/// detailing its Inspect status.
95/// We expect all (non-timestamp) properties' counts to equal the sum of that property from all
96/// files opened on that device. So for example, if a device had 3 separate input files opened, we
97/// would expect it's `total_fidl_events_received_count` to equal the sum of
98/// `fidl_events_received_count` from all 3 files and so forth.
99pub struct InputDeviceStatus {
100    /// A node that contains the state below.
101    pub node: fuchsia_inspect::Node,
102
103    /// Hold onto inspect nodes for files opened on this device, so that when these files are
104    /// closed, their inspect data is maintained.
105    pub file_nodes: Mutex<Vec<fuchsia_inspect::Node>>,
106
107    /// The number of FIDL events received by this device from Fuchsia input system.
108    ///
109    /// We expect:
110    /// total_fidl_events_received_count = total_fidl_events_ignored_count +
111    ///                                    total_fidl_events_unexpected_count +
112    ///                                    total_fidl_events_converted_count
113    /// otherwise starnix ignored events unexpectedly.
114    ///
115    /// total_fidl_events_unexpected_count should be 0, if not it hints issues from upstream of
116    /// ui stack.
117    pub total_fidl_events_received_count: AtomicU64,
118
119    /// The number of FIDL events ignored by this device when attempting conversion to this
120    /// module’s representation of a TouchEvent.
121    pub total_fidl_events_ignored_count: AtomicU64,
122
123    /// The unexpected number of FIDL events reached to this module should be filtered out
124    /// earlier in the UI stack.
125    /// It maybe unexpected format or unexpected order.
126    pub total_fidl_events_unexpected_count: AtomicU64,
127
128    /// The number of FIDL events converted by this device to this module’s representation of
129    /// TouchEvent.
130    pub total_fidl_events_converted_count: AtomicU64,
131
132    /// The number of uapi::input_events generated by this device from TouchEvents.
133    pub total_uapi_events_generated_count: AtomicU64,
134
135    /// The event time of the last generated uapi::input_event by one of this device's InputFiles.
136    pub last_generated_uapi_event_timestamp_ns: AtomicI64,
137}
138
139impl InputDeviceStatus {
140    fn new(node: fuchsia_inspect::Node) -> Arc<Self> {
141        let status = Arc::new(Self {
142            node,
143            file_nodes: Mutex::new(vec![]),
144            total_fidl_events_received_count: AtomicU64::new(0),
145            total_fidl_events_ignored_count: AtomicU64::new(0),
146            total_fidl_events_unexpected_count: AtomicU64::new(0),
147            total_fidl_events_converted_count: AtomicU64::new(0),
148            total_uapi_events_generated_count: AtomicU64::new(0),
149            last_generated_uapi_event_timestamp_ns: AtomicI64::new(0),
150        });
151
152        let weak_status = Arc::downgrade(&status);
153        status.node.record_lazy_values("status", move || {
154            let status = weak_status.upgrade();
155            async move {
156                let inspector = fuchsia_inspect::Inspector::default();
157                if let Some(status) = status {
158                    let root = inspector.root();
159                    root.record_uint(
160                        "total_fidl_events_received_count",
161                        status.total_fidl_events_received_count.load(Ordering::Relaxed),
162                    );
163                    root.record_uint(
164                        "total_fidl_events_ignored_count",
165                        status.total_fidl_events_ignored_count.load(Ordering::Relaxed),
166                    );
167                    root.record_uint(
168                        "total_fidl_events_unexpected_count",
169                        status.total_fidl_events_unexpected_count.load(Ordering::Relaxed),
170                    );
171                    root.record_uint(
172                        "total_fidl_events_converted_count",
173                        status.total_fidl_events_converted_count.load(Ordering::Relaxed),
174                    );
175                    root.record_uint(
176                        "total_uapi_events_generated_count",
177                        status.total_uapi_events_generated_count.load(Ordering::Relaxed),
178                    );
179                    root.record_int(
180                        "last_generated_uapi_event_timestamp_ns",
181                        status.last_generated_uapi_event_timestamp_ns.load(Ordering::Relaxed),
182                    );
183                }
184                Ok(inspector)
185            }
186            .boxed()
187        });
188
189        status
190    }
191
192    pub fn count_total_received_events(&self, count: u64) {
193        self.total_fidl_events_received_count.fetch_add(count, Ordering::Relaxed);
194    }
195
196    pub fn count_total_ignored_events(&self, count: u64) {
197        self.total_fidl_events_ignored_count.fetch_add(count, Ordering::Relaxed);
198    }
199
200    pub fn count_total_unexpected_events(&self, count: u64) {
201        self.total_fidl_events_unexpected_count.fetch_add(count, Ordering::Relaxed);
202    }
203
204    pub fn count_total_converted_events(&self, count: u64) {
205        self.total_fidl_events_converted_count.fetch_add(count, Ordering::Relaxed);
206    }
207
208    pub fn count_total_generated_events(&self, count: u64, event_time_ns: i64) {
209        self.total_uapi_events_generated_count.fetch_add(count, Ordering::Relaxed);
210        self.last_generated_uapi_event_timestamp_ns.store(event_time_ns, Ordering::Relaxed);
211    }
212}
213
214#[derive(Clone)]
215pub struct InputDevice {
216    device_type: InputDeviceType,
217
218    pub open_files: OpenedFiles,
219
220    pub inspect_status: Arc<InputDeviceStatus>,
221}
222
223impl InputDevice {
224    pub fn new_touch(
225        display_width: i32,
226        display_height: i32,
227        inspect_node: &fuchsia_inspect::Node,
228    ) -> Self {
229        let node = inspect_node.create_child("touch_device");
230        InputDevice {
231            device_type: InputDeviceType::Touch(display_width, display_height),
232            open_files: Default::default(),
233            inspect_status: InputDeviceStatus::new(node),
234        }
235    }
236
237    pub fn new_keyboard(inspect_node: &fuchsia_inspect::Node) -> Self {
238        let node = inspect_node.create_child("keyboard_device");
239        InputDevice {
240            device_type: InputDeviceType::Keyboard,
241            open_files: Default::default(),
242            inspect_status: InputDeviceStatus::new(node),
243        }
244    }
245
246    pub fn new_mouse(inspect_node: &fuchsia_inspect::Node) -> Self {
247        let node = inspect_node.create_child("mouse_device");
248        InputDevice {
249            device_type: InputDeviceType::Mouse,
250            open_files: Default::default(),
251            inspect_status: InputDeviceStatus::new(node),
252        }
253    }
254
255    pub fn register<L>(
256        self,
257        locked: &mut Locked<L>,
258        system_task: &CurrentTask,
259        device_id: DeviceId,
260    ) -> Result<(), Errno>
261    where
262        L: LockEqualOrBefore<FileOpsCore>,
263    {
264        let kernel = system_task.kernel();
265        let registry = &kernel.device_registry;
266
267        let input_class = registry.objects.input_class();
268        registry.register_device(
269            locked,
270            system_task,
271            FsString::from(format!("event{}", device_id)).as_ref(),
272            DeviceMetadata::new(
273                format!("input/event{}", device_id).into(),
274                DeviceType::new(INPUT_MAJOR, device_id),
275                DeviceMode::Char,
276            ),
277            input_class,
278            self,
279        )?;
280        Ok(())
281    }
282
283    pub fn open_internal(&self) -> Box<dyn FileOps> {
284        let input_file = match self.device_type {
285            InputDeviceType::Touch(display_width, display_height) => {
286                let mut file_nodes = self.inspect_status.file_nodes.lock();
287                let child_node = self
288                    .inspect_status
289                    .node
290                    .create_child(format!("touch_file_{}", file_nodes.len()));
291                let file = Arc::new(InputFile::new_touch(
292                    TOUCH_INPUT_ID,
293                    display_width,
294                    display_height,
295                    Some(&child_node),
296                ));
297                file_nodes.push(child_node);
298                file
299            }
300            InputDeviceType::Keyboard => {
301                let mut file_nodes = self.inspect_status.file_nodes.lock();
302                let child_node = self
303                    .inspect_status
304                    .node
305                    .create_child(format!("keyboard_file_{}", file_nodes.len()));
306                let file = Arc::new(InputFile::new_keyboard(KEYBOARD_INPUT_ID, Some(&child_node)));
307                file_nodes.push(child_node);
308                file
309            }
310            InputDeviceType::Mouse => {
311                let mut file_nodes = self.inspect_status.file_nodes.lock();
312                let child_node = self
313                    .inspect_status
314                    .node
315                    .create_child(format!("mouse_file_{}", file_nodes.len()));
316                let file = Arc::new(InputFile::new_mouse(MOUSE_INPUT_ID, Some(&child_node)));
317                file_nodes.push(child_node);
318                file
319            }
320        };
321        self.open_files.lock().push(Arc::downgrade(&input_file));
322        Box::new(input_file)
323    }
324
325    #[cfg(test)]
326    pub fn open_test(
327        &self,
328        locked: &mut Locked<Unlocked>,
329        current_task: &CurrentTask,
330    ) -> Result<starnix_core::vfs::FileHandle, Errno> {
331        let input_file = self.open_internal();
332        let root_namespace_node = current_task
333            .lookup_path_from_root(locked, ".".into())
334            .expect("failed to get namespace node for root");
335
336        let file_object = starnix_core::vfs::FileObject::new(
337            locked,
338            current_task,
339            input_file,
340            root_namespace_node,
341            OpenFlags::empty(),
342        )
343        .expect("FileObject::new failed");
344        Ok(file_object)
345    }
346}
347
348impl DeviceOps for InputDevice {
349    fn open(
350        &self,
351        _locked: &mut Locked<FileOpsCore>,
352        _current_task: &CurrentTask,
353        _id: DeviceType,
354        _node: &NamespaceNode,
355        _flags: OpenFlags,
356    ) -> Result<Box<dyn FileOps>, Errno> {
357        let input_file = self.open_internal();
358        Ok(input_file)
359    }
360}
361
362#[cfg(test)]
363mod test {
364    #![allow(clippy::unused_unit)] // for compatibility with `test_case`
365
366    use super::*;
367    use crate::input_event_relay::{self, EventProxyMode};
368    use anyhow::anyhow;
369    use assert_matches::assert_matches;
370    use diagnostics_assertions::{AnyProperty, assert_data_tree};
371    use fidl_fuchsia_ui_input::MediaButtonsEvent;
372    use fidl_fuchsia_ui_input3 as fuiinput;
373    use fidl_fuchsia_ui_pointer as fuipointer;
374    use fidl_fuchsia_ui_policy as fuipolicy;
375    use fuipointer::{
376        EventPhase, TouchEvent, TouchInteractionId, TouchPointerSample, TouchResponse,
377        TouchSourceMarker, TouchSourceRequest,
378    };
379    use futures::StreamExt as _;
380    use pretty_assertions::assert_eq;
381    use starnix_core::task::dynamic_thread_spawner::SpawnRequestBuilder;
382    use starnix_core::task::{EventHandler, Waiter};
383    #[allow(deprecated, reason = "pre-existing usage")]
384    use starnix_core::testing::create_kernel_task_and_unlocked;
385    use starnix_core::vfs::FileHandle;
386    use starnix_core::vfs::buffers::VecOutputBuffer;
387    use starnix_types::time::timeval_from_time;
388    use starnix_uapi::errors::EAGAIN;
389    use starnix_uapi::uapi;
390    use starnix_uapi::vfs::FdEvents;
391    use test_case::test_case;
392    use test_util::assert_near;
393    use zerocopy::FromBytes as _;
394
395    const INPUT_EVENT_SIZE: usize = std::mem::size_of::<uapi::input_event>();
396
397    async fn start_touch_input(
398        locked: &mut Locked<Unlocked>,
399        current_task: &CurrentTask,
400    ) -> (InputDevice, FileHandle, fuipointer::TouchSourceRequestStream) {
401        let inspector = fuchsia_inspect::Inspector::default();
402        start_touch_input_inspect_and_dimensions(locked, current_task, 700, 1200, &inspector).await
403    }
404
405    async fn start_touch_input_inspect(
406        locked: &mut Locked<Unlocked>,
407        current_task: &CurrentTask,
408        inspector: &fuchsia_inspect::Inspector,
409    ) -> (InputDevice, FileHandle, fuipointer::TouchSourceRequestStream) {
410        start_touch_input_inspect_and_dimensions(locked, current_task, 700, 1200, &inspector).await
411    }
412
413    async fn init_keyboard_listener(
414        keyboard_stream: &mut fuiinput::KeyboardRequestStream,
415    ) -> fuiinput::KeyboardListenerProxy {
416        let keyboard_listener = match keyboard_stream.next().await {
417            Some(Ok(fuiinput::KeyboardRequest::AddListener {
418                view_ref: _,
419                listener,
420                responder,
421            })) => {
422                let _ = responder.send();
423                listener.into_proxy()
424            }
425            _ => {
426                panic!("Failed to get event");
427            }
428        };
429
430        keyboard_listener
431    }
432
433    async fn init_button_listeners(
434        device_listener_stream: &mut fuipolicy::DeviceListenerRegistryRequestStream,
435    ) -> (fuipolicy::MediaButtonsListenerProxy, fuipolicy::TouchButtonsListenerProxy) {
436        let media_buttons_listener = match device_listener_stream.next().await {
437            Some(Ok(fuipolicy::DeviceListenerRegistryRequest::RegisterListener {
438                listener,
439                responder,
440            })) => {
441                let _ = responder.send();
442                listener.into_proxy()
443            }
444            _ => {
445                panic!("Failed to get event");
446            }
447        };
448
449        let touch_buttons_listener = match device_listener_stream.next().await {
450            Some(Ok(fuipolicy::DeviceListenerRegistryRequest::RegisterTouchButtonsListener {
451                listener,
452                responder,
453            })) => {
454                let _ = responder.send();
455                listener.into_proxy()
456            }
457            _ => {
458                panic!("Failed to get event");
459            }
460        };
461
462        (media_buttons_listener, touch_buttons_listener)
463    }
464
465    async fn start_touch_input_inspect_and_dimensions(
466        locked: &mut Locked<Unlocked>,
467        current_task: &CurrentTask,
468        x_max: i32,
469        y_max: i32,
470        inspector: &fuchsia_inspect::Inspector,
471    ) -> (InputDevice, FileHandle, fuipointer::TouchSourceRequestStream) {
472        let input_device = InputDevice::new_touch(x_max, y_max, inspector.root());
473        let input_file =
474            input_device.open_test(locked, current_task).expect("Failed to create input file");
475
476        let (touch_source_client_end, touch_source_stream) =
477            fidl::endpoints::create_request_stream::<TouchSourceMarker>();
478
479        let (mouse_source_client_end, _mouse_source_stream) =
480            fidl::endpoints::create_request_stream::<fuipointer::MouseSourceMarker>();
481
482        let (keyboard_proxy, mut keyboard_stream) =
483            fidl::endpoints::create_sync_proxy_and_stream::<fuiinput::KeyboardMarker>();
484        let view_ref_pair =
485            fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
486
487        let (device_registry_proxy, mut device_listener_stream) =
488            fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
489            );
490
491        let (relay, _relay_handle) = input_event_relay::new_input_relay();
492        relay.start_relays(
493            &current_task.kernel(),
494            EventProxyMode::None,
495            touch_source_client_end,
496            keyboard_proxy,
497            mouse_source_client_end,
498            view_ref_pair.view_ref,
499            device_registry_proxy,
500            input_device.open_files.clone(),
501            Default::default(),
502            Default::default(),
503            Some(input_device.inspect_status.clone()),
504            None,
505            None,
506        );
507
508        let _ = init_keyboard_listener(&mut keyboard_stream).await;
509        let _ = init_button_listeners(&mut device_listener_stream).await;
510
511        (input_device, input_file, touch_source_stream)
512    }
513
514    async fn start_keyboard_input(
515        locked: &mut Locked<Unlocked>,
516        current_task: &CurrentTask,
517    ) -> (InputDevice, FileHandle, fuiinput::KeyboardListenerProxy) {
518        let inspector = fuchsia_inspect::Inspector::default();
519        let input_device = InputDevice::new_keyboard(inspector.root());
520        let input_file =
521            input_device.open_test(locked, current_task).expect("Failed to create input file");
522        let (keyboard_proxy, mut keyboard_stream) =
523            fidl::endpoints::create_sync_proxy_and_stream::<fuiinput::KeyboardMarker>();
524        let view_ref_pair =
525            fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
526
527        let (device_registry_proxy, mut device_listener_stream) =
528            fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
529            );
530
531        let (touch_source_client_end, _touch_source_stream) =
532            fidl::endpoints::create_request_stream::<TouchSourceMarker>();
533
534        let (mouse_source_client_end, _mouse_source_stream) =
535            fidl::endpoints::create_request_stream::<fuipointer::MouseSourceMarker>();
536
537        let (relay, _relay_handle) = input_event_relay::new_input_relay();
538        relay.start_relays(
539            current_task.kernel(),
540            EventProxyMode::None,
541            touch_source_client_end,
542            keyboard_proxy,
543            mouse_source_client_end,
544            view_ref_pair.view_ref,
545            device_registry_proxy,
546            Default::default(),
547            input_device.open_files.clone(),
548            Default::default(),
549            None,
550            Some(input_device.inspect_status.clone()),
551            None,
552        );
553
554        let keyboad_listener = init_keyboard_listener(&mut keyboard_stream).await;
555        let _ = init_button_listeners(&mut device_listener_stream).await;
556
557        (input_device, input_file, keyboad_listener)
558    }
559
560    async fn start_button_input(
561        locked: &mut Locked<Unlocked>,
562        current_task: &CurrentTask,
563    ) -> (InputDevice, FileHandle, fuipolicy::MediaButtonsListenerProxy) {
564        let inspector = fuchsia_inspect::Inspector::default();
565        start_button_input_inspect(locked, current_task, &inspector).await
566    }
567
568    async fn start_button_input_inspect(
569        locked: &mut Locked<Unlocked>,
570        current_task: &CurrentTask,
571        inspector: &fuchsia_inspect::Inspector,
572    ) -> (InputDevice, FileHandle, fuipolicy::MediaButtonsListenerProxy) {
573        let input_device = InputDevice::new_keyboard(inspector.root());
574        let input_file =
575            input_device.open_test(locked, current_task).expect("Failed to create input file");
576        let (device_registry_proxy, mut device_listener_stream) =
577            fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
578            );
579
580        let (touch_source_client_end, _touch_source_stream) =
581            fidl::endpoints::create_request_stream::<TouchSourceMarker>();
582        let (mouse_source_client_end, _mouse_source_stream) =
583            fidl::endpoints::create_request_stream::<fuipointer::MouseSourceMarker>();
584        let (keyboard_proxy, mut keyboard_stream) =
585            fidl::endpoints::create_sync_proxy_and_stream::<fuiinput::KeyboardMarker>();
586        let view_ref_pair =
587            fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
588
589        let (relay, _relay_handle) = input_event_relay::new_input_relay();
590        relay.start_relays(
591            current_task.kernel(),
592            EventProxyMode::None,
593            touch_source_client_end,
594            keyboard_proxy,
595            mouse_source_client_end,
596            view_ref_pair.view_ref,
597            device_registry_proxy,
598            Default::default(),
599            input_device.open_files.clone(),
600            Default::default(),
601            None,
602            Some(input_device.inspect_status.clone()),
603            None,
604        );
605
606        let _ = init_keyboard_listener(&mut keyboard_stream).await;
607        let (button_listener, _) = init_button_listeners(&mut device_listener_stream).await;
608
609        (input_device, input_file, button_listener)
610    }
611
612    async fn start_mouse_input(
613        locked: &mut Locked<Unlocked>,
614        current_task: &CurrentTask,
615    ) -> (InputDevice, FileHandle, fuipointer::MouseSourceRequestStream) {
616        let inspector = fuchsia_inspect::Inspector::default();
617        start_mouse_input_inspect(locked, current_task, &inspector).await
618    }
619
620    async fn start_mouse_input_inspect(
621        locked: &mut Locked<Unlocked>,
622        current_task: &CurrentTask,
623        inspector: &fuchsia_inspect::Inspector,
624    ) -> (InputDevice, FileHandle, fuipointer::MouseSourceRequestStream) {
625        let input_device = InputDevice::new_mouse(inspector.root());
626        let input_file =
627            input_device.open_test(locked, current_task).expect("Failed to create input file");
628
629        let (touch_source_client_end, _touch_source_stream) =
630            fidl::endpoints::create_request_stream::<TouchSourceMarker>();
631
632        let (mouse_source_client_end, mouse_source_stream) =
633            fidl::endpoints::create_request_stream::<fuipointer::MouseSourceMarker>();
634
635        let (keyboard_proxy, mut keyboard_stream) =
636            fidl::endpoints::create_sync_proxy_and_stream::<fuiinput::KeyboardMarker>();
637        let view_ref_pair =
638            fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
639
640        let (device_registry_proxy, mut device_listener_stream) =
641            fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
642            );
643
644        let (relay, _relay_handle) = input_event_relay::new_input_relay();
645        relay.start_relays(
646            &current_task.kernel(),
647            EventProxyMode::None,
648            touch_source_client_end,
649            keyboard_proxy,
650            mouse_source_client_end,
651            view_ref_pair.view_ref,
652            device_registry_proxy,
653            Default::default(),
654            Default::default(),
655            input_device.open_files.clone(),
656            None,
657            None,
658            Some(input_device.inspect_status.clone()),
659        );
660
661        let _ = init_keyboard_listener(&mut keyboard_stream).await;
662        let _ = init_button_listeners(&mut device_listener_stream).await;
663
664        (input_device, input_file, mouse_source_stream)
665    }
666
667    fn make_touch_event(pointer_id: u32) -> fuipointer::TouchEvent {
668        // Default to `Change`, because that has the fewest side effects.
669        make_touch_event_with_phase(EventPhase::Change, pointer_id)
670    }
671
672    fn make_touch_event_with_phase(phase: EventPhase, pointer_id: u32) -> fuipointer::TouchEvent {
673        make_touch_event_with_coords_phase(0.0, 0.0, phase, pointer_id)
674    }
675
676    fn make_touch_event_with_coords_phase(
677        x: f32,
678        y: f32,
679        phase: EventPhase,
680        pointer_id: u32,
681    ) -> fuipointer::TouchEvent {
682        make_touch_event_with_coords_phase_timestamp(x, y, phase, pointer_id, 0)
683    }
684
685    fn make_touch_event_with_coords(x: f32, y: f32, pointer_id: u32) -> fuipointer::TouchEvent {
686        make_touch_event_with_coords_phase(x, y, EventPhase::Change, pointer_id)
687    }
688
689    fn make_touch_event_with_coords_phase_timestamp(
690        x: f32,
691        y: f32,
692        phase: EventPhase,
693        pointer_id: u32,
694        time_nanos: i64,
695    ) -> fuipointer::TouchEvent {
696        make_touch_event_with_coords_phase_timestamp_device_id(
697            x, y, phase, pointer_id, time_nanos, 0,
698        )
699    }
700
701    fn make_empty_touch_event() -> fuipointer::TouchEvent {
702        TouchEvent {
703            pointer_sample: Some(TouchPointerSample {
704                interaction: Some(TouchInteractionId {
705                    pointer_id: 0,
706                    device_id: 0,
707                    interaction_id: 0,
708                }),
709                ..Default::default()
710            }),
711            ..Default::default()
712        }
713    }
714
715    fn make_touch_event_with_coords_phase_timestamp_device_id(
716        x: f32,
717        y: f32,
718        phase: EventPhase,
719        pointer_id: u32,
720        time_nanos: i64,
721        device_id: u32,
722    ) -> fuipointer::TouchEvent {
723        TouchEvent {
724            timestamp: Some(time_nanos),
725            pointer_sample: Some(TouchPointerSample {
726                position_in_viewport: Some([x, y]),
727                // Default to `Change`, because that has the fewest side effects.
728                phase: Some(phase),
729                interaction: Some(TouchInteractionId { pointer_id, device_id, interaction_id: 0 }),
730                ..Default::default()
731            }),
732            ..Default::default()
733        }
734    }
735
736    fn make_mouse_wheel_event(ticks: i64) -> fuipointer::MouseEvent {
737        make_mouse_wheel_event_with_timestamp(ticks, 0)
738    }
739
740    fn make_mouse_wheel_event_with_timestamp(ticks: i64, timestamp: i64) -> fuipointer::MouseEvent {
741        fuipointer::MouseEvent {
742            timestamp: Some(timestamp),
743            pointer_sample: Some(fuipointer::MousePointerSample {
744                device_id: Some(0),
745                scroll_v: Some(ticks),
746                ..Default::default()
747            }),
748            ..Default::default()
749        }
750    }
751
752    fn read_uapi_events<L>(
753        locked: &mut Locked<L>,
754        file: &FileHandle,
755        current_task: &CurrentTask,
756    ) -> Vec<uapi::input_event>
757    where
758        L: LockEqualOrBefore<FileOpsCore>,
759    {
760        std::iter::from_fn(|| {
761            let locked = locked.cast_locked::<FileOpsCore>();
762            let mut event_bytes = VecOutputBuffer::new(INPUT_EVENT_SIZE);
763            match file.read(locked, current_task, &mut event_bytes) {
764                Ok(INPUT_EVENT_SIZE) => Some(
765                    uapi::input_event::read_from_bytes(Vec::from(event_bytes).as_slice())
766                        .map_err(|_| anyhow!("failed to read input_event from buffer")),
767                ),
768                Ok(other_size) => {
769                    Some(Err(anyhow!("got {} bytes (expected {})", other_size, INPUT_EVENT_SIZE)))
770                }
771                Err(Errno { code: EAGAIN, .. }) => None,
772                Err(other_error) => Some(Err(anyhow!("read failed: {:?}", other_error))),
773            }
774        })
775        .enumerate()
776        .map(|(i, read_res)| match read_res {
777            Ok(event) => event,
778            Err(e) => panic!("unexpected result {:?} on iteration {}", e, i),
779        })
780        .collect()
781    }
782
783    // Waits for a `Watch()` request to arrive on `request_stream`, and responds with
784    // `touch_event`. Returns the arguments to the `Watch()` call.
785    async fn answer_next_touch_watch_request(
786        request_stream: &mut fuipointer::TouchSourceRequestStream,
787        touch_events: Vec<TouchEvent>,
788    ) -> Vec<TouchResponse> {
789        match request_stream.next().await {
790            Some(Ok(TouchSourceRequest::Watch { responses, responder })) => {
791                responder.send(touch_events).expect("failure sending Watch reply");
792                responses
793            }
794            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
795        }
796    }
797
798    // Waits for a `Watch()` request to arrive on `request_stream`, and responds with
799    // `mouse_events`.
800    async fn answer_next_mouse_watch_request(
801        request_stream: &mut fuipointer::MouseSourceRequestStream,
802        mouse_events: Vec<fuipointer::MouseEvent>,
803    ) {
804        match request_stream.next().await {
805            Some(Ok(fuipointer::MouseSourceRequest::Watch { responder })) => {
806                responder.send(mouse_events).expect("failure sending Watch reply");
807            }
808            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
809        }
810    }
811
812    #[::fuchsia::test()]
813    async fn initial_watch_request_has_empty_responses_arg() {
814        #[allow(deprecated, reason = "pre-existing usage")]
815        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
816        // Set up resources.
817        let (_input_device, _input_file, mut touch_source_stream) =
818            start_touch_input(locked, &current_task).await;
819
820        // Verify that the watch request has empty `responses`.
821        assert_matches!(
822            touch_source_stream.next().await,
823            Some(Ok(TouchSourceRequest::Watch { responses, .. }))
824                => assert_eq!(responses.as_slice(), [])
825        );
826    }
827
828    #[::fuchsia::test]
829    async fn later_watch_requests_have_responses_arg_matching_earlier_watch_replies() {
830        // Set up resources.
831        #[allow(deprecated, reason = "pre-existing usage")]
832        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
833        let (_input_device, _input_file, mut touch_source_stream) =
834            start_touch_input(locked, &current_task).await;
835
836        // Reply to first `Watch` with two `TouchEvent`s.
837        match touch_source_stream.next().await {
838            Some(Ok(TouchSourceRequest::Watch { responder, .. })) => responder
839                .send(vec![make_empty_touch_event(), make_empty_touch_event()])
840                .expect("failure sending Watch reply"),
841            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
842        }
843
844        // Verify second `Watch` has two elements in `responses`.
845        // Then reply with five `TouchEvent`s.
846        match touch_source_stream.next().await {
847            Some(Ok(TouchSourceRequest::Watch { responses, responder })) => {
848                assert_matches!(responses.as_slice(), [_, _]);
849                responder
850                    .send(vec![
851                        make_empty_touch_event(),
852                        make_empty_touch_event(),
853                        make_empty_touch_event(),
854                        make_empty_touch_event(),
855                        make_empty_touch_event(),
856                    ])
857                    .expect("failure sending Watch reply")
858            }
859            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
860        }
861
862        // Verify third `Watch` has five elements in `responses`.
863        match touch_source_stream.next().await {
864            Some(Ok(TouchSourceRequest::Watch { responses, .. })) => {
865                assert_matches!(responses.as_slice(), [_, _, _, _, _]);
866            }
867            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
868        }
869    }
870
871    #[::fuchsia::test]
872    async fn notifies_polling_waiters_of_new_data() {
873        // Set up resources.
874        #[allow(deprecated, reason = "pre-existing usage")]
875        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
876        let (_input_device, input_file, mut touch_source_stream) =
877            start_touch_input(locked, &current_task).await;
878        let waiter1 = Waiter::new();
879        let waiter2 = Waiter::new();
880
881        // Ask `input_file` to notify waiters when data is available to read.
882        [&waiter1, &waiter2].iter().for_each(|waiter| {
883            input_file.wait_async(
884                locked,
885                &current_task,
886                waiter,
887                FdEvents::POLLIN,
888                EventHandler::None,
889            );
890        });
891        assert_matches!(
892            waiter1.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
893            Err(_)
894        );
895        assert_matches!(
896            waiter2.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
897            Err(_)
898        );
899
900        // Reply to first `Watch` request.
901        answer_next_touch_watch_request(
902            &mut touch_source_stream,
903            vec![make_touch_event_with_phase(EventPhase::Add, 1)],
904        )
905        .await;
906        answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)]).await;
907
908        // `InputFile` should be done processing the first reply, since it has sent its second
909        // request. And, as part of processing the first reply, `InputFile` should have notified
910        // the interested waiters.
911        assert_eq!(waiter1.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO), Ok(()));
912        assert_eq!(waiter2.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO), Ok(()));
913    }
914
915    #[::fuchsia::test]
916    async fn notifies_blocked_waiter_of_new_data() {
917        // Set up resources.
918        #[allow(deprecated, reason = "pre-existing usage")]
919        let (kernel, current_task, locked) = create_kernel_task_and_unlocked();
920        let (_input_device, input_file, mut touch_source_stream) =
921            start_touch_input(locked, &current_task).await;
922        let waiter = Waiter::new();
923
924        // Ask `input_file` to notify `waiter` when data is available to read.
925        input_file.wait_async(locked, &current_task, &waiter, FdEvents::POLLIN, EventHandler::None);
926
927        let closure =
928            move |locked: &mut Locked<Unlocked>, task: &CurrentTask| waiter.wait(locked, &task);
929
930        let (waiter_thread, req) = SpawnRequestBuilder::new()
931            .with_debug_name("input-device-waiter")
932            .with_sync_closure(closure)
933            .build_with_async_result();
934        kernel.kthreads.spawner().spawn_from_request(req);
935
936        let mut waiter_thread = Box::pin(waiter_thread);
937        assert_matches!(futures::poll!(&mut waiter_thread), futures::task::Poll::Pending);
938
939        // Reply to first `Watch` request.
940        answer_next_touch_watch_request(
941            &mut touch_source_stream,
942            vec![make_touch_event_with_phase(EventPhase::Add, 1)],
943        )
944        .await;
945
946        // Wait for another `Watch`.
947        //
948        // TODO(https://fxbug.dev/42075452): Without this, `relay_thread` gets stuck `await`-ing
949        // the reply to its first request. Figure out why that happens, and remove this second
950        // reply.
951        answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)]).await;
952    }
953
954    #[::fuchsia::test]
955    async fn does_not_notify_polling_waiters_without_new_data() {
956        // Set up resources.
957        #[allow(deprecated, reason = "pre-existing usage")]
958        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
959        let (_input_device, input_file, mut touch_source_stream) =
960            start_touch_input(locked, &current_task).await;
961        let waiter1 = Waiter::new();
962        let waiter2 = Waiter::new();
963
964        // Ask `input_file` to notify waiters when data is available to read.
965        [&waiter1, &waiter2].iter().for_each(|waiter| {
966            input_file.wait_async(
967                locked,
968                &current_task,
969                waiter,
970                FdEvents::POLLIN,
971                EventHandler::None,
972            );
973        });
974        assert_matches!(
975            waiter1.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
976            Err(_)
977        );
978        assert_matches!(
979            waiter2.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
980            Err(_)
981        );
982
983        // Reply to first `Watch` request with an empty set of events.
984        answer_next_touch_watch_request(&mut touch_source_stream, vec![]).await;
985
986        // `InputFile` should be done processing the first reply. Since there
987        // were no touch_events given, `InputFile` should not have notified the
988        // interested waiters.
989        assert_matches!(
990            waiter1.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
991            Err(_)
992        );
993        assert_matches!(
994            waiter2.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
995            Err(_)
996        );
997    }
998
999    // Note: a user program may also want to be woken if events were already ready at the
1000    // time that the program called `epoll_wait()`. However, there's no test for that case
1001    // in this module, because:
1002    //
1003    // 1. Not all programs will want to be woken in such a case. In particular, some programs
1004    //    use "edge-triggered" mode instead of "level-tiggered" mode. For details on the
1005    //    two modes, see https://man7.org/linux/man-pages/man7/epoll.7.html.
1006    // 2. For programs using "level-triggered" mode, the relevant behavior is implemented in
1007    //    the `epoll` module, and verified by `epoll::tests::test_epoll_ready_then_wait()`.
1008    //
1009    // See also: the documentation for `FileOps::wait_async()`.
1010
1011    #[::fuchsia::test]
1012    async fn honors_wait_cancellation() {
1013        // Set up input resources.
1014        #[allow(deprecated, reason = "pre-existing usage")]
1015        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1016        let (_input_device, input_file, mut touch_source_stream) =
1017            start_touch_input(locked, &current_task).await;
1018        let waiter1 = Waiter::new();
1019        let waiter2 = Waiter::new();
1020
1021        // Ask `input_file` to notify `waiter` when data is available to read.
1022        let waitkeys = [&waiter1, &waiter2]
1023            .iter()
1024            .map(|waiter| {
1025                input_file
1026                    .wait_async(locked, &current_task, waiter, FdEvents::POLLIN, EventHandler::None)
1027                    .expect("wait_async")
1028            })
1029            .collect::<Vec<_>>();
1030
1031        // Cancel wait for `waiter1`.
1032        waitkeys.into_iter().next().expect("failed to get first waitkey").cancel();
1033
1034        // Reply to first `Watch` request.
1035        answer_next_touch_watch_request(
1036            &mut touch_source_stream,
1037            vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1038        )
1039        .await;
1040        // Wait for another `Watch`.
1041        answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)]).await;
1042
1043        // `InputFile` should be done processing the first reply, since it has sent its second
1044        // request. And, as part of processing the first reply, `InputFile` should have notified
1045        // the interested waiters.
1046        assert_matches!(
1047            waiter1.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO),
1048            Err(_)
1049        );
1050        assert_eq!(waiter2.wait_until(locked, &current_task, zx::MonotonicInstant::ZERO), Ok(()));
1051    }
1052
1053    #[::fuchsia::test]
1054    async fn query_events() {
1055        // Set up resources.
1056        #[allow(deprecated, reason = "pre-existing usage")]
1057        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1058        let (_input_device, input_file, mut touch_source_stream) =
1059            start_touch_input(locked, &current_task).await;
1060
1061        // Check initial expectation.
1062        assert_eq!(
1063            input_file.query_events(locked, &current_task).expect("query_events"),
1064            FdEvents::empty(),
1065            "events should be empty before data arrives"
1066        );
1067
1068        // Reply to first `Watch` request.
1069        answer_next_touch_watch_request(
1070            &mut touch_source_stream,
1071            vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1072        )
1073        .await;
1074
1075        // Wait for another `Watch`.
1076        answer_next_touch_watch_request(&mut touch_source_stream, vec![make_touch_event(1)]).await;
1077
1078        // Check post-watch expectation.
1079        assert_eq!(
1080            input_file.query_events(locked, &current_task).expect("query_events"),
1081            FdEvents::POLLIN | FdEvents::POLLRDNORM,
1082            "events should be POLLIN after data arrives"
1083        );
1084    }
1085
1086    fn make_uapi_input_event(ty: u32, code: u32, value: i32) -> uapi::input_event {
1087        make_uapi_input_event_with_timestamp(ty, code, value, 0)
1088    }
1089
1090    fn make_uapi_input_event_with_timestamp(
1091        ty: u32,
1092        code: u32,
1093        value: i32,
1094        time_nanos: i64,
1095    ) -> uapi::input_event {
1096        uapi::input_event {
1097            time: timeval_from_time(zx::MonotonicInstant::from_nanos(time_nanos)),
1098            type_: ty as u16,
1099            code: code as u16,
1100            value,
1101        }
1102    }
1103
1104    #[::fuchsia::test]
1105    async fn touch_event_ignored() {
1106        // Set up resources.
1107        let inspector = fuchsia_inspect::Inspector::default();
1108        #[allow(deprecated, reason = "pre-existing usage")]
1109        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1110        let (_input_device, input_file, mut touch_source_stream) =
1111            start_touch_input_inspect(locked, &current_task, &inspector).await;
1112
1113        // Touch add for pointer 1. This should be counted as a received event and a converted
1114        // event. It should also yield 6 generated events.
1115        answer_next_touch_watch_request(
1116            &mut touch_source_stream,
1117            vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1118        )
1119        .await;
1120
1121        // Wait for another `Watch` to ensure input_file done processing the first reply.
1122        // Use an empty `TouchEvent`, to minimize the chance that this event creates unexpected
1123        // `uapi::input_event`s. This should be counted as a received event and an ignored event.
1124        answer_next_touch_watch_request(&mut touch_source_stream, vec![make_empty_touch_event()])
1125            .await;
1126
1127        // Consume all of the `uapi::input_event`s that are available.
1128        let events = read_uapi_events(locked, &input_file, &current_task);
1129
1130        assert_eq!(events.len(), 6);
1131
1132        // Reply to `Watch` request of empty event. This should be counted as a received event and
1133        // an ignored event.
1134        answer_next_touch_watch_request(&mut touch_source_stream, vec![make_empty_touch_event()])
1135            .await;
1136
1137        // Wait for another `Watch`.
1138        match touch_source_stream.next().await {
1139            Some(Ok(TouchSourceRequest::Watch { responses, .. })) => {
1140                assert_matches!(responses.as_slice(), [_])
1141            }
1142            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
1143        }
1144
1145        let events = read_uapi_events(locked, &input_file, &current_task);
1146        assert_eq!(events, vec![]);
1147        assert_data_tree!(inspector, root: {
1148            touch_device: {
1149                total_fidl_events_received_count: 3u64,
1150                total_fidl_events_ignored_count: 2u64,
1151                total_fidl_events_unexpected_count: 0u64,
1152                total_fidl_events_converted_count: 1u64,
1153                total_uapi_events_generated_count: 6u64,
1154                last_generated_uapi_event_timestamp_ns: 0i64,
1155                touch_file_0: {
1156                    fidl_events_received_count: 3u64,
1157                    fidl_events_ignored_count: 2u64,
1158                    fidl_events_unexpected_count: 0u64,
1159                    fidl_events_converted_count: 1u64,
1160                    uapi_events_generated_count: 6u64,
1161                    uapi_events_read_count: 6u64,
1162                    last_generated_uapi_event_timestamp_ns: 0i64,
1163                    last_read_uapi_event_timestamp_ns: 0i64,
1164                },
1165            }
1166        });
1167    }
1168
1169    #[test_case(make_touch_event_with_phase(EventPhase::Add, 1); "touch add for pointer already added")]
1170    #[test_case(make_touch_event_with_phase(EventPhase::Change, 2); "touch change for pointer not added")]
1171    #[test_case(make_touch_event_with_phase(EventPhase::Remove, 2); "touch remove for pointer not added")]
1172    #[test_case(make_touch_event_with_phase(EventPhase::Cancel, 1); "touch cancel")]
1173    #[::fuchsia::test]
1174    async fn touch_event_unexpected(event: TouchEvent) {
1175        // Set up resources.
1176        let inspector = fuchsia_inspect::Inspector::default();
1177        #[allow(deprecated, reason = "pre-existing usage")]
1178        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1179        let (_input_device, input_file, mut touch_source_stream) =
1180            start_touch_input_inspect(locked, &current_task, &inspector).await;
1181
1182        // Touch add for pointer 1. This should be counted as a received event and a converted
1183        // event. It should also yield 6 generated events.
1184        answer_next_touch_watch_request(
1185            &mut touch_source_stream,
1186            vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1187        )
1188        .await;
1189
1190        // Wait for another `Watch` to ensure input_file done processing the first reply.
1191        // Use an empty `TouchEvent`, to minimize the chance that this event creates unexpected
1192        // `uapi::input_event`s. This should be counted as a received event and an ignored event.
1193        answer_next_touch_watch_request(&mut touch_source_stream, vec![make_empty_touch_event()])
1194            .await;
1195
1196        // Consume all of the `uapi::input_event`s that are available.
1197        let events = read_uapi_events(locked, &input_file, &current_task);
1198
1199        assert_eq!(events.len(), 6);
1200
1201        // Reply to `Watch` request of given event. This should be counted as a received event and
1202        // an unexpected event.
1203        answer_next_touch_watch_request(&mut touch_source_stream, vec![event]).await;
1204
1205        // Wait for another `Watch`.
1206        match touch_source_stream.next().await {
1207            Some(Ok(TouchSourceRequest::Watch { responses, .. })) => {
1208                assert_matches!(responses.as_slice(), [_])
1209            }
1210            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
1211        }
1212
1213        let events = read_uapi_events(locked, &input_file, &current_task);
1214        assert_eq!(events, vec![]);
1215        assert_data_tree!(inspector, root: {
1216            touch_device: {
1217                total_fidl_events_received_count: 3u64,
1218                total_fidl_events_ignored_count: 1u64,
1219                total_fidl_events_unexpected_count: 1u64,
1220                total_fidl_events_converted_count: 1u64,
1221                total_uapi_events_generated_count: 6u64,
1222                last_generated_uapi_event_timestamp_ns: 0i64,
1223                touch_file_0: {
1224                    fidl_events_received_count: 3u64,
1225                    fidl_events_ignored_count: 1u64,
1226                    fidl_events_unexpected_count: 1u64,
1227                    fidl_events_converted_count: 1u64,
1228                    uapi_events_generated_count: 6u64,
1229                    uapi_events_read_count: 6u64,
1230                    last_generated_uapi_event_timestamp_ns: 0i64,
1231                    last_read_uapi_event_timestamp_ns: 0i64,
1232                },
1233            }
1234        });
1235    }
1236
1237    #[::fuchsia::test]
1238    async fn translates_touch_add() {
1239        // Set up resources.
1240        #[allow(deprecated, reason = "pre-existing usage")]
1241        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1242        let (_input_device, input_file, mut touch_source_stream) =
1243            start_touch_input(locked, &current_task).await;
1244
1245        // Touch add for pointer 1.
1246        answer_next_touch_watch_request(
1247            &mut touch_source_stream,
1248            vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1249        )
1250        .await;
1251
1252        // Wait for another `Watch` to ensure input_file done processing the first reply.
1253        // Use an empty `TouchEvent`, to minimize the chance that this event
1254        // creates unexpected `uapi::input_event`s.
1255        answer_next_touch_watch_request(&mut touch_source_stream, vec![make_empty_touch_event()])
1256            .await;
1257
1258        // Consume all of the `uapi::input_event`s that are available.
1259        let events = read_uapi_events(locked, &input_file, &current_task);
1260
1261        assert_eq!(
1262            events,
1263            vec![
1264                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1265                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 1),
1266                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 0),
1267                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 0),
1268                make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 1),
1269                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1270            ]
1271        );
1272    }
1273
1274    #[::fuchsia::test]
1275    async fn translates_touch_change() {
1276        // Set up resources.
1277        #[allow(deprecated, reason = "pre-existing usage")]
1278        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1279        let (_input_device, input_file, mut touch_source_stream) =
1280            start_touch_input(locked, &current_task).await;
1281
1282        // Touch add for pointer 1.
1283        answer_next_touch_watch_request(
1284            &mut touch_source_stream,
1285            vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1286        )
1287        .await;
1288
1289        // Wait for another `Watch` to ensure input_file done processing the first reply.
1290        // Use an empty `TouchEvent`, to minimize the chance that this event
1291        // creates unexpected `uapi::input_event`s.
1292        answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1293            .await;
1294
1295        // Consume all of the `uapi::input_event`s that are available.
1296        let events = read_uapi_events(locked, &input_file, &current_task);
1297
1298        assert_eq!(events.len(), 6);
1299
1300        // Reply to touch change.
1301        answer_next_touch_watch_request(
1302            &mut touch_source_stream,
1303            vec![make_touch_event_with_coords(10.0, 20.0, 1)],
1304        )
1305        .await;
1306
1307        // Wait for another `Watch`.
1308        answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1309            .await;
1310
1311        let events = read_uapi_events(locked, &input_file, &current_task);
1312        assert_eq!(
1313            events,
1314            vec![
1315                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1316                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 10),
1317                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 20),
1318                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1319            ]
1320        );
1321    }
1322
1323    #[::fuchsia::test]
1324    async fn translates_touch_remove() {
1325        // Set up resources.
1326        #[allow(deprecated, reason = "pre-existing usage")]
1327        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1328        let (_input_device, input_file, mut touch_source_stream) =
1329            start_touch_input(locked, &current_task).await;
1330
1331        // Touch add for pointer 1.
1332        answer_next_touch_watch_request(
1333            &mut touch_source_stream,
1334            vec![make_touch_event_with_phase(EventPhase::Add, 1)],
1335        )
1336        .await;
1337
1338        // Wait for another `Watch` to ensure input_file done processing the first reply.
1339        // Use an empty `TouchEvent`, to minimize the chance that this event
1340        // creates unexpected `uapi::input_event`s.
1341        answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1342            .await;
1343
1344        // Consume all of the `uapi::input_event`s that are available.
1345        let events = read_uapi_events(locked, &input_file, &current_task);
1346
1347        assert_eq!(events.len(), 6);
1348
1349        // Reply to touch change.
1350        answer_next_touch_watch_request(
1351            &mut touch_source_stream,
1352            vec![make_touch_event_with_phase(EventPhase::Remove, 1)],
1353        )
1354        .await;
1355
1356        // Wait for another `Watch`.
1357        answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1358            .await;
1359
1360        let events = read_uapi_events(locked, &input_file, &current_task);
1361        assert_eq!(
1362            events,
1363            vec![
1364                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1365                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
1366                make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 0),
1367                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1368            ]
1369        );
1370    }
1371
1372    #[::fuchsia::test]
1373    async fn multi_touch_event_sequence() {
1374        // Set up resources.
1375        #[allow(deprecated, reason = "pre-existing usage")]
1376        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1377        let (_input_device, input_file, mut touch_source_stream) =
1378            start_touch_input(locked, &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`.
1388        answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1389            .await;
1390        let events = read_uapi_events(locked, &input_file, &current_task);
1391
1392        assert_eq!(events.len(), 6);
1393
1394        // Touch add for pointer 2.
1395        answer_next_touch_watch_request(
1396            &mut touch_source_stream,
1397            vec![
1398                make_touch_event_with_coords(10.0, 20.0, 1),
1399                make_touch_event_with_phase(EventPhase::Add, 2),
1400            ],
1401        )
1402        .await;
1403
1404        // Wait for another `Watch`.
1405        answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1406            .await;
1407        let events = read_uapi_events(locked, &input_file, &current_task);
1408
1409        assert_eq!(
1410            events,
1411            vec![
1412                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1413                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 10),
1414                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 20),
1415                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
1416                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 2),
1417                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 0),
1418                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 0),
1419                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1420            ]
1421        );
1422
1423        // Both pointers move.
1424        answer_next_touch_watch_request(
1425            &mut touch_source_stream,
1426            vec![
1427                make_touch_event_with_coords(11.0, 21.0, 1),
1428                make_touch_event_with_coords(101.0, 201.0, 2),
1429            ],
1430        )
1431        .await;
1432
1433        // Wait for another `Watch`.
1434        answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1435            .await;
1436        let events = read_uapi_events(locked, &input_file, &current_task);
1437
1438        assert_eq!(
1439            events,
1440            vec![
1441                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1442                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 11),
1443                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 21),
1444                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
1445                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 101),
1446                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 201),
1447                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1448            ]
1449        );
1450
1451        // Pointer 1 up.
1452        answer_next_touch_watch_request(
1453            &mut touch_source_stream,
1454            vec![
1455                make_touch_event_with_phase(EventPhase::Remove, 1),
1456                make_touch_event_with_coords(102.0, 202.0, 2),
1457            ],
1458        )
1459        .await;
1460
1461        // Wait for another `Watch`.
1462        answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1463            .await;
1464        let events = read_uapi_events(locked, &input_file, &current_task);
1465
1466        assert_eq!(
1467            events,
1468            vec![
1469                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
1470                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
1471                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
1472                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 102),
1473                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 202),
1474                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1475            ]
1476        );
1477
1478        // Pointer 2 up.
1479        answer_next_touch_watch_request(
1480            &mut touch_source_stream,
1481            vec![make_touch_event_with_phase(EventPhase::Remove, 2)],
1482        )
1483        .await;
1484
1485        // Wait for another `Watch`.
1486        answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1487            .await;
1488        let events = read_uapi_events(locked, &input_file, &current_task);
1489
1490        assert_eq!(
1491            events,
1492            vec![
1493                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
1494                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
1495                make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 0),
1496                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
1497            ]
1498        );
1499    }
1500
1501    #[::fuchsia::test]
1502    async fn multi_event_sequence_unsorted_in_one_watch() {
1503        // Set up resources.
1504        #[allow(deprecated, reason = "pre-existing usage")]
1505        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1506        let (_input_device, input_file, mut touch_source_stream) =
1507            start_touch_input(locked, &current_task).await;
1508
1509        // Touch add for pointer 1.
1510        answer_next_touch_watch_request(
1511            &mut touch_source_stream,
1512            vec![
1513                make_touch_event_with_coords_phase_timestamp(
1514                    10.0,
1515                    20.0,
1516                    EventPhase::Change,
1517                    1,
1518                    100,
1519                ),
1520                make_touch_event_with_coords_phase_timestamp(0.0, 0.0, EventPhase::Add, 1, 1),
1521            ],
1522        )
1523        .await;
1524
1525        // Wait for another `Watch`.
1526        answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1527            .await;
1528        let events = read_uapi_events(locked, &input_file, &current_task);
1529
1530        assert_eq!(
1531            events,
1532            vec![
1533                make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0, 1),
1534                make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 1, 1),
1535                make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 0, 1),
1536                make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 0, 1),
1537                make_uapi_input_event_with_timestamp(uapi::EV_KEY, uapi::BTN_TOUCH, 1, 1),
1538                make_uapi_input_event_with_timestamp(uapi::EV_SYN, uapi::SYN_REPORT, 0, 1),
1539                make_uapi_input_event_with_timestamp(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0, 100),
1540                make_uapi_input_event_with_timestamp(
1541                    uapi::EV_ABS,
1542                    uapi::ABS_MT_POSITION_X,
1543                    10,
1544                    100
1545                ),
1546                make_uapi_input_event_with_timestamp(
1547                    uapi::EV_ABS,
1548                    uapi::ABS_MT_POSITION_Y,
1549                    20,
1550                    100
1551                ),
1552                make_uapi_input_event_with_timestamp(uapi::EV_SYN, uapi::SYN_REPORT, 0, 100),
1553            ]
1554        );
1555    }
1556
1557    #[test_case((0.0, 0.0); "origin")]
1558    #[test_case((100.7, 200.7); "above midpoint")]
1559    #[test_case((100.3, 200.3); "below midpoint")]
1560    #[test_case((100.5, 200.5); "midpoint")]
1561    #[::fuchsia::test]
1562    async fn sends_acceptable_coordinates((x, y): (f32, f32)) {
1563        // Set up resources.
1564        #[allow(deprecated, reason = "pre-existing usage")]
1565        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1566        let (_input_device, input_file, mut touch_source_stream) =
1567            start_touch_input(locked, &current_task).await;
1568
1569        // Touch add.
1570        answer_next_touch_watch_request(
1571            &mut touch_source_stream,
1572            vec![make_touch_event_with_coords_phase(x, y, EventPhase::Add, 1)],
1573        )
1574        .await;
1575
1576        // Wait for another `Watch`.
1577        answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1578            .await;
1579        let events = read_uapi_events(locked, &input_file, &current_task);
1580
1581        // Check that the reported positions are within the acceptable error. The acceptable
1582        // error is chosen to allow either rounding or truncation.
1583        const ACCEPTABLE_ERROR: f32 = 1.0;
1584        let actual_x = events
1585            .iter()
1586            .find(|event| {
1587                event.type_ == uapi::EV_ABS as u16 && event.code == uapi::ABS_MT_POSITION_X as u16
1588            })
1589            .unwrap_or_else(|| panic!("did not find `ABS_X` event in {:?}", events))
1590            .value;
1591        let actual_y = events
1592            .iter()
1593            .find(|event| {
1594                event.type_ == uapi::EV_ABS as u16 && event.code == uapi::ABS_MT_POSITION_Y as u16
1595            })
1596            .unwrap_or_else(|| panic!("did not find `ABS_Y` event in {:?}", events))
1597            .value;
1598        assert_near!(x, actual_x as f32, ACCEPTABLE_ERROR);
1599        assert_near!(y, actual_y as f32, ACCEPTABLE_ERROR);
1600    }
1601
1602    // Per the FIDL documentation for `TouchSource::Watch()`:
1603    //
1604    // > non-sample events should return an empty |TouchResponse| table to the
1605    // > server
1606    #[test_case(
1607        make_touch_event_with_phase(EventPhase::Add, 2)
1608            => matches Some(TouchResponse { response_type: Some(_), ..});
1609        "event_with_sample_yields_some_response_type")]
1610    #[test_case(
1611        TouchEvent::default() => matches Some(TouchResponse { response_type: None, ..});
1612        "event_without_sample_yields_no_response_type")]
1613    #[::fuchsia::test]
1614    async fn sends_appropriate_reply_to_touch_source_server(
1615        event: TouchEvent,
1616    ) -> Option<TouchResponse> {
1617        // Set up resources.
1618        #[allow(deprecated, reason = "pre-existing usage")]
1619        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1620        let (_input_device, _input_file, mut touch_source_stream) =
1621            start_touch_input(locked, &current_task).await;
1622
1623        // Reply to first `Watch` request.
1624        answer_next_touch_watch_request(&mut touch_source_stream, vec![event]).await;
1625
1626        // Get response to `event`.
1627        let responses =
1628            answer_next_touch_watch_request(&mut touch_source_stream, vec![TouchEvent::default()])
1629                .await;
1630
1631        // Return the value for `test_case` to match on.
1632        responses.get(0).cloned()
1633    }
1634
1635    #[test_case(fidl_fuchsia_input::Key::Escape, uapi::KEY_POWER; "Esc maps to Power")]
1636    #[test_case(fidl_fuchsia_input::Key::A, uapi::KEY_A; "A maps to A")]
1637    #[::fuchsia::test]
1638    async fn sends_keyboard_events(fkey: fidl_fuchsia_input::Key, lkey: u32) {
1639        #[allow(deprecated, reason = "pre-existing usage")]
1640        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1641        let (_keyboard_device, keyboard_file, keyboard_listener) =
1642            start_keyboard_input(locked, &current_task).await;
1643
1644        let key_event = fuiinput::KeyEvent {
1645            timestamp: Some(0),
1646            type_: Some(fuiinput::KeyEventType::Pressed),
1647            key: Some(fkey),
1648            ..Default::default()
1649        };
1650
1651        let _ = keyboard_listener.on_key_event(&key_event).await;
1652        std::mem::drop(keyboard_listener); // Close Zircon channel.
1653        let events = read_uapi_events(locked, &keyboard_file, &current_task);
1654        assert_eq!(events.len(), 2);
1655        assert_eq!(events[0].code, lkey as u16);
1656    }
1657
1658    #[::fuchsia::test]
1659    async fn skips_unknown_keyboard_events() {
1660        #[allow(deprecated, reason = "pre-existing usage")]
1661        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1662        let (_keyboard_device, keyboard_file, keyboard_listener) =
1663            start_keyboard_input(locked, &current_task).await;
1664
1665        let key_event = fuiinput::KeyEvent {
1666            timestamp: Some(0),
1667            type_: Some(fuiinput::KeyEventType::Pressed),
1668            key: Some(fidl_fuchsia_input::Key::AcRefresh),
1669            ..Default::default()
1670        };
1671
1672        let _ = keyboard_listener.on_key_event(&key_event).await;
1673        std::mem::drop(keyboard_listener); // Close Zircon channel.
1674        let events = read_uapi_events(locked, &keyboard_file, &current_task);
1675        assert_eq!(events.len(), 0);
1676    }
1677
1678    #[::fuchsia::test]
1679    async fn sends_power_button_events() {
1680        #[allow(deprecated, reason = "pre-existing usage")]
1681        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1682        let (_input_device, input_file, buttons_listener) =
1683            start_button_input(locked, &current_task).await;
1684
1685        let power_event = MediaButtonsEvent {
1686            volume: Some(0),
1687            mic_mute: Some(false),
1688            pause: Some(false),
1689            camera_disable: Some(false),
1690            power: Some(true),
1691            function: Some(false),
1692            ..Default::default()
1693        };
1694
1695        let _ = buttons_listener.on_event(power_event).await;
1696        std::mem::drop(buttons_listener); // Close Zircon channel.
1697
1698        let events = read_uapi_events(locked, &input_file, &current_task);
1699        assert_eq!(events.len(), 2);
1700        assert_eq!(events[0].code, uapi::KEY_POWER as u16);
1701        assert_eq!(events[0].value, 1);
1702    }
1703
1704    #[::fuchsia::test]
1705    async fn sends_function_button_events() {
1706        #[allow(deprecated, reason = "pre-existing usage")]
1707        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1708        let (_input_device, input_file, buttons_listener) =
1709            start_button_input(locked, &current_task).await;
1710
1711        let function_event = MediaButtonsEvent {
1712            volume: Some(0),
1713            mic_mute: Some(false),
1714            pause: Some(false),
1715            camera_disable: Some(false),
1716            power: Some(false),
1717            function: Some(true),
1718            ..Default::default()
1719        };
1720
1721        let _ = buttons_listener.on_event(function_event).await;
1722        std::mem::drop(buttons_listener); // Close Zircon channel.
1723
1724        let events = read_uapi_events(locked, &input_file, &current_task);
1725        assert_eq!(events.len(), 2);
1726        assert_eq!(events[0].code, uapi::KEY_VOLUMEDOWN as u16);
1727        assert_eq!(events[0].value, 1);
1728    }
1729
1730    #[::fuchsia::test]
1731    async fn sends_overlapping_button_events() {
1732        #[allow(deprecated, reason = "pre-existing usage")]
1733        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1734        let (_input_device, input_file, buttons_listener) =
1735            start_button_input(locked, &current_task).await;
1736
1737        let power_event = MediaButtonsEvent {
1738            volume: Some(0),
1739            mic_mute: Some(false),
1740            pause: Some(false),
1741            camera_disable: Some(false),
1742            power: Some(true),
1743            function: Some(false),
1744            ..Default::default()
1745        };
1746
1747        let function_event = MediaButtonsEvent {
1748            volume: Some(0),
1749            mic_mute: Some(false),
1750            pause: Some(false),
1751            camera_disable: Some(false),
1752            power: Some(true),
1753            function: Some(true),
1754            ..Default::default()
1755        };
1756
1757        let function_release_event = MediaButtonsEvent {
1758            volume: Some(0),
1759            mic_mute: Some(false),
1760            pause: Some(false),
1761            camera_disable: Some(false),
1762            power: Some(true),
1763            function: Some(false),
1764            ..Default::default()
1765        };
1766
1767        let power_release_event = MediaButtonsEvent {
1768            volume: Some(0),
1769            mic_mute: Some(false),
1770            pause: Some(false),
1771            camera_disable: Some(false),
1772            power: Some(false),
1773            function: Some(false),
1774            ..Default::default()
1775        };
1776
1777        let _ = buttons_listener.on_event(power_event).await;
1778        let _ = buttons_listener.on_event(function_event).await;
1779        let _ = buttons_listener.on_event(function_release_event).await;
1780        let _ = buttons_listener.on_event(power_release_event).await;
1781        std::mem::drop(buttons_listener); // Close Zircon channel.
1782
1783        let events = read_uapi_events(locked, &input_file, &current_task);
1784        assert_eq!(events.len(), 8);
1785        assert_eq!(events[0].code, uapi::KEY_POWER as u16);
1786        assert_eq!(events[0].value, 1);
1787        assert_eq!(events[2].code, uapi::KEY_VOLUMEDOWN as u16);
1788        assert_eq!(events[2].value, 1);
1789        assert_eq!(events[4].code, uapi::KEY_VOLUMEDOWN as u16);
1790        assert_eq!(events[4].value, 0);
1791        assert_eq!(events[6].code, uapi::KEY_POWER as u16);
1792        assert_eq!(events[6].value, 0);
1793    }
1794
1795    #[::fuchsia::test]
1796    async fn sends_simultaneous_button_events() {
1797        #[allow(deprecated, reason = "pre-existing usage")]
1798        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1799        let (_input_device, input_file, buttons_listener) =
1800            start_button_input(locked, &current_task).await;
1801
1802        let power_and_function_event = MediaButtonsEvent {
1803            volume: Some(0),
1804            mic_mute: Some(false),
1805            pause: Some(false),
1806            camera_disable: Some(false),
1807            power: Some(true),
1808            function: Some(true),
1809            ..Default::default()
1810        };
1811
1812        let _ = buttons_listener.on_event(power_and_function_event).await;
1813        std::mem::drop(buttons_listener); // Close Zircon channel.
1814
1815        let events = read_uapi_events(locked, &input_file, &current_task);
1816        assert_eq!(events.len(), 4);
1817        assert_eq!(events[0].code, uapi::KEY_POWER as u16);
1818        assert_eq!(events[0].value, 1);
1819        assert_eq!(events[2].code, uapi::KEY_VOLUMEDOWN as u16);
1820        assert_eq!(events[2].value, 1);
1821    }
1822
1823    #[test_case(1; "Scroll up")]
1824    #[test_case(-1; "Scroll down")]
1825    #[::fuchsia::test]
1826    async fn sends_mouse_wheel_events(ticks: i64) {
1827        let time = 100;
1828        let uapi_event = uapi::input_event {
1829            time: timeval_from_time(zx::MonotonicInstant::from_nanos(time)),
1830            type_: uapi::EV_REL as u16,
1831            code: uapi::REL_WHEEL as u16,
1832            value: ticks as i32,
1833        };
1834        #[allow(deprecated, reason = "pre-existing usage")]
1835        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1836        let (_mouse_device, mouse_file, mut mouse_stream) =
1837            start_mouse_input(locked, &current_task).await;
1838
1839        answer_next_mouse_watch_request(
1840            &mut mouse_stream,
1841            vec![make_mouse_wheel_event_with_timestamp(ticks, time)],
1842        )
1843        .await;
1844
1845        // Wait for another `Watch` to ensure mouse_file is done processing the other replies.
1846        // Use an empty vec, to ensure no unexpected `uapi::input_event`s are created.
1847        answer_next_mouse_watch_request(&mut mouse_stream, vec![]).await;
1848
1849        let events = read_uapi_events(locked, &mouse_file, &current_task);
1850        assert_eq!(events.len(), 2);
1851        assert_eq!(events[0], uapi_event);
1852    }
1853
1854    #[::fuchsia::test]
1855    async fn ignore_mouse_non_wheel_events() {
1856        let mouse_move_event = fuipointer::MouseEvent {
1857            timestamp: Some(0),
1858            pointer_sample: Some(fuipointer::MousePointerSample {
1859                device_id: Some(0),
1860                position_in_viewport: Some([50.0, 50.0]),
1861                ..Default::default()
1862            }),
1863            ..Default::default()
1864        };
1865        let mouse_click_event = fuipointer::MouseEvent {
1866            timestamp: Some(0),
1867            pointer_sample: Some(fuipointer::MousePointerSample {
1868                device_id: Some(0),
1869                pressed_buttons: Some(vec![1]),
1870                ..Default::default()
1871            }),
1872            ..Default::default()
1873        };
1874        #[allow(deprecated, reason = "pre-existing usage")]
1875        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1876        let (_mouse_device, mouse_file, mut mouse_stream) =
1877            start_mouse_input(locked, &current_task).await;
1878
1879        // Expect mouse relay to discard MouseEvents without vertical scroll.
1880        answer_next_mouse_watch_request(&mut mouse_stream, vec![mouse_move_event]).await;
1881        answer_next_mouse_watch_request(&mut mouse_stream, vec![mouse_click_event]).await;
1882
1883        // Wait for another `Watch` to ensure mouse_file is done processing the other replies.
1884        // Use an empty vec, to ensure no unexpected `uapi::input_event`s are created.
1885        answer_next_mouse_watch_request(&mut mouse_stream, vec![]).await;
1886
1887        let events = read_uapi_events(locked, &mouse_file, &current_task);
1888        assert_eq!(events.len(), 0);
1889    }
1890
1891    #[::fuchsia::test]
1892    async fn touch_input_initialized_with_inspect_node() {
1893        #[allow(deprecated, reason = "pre-existing usage")]
1894        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1895        let inspector = fuchsia_inspect::Inspector::default();
1896        let touch_device = InputDevice::new_touch(
1897            1200, /* screen width */
1898            720,  /* screen height */
1899            &inspector.root(),
1900        );
1901        let _file_obj = touch_device.open_test(locked, &current_task);
1902
1903        assert_data_tree!(inspector, root: {
1904            touch_device: {
1905                total_fidl_events_received_count: 0u64,
1906                total_fidl_events_ignored_count: 0u64,
1907                total_fidl_events_unexpected_count: 0u64,
1908                total_fidl_events_converted_count: 0u64,
1909                total_uapi_events_generated_count: 0u64,
1910                last_generated_uapi_event_timestamp_ns: 0i64,
1911                touch_file_0: {
1912                    fidl_events_received_count: 0u64,
1913                    fidl_events_ignored_count: 0u64,
1914                    fidl_events_unexpected_count: 0u64,
1915                    fidl_events_converted_count: 0u64,
1916                    uapi_events_generated_count: 0u64,
1917                    uapi_events_read_count: 0u64,
1918                    last_generated_uapi_event_timestamp_ns: 0i64,
1919                    last_read_uapi_event_timestamp_ns: 0i64,
1920                }
1921            }
1922        });
1923    }
1924
1925    #[::fuchsia::test]
1926    async fn touch_relay_updates_touch_inspect_status() {
1927        let inspector = fuchsia_inspect::Inspector::default();
1928        #[allow(deprecated, reason = "pre-existing usage")]
1929        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1930        let (_input_device, input_file, mut touch_source_stream) =
1931            start_touch_input_inspect(locked, &current_task, &inspector).await;
1932
1933        // Send 2 TouchEvents to proxy that should be counted as `received` by InputFile
1934        // A TouchEvent::default() has no pointer sample so these events should be discarded.
1935        match touch_source_stream.next().await {
1936            Some(Ok(TouchSourceRequest::Watch { responder, .. })) => responder
1937                .send(vec![make_empty_touch_event(), make_empty_touch_event()])
1938                .expect("failure sending Watch reply"),
1939            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
1940        }
1941
1942        // Send 5 TouchEvents with pointer sample to proxy, these should be received and converted
1943        // Add/Remove events generate 5 uapi events each. Change events generate 3 uapi events each.
1944        match touch_source_stream.next().await {
1945            Some(Ok(TouchSourceRequest::Watch { responses, responder })) => {
1946                assert_matches!(responses.as_slice(), [_, _]);
1947                responder
1948                    .send(vec![
1949                        make_touch_event_with_coords_phase_timestamp(
1950                            0.0,
1951                            0.0,
1952                            EventPhase::Add,
1953                            1,
1954                            1000,
1955                        ),
1956                        make_touch_event_with_coords_phase_timestamp(
1957                            1.0,
1958                            1.0,
1959                            EventPhase::Change,
1960                            1,
1961                            2000,
1962                        ),
1963                        make_touch_event_with_coords_phase_timestamp(
1964                            2.0,
1965                            2.0,
1966                            EventPhase::Change,
1967                            1,
1968                            3000,
1969                        ),
1970                        make_touch_event_with_coords_phase_timestamp(
1971                            3.0,
1972                            3.0,
1973                            EventPhase::Change,
1974                            1,
1975                            4000,
1976                        ),
1977                        make_touch_event_with_coords_phase_timestamp(
1978                            3.0,
1979                            3.0,
1980                            EventPhase::Remove,
1981                            1,
1982                            5000,
1983                        ),
1984                    ])
1985                    .expect("failure sending Watch reply");
1986            }
1987            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
1988        }
1989
1990        // Wait for next `Watch` call and verify it has five elements in `responses`.
1991        match touch_source_stream.next().await {
1992            Some(Ok(TouchSourceRequest::Watch { responses, .. })) => {
1993                assert_matches!(responses.as_slice(), [_, _, _, _, _])
1994            }
1995            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
1996        }
1997
1998        let _events = read_uapi_events(locked, &input_file, &current_task);
1999        assert_data_tree!(inspector, root: {
2000            touch_device: {
2001                total_fidl_events_received_count: 7u64,
2002                total_fidl_events_ignored_count: 2u64,
2003                total_fidl_events_unexpected_count: 0u64,
2004                total_fidl_events_converted_count: 5u64,
2005                total_uapi_events_generated_count: 22u64,
2006                last_generated_uapi_event_timestamp_ns: 5000i64,
2007                touch_file_0: {
2008                    fidl_events_received_count: 7u64,
2009                    fidl_events_ignored_count: 2u64,
2010                    fidl_events_unexpected_count: 0u64,
2011                    fidl_events_converted_count: 5u64,
2012                    uapi_events_generated_count: 22u64,
2013                    uapi_events_read_count: 22u64,
2014                    last_generated_uapi_event_timestamp_ns: 5000i64,
2015                    last_read_uapi_event_timestamp_ns: 5000i64,
2016                },
2017            }
2018        });
2019    }
2020
2021    #[::fuchsia::test]
2022    async fn new_file_updates_inspect_status() {
2023        let inspector = fuchsia_inspect::Inspector::default();
2024        #[allow(deprecated, reason = "pre-existing usage")]
2025        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
2026
2027        let input_device = InputDevice::new_touch(700, 700, inspector.root());
2028        let input_file_0 =
2029            input_device.open_test(locked, &current_task).expect("Failed to create input file");
2030
2031        let (touch_source_client_end, mut touch_source_stream) =
2032            fidl::endpoints::create_request_stream::<TouchSourceMarker>();
2033        let (mouse_source_client_end, _mouse_source_stream) =
2034            fidl::endpoints::create_request_stream::<fuipointer::MouseSourceMarker>();
2035        let (keyboard_proxy, mut keyboard_stream) =
2036            fidl::endpoints::create_sync_proxy_and_stream::<fuiinput::KeyboardMarker>();
2037        let view_ref_pair =
2038            fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
2039        let (device_registry_proxy, mut device_listener_stream) =
2040            fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>(
2041            );
2042
2043        let (relay, relay_handle) = input_event_relay::new_input_relay();
2044        relay.start_relays(
2045            &current_task.kernel(),
2046            EventProxyMode::None,
2047            touch_source_client_end,
2048            keyboard_proxy,
2049            mouse_source_client_end,
2050            view_ref_pair.view_ref,
2051            device_registry_proxy,
2052            input_device.open_files.clone(),
2053            Default::default(),
2054            Default::default(),
2055            Some(input_device.inspect_status.clone()),
2056            None,
2057            None,
2058        );
2059
2060        let _ = init_keyboard_listener(&mut keyboard_stream).await;
2061        let _ = init_button_listeners(&mut device_listener_stream).await;
2062
2063        relay_handle.add_touch_device(
2064            0,
2065            input_device.open_files.clone(),
2066            Some(input_device.inspect_status.clone()),
2067        );
2068
2069        // Send 2 TouchEvents to proxy that should be counted as `received` by InputFile
2070        // A TouchEvent::default() has no pointer sample so these events should be discarded.
2071        match touch_source_stream.next().await {
2072            Some(Ok(TouchSourceRequest::Watch { responder, .. })) => responder
2073                .send(vec![make_empty_touch_event(), make_empty_touch_event()])
2074                .expect("failure sending Watch reply"),
2075            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
2076        }
2077
2078        // Wait for next `Watch` call and verify it has two elements in `responses`.
2079        match touch_source_stream.next().await {
2080            Some(Ok(TouchSourceRequest::Watch { responses, responder })) => {
2081                assert_matches!(responses.as_slice(), [_, _]);
2082                responder.send(vec![]).expect("failure sending Watch reply");
2083            }
2084            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
2085        }
2086
2087        // Verify file node & properties remain in inspect tree when file is closed
2088        input_device.open_files.lock().clear();
2089        drop(input_file_0);
2090
2091        // Open new file which should receive input_device's subsequent events
2092        let input_file_1 =
2093            input_device.open_test(locked, &current_task).expect("Failed to create input file");
2094
2095        // Send 5 TouchEvents with pointer sample to proxy, these should be received and converted
2096        // Add/Remove events generate 5 uapi events each. Change events generate 3 uapi events each.
2097        match touch_source_stream.next().await {
2098            Some(Ok(TouchSourceRequest::Watch { responder, .. })) => {
2099                responder
2100                    .send(vec![
2101                        make_touch_event_with_coords_phase_timestamp(
2102                            0.0,
2103                            0.0,
2104                            EventPhase::Add,
2105                            1,
2106                            1000,
2107                        ),
2108                        make_touch_event_with_coords_phase_timestamp(
2109                            1.0,
2110                            1.0,
2111                            EventPhase::Change,
2112                            1,
2113                            2000,
2114                        ),
2115                        make_touch_event_with_coords_phase_timestamp(
2116                            2.0,
2117                            2.0,
2118                            EventPhase::Change,
2119                            1,
2120                            3000,
2121                        ),
2122                        make_touch_event_with_coords_phase_timestamp(
2123                            3.0,
2124                            3.0,
2125                            EventPhase::Change,
2126                            1,
2127                            4000,
2128                        ),
2129                        make_touch_event_with_coords_phase_timestamp(
2130                            3.0,
2131                            3.0,
2132                            EventPhase::Remove,
2133                            1,
2134                            5000,
2135                        ),
2136                    ])
2137                    .expect("failure sending Watch reply");
2138            }
2139            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
2140        }
2141
2142        // Wait for next `Watch` call and verify it has five elements in `responses`.
2143        match touch_source_stream.next().await {
2144            Some(Ok(TouchSourceRequest::Watch { responses, .. })) => {
2145                assert_matches!(responses.as_slice(), [_, _, _, _, _])
2146            }
2147            unexpected_request => panic!("unexpected request {:?}", unexpected_request),
2148        }
2149
2150        let _events = read_uapi_events(locked, &input_file_1, &current_task);
2151
2152        // Verify file node & properties remain in inspect tree when file is closed
2153        input_device.open_files.lock().clear();
2154        drop(input_file_1);
2155
2156        assert_data_tree!(inspector, root: {
2157            touch_device: {
2158                total_fidl_events_received_count: 7u64,
2159                total_fidl_events_ignored_count: 2u64,
2160                total_fidl_events_unexpected_count: 0u64,
2161                total_fidl_events_converted_count: 5u64,
2162                total_uapi_events_generated_count: 22u64,
2163                last_generated_uapi_event_timestamp_ns: 5000i64,
2164                touch_file_0: {
2165                    fidl_events_received_count: 2u64,
2166                    fidl_events_ignored_count: 2u64,
2167                    fidl_events_unexpected_count: 0u64,
2168                    fidl_events_converted_count: 0u64,
2169                    uapi_events_generated_count: 0u64,
2170                    uapi_events_read_count: 0u64,
2171                    last_generated_uapi_event_timestamp_ns: 0i64,
2172                    last_read_uapi_event_timestamp_ns: 0i64,
2173                },
2174                touch_file_1: {
2175                    fidl_events_received_count: 5u64,
2176                    fidl_events_ignored_count: 0u64,
2177                    fidl_events_unexpected_count: 0u64,
2178                    fidl_events_converted_count: 5u64,
2179                    uapi_events_generated_count: 22u64,
2180                    uapi_events_read_count: 22u64,
2181                    last_generated_uapi_event_timestamp_ns: 5000i64,
2182                    last_read_uapi_event_timestamp_ns: 5000i64,
2183                },
2184            }
2185        });
2186    }
2187
2188    #[::fuchsia::test]
2189    async fn keyboard_input_initialized_with_inspect_node() {
2190        #[allow(deprecated, reason = "pre-existing usage")]
2191        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
2192        let inspector = fuchsia_inspect::Inspector::default();
2193        let keyboard_device = InputDevice::new_keyboard(&inspector.root());
2194        let _file_obj = keyboard_device.open_test(locked, &current_task);
2195
2196        assert_data_tree!(inspector, root: {
2197            keyboard_device: {
2198                total_fidl_events_received_count: 0u64,
2199                total_fidl_events_ignored_count: 0u64,
2200                total_fidl_events_unexpected_count: 0u64,
2201                total_fidl_events_converted_count: 0u64,
2202                total_uapi_events_generated_count: 0u64,
2203                last_generated_uapi_event_timestamp_ns: 0i64,
2204                keyboard_file_0: {
2205                    fidl_events_received_count: 0u64,
2206                    fidl_events_ignored_count: 0u64,
2207                    fidl_events_unexpected_count: 0u64,
2208                    fidl_events_converted_count: 0u64,
2209                    uapi_events_generated_count: 0u64,
2210                    uapi_events_read_count: 0u64,
2211                    last_generated_uapi_event_timestamp_ns: 0i64,
2212                    last_read_uapi_event_timestamp_ns: 0i64,
2213                }
2214            }
2215        });
2216    }
2217
2218    #[::fuchsia::test]
2219    async fn button_relay_updates_keyboard_inspect_status() {
2220        let inspector = fuchsia_inspect::Inspector::default();
2221        #[allow(deprecated, reason = "pre-existing usage")]
2222        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
2223        let (_input_device, input_file, buttons_listener) =
2224            start_button_input_inspect(locked, &current_task, &inspector).await;
2225
2226        // Each of these events should count toward received and converted.
2227        // They also generate 2 uapi events each.
2228        let power_event = MediaButtonsEvent {
2229            volume: Some(0),
2230            mic_mute: Some(false),
2231            pause: Some(false),
2232            camera_disable: Some(false),
2233            power: Some(true),
2234            function: Some(false),
2235            ..Default::default()
2236        };
2237
2238        let power_release_event = MediaButtonsEvent {
2239            volume: Some(0),
2240            mic_mute: Some(false),
2241            pause: Some(false),
2242            camera_disable: Some(false),
2243            power: Some(false),
2244            function: Some(false),
2245            ..Default::default()
2246        };
2247
2248        let _ = buttons_listener.on_event(power_event).await;
2249        let _ = buttons_listener.on_event(power_release_event).await;
2250
2251        let events = read_uapi_events(locked, &input_file, &current_task);
2252        assert_eq!(events.len(), 4);
2253        assert_eq!(events[0].code, uapi::KEY_POWER as u16);
2254        assert_eq!(events[0].value, 1);
2255        assert_eq!(events[2].code, uapi::KEY_POWER as u16);
2256        assert_eq!(events[2].value, 0);
2257
2258        let _events = read_uapi_events(locked, &input_file, &current_task);
2259
2260        assert_data_tree!(inspector, root: {
2261            keyboard_device: {
2262                total_fidl_events_received_count: 2u64,
2263                total_fidl_events_ignored_count: 0u64,
2264                total_fidl_events_unexpected_count: 0u64,
2265                total_fidl_events_converted_count: 2u64,
2266                total_uapi_events_generated_count: 4u64,
2267                // Button events perform a realtime clockread, so any value will do.
2268                last_generated_uapi_event_timestamp_ns: AnyProperty,
2269                keyboard_file_0: {
2270                    fidl_events_received_count: 2u64,
2271                    fidl_events_ignored_count: 0u64,
2272                    fidl_events_unexpected_count: 0u64,
2273                    fidl_events_converted_count: 2u64,
2274                    uapi_events_generated_count: 4u64,
2275                    uapi_events_read_count: 4u64,
2276                    // Button events perform a realtime clockread, so any value will do.
2277                    last_generated_uapi_event_timestamp_ns: AnyProperty,
2278                    last_read_uapi_event_timestamp_ns: AnyProperty,
2279                },
2280            }
2281        });
2282    }
2283
2284    #[::fuchsia::test]
2285    async fn mouse_input_initialized_with_inspect_node() {
2286        #[allow(deprecated, reason = "pre-existing usage")]
2287        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
2288        let inspector = fuchsia_inspect::Inspector::default();
2289        let mouse_device = InputDevice::new_mouse(&inspector.root());
2290        let _file_obj = mouse_device.open_test(locked, &current_task);
2291
2292        assert_data_tree!(inspector, root: {
2293            mouse_device: {
2294                total_fidl_events_received_count: 0u64,
2295                total_fidl_events_ignored_count: 0u64,
2296                total_fidl_events_unexpected_count: 0u64,
2297                total_fidl_events_converted_count: 0u64,
2298                total_uapi_events_generated_count: 0u64,
2299                last_generated_uapi_event_timestamp_ns: 0i64,
2300                mouse_file_0: {
2301                    fidl_events_received_count: 0u64,
2302                    fidl_events_ignored_count: 0u64,
2303                    fidl_events_unexpected_count: 0u64,
2304                    fidl_events_converted_count: 0u64,
2305                    uapi_events_generated_count: 0u64,
2306                    uapi_events_read_count: 0u64,
2307                    last_generated_uapi_event_timestamp_ns: 0i64,
2308                    last_read_uapi_event_timestamp_ns: 0i64,
2309                }
2310            }
2311        });
2312    }
2313
2314    #[::fuchsia::test]
2315    async fn mouse_relay_updates_mouse_inspect_status() {
2316        let inspector = fuchsia_inspect::Inspector::default();
2317        #[allow(deprecated, reason = "pre-existing usage")]
2318        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
2319        let (_input_device, input_file, mut mouse_source_stream) =
2320            start_mouse_input_inspect(locked, &current_task, &inspector).await;
2321
2322        let mouse_move_event = fuipointer::MouseEvent {
2323            timestamp: Some(0),
2324            pointer_sample: Some(fuipointer::MousePointerSample {
2325                device_id: Some(0),
2326                position_in_viewport: Some([50.0, 50.0]),
2327                scroll_v: Some(0),
2328                ..Default::default()
2329            }),
2330            ..Default::default()
2331        };
2332        let mouse_click_event = fuipointer::MouseEvent {
2333            timestamp: Some(0),
2334            pointer_sample: Some(fuipointer::MousePointerSample {
2335                device_id: Some(0),
2336                scroll_v: Some(0),
2337                pressed_buttons: Some(vec![1]),
2338                ..Default::default()
2339            }),
2340            ..Default::default()
2341        };
2342
2343        // Send 2 non-wheel MouseEvents to proxy that should be counted as `received` by InputFile
2344        // These events have no scroll_v delta in the pointer sample so they should be ignored.
2345        answer_next_mouse_watch_request(
2346            &mut mouse_source_stream,
2347            vec![mouse_move_event, mouse_click_event],
2348        )
2349        .await;
2350
2351        // Send 5 MouseEvents with non-zero scroll_v delta to proxy, these should be received and
2352        // converted to 1 uapi event each, with an extra sync event to signify end of the batch.
2353        answer_next_mouse_watch_request(
2354            &mut mouse_source_stream,
2355            (0..5).map(|_| make_mouse_wheel_event(1)).collect(),
2356        )
2357        .await;
2358
2359        // Send a final mouse wheel event and ensure the inspect tree reflects it's timestamp under
2360        // last_generated_uapi_event_timestamp_ns and last_read_uapi_event_timestamp_ns.
2361        answer_next_mouse_watch_request(
2362            &mut mouse_source_stream,
2363            vec![make_mouse_wheel_event_with_timestamp(-1, 5000)],
2364        )
2365        .await;
2366
2367        // Wait for another `Watch` to ensure mouse_file is done processing the other replies.
2368        // Use an empty vec, to ensure no unexpected `uapi::input_event`s are created.
2369        answer_next_mouse_watch_request(&mut mouse_source_stream, vec![]).await;
2370
2371        let _events = read_uapi_events(locked, &input_file, &current_task);
2372        assert_data_tree!(inspector, root: {
2373            mouse_device: {
2374                total_fidl_events_received_count: 8u64,
2375                total_fidl_events_ignored_count: 2u64,
2376                total_fidl_events_unexpected_count: 0u64,
2377                total_fidl_events_converted_count: 6u64,
2378                total_uapi_events_generated_count: 8u64,
2379                last_generated_uapi_event_timestamp_ns: 5000i64,
2380                mouse_file_0: {
2381                    fidl_events_received_count: 8u64,
2382                    fidl_events_ignored_count: 2u64,
2383                    fidl_events_unexpected_count: 0u64,
2384                    fidl_events_converted_count: 6u64,
2385                    uapi_events_generated_count: 8u64,
2386                    uapi_events_read_count: 8u64,
2387                    last_generated_uapi_event_timestamp_ns: 5000i64,
2388                    last_read_uapi_event_timestamp_ns: 5000i64,
2389                },
2390            }
2391        });
2392    }
2393}