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