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