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