Skip to main content

starnix_modules_input/
input_device.rs

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