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