Skip to main content

input_pipeline/
touch_binding.rs

1// Copyright 2019 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::input_device::{self, Handled, InputDeviceBinding, InputDeviceStatus, InputEvent};
6use crate::utils::{self, Position, Size};
7use crate::{Transport, metrics, mouse_binding};
8use anyhow::{Context, Error, format_err};
9use async_trait::async_trait;
10use fuchsia_inspect::ArrayProperty;
11use fuchsia_inspect::health::Reporter;
12use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
13use zx;
14
15use fidl_fuchsia_input_report as fidl_input_report;
16use fidl_fuchsia_ui_input as fidl_ui_input;
17use fidl_next_fuchsia_ui_pointerinjector as pointerinjector;
18
19use metrics_registry::*;
20use sorted_vec_map::{SortedVecMap, SortedVecSet};
21
22/// A [`TouchScreenEvent`] represents a set of contacts and the phase those contacts are in.
23///
24/// For example, when a user touches a touch screen with two fingers, there will be two
25/// [`TouchContact`]s. When a user removes one finger, there will still be two contacts
26/// but one will be reported as removed.
27///
28/// The expected sequence for any given contact is:
29/// 1. [`fidl_fuchsia_ui_input::PointerEventPhase::Add`]
30/// 2. [`fidl_fuchsia_ui_input::PointerEventPhase::Down`]
31/// 3. 0 or more [`fidl_fuchsia_ui_input::PointerEventPhase::Move`]
32/// 4. [`fidl_fuchsia_ui_input::PointerEventPhase::Up`]
33/// 5. [`fidl_fuchsia_ui_input::PointerEventPhase::Remove`]
34///
35/// Additionally, a [`fidl_fuchsia_ui_input::PointerEventPhase::Cancel`] may be sent at any time
36/// signalling that the event is no longer directed towards the receiver.
37#[derive(Debug, PartialEq)]
38pub struct TouchScreenEvent {
39    /// Deprecated. To be removed with https://fxbug.dev/42155652.
40    /// The contacts associated with the touch event. For example, a two-finger touch would result
41    /// in one touch event with two [`TouchContact`]s.
42    ///
43    /// Contacts are grouped based on their current phase (e.g., down, move).
44    pub contacts: SortedVecMap<fidl_ui_input::PointerEventPhase, Vec<TouchContact>>,
45
46    /// The contacts associated with the touch event. For example, a two-finger touch would result
47    /// in one touch event with two [`TouchContact`]s.
48    ///
49    /// Contacts are grouped based on their current phase (e.g., add, change).
50    pub injector_contacts: SortedVecMap<pointerinjector::EventPhase, Vec<TouchContact>>,
51
52    /// Indicates whether any touch buttons are pressed.
53    pub pressed_buttons: Vec<fidl_next_fuchsia_input_report::TouchButton>,
54
55    /// The wake lease for this event.
56    pub wake_lease: Option<zx::EventPair>,
57}
58
59impl Clone for TouchScreenEvent {
60    fn clone(&self) -> Self {
61        log::debug!("TouchScreenEvent cloned without wake lease.");
62        Self {
63            contacts: self.contacts.clone(),
64            injector_contacts: self.injector_contacts.clone(),
65            pressed_buttons: self.pressed_buttons.clone(),
66            wake_lease: None,
67        }
68    }
69}
70
71impl Drop for TouchScreenEvent {
72    fn drop(&mut self) {
73        log::debug!("TouchScreenEvent dropped, had_wake_lease: {:?}", self.wake_lease);
74    }
75}
76
77impl TouchScreenEvent {
78    pub fn record_inspect(&self, node: &fuchsia_inspect::Node) {
79        let contacts_clone = self.injector_contacts.clone();
80        node.record_child("injector_contacts", move |contacts_node| {
81            for (phase, contacts) in contacts_clone.iter() {
82                let phase_str = match pointerinjector::EventPhase::try_from(*phase) {
83                    Ok(pointerinjector::EventPhase::Add) => "add",
84                    Ok(pointerinjector::EventPhase::Change) => "change",
85                    Ok(pointerinjector::EventPhase::Remove) => "remove",
86                    Ok(pointerinjector::EventPhase::Cancel) => "cancel",
87                    Err(_) => unreachable!("invalid phase"),
88                };
89                contacts_node.record_child(phase_str, move |phase_node| {
90                    for contact in contacts.iter() {
91                        phase_node.record_child(contact.id.to_string(), move |contact_node| {
92                            contact_node
93                                .record_double("position_x_mm", f64::from(contact.position.x));
94                            contact_node
95                                .record_double("position_y_mm", f64::from(contact.position.y));
96                            if let Some(pressure) = contact.pressure {
97                                contact_node.record_int("pressure", pressure);
98                            }
99                            if let Some(contact_size) = contact.contact_size {
100                                contact_node.record_double(
101                                    "contact_width_mm",
102                                    f64::from(contact_size.width),
103                                );
104                                contact_node.record_double(
105                                    "contact_height_mm",
106                                    f64::from(contact_size.height),
107                                );
108                            }
109                        });
110                    }
111                });
112            }
113        });
114
115        let pressed_buttons_node =
116            node.create_string_array("pressed_buttons", self.pressed_buttons.len());
117        self.pressed_buttons.iter().enumerate().for_each(|(i, &ref button)| {
118            let button_name: String = match button {
119                fidl_next_fuchsia_input_report::TouchButton::Palm => "palm".into(),
120                unknown_value => {
121                    format!("unknown({:?})", unknown_value)
122                }
123            };
124            pressed_buttons_node.set(i, &button_name);
125        });
126        node.record(pressed_buttons_node);
127    }
128}
129
130/// A [`TouchpadEvent`] represents a set of contacts.
131///
132/// For example, when a user touches a touch screen with two fingers, there will be two
133/// [`TouchContact`]s in the vector.
134#[derive(Clone, Debug, PartialEq)]
135pub struct TouchpadEvent {
136    /// The contacts associated with the touch event. For example, a two-finger touch would result
137    /// in one touch event with two [`TouchContact`]s.
138    pub injector_contacts: Vec<TouchContact>,
139
140    /// The complete button state including this event.
141    pub pressed_buttons: SortedVecSet<mouse_binding::MouseButton>,
142}
143
144impl TouchpadEvent {
145    pub fn record_inspect(&self, node: &fuchsia_inspect::Node) {
146        let pressed_buttons_node =
147            node.create_uint_array("pressed_buttons", self.pressed_buttons.len());
148        self.pressed_buttons.iter().enumerate().for_each(|(i, button)| {
149            pressed_buttons_node.set(i, *button);
150        });
151        node.record(pressed_buttons_node);
152
153        // Populate TouchpadEvent contact details.
154        let contacts_clone = self.injector_contacts.clone();
155        node.record_child("injector_contacts", move |contacts_node| {
156            for contact in contacts_clone.iter() {
157                contacts_node.record_child(contact.id.to_string(), move |contact_node| {
158                    contact_node.record_double("position_x_mm", f64::from(contact.position.x));
159                    contact_node.record_double("position_y_mm", f64::from(contact.position.y));
160                    if let Some(pressure) = contact.pressure {
161                        contact_node.record_int("pressure", pressure);
162                    }
163                    if let Some(contact_size) = contact.contact_size {
164                        contact_node
165                            .record_double("contact_width_mm", f64::from(contact_size.width));
166                        contact_node
167                            .record_double("contact_height_mm", f64::from(contact_size.height));
168                    }
169                })
170            }
171        });
172    }
173}
174
175/// [`TouchDeviceType`] indicates the type of touch device. Both Touch Screen and Windows Precision
176/// Touchpad send touch event from driver but need different process inside input pipeline.
177#[derive(Clone, Copy, Debug, Eq, PartialEq)]
178pub enum TouchDeviceType {
179    TouchScreen,
180    WindowsPrecisionTouchpad,
181}
182
183/// A [`TouchContact`] represents a single contact (e.g., one touch of a multi-touch gesture) related
184/// to a touch event.
185#[derive(Clone, Copy, Debug, PartialEq)]
186pub struct TouchContact {
187    /// The identifier of the contact. Unique per touch device.
188    pub id: u32,
189
190    /// The position of the touch event, in the units of the associated
191    /// [`ContactDeviceDescriptor`]'s `range`.
192    pub position: Position,
193
194    /// The pressure associated with the contact, in the units of the associated
195    /// [`ContactDeviceDescriptor`]'s `pressure_range`.
196    pub pressure: Option<i64>,
197
198    /// The size of the touch event, in the units of the associated
199    /// [`ContactDeviceDescriptor`]'s `range`.
200    pub contact_size: Option<Size>,
201}
202
203impl Eq for TouchContact {}
204
205impl TryFrom<&fidl_next_fuchsia_input_report::ContactInputReport> for TouchContact {
206    type Error = anyhow::Error;
207
208    fn try_from(
209        fidl_contact: &fidl_next_fuchsia_input_report::ContactInputReport,
210    ) -> anyhow::Result<TouchContact> {
211        let contact_size =
212            if fidl_contact.contact_width.is_some() && fidl_contact.contact_height.is_some() {
213                Some(Size {
214                    width: fidl_contact.contact_width.unwrap() as f32,
215                    height: fidl_contact.contact_height.unwrap() as f32,
216                })
217            } else {
218                None
219            };
220
221        let id = fidl_contact.contact_id.context("contact_id is required")?;
222        let position_x = fidl_contact.position_x.context("position_x is required")?;
223        let position_y = fidl_contact.position_y.context("position_y is required")?;
224
225        Ok(TouchContact {
226            id,
227            position: Position { x: position_x as f32, y: position_y as f32 },
228            pressure: fidl_contact.pressure,
229            contact_size,
230        })
231    }
232}
233
234impl TryFrom<&fidl_next_fuchsia_input_report::wire::ContactInputReport<'_>> for TouchContact {
235    type Error = anyhow::Error;
236
237    fn try_from(
238        fidl_contact: &fidl_next_fuchsia_input_report::wire::ContactInputReport<'_>,
239    ) -> Result<Self, Self::Error> {
240        let contact_size =
241            if fidl_contact.contact_width().is_some() && fidl_contact.contact_height().is_some() {
242                Some(Size {
243                    width: fidl_contact.contact_width().map(|w| w.0).unwrap() as f32,
244                    height: fidl_contact.contact_height().map(|h| h.0).unwrap() as f32,
245                })
246            } else {
247                None
248            };
249
250        let id = fidl_contact.contact_id().map(|id| id.0).context("contact_id is required")?;
251        let position_x =
252            fidl_contact.position_x().map(|x| x.0).context("position_x is required")?;
253        let position_y =
254            fidl_contact.position_y().map(|y| y.0).context("position_y is required")?;
255
256        Ok(TouchContact {
257            id,
258            position: Position { x: position_x as f32, y: position_y as f32 },
259            pressure: fidl_contact.pressure().map(|p| p.0),
260            contact_size,
261        })
262    }
263}
264
265#[derive(Clone, Debug, Eq, PartialEq)]
266pub struct TouchScreenDeviceDescriptor {
267    /// The id of the connected touch screen input device.
268    pub device_id: u32,
269
270    /// The descriptors for the possible contacts associated with the device.
271    pub contacts: Vec<ContactDeviceDescriptor>,
272}
273
274#[derive(Clone, Debug, Eq, PartialEq)]
275pub struct TouchpadDeviceDescriptor {
276    /// The id of the connected touchpad input device.
277    pub device_id: u32,
278
279    /// The descriptors for the possible contacts associated with the device.
280    pub contacts: Vec<ContactDeviceDescriptor>,
281}
282
283#[derive(Clone, Debug, Eq, PartialEq)]
284enum TouchDeviceDescriptor {
285    TouchScreen(TouchScreenDeviceDescriptor),
286    Touchpad(TouchpadDeviceDescriptor),
287}
288
289/// A [`ContactDeviceDescriptor`] describes the possible values touch contact properties can take on.
290///
291/// This descriptor can be used, for example, to determine where on a screen a touch made contact.
292///
293/// # Example
294///
295/// ```
296/// // Determine the scaling factor between the display and the touch device's x range.
297/// let scaling_factor =
298///     display_width / (contact_descriptor._x_range.end - contact_descriptor._x_range.start);
299/// // Use the scaling factor to scale the contact report's x position.
300/// let hit_location =
301///     scaling_factor * (contact_report.position_x - contact_descriptor._x_range.start);
302#[derive(Clone, Debug, Eq, PartialEq)]
303pub struct ContactDeviceDescriptor {
304    /// The range of possible x values for this touch contact.
305    pub x_range: fidl_input_report::Range,
306
307    /// The range of possible y values for this touch contact.
308    pub y_range: fidl_input_report::Range,
309
310    /// The unit of measure for `x_range`.
311    pub x_unit: fidl_input_report::Unit,
312
313    /// The unit of measure for `y_range`.
314    pub y_unit: fidl_input_report::Unit,
315
316    /// The range of possible pressure values for this touch contact.
317    pub pressure_range: Option<fidl_input_report::Range>,
318
319    /// The range of possible widths for this touch contact.
320    pub width_range: Option<fidl_input_report::Range>,
321
322    /// The range of possible heights for this touch contact.
323    pub height_range: Option<fidl_input_report::Range>,
324}
325
326/// A [`TouchBinding`] represents a connection to a touch input device.
327///
328/// The [`TouchBinding`] parses and exposes touch descriptor properties (e.g., the range of
329/// possible x values for touch contacts) for the device it is associated with.
330/// It also parses [`InputReport`]s from the device, and sends them to the device binding owner over
331/// `event_sender`.
332pub struct TouchBinding {
333    /// The channel to stream InputEvents to.
334    event_sender: UnboundedSender<Vec<InputEvent>>,
335
336    /// Holds information about this device.
337    device_descriptor: TouchDeviceDescriptor,
338
339    /// Touch device type of the touch device.
340    touch_device_type: TouchDeviceType,
341
342    /// Proxy to the device.
343    device_proxy: fidl_next::Client<fidl_next_fuchsia_input_report::InputDevice, Transport>,
344}
345
346#[async_trait]
347impl input_device::InputDeviceBinding for TouchBinding {
348    fn input_event_sender(&self) -> UnboundedSender<Vec<InputEvent>> {
349        self.event_sender.clone()
350    }
351
352    fn get_device_descriptor(&self) -> input_device::InputDeviceDescriptor {
353        match self.device_descriptor.clone() {
354            TouchDeviceDescriptor::TouchScreen(desc) => {
355                input_device::InputDeviceDescriptor::TouchScreen(desc)
356            }
357            TouchDeviceDescriptor::Touchpad(desc) => {
358                input_device::InputDeviceDescriptor::Touchpad(desc)
359            }
360        }
361    }
362}
363
364impl TouchBinding {
365    /// Creates a new [`InputDeviceBinding`] from the `device_proxy`.
366    ///
367    /// The binding will start listening for input reports immediately and send new InputEvents
368    /// to the device binding owner over `input_event_sender`.
369    ///
370    /// # Parameters
371    /// - `device_proxy`: The proxy to bind the new [`InputDeviceBinding`] to.
372    /// - `device_id`: The id of the connected touch device.
373    /// - `input_event_sender`: The channel to send new InputEvents to.
374    /// - `device_node`: The inspect node for this device binding
375    /// - `metrics_logger`: The metrics logger.
376    ///
377    /// # Errors
378    /// If there was an error binding to the proxy.
379    pub async fn new(
380        device_proxy: fidl_next::Client<fidl_next_fuchsia_input_report::InputDevice, Transport>,
381        device_id: u32,
382        input_event_sender: UnboundedSender<Vec<InputEvent>>,
383        device_node: fuchsia_inspect::Node,
384        feature_flags: input_device::InputPipelineFeatureFlags,
385        metrics_logger: metrics::MetricsLogger,
386    ) -> Result<Self, Error> {
387        let (device_binding, mut inspect_status) =
388            Self::bind_device(device_proxy.clone(), device_id, input_event_sender, device_node)
389                .await?;
390        device_binding
391            .set_touchpad_mode(true)
392            .await
393            .with_context(|| format!("enabling touchpad mode for device {}", device_id))?;
394        inspect_status.health_node.set_ok();
395        input_device::initialize_report_stream(
396            device_proxy,
397            device_binding.get_device_descriptor(),
398            device_binding.input_event_sender(),
399            inspect_status,
400            metrics_logger,
401            feature_flags,
402            Self::process_reports,
403        );
404
405        Ok(device_binding)
406    }
407
408    /// Binds the provided input device to a new instance of `Self`.
409    ///
410    /// # Parameters
411    /// - `device`: The device to use to initialize the binding.
412    /// - `device_id`: The id of the connected touch device.
413    /// - `input_event_sender`: The channel to send new InputEvents to.
414    /// - `device_node`: The inspect node for this device binding
415    ///
416    /// # Errors
417    /// If the device descriptor could not be retrieved, or the descriptor could not be parsed
418    /// correctly.
419    async fn bind_device(
420        device_proxy: fidl_next::Client<fidl_next_fuchsia_input_report::InputDevice, Transport>,
421        device_id: u32,
422        input_event_sender: UnboundedSender<Vec<InputEvent>>,
423        device_node: fuchsia_inspect::Node,
424    ) -> Result<(Self, InputDeviceStatus), Error> {
425        let mut input_device_status = InputDeviceStatus::new(device_node);
426        let device_descriptor: fidl_next_fuchsia_input_report::DeviceDescriptor = match device_proxy
427            .get_descriptor()
428            .await
429        {
430            Ok(res) => res.descriptor,
431            Err(_) => {
432                input_device_status.health_node.set_unhealthy("Could not get device descriptor.");
433                return Err(format_err!("Could not get descriptor for device_id: {}", device_id));
434            }
435        };
436
437        let touch_device_type = get_device_type(&device_proxy).await;
438
439        match device_descriptor.touch {
440            Some(fidl_next_fuchsia_input_report::TouchDescriptor {
441                input:
442                    Some(fidl_next_fuchsia_input_report::TouchInputDescriptor {
443                        contacts: Some(contact_descriptors),
444                        max_contacts: _,
445                        touch_type: _,
446                        buttons: _,
447                        ..
448                    }),
449                ..
450            }) => Ok((
451                TouchBinding {
452                    event_sender: input_event_sender,
453                    device_descriptor: match touch_device_type {
454                        TouchDeviceType::TouchScreen => {
455                            TouchDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
456                                device_id,
457                                contacts: contact_descriptors
458                                    .iter()
459                                    .map(TouchBinding::parse_contact_descriptor)
460                                    .filter_map(Result::ok)
461                                    .collect(),
462                            })
463                        }
464                        TouchDeviceType::WindowsPrecisionTouchpad => {
465                            TouchDeviceDescriptor::Touchpad(TouchpadDeviceDescriptor {
466                                device_id,
467                                contacts: contact_descriptors
468                                    .iter()
469                                    .map(TouchBinding::parse_contact_descriptor)
470                                    .filter_map(Result::ok)
471                                    .collect(),
472                            })
473                        }
474                    },
475                    touch_device_type,
476                    device_proxy,
477                },
478                input_device_status,
479            )),
480            descriptor => {
481                input_device_status
482                    .health_node
483                    .set_unhealthy("Touch Device Descriptor failed to parse.");
484                Err(format_err!("Touch Descriptor failed to parse: \n {:?}", descriptor))
485            }
486        }
487    }
488
489    async fn set_touchpad_mode(&self, enable: bool) -> Result<(), Error> {
490        match self.touch_device_type {
491            TouchDeviceType::TouchScreen => Ok(()),
492            TouchDeviceType::WindowsPrecisionTouchpad => {
493                // `get_feature_report` to only modify the input_mode and
494                // keep other feature as is.
495                let mut report = match self.device_proxy.get_feature_report().await? {
496                    Ok(res) => res.report,
497                    Err(e) => return Err(format_err!("get_feature_report failed: {}", e)),
498                };
499                let mut touch = report
500                    .touch
501                    .unwrap_or_else(fidl_next_fuchsia_input_report::TouchFeatureReport::default);
502                touch.input_mode = match enable {
503                            true => Some(fidl_next_fuchsia_input_report::TouchConfigurationInputMode::WindowsPrecisionTouchpadCollection),
504                            false => Some(fidl_next_fuchsia_input_report::TouchConfigurationInputMode::MouseCollection),
505                        };
506                report.touch = Some(touch);
507                match self.device_proxy.set_feature_report(&report).await? {
508                    Ok(_) => {
509                        // TODO(https://fxbug.dev/42056283): Remove log message.
510                        log::info!("touchpad: set touchpad_enabled to {}", enable);
511                        Ok(())
512                    }
513                    Err(e) => Err(format_err!("set_feature_report failed: {}", e)),
514                }
515            }
516        }
517    }
518
519    /// Parses an [`InputReport`] into one or more [`InputEvent`]s.
520    ///
521    /// The [`InputEvent`]s are sent to the device binding owner via [`input_event_sender`].
522    ///
523    /// # Parameters
524    /// - `reports`: The incoming [`InputReport`].
525    /// - `previous_report`: The previous [`InputReport`] seen for the same device. This can be
526    ///                    used to determine, for example, which keys are no longer present in
527    ///                    a keyboard report to generate key released events. If `None`, no
528    ///                    previous report was found.
529    /// - `device_descriptor`: The descriptor for the input device generating the input reports.
530    /// - `input_event_sender`: The sender for the device binding's input event stream.
531    ///
532    /// # Returns
533    /// An [`InputReport`] which will be passed to the next call to [`process_reports`], as
534    /// [`previous_report`]. If `None`, the next call's [`previous_report`] will be `None`.
535    /// A [`UnboundedReceiver<InputEvent>`] which will poll asynchronously generated events to be
536    /// recorded by `inspect_status` in `input_device::initialize_report_stream()`. If device
537    /// binding does not generate InputEvents asynchronously, this will be `None`.
538    ///
539    /// The returned [`InputReport`] is guaranteed to have no `wake_lease`.
540    fn process_reports(
541        reports: &[fidl_next_fuchsia_input_report::wire::InputReport<'_>],
542        previous_state: Option<input_device::PreviousDeviceState>,
543        device_descriptor: &input_device::InputDeviceDescriptor,
544        input_event_sender: &mut UnboundedSender<Vec<InputEvent>>,
545        inspect_status: &InputDeviceStatus,
546        metrics_logger: &metrics::MetricsLogger,
547        feature_flags: &input_device::InputPipelineFeatureFlags,
548    ) -> (Option<input_device::PreviousDeviceState>, Option<UnboundedReceiver<InputEvent>>) {
549        fuchsia_trace::duration!(
550            "input",
551            "touch-binding-process-report",
552            "num_reports" => reports.len(),
553        );
554        match device_descriptor {
555            input_device::InputDeviceDescriptor::TouchScreen(_) => process_touch_screen_reports(
556                reports,
557                previous_state,
558                device_descriptor,
559                input_event_sender,
560                inspect_status,
561                metrics_logger,
562                feature_flags.enable_merge_touch_events,
563            ),
564            input_device::InputDeviceDescriptor::Touchpad(_) => {
565                // TODO(b/512925135): support touchpad in starnix
566                (previous_state, None)
567            }
568            _ => (previous_state, None),
569        }
570    }
571
572    /// Parses a fidl_input_report contact descriptor into a [`ContactDeviceDescriptor`]
573    ///
574    /// # Parameters
575    /// - `contact_device_descriptor`: The contact descriptor to parse.
576    ///
577    /// # Errors
578    /// If the contact description fails to parse because required fields aren't present.
579    fn parse_contact_descriptor(
580        contact_device_descriptor: &fidl_next_fuchsia_input_report::ContactInputDescriptor,
581    ) -> Result<ContactDeviceDescriptor, Error> {
582        match contact_device_descriptor {
583            fidl_next_fuchsia_input_report::ContactInputDescriptor {
584                position_x: Some(x_axis),
585                position_y: Some(y_axis),
586                pressure: pressure_axis,
587                contact_width: width_axis,
588                contact_height: height_axis,
589                ..
590            } => Ok(ContactDeviceDescriptor {
591                x_range: utils::range_to_old(&x_axis.range),
592                y_range: utils::range_to_old(&y_axis.range),
593                x_unit: utils::unit_to_old(&x_axis.unit),
594                y_unit: utils::unit_to_old(&y_axis.unit),
595                pressure_range: pressure_axis.as_ref().map(|axis| utils::range_to_old(&axis.range)),
596                width_range: width_axis.as_ref().map(|axis| utils::range_to_old(&axis.range)),
597                height_range: height_axis.as_ref().map(|axis| utils::range_to_old(&axis.range)),
598            }),
599            descriptor => {
600                Err(format_err!("Touch Contact Descriptor failed to parse: \n {:?}", descriptor))
601            }
602        }
603    }
604}
605
606fn is_move_only(event: &InputEvent) -> bool {
607    matches!(
608        &event.device_event,
609        input_device::InputDeviceEvent::TouchScreen(event)
610            if event
611                .injector_contacts
612                .get(&pointerinjector::EventPhase::Add)
613                .map_or(true, |c| c.is_empty())
614                && event
615                    .injector_contacts
616                    .get(&pointerinjector::EventPhase::Remove)
617                    .map_or(true, |c| c.is_empty())
618                && event
619                    .injector_contacts
620                    .get(&pointerinjector::EventPhase::Cancel)
621                    .map_or(true, |c| c.is_empty())
622    )
623}
624
625fn has_pressed_buttons(event: &InputEvent) -> bool {
626    match &event.device_event {
627        input_device::InputDeviceEvent::TouchScreen(event) => !event.pressed_buttons.is_empty(),
628        _ => false,
629    }
630}
631
632fn process_touch_screen_reports(
633    reports: &[fidl_next_fuchsia_input_report::wire::InputReport<'_>],
634    mut previous_state: Option<input_device::PreviousDeviceState>,
635    device_descriptor: &input_device::InputDeviceDescriptor,
636    input_event_sender: &mut UnboundedSender<Vec<InputEvent>>,
637    inspect_status: &InputDeviceStatus,
638    metrics_logger: &metrics::MetricsLogger,
639    enable_merge_touch_events: bool,
640) -> (Option<input_device::PreviousDeviceState>, Option<UnboundedReceiver<InputEvent>>) {
641    let num_reports = reports.len();
642    let mut batch: Vec<InputEvent> = Vec::with_capacity(num_reports);
643    for report in reports {
644        inspect_status.count_received_report_wire(report);
645        let (prev_state, event) = process_single_touch_screen_report(
646            report,
647            previous_state,
648            device_descriptor,
649            inspect_status,
650            metrics_logger,
651        );
652        previous_state = prev_state;
653        if let Some(event) = event {
654            batch.push(event);
655        }
656    }
657
658    if !batch.is_empty() {
659        if enable_merge_touch_events {
660            // Pre-calculate move-only status for all events
661            let mut is_event_move_only: Vec<bool> = Vec::with_capacity(batch.len());
662            let mut pressed_buttons: Vec<bool> = Vec::with_capacity(batch.len());
663            for event in &batch {
664                is_event_move_only.push(is_move_only(event));
665                pressed_buttons.push(has_pressed_buttons(event));
666            }
667            let size_of_batch = batch.len();
668
669            // Merge consecutive move-only events into a single event.
670            let mut merged_batch = Vec::with_capacity(size_of_batch);
671
672            // Use into_iter().enumerate() to move elements without cloning
673            for (i, current_event) in batch.into_iter().enumerate() {
674                let current_is_move = is_event_move_only[i];
675                let current_pressed_buttons = pressed_buttons[i];
676                let is_last_event = i == size_of_batch - 1;
677
678                // Check if the NEXT event is also move-only
679                let next_is_move =
680                    if i + 1 < size_of_batch { is_event_move_only[i + 1] } else { false };
681
682                let next_pressed_buttons = if i + 1 < size_of_batch {
683                    pressed_buttons[i + 1]
684                } else {
685                    current_pressed_buttons
686                };
687
688                // If both are move-only, skip the current one (it's redundant).
689                // always keep the last event
690                if !is_last_event
691                    // both are move-only
692                    && (current_is_move && next_is_move)
693                    // same pressed buttons
694                    && (current_pressed_buttons == next_pressed_buttons)
695                {
696                    continue;
697                }
698
699                merged_batch.push(current_event);
700            }
701
702            batch = merged_batch;
703        }
704
705        let events_to_send: Vec<InputEvent> = {
706            fuchsia_trace::duration!("input", "prepare_events_to_send");
707            batch
708                .into_iter()
709                .map(|event| {
710                    // Unwrap is safe because trace_id is set when the event is created.
711                    // This unwrap will not move the trace_id out of the event because trace_id has
712                    // Copy trait.
713                    let trace_id: fuchsia_trace::Id = event.trace_id.unwrap();
714                    fuchsia_trace::flow_begin!("input", "event_in_input_pipeline", trace_id);
715                    event
716                })
717                .collect()
718        };
719        fuchsia_trace::instant!(
720            "input",
721            "events_to_input_handlers",
722            fuchsia_trace::Scope::Thread,
723            "num_reports" => num_reports,
724            "num_events_generated" => events_to_send.len()
725        );
726
727        // Record inspect data before sending, as unbounded_send consumes the vector.
728        inspect_status.count_generated_events(&events_to_send);
729
730        if let Err(e) = input_event_sender.unbounded_send(events_to_send) {
731            metrics_logger.log_error(
732                InputPipelineErrorMetricDimensionEvent::TouchFailedToSendTouchScreenEvent,
733                std::format!("Failed to send TouchScreenEvent with error: {:?}", e),
734            );
735        }
736    }
737    (previous_state, None)
738}
739
740fn process_single_touch_screen_report(
741    report: &fidl_next_fuchsia_input_report::wire::InputReport<'_>,
742    previous_state: Option<input_device::PreviousDeviceState>,
743    device_descriptor: &input_device::InputDeviceDescriptor,
744    inspect_status: &InputDeviceStatus,
745    metrics_logger: &metrics::MetricsLogger,
746) -> (Option<input_device::PreviousDeviceState>, Option<InputEvent>) {
747    fuchsia_trace::flow_end!(
748        "input",
749        "input_report",
750        report.trace_id().map(|x| x.0).unwrap_or(0).into()
751    );
752
753    // Extract the wake_lease early to prevent it from leaking. If this is moved
754    // below an early return, the lease could accidentally be stored inside
755    // `previous_report`, which would prevent the system from suspending.
756    let wake_lease = utils::duplicate_wake_lease(report.wake_lease());
757
758    // Input devices can have multiple types so ensure `report` is a TouchInputReport.
759    let touch_report = match report.touch() {
760        Some(touch) => touch,
761        None => {
762            inspect_status.count_filtered_report();
763            return (previous_state, None);
764        }
765    };
766
767    let (previous_contacts, previous_buttons): (
768        SortedVecMap<u32, TouchContact>,
769        Vec<fidl_next_fuchsia_input_report::TouchButton>,
770    ) = match &previous_state {
771        Some(input_device::PreviousDeviceState::TouchScreen {
772            active_contacts,
773            pressed_buttons,
774        }) => {
775            let contacts =
776                SortedVecMap::from_iter(active_contacts.iter().map(|c| (c.id, c.clone())));
777            (contacts, pressed_buttons.clone())
778        }
779        _ => (SortedVecMap::new(), vec![]),
780    };
781    let (current_contacts, current_buttons): (
782        SortedVecMap<u32, TouchContact>,
783        Vec<fidl_next_fuchsia_input_report::TouchButton>,
784    ) = touch_contacts_and_buttons_from_touch_report_wire(touch_report, metrics_logger);
785
786    if previous_contacts.is_empty()
787        && current_contacts.is_empty()
788        && previous_buttons.is_empty()
789        && current_buttons.is_empty()
790    {
791        inspect_status.count_filtered_report();
792        return (previous_state, None);
793    }
794
795    // Contacts which exist only in current.
796    let added_contacts: Vec<TouchContact> = Vec::from_iter(
797        current_contacts
798            .iter()
799            .map(|(_, v)| v.clone())
800            .filter(|contact| !previous_contacts.contains_key(&contact.id)),
801    );
802    // Contacts which exist in both previous and current.
803    let moved_contacts: Vec<TouchContact> = Vec::from_iter(
804        current_contacts
805            .iter()
806            .map(|(_, v)| v.clone())
807            .filter(|contact| previous_contacts.contains_key(&contact.id)),
808    );
809    // Contacts which exist only in previous.
810    let removed_contacts: Vec<TouchContact> =
811        Vec::from_iter(previous_contacts.iter().map(|(_, v)| v.clone()).filter(|contact| {
812            current_buttons.is_empty()
813                && previous_buttons.is_empty()
814                && !current_contacts.contains_key(&contact.id)
815        }));
816
817    let active_contacts: Vec<TouchContact> = if current_contacts.is_empty()
818        && !previous_contacts.is_empty()
819        && (!current_buttons.is_empty() || !previous_buttons.is_empty())
820    {
821        previous_contacts.values().cloned().collect()
822    } else {
823        added_contacts.iter().chain(moved_contacts.iter()).cloned().collect()
824    };
825
826    let trace_id = fuchsia_trace::Id::new();
827    let event = create_touch_screen_event(
828        SortedVecMap::from_iter(vec![
829            (fidl_ui_input::PointerEventPhase::Add, added_contacts.clone()),
830            (fidl_ui_input::PointerEventPhase::Down, added_contacts.clone()),
831            (fidl_ui_input::PointerEventPhase::Move, moved_contacts.clone()),
832            (fidl_ui_input::PointerEventPhase::Up, removed_contacts.clone()),
833            (fidl_ui_input::PointerEventPhase::Remove, removed_contacts.clone()),
834        ]),
835        SortedVecMap::from_iter(vec![
836            (pointerinjector::EventPhase::Add, added_contacts),
837            (pointerinjector::EventPhase::Change, moved_contacts),
838            (pointerinjector::EventPhase::Remove, removed_contacts),
839        ]),
840        current_buttons.clone(),
841        device_descriptor,
842        trace_id,
843        wake_lease,
844    );
845
846    let next_previous_state = input_device::PreviousDeviceState::TouchScreen {
847        active_contacts,
848        pressed_buttons: current_buttons,
849    };
850
851    (Some(next_previous_state), Some(event))
852}
853
854fn touch_contacts_and_buttons_from_touch_report_wire(
855    touch_report: &fidl_next_fuchsia_input_report::wire::TouchInputReport<'_>,
856    metrics_logger: &metrics::MetricsLogger,
857) -> (SortedVecMap<u32, TouchContact>, Vec<fidl_next_fuchsia_input_report::TouchButton>) {
858    let mut contacts = Vec::new();
859    if let Some(unwrapped_contacts) = touch_report.contacts() {
860        for contact in unwrapped_contacts.iter() {
861            match TouchContact::try_from(contact) {
862                Ok(c) => contacts.push(c),
863                Err(e) => {
864                    metrics_logger.log_warn(
865                        InputPipelineErrorMetricDimensionEvent::TouchReportContactMissingField,
866                        std::format!("failed to convert touch contact: {:?}", e),
867                    );
868                }
869            }
870        }
871    } else {
872        metrics_logger.log_warn(
873            InputPipelineErrorMetricDimensionEvent::TouchReportMissingContact,
874            "contacts missing in touch input report",
875        );
876    }
877
878    let pressed_buttons = touch_report
879        .pressed_buttons()
880        .map(|buttons| buttons.iter().map(|&b| fidl_next::FromWire::from_wire(b)).collect())
881        .unwrap_or_default();
882
883    (
884        SortedVecMap::from_iter(contacts.into_iter().map(|contact| (contact.id, contact))),
885        pressed_buttons,
886    )
887}
888
889/// Create a TouchScreenEvent.
890///
891/// # Parameters
892/// - `contacts`: The contact points relevant to the new TouchScreenEvent.
893/// - `injector_contacts`: The contact points relevant to the new TouchScreenEvent, used to send
894///                        pointer events into Scenic.
895/// - `device_descriptor`: The descriptor for the input device generating the input reports.
896/// - `trace_id`: The trace id to distinguish the event.
897/// - `wake_lease`: The wake lease to send with the event.
898fn create_touch_screen_event(
899    contacts: SortedVecMap<fidl_ui_input::PointerEventPhase, Vec<TouchContact>>,
900    injector_contacts: SortedVecMap<pointerinjector::EventPhase, Vec<TouchContact>>,
901    pressed_buttons: Vec<fidl_next_fuchsia_input_report::TouchButton>,
902    device_descriptor: &input_device::InputDeviceDescriptor,
903    trace_id: fuchsia_trace::Id,
904    wake_lease: Option<zx::EventPair>,
905) -> InputEvent {
906    input_device::InputEvent {
907        device_event: input_device::InputDeviceEvent::TouchScreen(TouchScreenEvent {
908            contacts,
909            injector_contacts,
910            pressed_buttons,
911            wake_lease,
912        }),
913        device_descriptor: device_descriptor.clone(),
914        event_time: zx::MonotonicInstant::get(),
915        handled: Handled::No,
916        trace_id: Some(trace_id),
917    }
918}
919
920/// [`get_device_type`] check if the touch device is a touchscreen or Windows Precision Touchpad.
921///
922/// Windows Precision Touchpad reports `MouseCollection` or `WindowsPrecisionTouchpadCollection`
923/// in `TouchFeatureReport`. Fallback all error responses on `get_feature_report` to TouchScreen
924/// because some touch screen does not report this method.
925async fn get_device_type(
926    input_device: &fidl_next::Client<fidl_next_fuchsia_input_report::InputDevice, Transport>,
927) -> TouchDeviceType {
928    match input_device.get_feature_report().await {
929        Ok(Ok(fidl_next_fuchsia_input_report::InputDeviceGetFeatureReportResponse {
930            report: fidl_next_fuchsia_input_report::FeatureReport {
931                touch:
932                    Some(fidl_next_fuchsia_input_report::TouchFeatureReport {
933                        input_mode:
934                            Some(
935                                fidl_next_fuchsia_input_report::TouchConfigurationInputMode::MouseCollection
936                                | fidl_next_fuchsia_input_report::TouchConfigurationInputMode::WindowsPrecisionTouchpadCollection,
937                            ),
938                        ..
939                    }),
940                ..
941            }
942        })) => TouchDeviceType::WindowsPrecisionTouchpad,
943        _ => TouchDeviceType::TouchScreen,
944    }
945}
946
947#[cfg(test)]
948mod tests {
949    use super::*;
950    use crate::testing_utilities::{
951        self, create_touch_contact, create_touch_input_report, create_touch_screen_event,
952        create_touch_screen_event_with_buttons, spawn_input_stream_handler,
953    };
954    use crate::utils::Position;
955    use assert_matches::assert_matches;
956    use diagnostics_assertions::AnyProperty;
957    use fuchsia_async as fasync;
958    use futures::StreamExt;
959    use pretty_assertions::assert_eq;
960    use test_case::test_case;
961
962    #[fasync::run_singlethreaded(test)]
963    async fn process_empty_reports() {
964        let report_time = zx::MonotonicInstant::get().into_nanos();
965        let report =
966            create_touch_input_report(vec![], /* pressed_buttons= */ None, report_time);
967
968        let descriptor =
969            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
970                device_id: 1,
971                contacts: vec![],
972            });
973        let (mut event_sender, mut event_receiver) = futures::channel::mpsc::unbounded();
974
975        let inspector = fuchsia_inspect::Inspector::default();
976        let test_node = inspector.root().create_child("TestDevice_Touch");
977        let mut inspect_status = InputDeviceStatus::new(test_node);
978        inspect_status.health_node.set_ok();
979
980        let previous_state = input_device::PreviousDeviceState::TouchScreen {
981            active_contacts: vec![],
982            pressed_buttons: vec![],
983        };
984
985        let reports_wire = crate::testing_utilities::reports_to_wire(vec![report]);
986        let (returned_state, _) = TouchBinding::process_reports(
987            &reports_wire,
988            Some(previous_state),
989            &descriptor,
990            &mut event_sender,
991            &inspect_status,
992            &metrics::MetricsLogger::default(),
993            &input_device::InputPipelineFeatureFlags::default(),
994        );
995        assert!(returned_state.is_some());
996        assert_eq!(
997            returned_state.unwrap(),
998            input_device::PreviousDeviceState::TouchScreen {
999                active_contacts: vec![],
1000                pressed_buttons: vec![]
1001            }
1002        );
1003
1004        // Assert there are no pending events on the receiver.
1005        let event = event_receiver.try_next();
1006        assert!(event.is_err());
1007
1008        diagnostics_assertions::assert_data_tree!(inspector, root: {
1009            "TestDevice_Touch": contains {
1010                reports_received_count: 1u64,
1011                reports_filtered_count: 1u64,
1012                events_generated: 0u64,
1013                last_received_timestamp_ns: report_time as u64,
1014                last_generated_timestamp_ns: 0u64,
1015                "fuchsia.inspect.Health": {
1016                    status: "OK",
1017                    // Timestamp value is unpredictable and not relevant in this context,
1018                    // so we only assert that the property is present.
1019                    start_timestamp_nanos: AnyProperty
1020                },
1021            }
1022        });
1023    }
1024
1025    // Tests that a input report with a new contact generates an event with an add and a down.
1026    #[fasync::run_singlethreaded(test)]
1027    async fn add_and_down() {
1028        const TOUCH_ID: u32 = 2;
1029
1030        let descriptor =
1031            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1032                device_id: 1,
1033                contacts: vec![],
1034            });
1035        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1036
1037        let contact = fidl_fuchsia_input_report::ContactInputReport {
1038            contact_id: Some(TOUCH_ID),
1039            position_x: Some(0),
1040            position_y: Some(0),
1041            pressure: None,
1042            contact_width: None,
1043            contact_height: None,
1044            ..Default::default()
1045        };
1046        let reports = vec![create_touch_input_report(
1047            vec![contact],
1048            /* pressed_buttons= */ None,
1049            event_time_i64,
1050        )];
1051
1052        let expected_events = vec![create_touch_screen_event(
1053            SortedVecMap::from_iter(vec![
1054                (
1055                    fidl_ui_input::PointerEventPhase::Add,
1056                    vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1057                ),
1058                (
1059                    fidl_ui_input::PointerEventPhase::Down,
1060                    vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1061                ),
1062            ]),
1063            event_time_u64,
1064            &descriptor,
1065        )];
1066
1067        assert_input_report_sequence_generates_events!(
1068            input_reports: reports,
1069            expected_events: expected_events,
1070            device_descriptor: descriptor,
1071            device_type: TouchBinding,
1072        );
1073    }
1074
1075    // Tests that up and remove events are sent when a touch is released.
1076    #[fasync::run_singlethreaded(test)]
1077    async fn up_and_remove() {
1078        const TOUCH_ID: u32 = 2;
1079
1080        let descriptor =
1081            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1082                device_id: 1,
1083                contacts: vec![],
1084            });
1085        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1086
1087        let contact = fidl_fuchsia_input_report::ContactInputReport {
1088            contact_id: Some(TOUCH_ID),
1089            position_x: Some(0),
1090            position_y: Some(0),
1091            pressure: None,
1092            contact_width: None,
1093            contact_height: None,
1094            ..Default::default()
1095        };
1096        let reports = vec![
1097            create_touch_input_report(
1098                vec![contact],
1099                /* pressed_buttons= */ None,
1100                event_time_i64,
1101            ),
1102            create_touch_input_report(vec![], /* pressed_buttons= */ None, event_time_i64),
1103        ];
1104
1105        let expected_events = vec![
1106            create_touch_screen_event(
1107                SortedVecMap::from_iter(vec![
1108                    (
1109                        fidl_ui_input::PointerEventPhase::Add,
1110                        vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1111                    ),
1112                    (
1113                        fidl_ui_input::PointerEventPhase::Down,
1114                        vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1115                    ),
1116                ]),
1117                event_time_u64,
1118                &descriptor,
1119            ),
1120            create_touch_screen_event(
1121                SortedVecMap::from_iter(vec![
1122                    (
1123                        fidl_ui_input::PointerEventPhase::Up,
1124                        vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1125                    ),
1126                    (
1127                        fidl_ui_input::PointerEventPhase::Remove,
1128                        vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1129                    ),
1130                ]),
1131                event_time_u64,
1132                &descriptor,
1133            ),
1134        ];
1135
1136        assert_input_report_sequence_generates_events!(
1137            input_reports: reports,
1138            expected_events: expected_events,
1139            device_descriptor: descriptor,
1140            device_type: TouchBinding,
1141        );
1142    }
1143
1144    // Tests that a move generates the correct event.
1145    #[fasync::run_singlethreaded(test)]
1146    async fn add_down_move() {
1147        const TOUCH_ID: u32 = 2;
1148        let first = Position { x: 10.0, y: 30.0 };
1149        let second = Position { x: first.x * 2.0, y: first.y * 2.0 };
1150
1151        let descriptor =
1152            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1153                device_id: 1,
1154                contacts: vec![],
1155            });
1156        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1157
1158        let first_contact = fidl_fuchsia_input_report::ContactInputReport {
1159            contact_id: Some(TOUCH_ID),
1160            position_x: Some(first.x as i64),
1161            position_y: Some(first.y as i64),
1162            pressure: None,
1163            contact_width: None,
1164            contact_height: None,
1165            ..Default::default()
1166        };
1167        let second_contact = fidl_fuchsia_input_report::ContactInputReport {
1168            contact_id: Some(TOUCH_ID),
1169            position_x: Some(first.x as i64 * 2),
1170            position_y: Some(first.y as i64 * 2),
1171            pressure: None,
1172            contact_width: None,
1173            contact_height: None,
1174            ..Default::default()
1175        };
1176
1177        let reports = vec![
1178            create_touch_input_report(
1179                vec![first_contact],
1180                /* pressed_buttons= */ None,
1181                event_time_i64,
1182            ),
1183            create_touch_input_report(
1184                vec![second_contact],
1185                /* pressed_buttons= */ None,
1186                event_time_i64,
1187            ),
1188        ];
1189
1190        let expected_events = vec![
1191            create_touch_screen_event(
1192                SortedVecMap::from_iter(vec![
1193                    (
1194                        fidl_ui_input::PointerEventPhase::Add,
1195                        vec![create_touch_contact(TOUCH_ID, first)],
1196                    ),
1197                    (
1198                        fidl_ui_input::PointerEventPhase::Down,
1199                        vec![create_touch_contact(TOUCH_ID, first)],
1200                    ),
1201                ]),
1202                event_time_u64,
1203                &descriptor,
1204            ),
1205            create_touch_screen_event(
1206                SortedVecMap::from_iter(vec![(
1207                    fidl_ui_input::PointerEventPhase::Move,
1208                    vec![create_touch_contact(TOUCH_ID, second)],
1209                )]),
1210                event_time_u64,
1211                &descriptor,
1212            ),
1213        ];
1214
1215        assert_input_report_sequence_generates_events!(
1216            input_reports: reports,
1217            expected_events: expected_events,
1218            device_descriptor: descriptor,
1219            device_type: TouchBinding,
1220        );
1221    }
1222
1223    #[fasync::run_singlethreaded(test)]
1224    async fn sent_event_has_trace_id() {
1225        let report_time = zx::MonotonicInstant::get().into_nanos();
1226        let contact = fidl_fuchsia_input_report::ContactInputReport {
1227            contact_id: Some(222),
1228            position_x: Some(333),
1229            position_y: Some(444),
1230            ..Default::default()
1231        };
1232        let report =
1233            create_touch_input_report(vec![contact], /* pressed_buttons= */ None, report_time);
1234
1235        let descriptor =
1236            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1237                device_id: 1,
1238                contacts: vec![],
1239            });
1240        let (mut event_sender, mut event_receiver) = futures::channel::mpsc::unbounded();
1241
1242        let inspector = fuchsia_inspect::Inspector::default();
1243        let test_node = inspector.root().create_child("TestDevice_Touch");
1244        let mut inspect_status = InputDeviceStatus::new(test_node);
1245        inspect_status.health_node.set_ok();
1246
1247        let previous_state = input_device::PreviousDeviceState::TouchScreen {
1248            active_contacts: vec![],
1249            pressed_buttons: vec![],
1250        };
1251
1252        let reports_wire = crate::testing_utilities::reports_to_wire(vec![report]);
1253        let _ = TouchBinding::process_reports(
1254            &reports_wire,
1255            Some(previous_state),
1256            &descriptor,
1257            &mut event_sender,
1258            &inspect_status,
1259            &metrics::MetricsLogger::default(),
1260            &input_device::InputPipelineFeatureFlags::default(),
1261        );
1262        assert_matches!(event_receiver.try_next(), Ok(Some(events)) if events.len() == 1 && events[0].trace_id.is_some());
1263    }
1264
1265    #[fuchsia::test(allow_stalls = false)]
1266    async fn enables_touchpad_mode_automatically() {
1267        let (set_feature_report_sender, set_feature_report_receiver) =
1268            futures::channel::mpsc::unbounded();
1269        let input_device_proxy = spawn_input_stream_handler(move |input_device_request| {
1270            let set_feature_report_sender = set_feature_report_sender.clone();
1271            async move {
1272                match input_device_request {
1273                    fidl_input_report::InputDeviceRequest::GetDescriptor { responder } => {
1274                        let _ = responder.send(&get_touchpad_device_descriptor(
1275                            true, /* has_mouse_descriptor */
1276                        ));
1277                    }
1278                    fidl_input_report::InputDeviceRequest::GetFeatureReport { responder } => {
1279                        let _ = responder.send(Ok(&fidl_input_report::FeatureReport {
1280                            touch: Some(fidl_input_report::TouchFeatureReport {
1281                                input_mode: Some(
1282                                    fidl_input_report::TouchConfigurationInputMode::MouseCollection,
1283                                ),
1284                                ..Default::default()
1285                            }),
1286                            ..Default::default()
1287                        }));
1288                    }
1289                    fidl_input_report::InputDeviceRequest::SetFeatureReport {
1290                        responder,
1291                        report,
1292                    } => {
1293                        match set_feature_report_sender.unbounded_send(report) {
1294                            Ok(_) => {
1295                                let _ = responder.send(Ok(()));
1296                            }
1297                            Err(e) => {
1298                                panic!("try_send set_feature_report_request failed: {}", e);
1299                            }
1300                        };
1301                    }
1302                    fidl_input_report::InputDeviceRequest::GetInputReportsReader { .. } => {
1303                        // Do not panic as `initialize_report_stream()` will call this protocol.
1304                    }
1305                    r => panic!("unsupported request {:?}", r),
1306                }
1307            }
1308        });
1309
1310        let (device_event_sender, _) = futures::channel::mpsc::unbounded();
1311
1312        // Create a test inspect node as required by TouchBinding::new()
1313        let inspector = fuchsia_inspect::Inspector::default();
1314        let test_node = inspector.root().create_child("test_node");
1315
1316        // Create a `TouchBinding` to exercise its call to `SetFeatureReport`. But drop
1317        // the binding immediately, so that `set_feature_report_receiver.collect()`
1318        // does not hang.
1319        TouchBinding::new(
1320            input_device_proxy,
1321            0,
1322            device_event_sender,
1323            test_node,
1324            input_device::InputPipelineFeatureFlags::default(),
1325            metrics::MetricsLogger::default(),
1326        )
1327        .await
1328        .unwrap();
1329        assert_matches!(
1330            set_feature_report_receiver.collect::<Vec<_>>().await.as_slice(),
1331            [fidl_input_report::FeatureReport {
1332                touch: Some(fidl_input_report::TouchFeatureReport {
1333                    input_mode: Some(
1334                        fidl_input_report::TouchConfigurationInputMode::WindowsPrecisionTouchpadCollection
1335                    ),
1336                    ..
1337                }),
1338                ..
1339            }]
1340        );
1341    }
1342
1343    #[test_case(true, None, TouchDeviceType::TouchScreen; "touch screen")]
1344    #[test_case(false, None, TouchDeviceType::TouchScreen; "no mouse descriptor, no touch_input_mode")]
1345    #[test_case(true, Some(fidl_input_report::TouchConfigurationInputMode::MouseCollection), TouchDeviceType::WindowsPrecisionTouchpad; "touchpad in mouse mode")]
1346    #[test_case(true, Some(fidl_input_report::TouchConfigurationInputMode::WindowsPrecisionTouchpadCollection), TouchDeviceType::WindowsPrecisionTouchpad; "touchpad in touchpad mode")]
1347    #[fuchsia::test(allow_stalls = false)]
1348    async fn identifies_correct_touch_device_type(
1349        has_mouse_descriptor: bool,
1350        touch_input_mode: Option<fidl_input_report::TouchConfigurationInputMode>,
1351        expect_touch_device_type: TouchDeviceType,
1352    ) {
1353        let input_device_proxy =
1354            spawn_input_stream_handler(move |input_device_request| async move {
1355                match input_device_request {
1356                    fidl_input_report::InputDeviceRequest::GetDescriptor { responder } => {
1357                        let _ =
1358                            responder.send(&get_touchpad_device_descriptor(has_mouse_descriptor));
1359                    }
1360                    fidl_input_report::InputDeviceRequest::GetFeatureReport { responder } => {
1361                        let _ = responder.send(Ok(&fidl_input_report::FeatureReport {
1362                            touch: Some(fidl_input_report::TouchFeatureReport {
1363                                input_mode: touch_input_mode,
1364                                ..Default::default()
1365                            }),
1366                            ..Default::default()
1367                        }));
1368                    }
1369                    fidl_input_report::InputDeviceRequest::SetFeatureReport {
1370                        responder, ..
1371                    } => {
1372                        let _ = responder.send(Ok(()));
1373                    }
1374                    r => panic!("unsupported request {:?}", r),
1375                }
1376            });
1377
1378        let (device_event_sender, _) = futures::channel::mpsc::unbounded();
1379
1380        // Create a test inspect node as required by TouchBinding::new()
1381        let inspector = fuchsia_inspect::Inspector::default();
1382        let test_node = inspector.root().create_child("test_node");
1383
1384        let binding = TouchBinding::new(
1385            input_device_proxy,
1386            0,
1387            device_event_sender,
1388            test_node,
1389            input_device::InputPipelineFeatureFlags::default(),
1390            metrics::MetricsLogger::default(),
1391        )
1392        .await
1393        .unwrap();
1394        pretty_assertions::assert_eq!(binding.touch_device_type, expect_touch_device_type);
1395    }
1396
1397    /// Returns an |fidl_fuchsia_input_report::DeviceDescriptor| for
1398    /// touchpad related tests.
1399    fn get_touchpad_device_descriptor(
1400        has_mouse_descriptor: bool,
1401    ) -> fidl_fuchsia_input_report::DeviceDescriptor {
1402        fidl_input_report::DeviceDescriptor {
1403            mouse: match has_mouse_descriptor {
1404                true => Some(fidl_input_report::MouseDescriptor::default()),
1405                false => None,
1406            },
1407            touch: Some(fidl_input_report::TouchDescriptor {
1408                input: Some(fidl_input_report::TouchInputDescriptor {
1409                    contacts: Some(vec![fidl_input_report::ContactInputDescriptor {
1410                        position_x: Some(fidl_input_report::Axis {
1411                            range: fidl_input_report::Range { min: 1, max: 2 },
1412                            unit: fidl_input_report::Unit {
1413                                type_: fidl_input_report::UnitType::None,
1414                                exponent: 0,
1415                            },
1416                        }),
1417                        position_y: Some(fidl_input_report::Axis {
1418                            range: fidl_input_report::Range { min: 2, max: 3 },
1419                            unit: fidl_input_report::Unit {
1420                                type_: fidl_input_report::UnitType::Other,
1421                                exponent: 100000,
1422                            },
1423                        }),
1424                        pressure: Some(fidl_input_report::Axis {
1425                            range: fidl_input_report::Range { min: 3, max: 4 },
1426                            unit: fidl_input_report::Unit {
1427                                type_: fidl_input_report::UnitType::Grams,
1428                                exponent: -991,
1429                            },
1430                        }),
1431                        contact_width: Some(fidl_input_report::Axis {
1432                            range: fidl_input_report::Range { min: 5, max: 6 },
1433                            unit: fidl_input_report::Unit {
1434                                type_: fidl_input_report::UnitType::EnglishAngularVelocity,
1435                                exponent: 123,
1436                            },
1437                        }),
1438                        contact_height: Some(fidl_input_report::Axis {
1439                            range: fidl_input_report::Range { min: 7, max: 8 },
1440                            unit: fidl_input_report::Unit {
1441                                type_: fidl_input_report::UnitType::Pascals,
1442                                exponent: 100,
1443                            },
1444                        }),
1445                        ..Default::default()
1446                    }]),
1447                    ..Default::default()
1448                }),
1449                ..Default::default()
1450            }),
1451            ..Default::default()
1452        }
1453    }
1454
1455    // Tests that a pressed button with no contacts generates an event with the
1456    // button.
1457    #[test_case(true; "merge touch events enabled")]
1458    #[test_case(false; "merge touch events disabled")]
1459    #[fasync::run_singlethreaded(test)]
1460    async fn send_pressed_button_no_contact(enable_merge_touch_events: bool) {
1461        let descriptor =
1462            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1463                device_id: 1,
1464                contacts: vec![],
1465            });
1466        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1467
1468        let reports = vec![create_touch_input_report(
1469            vec![],
1470            Some(vec![fidl_fuchsia_input_report::TouchButton::Palm]),
1471            event_time_i64,
1472        )];
1473
1474        let expected_events = vec![create_touch_screen_event_with_buttons(
1475            SortedVecMap::new(),
1476            vec![fidl_fuchsia_input_report::TouchButton::Palm],
1477            event_time_u64,
1478            &descriptor,
1479        )];
1480
1481        assert_input_report_sequence_generates_events_with_feature_flags!(
1482            input_reports: reports,
1483            expected_events: expected_events,
1484            device_descriptor: descriptor,
1485            device_type: TouchBinding,
1486            feature_flags: input_device::InputPipelineFeatureFlags {
1487                enable_merge_touch_events,
1488                ..Default::default()
1489            },
1490        );
1491    }
1492
1493    // Tests that a pressed button with a contact generates an event with
1494    // contact and button.
1495    #[test_case(true; "merge touch events enabled")]
1496    #[test_case(false; "merge touch events disabled")]
1497    #[fasync::run_singlethreaded(test)]
1498    async fn send_pressed_button_with_contact(enable_merge_touch_events: bool) {
1499        const TOUCH_ID: u32 = 2;
1500
1501        let descriptor =
1502            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1503                device_id: 1,
1504                contacts: vec![],
1505            });
1506        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1507
1508        let contact = fidl_fuchsia_input_report::ContactInputReport {
1509            contact_id: Some(TOUCH_ID),
1510            position_x: Some(0),
1511            position_y: Some(0),
1512            pressure: None,
1513            contact_width: None,
1514            contact_height: None,
1515            ..Default::default()
1516        };
1517        let reports = vec![create_touch_input_report(
1518            vec![contact],
1519            Some(vec![fidl_fuchsia_input_report::TouchButton::Palm]),
1520            event_time_i64,
1521        )];
1522
1523        let expected_events = vec![create_touch_screen_event_with_buttons(
1524            SortedVecMap::from_iter(vec![
1525                (
1526                    fidl_ui_input::PointerEventPhase::Add,
1527                    vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1528                ),
1529                (
1530                    fidl_ui_input::PointerEventPhase::Down,
1531                    vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1532                ),
1533            ]),
1534            vec![fidl_fuchsia_input_report::TouchButton::Palm],
1535            event_time_u64,
1536            &descriptor,
1537        )];
1538
1539        assert_input_report_sequence_generates_events_with_feature_flags!(
1540            input_reports: reports,
1541            expected_events: expected_events,
1542            device_descriptor: descriptor,
1543            device_type: TouchBinding,
1544            feature_flags: input_device::InputPipelineFeatureFlags {
1545                enable_merge_touch_events,
1546                ..Default::default()
1547            },
1548        );
1549    }
1550
1551    // Tests that multiple pressed buttons with contacts generates an event
1552    // with contact and buttons.
1553    #[test_case(true; "merge touch events enabled")]
1554    #[test_case(false; "merge touch events disabled")]
1555    #[fasync::run_singlethreaded(test)]
1556    async fn send_multiple_pressed_buttons_with_contact(enable_merge_touch_events: bool) {
1557        const TOUCH_ID: u32 = 2;
1558
1559        let descriptor =
1560            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1561                device_id: 1,
1562                contacts: vec![],
1563            });
1564        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1565
1566        let contact = fidl_fuchsia_input_report::ContactInputReport {
1567            contact_id: Some(TOUCH_ID),
1568            position_x: Some(0),
1569            position_y: Some(0),
1570            pressure: None,
1571            contact_width: None,
1572            contact_height: None,
1573            ..Default::default()
1574        };
1575        let reports = vec![create_touch_input_report(
1576            vec![contact],
1577            Some(vec![
1578                fidl_fuchsia_input_report::TouchButton::Palm,
1579                fidl_fuchsia_input_report::TouchButton::__SourceBreaking { unknown_ordinal: 2 },
1580            ]),
1581            event_time_i64,
1582        )];
1583
1584        let expected_events = vec![create_touch_screen_event_with_buttons(
1585            SortedVecMap::from_iter(vec![
1586                (
1587                    fidl_ui_input::PointerEventPhase::Add,
1588                    vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1589                ),
1590                (
1591                    fidl_ui_input::PointerEventPhase::Down,
1592                    vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1593                ),
1594            ]),
1595            vec![
1596                fidl_fuchsia_input_report::TouchButton::Palm,
1597                fidl_fuchsia_input_report::TouchButton::__SourceBreaking { unknown_ordinal: 2 },
1598            ],
1599            event_time_u64,
1600            &descriptor,
1601        )];
1602
1603        assert_input_report_sequence_generates_events_with_feature_flags!(
1604            input_reports: reports,
1605            expected_events: expected_events,
1606            device_descriptor: descriptor,
1607            device_type: TouchBinding,
1608            feature_flags: input_device::InputPipelineFeatureFlags {
1609                enable_merge_touch_events,
1610                ..Default::default()
1611            },
1612        );
1613    }
1614
1615    // Tests that no buttons and no contacts generates no events.
1616    #[test_case(true; "merge touch events enabled")]
1617    #[test_case(false; "merge touch events disabled")]
1618    #[fasync::run_singlethreaded(test)]
1619    async fn send_no_buttons_no_contacts(enable_merge_touch_events: bool) {
1620        let descriptor =
1621            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1622                device_id: 1,
1623                contacts: vec![],
1624            });
1625        let (event_time_i64, _) = testing_utilities::event_times();
1626
1627        let reports = vec![create_touch_input_report(vec![], Some(vec![]), event_time_i64)];
1628
1629        let expected_events: Vec<input_device::InputEvent> = vec![];
1630
1631        assert_input_report_sequence_generates_events_with_feature_flags!(
1632            input_reports: reports,
1633            expected_events: expected_events,
1634            device_descriptor: descriptor,
1635            device_type: TouchBinding,
1636            feature_flags: input_device::InputPipelineFeatureFlags {
1637                enable_merge_touch_events,
1638                ..Default::default()
1639            },
1640        );
1641    }
1642
1643    // Tests a buttons event after a contact event does not remove contacts.
1644    #[test_case(true; "merge touch events enabled")]
1645    #[test_case(false; "merge touch events disabled")]
1646    #[fasync::run_singlethreaded(test)]
1647    async fn send_button_does_not_remove_contacts(enable_merge_touch_events: bool) {
1648        const TOUCH_ID: u32 = 2;
1649
1650        let descriptor =
1651            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1652                device_id: 1,
1653                contacts: vec![],
1654            });
1655        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1656
1657        let contact = fidl_fuchsia_input_report::ContactInputReport {
1658            contact_id: Some(TOUCH_ID),
1659            position_x: Some(0),
1660            position_y: Some(0),
1661            pressure: None,
1662            contact_width: None,
1663            contact_height: None,
1664            ..Default::default()
1665        };
1666        let reports = vec![
1667            create_touch_input_report(vec![contact], None, event_time_i64),
1668            create_touch_input_report(
1669                vec![],
1670                Some(vec![fidl_fuchsia_input_report::TouchButton::Palm]),
1671                event_time_i64,
1672            ),
1673            create_touch_input_report(vec![], Some(vec![]), event_time_i64),
1674        ];
1675
1676        let expected_events = vec![
1677            create_touch_screen_event_with_buttons(
1678                SortedVecMap::from_iter(vec![
1679                    (
1680                        fidl_ui_input::PointerEventPhase::Add,
1681                        vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1682                    ),
1683                    (
1684                        fidl_ui_input::PointerEventPhase::Down,
1685                        vec![create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 })],
1686                    ),
1687                ]),
1688                vec![],
1689                event_time_u64,
1690                &descriptor,
1691            ),
1692            create_touch_screen_event_with_buttons(
1693                SortedVecMap::new(),
1694                vec![fidl_fuchsia_input_report::TouchButton::Palm],
1695                event_time_u64,
1696                &descriptor,
1697            ),
1698            create_touch_screen_event_with_buttons(
1699                SortedVecMap::new(),
1700                vec![],
1701                event_time_u64,
1702                &descriptor,
1703            ),
1704        ];
1705
1706        assert_input_report_sequence_generates_events_with_feature_flags!(
1707            input_reports: reports,
1708            expected_events: expected_events,
1709            device_descriptor: descriptor,
1710            device_type: TouchBinding,
1711            feature_flags: input_device::InputPipelineFeatureFlags {
1712                enable_merge_touch_events,
1713                ..Default::default()
1714            },
1715        );
1716    }
1717
1718    #[fasync::run_singlethreaded(test)]
1719    async fn process_reports_batches_events() {
1720        const TOUCH_ID: u32 = 2;
1721
1722        let descriptor =
1723            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1724                device_id: 1,
1725                contacts: vec![],
1726            });
1727        let (event_time_i64, _) = testing_utilities::event_times();
1728
1729        let contact1 = fidl_fuchsia_input_report::ContactInputReport {
1730            contact_id: Some(TOUCH_ID),
1731            position_x: Some(0),
1732            position_y: Some(0),
1733            ..Default::default()
1734        };
1735        let contact2 = fidl_fuchsia_input_report::ContactInputReport {
1736            contact_id: Some(TOUCH_ID),
1737            position_x: Some(10),
1738            position_y: Some(10),
1739            ..Default::default()
1740        };
1741        let reports = vec![
1742            create_touch_input_report(vec![contact1], None, event_time_i64),
1743            create_touch_input_report(vec![contact2], None, event_time_i64),
1744        ];
1745
1746        let (mut event_sender, mut event_receiver) = futures::channel::mpsc::unbounded();
1747
1748        let inspector = fuchsia_inspect::Inspector::default();
1749        let test_node = inspector.root().create_child("TestDevice_Touch");
1750        let mut inspect_status = InputDeviceStatus::new(test_node);
1751        inspect_status.health_node.set_ok();
1752
1753        let reports_wire = crate::testing_utilities::reports_to_wire(reports);
1754        let _ = TouchBinding::process_reports(
1755            &reports_wire,
1756            None,
1757            &descriptor,
1758            &mut event_sender,
1759            &inspect_status,
1760            &metrics::MetricsLogger::default(),
1761            &input_device::InputPipelineFeatureFlags::default(),
1762        );
1763
1764        // Expect EXACTLY one batch containing two events.
1765        let batch = event_receiver.try_next().expect("Expected a batch of events");
1766        let events = batch.expect("Expected events in the batch");
1767        assert_eq!(events.len(), 2);
1768
1769        // Verify no more batches.
1770        assert!(event_receiver.try_next().is_err());
1771    }
1772
1773    #[fasync::run_singlethreaded(test)]
1774    async fn process_reports_merges_touch_events_when_enabled() {
1775        const TOUCH_ID: u32 = 2;
1776        let descriptor =
1777            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1778                device_id: 1,
1779                contacts: vec![],
1780            });
1781        let (event_time_i64, _) = testing_utilities::event_times();
1782
1783        let contact_add = fidl_fuchsia_input_report::ContactInputReport {
1784            contact_id: Some(TOUCH_ID),
1785            position_x: Some(0),
1786            position_y: Some(0),
1787            ..Default::default()
1788        };
1789        let contact_move1 = fidl_fuchsia_input_report::ContactInputReport {
1790            contact_id: Some(TOUCH_ID),
1791            position_x: Some(10),
1792            position_y: Some(10),
1793            ..Default::default()
1794        };
1795        let contact_move2 = fidl_fuchsia_input_report::ContactInputReport {
1796            contact_id: Some(TOUCH_ID),
1797            position_x: Some(20),
1798            position_y: Some(20),
1799            ..Default::default()
1800        };
1801        let contact_move3 = fidl_fuchsia_input_report::ContactInputReport {
1802            contact_id: Some(TOUCH_ID),
1803            position_x: Some(30),
1804            position_y: Some(30),
1805            ..Default::default()
1806        };
1807        let reports = vec![
1808            create_touch_input_report(vec![contact_add], None, event_time_i64),
1809            create_touch_input_report(vec![contact_move1], None, event_time_i64),
1810            create_touch_input_report(vec![contact_move2], None, event_time_i64),
1811            create_touch_input_report(vec![contact_move3], None, event_time_i64),
1812            create_touch_input_report(vec![], None, event_time_i64),
1813        ];
1814
1815        let (mut event_sender, mut event_receiver) = futures::channel::mpsc::unbounded();
1816        let inspector = fuchsia_inspect::Inspector::default();
1817        let mut inspect_status =
1818            InputDeviceStatus::new(inspector.root().create_child("TestDevice_Touch"));
1819        inspect_status.health_node.set_ok();
1820
1821        let reports_wire = crate::testing_utilities::reports_to_wire(reports);
1822        let _ = TouchBinding::process_reports(
1823            &reports_wire,
1824            None,
1825            &descriptor,
1826            &mut event_sender,
1827            &inspect_status,
1828            &metrics::MetricsLogger::default(),
1829            &input_device::InputPipelineFeatureFlags {
1830                enable_merge_touch_events: true,
1831                ..Default::default()
1832            },
1833        );
1834
1835        let batch = event_receiver.try_next().unwrap().unwrap();
1836
1837        // Expected events: Add, Move(30), Remove.
1838        assert_eq!(batch.len(), 3);
1839
1840        // Verify Add event
1841        assert_matches!(
1842            &batch[0].device_event,
1843            input_device::InputDeviceEvent::TouchScreen(event)
1844                if event.injector_contacts.get(&pointerinjector::EventPhase::Add).is_some()
1845        );
1846        // Verify Move event (merged to the last one)
1847        assert_matches!(
1848            &batch[1].device_event,
1849            input_device::InputDeviceEvent::TouchScreen(event)
1850                if event.injector_contacts.get(&pointerinjector::EventPhase::Change).map(|c| c[0].position.x) == Some(30.0)
1851        );
1852        // Verify Remove event
1853        assert_matches!(
1854            &batch[2].device_event,
1855            input_device::InputDeviceEvent::TouchScreen(event)
1856                if event.injector_contacts.get(&pointerinjector::EventPhase::Remove).is_some()
1857        );
1858    }
1859
1860    #[fasync::run_singlethreaded(test)]
1861    async fn process_reports_does_not_merge_touch_events_when_disabled() {
1862        const TOUCH_ID: u32 = 2;
1863        let descriptor =
1864            input_device::InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
1865                device_id: 1,
1866                contacts: vec![],
1867            });
1868        let (event_time_i64, _) = testing_utilities::event_times();
1869
1870        let contact_add = fidl_fuchsia_input_report::ContactInputReport {
1871            contact_id: Some(TOUCH_ID),
1872            position_x: Some(0),
1873            position_y: Some(0),
1874            ..Default::default()
1875        };
1876        let contact_move1 = fidl_fuchsia_input_report::ContactInputReport {
1877            contact_id: Some(TOUCH_ID),
1878            position_x: Some(10),
1879            position_y: Some(10),
1880            ..Default::default()
1881        };
1882        let contact_move2 = fidl_fuchsia_input_report::ContactInputReport {
1883            contact_id: Some(TOUCH_ID),
1884            position_x: Some(20),
1885            position_y: Some(20),
1886            ..Default::default()
1887        };
1888        let contact_move3 = fidl_fuchsia_input_report::ContactInputReport {
1889            contact_id: Some(TOUCH_ID),
1890            position_x: Some(30),
1891            position_y: Some(30),
1892            ..Default::default()
1893        };
1894        let reports = vec![
1895            create_touch_input_report(vec![contact_add], None, event_time_i64),
1896            create_touch_input_report(vec![contact_move1], None, event_time_i64),
1897            create_touch_input_report(vec![contact_move2], None, event_time_i64),
1898            create_touch_input_report(vec![contact_move3], None, event_time_i64),
1899            create_touch_input_report(vec![], None, event_time_i64),
1900        ];
1901
1902        let (mut event_sender, mut event_receiver) = futures::channel::mpsc::unbounded();
1903        let inspector = fuchsia_inspect::Inspector::default();
1904        let mut inspect_status =
1905            InputDeviceStatus::new(inspector.root().create_child("TestDevice_Touch"));
1906        inspect_status.health_node.set_ok();
1907
1908        let reports_wire = crate::testing_utilities::reports_to_wire(reports);
1909        let _ = TouchBinding::process_reports(
1910            &reports_wire,
1911            None,
1912            &descriptor,
1913            &mut event_sender,
1914            &inspect_status,
1915            &metrics::MetricsLogger::default(),
1916            &input_device::InputPipelineFeatureFlags {
1917                enable_merge_touch_events: false,
1918                ..Default::default()
1919            },
1920        );
1921
1922        let batch = event_receiver.try_next().unwrap().unwrap();
1923
1924        // Expected events: Add, Move(10), Move(20), Move(30), Remove.
1925        assert_eq!(batch.len(), 5);
1926    }
1927}