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