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