Skip to main content

input_pipeline/gestures/
gesture_arena.rs

1// Copyright 2022 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 super::{
6    args, motion, one_finger_button, primary_tap, scroll, secondary_button, secondary_tap,
7};
8use crate::input_handler::{Handler, InputHandler, InputHandlerStatus};
9use crate::utils::Size;
10use crate::{input_device, metrics, mouse_binding, touch_binding};
11use anyhow::{Context, Error, format_err};
12use async_trait::async_trait;
13use core::cell::RefCell;
14use fidl_fuchsia_input_report as fidl_input_report;
15use fuchsia_inspect::health::Reporter;
16use fuchsia_inspect::{ArrayProperty, Node as InspectNode};
17use fuchsia_inspect_contrib::nodes::BoundedListNode;
18use metrics_registry::InputPipelineErrorMetricDimensionEvent;
19use std::any::Any;
20use std::fmt::Debug;
21
22struct GestureArenaInitialContenders {}
23
24impl ContenderFactory for GestureArenaInitialContenders {
25    fn make_contenders(&self) -> Vec<Box<dyn Contender>> {
26        vec![
27            Box::new(motion::InitialContender {
28                min_movement_in_mm: args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
29            }),
30            Box::new(primary_tap::InitialContender {
31                max_finger_displacement_in_mm: args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
32                max_time_elapsed: args::TAP_TIMEOUT,
33            }),
34            Box::new(secondary_tap::InitialContender {
35                max_finger_displacement_in_mm: args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
36                max_time_elapsed: args::TAP_TIMEOUT,
37            }),
38            Box::new(scroll::InitialContender {
39                motion_threshold_in_mm: args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
40                min_movement_in_mm: args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
41                max_movement_in_mm: args::MAX_SPURIOUS_TO_INTENTIONAL_SCROLL_THRESHOLD_MM,
42                limit_tangent_for_direction: args::MAX_SCROLL_DIRECTION_SKEW_DEGREES
43                    .to_radians()
44                    .tan(),
45            }),
46            Box::new(one_finger_button::InitialContender {
47                spurious_to_intentional_motion_threshold_mm:
48                    args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
49                spurious_to_intentional_motion_threshold_button_change_mm:
50                    args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
51                button_change_state_timeout: args::BUTTON_CHANGE_STATE_TIMEOUT,
52            }),
53            Box::new(secondary_button::InitialContender {
54                spurious_to_intentional_motion_threshold_mm:
55                    args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
56                spurious_to_intentional_motion_threshold_button_change_mm:
57                    args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
58                button_change_state_timeout: args::BUTTON_CHANGE_STATE_TIMEOUT,
59            }),
60        ]
61    }
62}
63
64pub fn make_input_handler(
65    inspect_node: &InspectNode,
66    input_handlers_node: &InspectNode,
67    metrics_logger: metrics::MetricsLogger,
68) -> std::rc::Rc<dyn crate::input_handler::BatchInputHandler> {
69    // TODO(https://fxbug.dev/42056283): Remove log message.
70    log::info!("touchpad: created input handler");
71    std::rc::Rc::new(GestureArena::new_internal(
72        Box::new(GestureArenaInitialContenders {}),
73        inspect_node,
74        MAX_TOUCHPAD_EVENT_LOG_ENTRIES,
75        input_handlers_node,
76        metrics_logger,
77    ))
78}
79
80pub(super) const PRIMARY_BUTTON: mouse_binding::MouseButton = 1;
81pub(super) const SECONDARY_BUTTON: mouse_binding::MouseButton = 2;
82
83// TODO(https://fxbug.dev/42053614): check that we've removed all leading `_` from types
84// and variables in this file.
85#[derive(Debug, Clone, PartialEq)]
86pub(super) struct TouchpadEvent {
87    pub(super) timestamp: zx::MonotonicInstant,
88    // TODO(https://fxbug.dev/42053615): replace these fields with a field that embeds
89    // `touch_data: super::touch_binding::TouchpadEvent`.
90    pub(super) pressed_buttons: Vec<u8>,
91    pub(super) contacts: Vec<touch_binding::TouchContact>,
92    pub(super) filtered_palm_contacts: Vec<touch_binding::TouchContact>,
93}
94
95#[derive(Debug, PartialEq)]
96pub(super) struct MouseEvent {
97    pub(super) timestamp: zx::MonotonicInstant,
98    pub(super) mouse_data: mouse_binding::MouseEvent,
99}
100
101#[derive(Debug)]
102pub(super) struct DetailedReasonUint {
103    pub(super) criterion: &'static str,
104    pub(super) min: Option<u64>,
105    pub(super) max: Option<u64>,
106    pub(super) actual: usize,
107}
108
109#[derive(Debug)]
110pub(super) struct DetailedReasonFloat {
111    pub(super) criterion: &'static str,
112    pub(super) min: Option<f32>,
113    pub(super) max: Option<f32>,
114    pub(super) actual: f32,
115}
116
117#[derive(Debug)]
118pub(super) struct DetailedReasonInt {
119    pub(super) criterion: &'static str,
120    pub(super) min: Option<i64>,
121    pub(super) max: Option<i64>,
122    pub(super) actual: i64,
123}
124
125#[derive(Debug)]
126pub(super) enum Reason {
127    Basic(&'static str),
128    DetailedUint(DetailedReasonUint),
129    DetailedFloat(DetailedReasonFloat),
130    DetailedInt(DetailedReasonInt),
131}
132
133#[derive(Debug)]
134pub(super) enum ExamineEventResult {
135    Contender(Box<dyn Contender>),
136    MatchedContender(Box<dyn MatchedContender>),
137    Mismatch(Reason),
138}
139
140pub(super) trait Contender: std::fmt::Debug + AsAny {
141    /// Examines `event`, to determine whether or not the gesture
142    /// is relevant to this `Recognizer`.
143    ///
144    /// Returns
145    /// * `ExamineEventResult::MatchedContender` if this recognizer wants
146    ///   to send (or start sending) events downstream, OR
147    /// * `ExamineEventResult::Contender` if this recognizer is not yet
148    ///   ready to send events downstream, but wants to continue
149    ///   contending for the gesture, OR
150    /// * `ExamineEventResult::Mismatch` if this recognizer no longer
151    ///   wants to contend for this gesture
152    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult;
153
154    /// Returns a string that uniquely identifies the concrete type
155    /// of this implementation of the `Contender` trait.
156    fn get_type_name(&self) -> &'static str {
157        std::any::type_name::<Self>()
158    }
159
160    /// If the gesture need to start from idle(no finger contacting the surface).
161    fn start_from_idle(&self) -> bool {
162        false
163    }
164}
165
166pub trait AsAny {
167    #[allow(dead_code)] // only used in test
168    fn as_any(&self) -> &dyn Any;
169}
170
171impl<T: Any> AsAny for T {
172    fn as_any(&self) -> &dyn Any {
173        self
174    }
175}
176
177#[derive(Debug)]
178pub(super) enum VerifyEventResult {
179    MatchedContender(Box<dyn MatchedContender>),
180    Mismatch(Reason),
181}
182
183#[derive(Clone, Copy, Debug, PartialEq)]
184pub(super) enum RecognizedGesture {
185    /// Contains one variant for each recognizer, and the
186    /// special value `Unrecognized` for when no recognizer
187    /// claims the gesture.
188    _Unrecognized,
189    PrimaryTap,
190    SecondaryTap,
191    Motion,
192    Scroll,
193    OneButtonDown,
194    SecondaryButtonDown,
195}
196
197#[derive(Debug)]
198pub(super) struct ProcessBufferedEventsResult {
199    pub(super) generated_events: Vec<MouseEvent>,
200    pub(super) winner: Option<Box<dyn Winner>>,
201    pub(super) recognized_gesture: RecognizedGesture, // for latency breakdown
202}
203
204pub(super) trait MatchedContender: std::fmt::Debug + AsAny {
205    /// Verifies that `event` still matches the gesture that is relevant
206    /// to this `Recognizer`.
207    ///
208    /// Returns
209    /// * `VerifyEventResult::MatchedContender` if this recognizer wants
210    ///   to send (or start sending) events downstream, OR
211    /// * `VerifyEventResult::Mismatch` if this recognizer no longer
212    ///   wants to contend for this gesture
213    fn verify_event(self: Box<Self>, event: &TouchpadEvent) -> VerifyEventResult;
214
215    /// Takes `events`, and generates corresponding `MouseEvent`s.
216    ///
217    /// Returns `ProcessBufferedEventsResult` with fields:
218    /// * `generated_events`: the sequence of `MouseEvent`s needed
219    ///   to effect the gesture downstream.
220    /// * `winner`:
221    ///   * `None` if the gesture is complete
222    ///   * `Some` otherwise
223    ///
224    /// Note:
225    /// * `generated_events` MAY be empty; for example, a palm
226    ///   recognizer wants to discard unintended events
227    /// * `events` is guaranteed to contains exactly the sequence of
228    ///   `TouchpadEvent`s that this recognizer has already examined
229    ///   and verified.
230    /// * recognizers MAY choose to ignore `events`
231    ///   e.g.:
232    ///   * a one-finger-tap recognizer does not need to inspect
233    ///     `events` to generate the `MouseEvent`s for the button click
234    ///   * a motion recognizer, in contrast, needs the details in
235    ///     `events` to generate the appropriate motion
236    fn process_buffered_events(
237        self: Box<Self>,
238        events: Vec<TouchpadEvent>,
239    ) -> ProcessBufferedEventsResult;
240
241    /// Returns a string that uniquely identifies the concrete type
242    /// of this implementation of the `MatchedContender` trait.
243    fn get_type_name(&self) -> &'static str {
244        std::any::type_name::<Self>()
245    }
246}
247
248#[derive(Debug, PartialEq)]
249pub(super) enum EndGestureEvent {
250    // It still possible to use EndGestureEvent::GeneratedEvent when
251    // we support scroll phase, keep it and related tests.
252    #[allow(dead_code)]
253    GeneratedEvent(MouseEvent),
254    UnconsumedEvent(TouchpadEvent),
255    NoEvent,
256}
257
258#[derive(Debug)]
259pub(super) enum ProcessNewEventResult {
260    ContinueGesture(Option<MouseEvent>, Box<dyn Winner>),
261    EndGesture(EndGestureEvent, Reason),
262}
263
264pub(super) trait Winner: std::fmt::Debug {
265    /// Takes `event`, and generates corresponding `MouseEvent`s.
266    ///
267    /// Returns:
268    /// * `ContinueGesture(Some, …)` if the gesture is still
269    ///   in progress, and a `MouseEvent` should be sent downstream;
270    ///   might be used, e.g., by a motion recognizer
271    /// * `ContinueGesutre(None, …)` if the gesture is still
272    ///   in progress, and no `MouseEvent` should be sent downstream;
273    ///   might be used, e.g., by a palm recognizer
274    /// * `EndGesture(UnconsumedEvent, …)` if the gesture has ended because
275    ///   `event` did not match; might be used, e.g., if the user
276    ///    presses the touchpad down after a motion gesture
277    /// * `EndGesture(GeneratedEvent, …)` if the gesture has ended because
278    ///   `event` did end the gesture; might be used, e.g., if the user
279    ///    release the touchpad down after a drag gesture
280    /// * `EndGesture(NoEvent, …)` if `event` matches a normal end
281    ///   of the gesture; might be used, e.g., if the user lifts
282    ///   their finger off the touchpad after a motion gesture
283    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult;
284
285    /// Returns a string that uniquely defines the concrete type
286    /// of this implementation of the `Winner` trait.
287    fn get_type_name(&self) -> &'static str {
288        std::any::type_name::<Self>()
289    }
290}
291
292const MAX_TOUCHPAD_EVENT_LOG_ENTRIES: usize = 1250; // 125 Hz * 10 seconds
293
294#[derive(Debug)]
295enum MutableState {
296    /// Looking for the start of any gesture.
297    Idle,
298
299    /// Looking for the start of a gesture that can be chained after
300    /// a previous gesture. "Chaining" is using two or more gestures
301    /// in sequence, while always having at least one finger in contact
302    /// with the pad.
303    ///
304    /// Note that this does _not_ require any particular finger to
305    /// be continually in contact with the pad. For example, the
306    /// following is a valid chain:
307    ///    1. Index finger
308    ///    2. Index finger + middle finger
309    ///    3. Middle finger
310    Chain,
311
312    /// Disambiguating the current gesture.
313    Matching {
314        contenders: Vec<Box<dyn Contender>>,
315        matched_contenders: Vec<Box<dyn MatchedContender>>,
316        first_event_timestamp: zx::MonotonicInstant,
317        buffered_events: Vec<TouchpadEvent>,
318    },
319
320    /// The matching gesture has been identified, and is still in progress.
321    Forwarding {
322        winner: Box<dyn Winner>,
323        current_gesture: RecognizedGesture,
324        gesture_start_timestamp: zx::MonotonicInstant,
325        num_events: usize,
326    },
327
328    /// A transient state during the processing of a single `InputEvent`.
329    Invalid,
330}
331
332/// Names for the `MutableState`s. Exists to support the state machine debug log.
333#[derive(Debug, PartialEq)]
334enum StateName {
335    Idle,
336    Chain,
337    Matching,
338    Forwarding,
339    Invalid,
340}
341
342trait ContenderFactory {
343    fn make_contenders(&self) -> Vec<Box<dyn Contender>>;
344}
345
346pub(super) struct GestureArena {
347    contender_factory: Box<dyn ContenderFactory>,
348    mutable_state: RefCell<MutableState>,
349    inspect_log: RefCell<BoundedListNode>,
350    /// The metrics logger.
351    metrics_logger: metrics::MetricsLogger,
352    /// The inventory of this handler's Inspect status.
353    inspect_status: InputHandlerStatus,
354}
355
356impl GestureArena {
357    #[cfg(test)]
358    fn new_for_test(
359        contender_factory: Box<dyn ContenderFactory>,
360        inspector: &fuchsia_inspect::Inspector,
361        max_inspect_log_entries: usize,
362        metrics_logger: metrics::MetricsLogger,
363    ) -> GestureArena {
364        let test_node = inspector.root().create_child("test_node");
365        Self::new_internal(
366            contender_factory,
367            inspector.root(),
368            max_inspect_log_entries,
369            &test_node,
370            metrics_logger,
371        )
372    }
373
374    fn new_internal(
375        contender_factory: Box<dyn ContenderFactory>,
376        inspect_node: &InspectNode,
377        max_inspect_log_entries: usize,
378        input_handlers_node: &InspectNode,
379        metrics_logger: metrics::MetricsLogger,
380    ) -> GestureArena {
381        let inspect_status = InputHandlerStatus::new(
382            input_handlers_node,
383            "gesture_arena",
384            /* generates_events */ true,
385        );
386        GestureArena {
387            contender_factory,
388            mutable_state: RefCell::new(MutableState::Idle),
389            inspect_log: RefCell::new(BoundedListNode::new(
390                inspect_node.create_child("gestures_event_log"),
391                max_inspect_log_entries,
392            )),
393            metrics_logger,
394            inspect_status,
395        }
396    }
397
398    #[cfg(test)]
399    pub(super) fn has_buffered_events(self: std::rc::Rc<Self>) -> bool {
400        match &*self.mutable_state.borrow() {
401            MutableState::Matching { buffered_events, .. } => buffered_events.len() > 0,
402            _ => false,
403        }
404    }
405}
406
407impl TouchpadEvent {
408    fn log_inspect(&self, log_entry_node: &InspectNode) {
409        let touchpad_event_node = log_entry_node.create_child("touchpad_event");
410
411        // Create an inspect array from the pressed buttons.
412        let pressed_buttons_node =
413            touchpad_event_node.create_uint_array("pressed_buttons", self.pressed_buttons.len());
414        self.pressed_buttons.iter().enumerate().for_each(|(i, &button_id)| {
415            pressed_buttons_node.set(i, button_id);
416        });
417
418        // Populate the touchpad event details
419        log_common(&touchpad_event_node, self.timestamp);
420        touchpad_event_node.record(pressed_buttons_node);
421        touchpad_event_node.record_child("contacts", |contact_set_node| {
422            self.contacts.iter().for_each(|contact| {
423                contact_set_node.record_child(contact.id.to_string(), |contact_node| {
424                    contact_node.record_double("pos_x_mm", f64::from(contact.position.x));
425                    contact_node.record_double("pos_y_mm", f64::from(contact.position.y));
426                    if let Some(contact_size) = contact.contact_size {
427                        contact_node.record_double("width_mm", f64::from(contact_size.width));
428                        contact_node.record_double("height_mm", f64::from(contact_size.height));
429                    }
430                })
431            })
432        });
433        touchpad_event_node.record_child("filtered_palm_contacts", |contact_set_node| {
434            self.filtered_palm_contacts.iter().for_each(|contact| {
435                contact_set_node.record_child(contact.id.to_string(), |contact_node| {
436                    contact_node.record_double("pos_x_mm", f64::from(contact.position.x));
437                    contact_node.record_double("pos_y_mm", f64::from(contact.position.y));
438                    if let Some(contact_size) = contact.contact_size {
439                        contact_node.record_double("width_mm", f64::from(contact_size.width));
440                        contact_node.record_double("height_mm", f64::from(contact_size.height));
441                    }
442                })
443            })
444        });
445
446        // Pass ownership of the touchpad event node to the log.
447        log_entry_node.record(touchpad_event_node);
448    }
449}
450
451impl RecognizedGesture {
452    fn to_str(&self) -> &'static str {
453        match self {
454            RecognizedGesture::_Unrecognized => "_unrecognized",
455            RecognizedGesture::PrimaryTap => "primary_tap",
456            RecognizedGesture::SecondaryTap => "secondary_tap",
457            RecognizedGesture::Motion => "motion",
458            RecognizedGesture::Scroll => "scroll",
459            RecognizedGesture::OneButtonDown => "one_button_down",
460            RecognizedGesture::SecondaryButtonDown => "secondary_button_down",
461        }
462    }
463}
464
465fn log_common(inspect_node: &InspectNode, driver_timestamp: zx::MonotonicInstant) {
466    inspect_node.record_int("driver_monotonic_nanos", driver_timestamp.into_nanos());
467    inspect_node.record_int(
468        "entry_latency_micros",
469        // Use lower precision for latency, to minimize space.
470        (fuchsia_async::MonotonicInstant::now().into_zx() - driver_timestamp).into_micros(),
471    );
472}
473
474impl MutableState {
475    fn get_state_name(&self) -> StateName {
476        match self {
477            Self::Idle => StateName::Idle,
478            Self::Chain => StateName::Chain,
479            Self::Matching { .. } => StateName::Matching,
480            Self::Forwarding { .. } => StateName::Forwarding,
481            Self::Invalid => StateName::Invalid,
482        }
483    }
484}
485
486fn parse_touchpad_event(
487    event_time: &zx::MonotonicInstant,
488    touchpad_event: &touch_binding::TouchpadEvent,
489    touchpad_descriptor: &touch_binding::TouchpadDeviceDescriptor,
490) -> Result<TouchpadEvent, Error> {
491    let position_divisor =
492        get_position_divisor_to_mm(touchpad_descriptor).context("failed to compute divisor")?;
493    Ok(TouchpadEvent {
494        timestamp: *event_time,
495        pressed_buttons: touchpad_event.pressed_buttons.iter().copied().collect::<Vec<_>>(),
496        contacts: touchpad_event
497            .injector_contacts
498            .iter()
499            .map(|contact| touch_binding::TouchContact {
500                position: contact.position / position_divisor,
501                contact_size: match contact.contact_size {
502                    Some(size) => Some(Size {
503                        width: size.width / position_divisor,
504                        height: size.height / position_divisor,
505                    }),
506                    None => None,
507                },
508                ..*contact
509            })
510            .collect(),
511        filtered_palm_contacts: vec![],
512    })
513}
514
515fn filter_palm_contact(touchpad_event: TouchpadEvent) -> TouchpadEvent {
516    // Button down will make the contact of finger larger. We are not able to
517    // use the same threshold to distinguish finger or palm.
518    if touchpad_event.pressed_buttons.len() > 0 {
519        return touchpad_event;
520    }
521    let (contacts, filtered_palm_contacts) = touchpad_event.contacts.into_iter().fold(
522        (
523            Vec::<touch_binding::TouchContact>::default(),
524            Vec::<touch_binding::TouchContact>::default(),
525        ),
526        |mut out, contact| {
527            match contact.contact_size {
528                Some(size) => {
529                    if size.width < args::MIN_PALM_SIZE_MM && size.height < args::MIN_PALM_SIZE_MM {
530                        out.0.push(contact);
531                    } else {
532                        out.1.push(contact);
533                    }
534                }
535                None => {
536                    out.0.push(contact);
537                }
538            }
539            out
540        },
541    );
542
543    TouchpadEvent { contacts, filtered_palm_contacts, ..touchpad_event }
544}
545
546const COUNTS_PER_MM: u32 = 12;
547
548impl std::convert::From<MouseEvent> for input_device::InputEvent {
549    fn from(mouse_event: MouseEvent) -> input_device::InputEvent {
550        input_device::InputEvent {
551            // TODO(https://fxbug.dev/42056058): Convert `mouse_event.mouse_data.wheel_delta_v`
552            // and `mouse_event.mouse_data.wheel_delta_h` from micrometers to counts.
553            device_event: input_device::InputDeviceEvent::Mouse(mouse_event.mouse_data),
554            device_descriptor: input_device::InputDeviceDescriptor::Mouse(
555                mouse_binding::MouseDeviceDescriptor {
556                    // Use a large number for the `device_id` for the gesture arena's
557                    // virtual mouse, to avoid conflicts with real devices (which start
558                    // at ID 0).
559                    //
560                    // However, don't use u32::MAX, since the `InputDeviceRegistry`
561                    // FIDL handling code uses that for the first registered device
562                    // (and works downwards).
563                    device_id: u32::MAX / 2,
564                    absolute_x_range: Some(fidl_input_report::Range { min: -127, max: 127 }),
565                    absolute_y_range: Some(fidl_input_report::Range { min: -127, max: 127 }),
566                    wheel_v_range: Some(fidl_input_report::Axis {
567                        // Set range based on the range of `position_y` values for the
568                        // Atlas touchpad. The touchpad is 2.48 inches tall, and the
569                        // position is reported in micrometers. That gives `position_y`
570                        // a range of (0, 62992).
571                        //
572                        // Note that this range is likely larger than needed, since users
573                        // probably won't swipe all the way up/down the pad in a single
574                        // sampling interval (8msec).
575                        //
576                        // TODO(https://fxbug.dev/42056058): Adjust this range to reflect
577                        // the range in counts instead of micrometers.
578                        range: fidl_input_report::Range { min: -65535, max: 65535 },
579                        unit: fidl_input_report::Unit {
580                            type_: fidl_fuchsia_input_report::UnitType::Other,
581                            exponent: 0,
582                        },
583                    }),
584                    wheel_h_range: None,
585                    buttons: Some(vec![PRIMARY_BUTTON, SECONDARY_BUTTON]),
586                    // Downstream stages ignore `counts_per_mm`, so any value is fine.
587                    // TODO(https://fxbug.dev/42072124): remove `counts_per_mm` from descriptor.
588                    counts_per_mm: COUNTS_PER_MM,
589                },
590            ),
591            event_time: mouse_event.timestamp,
592            handled: input_device::Handled::No,
593            trace_id: None,
594        }
595    }
596}
597
598impl GestureArena {
599    /// Interprets `TouchpadEvent`s, and sends corresponding `MouseEvent`s downstream.
600    fn handle_touchpad_event(
601        self: std::rc::Rc<Self>,
602        event_time: &zx::MonotonicInstant,
603        touchpad_event: &touch_binding::TouchpadEvent,
604        device_descriptor: &touch_binding::TouchpadDeviceDescriptor,
605    ) -> Result<Vec<input_device::InputEvent>, Error> {
606        let touchpad_event = parse_touchpad_event(event_time, touchpad_event, device_descriptor)
607            .context("dropping touchpad event")?;
608        let touchpad_event = filter_palm_contact(touchpad_event);
609        self.inspect_log.borrow_mut().add_entry(|node| {
610            touchpad_event.log_inspect(node);
611        });
612
613        let old_state_name = self.mutable_state.borrow().get_state_name();
614        let (new_state, generated_events) = match self.mutable_state.replace(MutableState::Invalid)
615        {
616            MutableState::Idle => self.handle_event_while_idle(touchpad_event),
617            MutableState::Chain => self.handle_event_while_chain(touchpad_event),
618            MutableState::Matching {
619                contenders,
620                matched_contenders,
621                first_event_timestamp,
622                buffered_events,
623            } => self.handle_event_while_matching(
624                contenders,
625                matched_contenders,
626                first_event_timestamp,
627                buffered_events,
628                touchpad_event,
629            ),
630            MutableState::Forwarding {
631                winner,
632                current_gesture,
633                gesture_start_timestamp,
634                num_events,
635            } => self.handle_event_while_forwarding(
636                winner,
637                current_gesture,
638                gesture_start_timestamp,
639                num_events + 1,
640                touchpad_event,
641            ),
642            MutableState::Invalid => {
643                unreachable!();
644            }
645        };
646        log::debug!("gesture_arena: {:?} -> {:?}", old_state_name, new_state.get_state_name());
647        self.mutable_state.replace(new_state);
648        // self.log_mutable_state();  // uncomment to log contender set
649        Ok(generated_events.into_iter().map(input_device::InputEvent::from).collect())
650    }
651
652    fn handle_event_while_idle(&self, new_event: TouchpadEvent) -> (MutableState, Vec<MouseEvent>) {
653        let (contenders, matched_contenders) = self
654            .contender_factory
655            .make_contenders()
656            .into_iter()
657            .map(|contender| (contender.get_type_name(), contender.examine_event(&new_event)))
658            .fold(
659                (vec![], vec![]),
660                |(mut contenders, mut matched_contenders), (type_name, examine_result)| {
661                    match examine_result {
662                        ExamineEventResult::Contender(contender) => {
663                            contenders.push(contender);
664                        }
665                        ExamineEventResult::MatchedContender(matched_contender) => {
666                            matched_contenders.push(matched_contender);
667                        }
668                        ExamineEventResult::Mismatch(mismatch_data) => {
669                            self.inspect_log.borrow_mut().add_entry(|node| {
670                                log_mismatch_reason(node, type_name, mismatch_data);
671                            });
672                        }
673                    }
674                    (contenders, matched_contenders)
675                },
676            );
677        let event_timestamp = new_event.timestamp;
678        self.transit_based_on_contenders_matched_contenders(
679            vec![],
680            new_event,
681            event_timestamp,
682            contenders,
683            matched_contenders,
684        )
685    }
686
687    fn handle_event_while_chain(
688        &self,
689        new_event: TouchpadEvent,
690    ) -> (MutableState, Vec<MouseEvent>) {
691        let (contenders, matched_contenders) = self
692            .contender_factory
693            .make_contenders()
694            .into_iter()
695            .filter(|contender| !contender.start_from_idle())
696            .map(|contender| (contender.get_type_name(), contender.examine_event(&new_event)))
697            .fold(
698                (vec![], vec![]),
699                |(mut contenders, mut matched_contenders), (type_name, examine_result)| {
700                    match examine_result {
701                        ExamineEventResult::Contender(contender) => {
702                            contenders.push(contender);
703                        }
704                        ExamineEventResult::MatchedContender(matched_contender) => {
705                            matched_contenders.push(matched_contender);
706                        }
707                        ExamineEventResult::Mismatch(mismatch_data) => {
708                            self.inspect_log.borrow_mut().add_entry(|node| {
709                                log_mismatch_reason(node, type_name, mismatch_data);
710                            });
711                        }
712                    }
713                    (contenders, matched_contenders)
714                },
715            );
716        let event_timestamp = new_event.timestamp;
717        self.transit_based_on_contenders_matched_contenders(
718            vec![],
719            new_event,
720            event_timestamp,
721            contenders,
722            matched_contenders,
723        )
724    }
725
726    fn handle_event_while_matching(
727        &self,
728        contenders: Vec<Box<dyn Contender>>,
729        matched_contenders: Vec<Box<dyn MatchedContender>>,
730        first_event_timestamp: zx::MonotonicInstant,
731        buffered_events: Vec<TouchpadEvent>,
732        new_event: TouchpadEvent,
733    ) -> (MutableState, Vec<MouseEvent>) {
734        // Note: This function processes `contenders` after `matched_contenders` to ensure
735        // that a recognizer doesn't get invoked twice with the same event. Otherwise, it
736        // would be possible for the following sequence of events to occur:
737        //
738        // 1. This function calls some_recognizer::Contender.examine_event(&new_event).
739        // 2. The call returns some_recognizer::MatchedContender.
740        // 3. This function calls some_recognizer::MatchedContender.verify_event(&new_event).
741        //
742        // See also: `does_not_repeat_event_to_matched_contender_returned_by_examine_event` test.
743        let matched_contenders = matched_contenders
744            .into_iter()
745            .map(|matched_contender| {
746                (matched_contender.get_type_name(), matched_contender.verify_event(&new_event))
747            })
748            .fold(vec![], |mut matched_contenders, (type_name, verify_result)| {
749                match verify_result {
750                    VerifyEventResult::MatchedContender(m) => {
751                        matched_contenders.push(m);
752                    }
753                    VerifyEventResult::Mismatch(mismatch_data) => {
754                        self.inspect_log.borrow_mut().add_entry(|node| {
755                            log_mismatch_reason(node, type_name, mismatch_data);
756                        });
757                    }
758                }
759                matched_contenders
760            });
761        let (contenders, matched_contenders) = contenders
762            .into_iter()
763            .map(|contender| (contender.get_type_name(), contender.examine_event(&new_event)))
764            .fold(
765                (vec![], matched_contenders),
766                |(mut contenders, mut matched_contenders), (type_name, examine_result)| {
767                    match examine_result {
768                        ExamineEventResult::Contender(contender) => {
769                            contenders.push(contender);
770                        }
771                        ExamineEventResult::MatchedContender(matched_contender) => {
772                            matched_contenders.push(matched_contender);
773                        }
774                        ExamineEventResult::Mismatch(mismatch_data) => {
775                            self.inspect_log.borrow_mut().add_entry(|node| {
776                                log_mismatch_reason(node, type_name, mismatch_data);
777                            });
778                        }
779                    }
780                    (contenders, matched_contenders)
781                },
782            );
783
784        self.transit_based_on_contenders_matched_contenders(
785            buffered_events,
786            new_event,
787            first_event_timestamp,
788            contenders,
789            matched_contenders,
790        )
791    }
792
793    fn transit_based_on_contenders_matched_contenders(
794        &self,
795        buffered_events: Vec<TouchpadEvent>,
796        new_event: TouchpadEvent,
797        first_event_timestamp: zx::MonotonicInstant,
798        contenders: Vec<Box<dyn Contender>>,
799        matched_contenders: Vec<Box<dyn MatchedContender>>,
800    ) -> (MutableState, Vec<MouseEvent>) {
801        let has_finger_contact = new_event.contacts.len() > 0;
802        let new_event_timestamp = new_event.timestamp;
803
804        match (
805            u8::try_from(contenders.len()).unwrap_or(u8::MAX),
806            u8::try_from(matched_contenders.len()).unwrap_or(u8::MAX),
807        ) {
808            // No `Contender`s or `MatchedContender`s. The contest has ended without
809            // identifying a gesture.
810            (0, 0) => {
811                if has_finger_contact {
812                    // One more contacts transit to chain. This is required when user
813                    // give an unknown gesture for example a 2 fingers swipe in no
814                    // direction and movement larger than threshold. Then finger lift,
815                    // Chain state will prevent it to be recognized as secondary tap.
816                    (MutableState::Chain, vec![])
817                } else {
818                    (MutableState::Idle, vec![])
819                }
820            }
821            // No `Contender`s, and exactly one `MatchedContender`. The contest has ended
822            // with a recognized gesture.
823            (0, 1) => {
824                let num_previous_events = buffered_events.len();
825                let mut buffered_events = buffered_events;
826                buffered_events.push(new_event);
827
828                let matched_contender = {
829                    let mut matched_contenders = matched_contenders;
830                    matched_contenders.remove(0)
831                };
832                let type_name = matched_contender.get_type_name();
833                let ProcessBufferedEventsResult { generated_events, winner, recognized_gesture } =
834                    matched_contender.process_buffered_events(buffered_events);
835                self.inspect_log.borrow_mut().add_entry(|node| {
836                    log_gesture_start(
837                        node,
838                        recognized_gesture,
839                        num_previous_events,
840                        new_event_timestamp - first_event_timestamp,
841                    );
842                });
843
844                match winner {
845                    Some(winner) => (
846                        MutableState::Forwarding {
847                            winner,
848                            current_gesture: recognized_gesture,
849                            gesture_start_timestamp: new_event_timestamp,
850                            num_events: 0,
851                        },
852                        generated_events,
853                    ),
854                    None => {
855                        self.inspect_log.borrow_mut().add_entry(|node| {
856                            log_gesture_end(
857                                node,
858                                type_name,
859                                recognized_gesture,
860                                Reason::Basic("discrete-recognizer"),
861                                0,
862                                zx::MonotonicDuration::from_nanos(0),
863                            );
864                        });
865                        if has_finger_contact {
866                            (MutableState::Chain, generated_events)
867                        } else {
868                            (MutableState::Idle, generated_events)
869                        }
870                    }
871                }
872            }
873            // At least 1 `Contender`, or 2 `MatchedContender`s; continue the contest.
874            (1.., _) | (_, 2..) => {
875                let mut buffered_events = buffered_events;
876                buffered_events.push(new_event);
877                (
878                    MutableState::Matching {
879                        contenders,
880                        matched_contenders,
881                        first_event_timestamp,
882                        buffered_events,
883                    },
884                    vec![],
885                )
886            }
887        }
888    }
889
890    fn handle_event_while_forwarding(
891        &self,
892        winner: Box<dyn Winner>,
893        current_gesture: RecognizedGesture,
894        gesture_start_timestamp: zx::MonotonicInstant,
895        num_events: usize,
896        new_event: TouchpadEvent,
897    ) -> (MutableState, Vec<MouseEvent>) {
898        let type_name = winner.get_type_name();
899        let has_finger_contact = new_event.contacts.len() > 0;
900        let new_event_timestamp = new_event.timestamp;
901
902        match winner.process_new_event(new_event) {
903            ProcessNewEventResult::ContinueGesture(generated_event, winner) => (
904                MutableState::Forwarding {
905                    winner,
906                    current_gesture,
907                    gesture_start_timestamp,
908                    num_events,
909                },
910                generated_event.into_iter().collect(),
911            ),
912            ProcessNewEventResult::EndGesture(
913                EndGestureEvent::GeneratedEvent(generated_event),
914                reason,
915            ) => {
916                log::debug!("touchpad: {} ended: {:?}", type_name, reason);
917                self.inspect_log.borrow_mut().add_entry(|node| {
918                    log_gesture_end(
919                        node,
920                        type_name,
921                        current_gesture,
922                        reason,
923                        num_events,
924                        new_event_timestamp - gesture_start_timestamp,
925                    );
926                });
927                if has_finger_contact {
928                    (MutableState::Chain, vec![generated_event])
929                } else {
930                    (MutableState::Idle, vec![generated_event])
931                }
932            }
933            ProcessNewEventResult::EndGesture(
934                EndGestureEvent::UnconsumedEvent(unconsumed_event),
935                reason,
936            ) => {
937                log::debug!("touchpad: {} ended: {:?}", type_name, reason);
938                self.inspect_log.borrow_mut().add_entry(|node| {
939                    log_gesture_end(
940                        node,
941                        type_name,
942                        current_gesture,
943                        reason,
944                        num_events,
945                        new_event_timestamp - gesture_start_timestamp,
946                    );
947                });
948                if unconsumed_event.contacts.len() > 0 {
949                    self.handle_event_while_chain(unconsumed_event)
950                } else {
951                    self.handle_event_while_idle(unconsumed_event)
952                }
953            }
954            ProcessNewEventResult::EndGesture(EndGestureEvent::NoEvent, reason) => {
955                log::debug!("touchpad: {} ended: {:?}", type_name, reason);
956                self.inspect_log.borrow_mut().add_entry(|node| {
957                    log_gesture_end(
958                        node,
959                        type_name,
960                        current_gesture,
961                        reason,
962                        num_events,
963                        new_event_timestamp - gesture_start_timestamp,
964                    );
965                });
966                if has_finger_contact {
967                    (MutableState::Chain, vec![])
968                } else {
969                    (MutableState::Idle, vec![])
970                }
971            }
972        }
973    }
974
975    #[allow(dead_code)] // only called in developer debug builds and tests.
976    pub(super) fn mutable_state_to_str(&self) -> String {
977        match &*self.mutable_state.borrow() {
978            MutableState::Idle => format!("touchpad: Idle"),
979            MutableState::Chain => format!("touchpad: Chain"),
980            MutableState::Matching {
981                contenders,
982                matched_contenders,
983                first_event_timestamp,
984                buffered_events,
985            } => {
986                format!(
987                    "touchpad: Matching {{ \
988                                contenders: [ {} ], \
989                                matched_contenders: [ {} ], \
990                                first_event_timestamp_nanos: {}, \
991                                n_buffered_events: {} \
992                              }}",
993                    contenders.iter().fold(String::new(), |accum, item| {
994                        accum + &format!("{}, ", item.get_type_name())
995                    }),
996                    matched_contenders.iter().fold(String::new(), |accum, item| {
997                        accum + &format!("{}, ", item.get_type_name())
998                    }),
999                    first_event_timestamp.into_nanos(),
1000                    buffered_events.len()
1001                )
1002            }
1003            MutableState::Forwarding {
1004                winner,
1005                current_gesture,
1006                gesture_start_timestamp,
1007                num_events,
1008            } => {
1009                format!(
1010                    "touchpad: Forwarding {{ \
1011                        winner: {}, \
1012                        current_gesture: {:?}, \
1013                        gesture_start_timestamp_nanos: {}, \
1014                        num_events: {} \
1015                    }}",
1016                    winner.get_type_name(),
1017                    current_gesture,
1018                    gesture_start_timestamp.into_nanos(),
1019                    num_events
1020                )
1021            }
1022            MutableState::Invalid => format!("touchpad: Invalid"),
1023        }
1024    }
1025
1026    #[allow(dead_code)] // only called in developer debug builds
1027    fn log_mutable_state(&self) {
1028        log::info!("{}", self.mutable_state_to_str());
1029    }
1030}
1031
1032impl Handler for GestureArena {
1033    fn set_handler_healthy(self: std::rc::Rc<Self>) {
1034        self.inspect_status.health_node.borrow_mut().set_ok();
1035    }
1036
1037    fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
1038        self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
1039    }
1040
1041    fn get_name(&self) -> &'static str {
1042        "GestureArena"
1043    }
1044
1045    fn interest(&self) -> Vec<input_device::InputEventType> {
1046        vec![input_device::InputEventType::Touchpad, input_device::InputEventType::Keyboard]
1047    }
1048}
1049
1050#[async_trait(?Send)]
1051impl InputHandler for GestureArena {
1052    /// Handle `input_event`:
1053    /// * For an unhandled touchpad event, try to interpret as a touchpad gesture.
1054    /// * For a keyboard event, log the timestamp, and pass the event onwards.
1055    /// * For any other event, pass onwards without logging.
1056    async fn handle_input_event(
1057        self: std::rc::Rc<Self>,
1058        input_event: input_device::InputEvent,
1059    ) -> Vec<input_device::InputEvent> {
1060        fuchsia_trace::duration!("input", "gesture_arena");
1061        match input_event {
1062            input_device::InputEvent {
1063                device_event: input_device::InputDeviceEvent::Touchpad(ref touchpad_event),
1064                ref event_time,
1065                device_descriptor:
1066                    input_device::InputDeviceDescriptor::Touchpad(ref touchpad_descriptor),
1067                handled: input_device::Handled::No,
1068                ..
1069            } => {
1070                fuchsia_trace::duration!("input", "gesture_arena[processing]");
1071                self.inspect_status.count_received_event(&event_time);
1072                match self.handle_touchpad_event(event_time, touchpad_event, touchpad_descriptor) {
1073                    Ok(r) => r,
1074                    Err(e) => {
1075                        log::error!("{}", e);
1076                        vec![input_event]
1077                    }
1078                }
1079            }
1080            input_device::InputEvent {
1081                device_event: input_device::InputDeviceEvent::Keyboard(_),
1082                event_time,
1083                ..
1084            } => {
1085                fuchsia_trace::duration!("input", "gesture_arena[processing]");
1086                self.inspect_log.borrow_mut().add_entry(|node| {
1087                    log_keyboard_event_timestamp(node, event_time);
1088                });
1089                vec![input_event]
1090            }
1091            _ => {
1092                self.metrics_logger.log_error(
1093                    InputPipelineErrorMetricDimensionEvent::HandlerReceivedUninterestedEvent,
1094                    std::format!("uninterested input event: {:?}", input_event.get_event_type()),
1095                );
1096                vec![input_event]
1097            }
1098        }
1099    }
1100}
1101
1102/// Returns the multiplier to translate position data for the device described by
1103/// `device_descriptor`, from the units in the corresponding `TouchpadEvent`, to
1104/// millimeters.
1105///
1106/// For example, if this function returns 1000, then the original data are in
1107/// micrometers, and dividing by 1000 will yield millimeters.
1108fn get_position_divisor_to_mm(
1109    touchpad_descriptor: &touch_binding::TouchpadDeviceDescriptor,
1110) -> Result<f32, Error> {
1111    const EXPONENT_MILLIS: i32 = -3;
1112    let divisors =
1113        touchpad_descriptor.contacts.iter().enumerate().map(|(i, contact_descriptor)| {
1114            match (contact_descriptor.x_unit, contact_descriptor.y_unit) {
1115                (
1116                    fidl_input_report::Unit {
1117                        type_: fidl_input_report::UnitType::Meters,
1118                        exponent: exponent_x,
1119                    },
1120                    fidl_input_report::Unit {
1121                        type_: fidl_input_report::UnitType::Meters,
1122                        exponent: exponent_y,
1123                    },
1124                ) => {
1125                    if exponent_x == exponent_y {
1126                        Ok(f32::powi(10.0, EXPONENT_MILLIS - exponent_x))
1127                    } else {
1128                        Err(format!(
1129                            "contact {}: mismatched exponents x={}, y={}",
1130                            i, exponent_x, exponent_y
1131                        ))
1132                    }
1133                }
1134                (
1135                    fidl_input_report::Unit { type_: x_unit_type, .. },
1136                    fidl_input_report::Unit { type_: fidl_input_report::UnitType::Meters, .. },
1137                ) => Err(format!(
1138                    "contact {}: expected x-unit-type of Meters but got {:?}",
1139                    i, x_unit_type
1140                )),
1141                (
1142                    fidl_input_report::Unit { type_: fidl_input_report::UnitType::Meters, .. },
1143                    fidl_input_report::Unit { type_: y_unit_type, .. },
1144                ) => Err(format!(
1145                    "contact {}: expected y-unit-type of Meters but got {:?}",
1146                    i, y_unit_type
1147                )),
1148                (
1149                    fidl_input_report::Unit { type_: x_unit_type, .. },
1150                    fidl_input_report::Unit { type_: y_unit_type, .. },
1151                ) => Err(format!(
1152                    "contact {}: expected x and y unit-types of Meters but got x={:?} and y={:?}",
1153                    i, x_unit_type, y_unit_type
1154                )),
1155            }
1156        });
1157
1158    let (divisors, errors): (Vec<_>, Vec<_>) =
1159        divisors.fold((vec![], vec![]), |(mut divisors, mut errors), divisor| {
1160            match divisor {
1161                Ok(d) => divisors.push(d),
1162                Err(e) => errors.push(e),
1163            };
1164            (divisors, errors)
1165        });
1166
1167    if !errors.is_empty() {
1168        return Err(format_err!(
1169            errors.into_iter().fold(String::new(), |prev_err_msgs, this_err_msg| prev_err_msgs
1170                + &this_err_msg
1171                + ", ")
1172        ));
1173    }
1174
1175    let first_divisor = match divisors.first() {
1176        Some(&divisor) => divisor,
1177        None => return Err(format_err!("no contact descriptors!")),
1178    };
1179
1180    if divisors.iter().any(|&divisor| divisor != first_divisor) {
1181        return Err(format_err!(
1182            divisors
1183                .into_iter()
1184                .enumerate()
1185                .filter(|(_i, divisor)| *divisor != first_divisor)
1186                .map(|(i, divisor)| format!(
1187                    "contact {} has a different divisor than the first contact ({:?} != {:?})",
1188                    i, divisor, first_divisor,
1189                ))
1190                .fold(String::new(), |prev_err_msgs, this_err_msg| prev_err_msgs
1191                    + &this_err_msg
1192                    + ", ")
1193        ));
1194    }
1195
1196    Ok(first_divisor)
1197}
1198
1199fn log_keyboard_event_timestamp(
1200    log_entry_node: &InspectNode,
1201    driver_timestamp: zx::MonotonicInstant,
1202) {
1203    log_entry_node.record_child("key_event", |key_event_node| {
1204        log_common(key_event_node, driver_timestamp);
1205    });
1206}
1207
1208fn log_basic_reason(reason_node: &InspectNode, text: &'static str) {
1209    reason_node.record_string("reason", text);
1210}
1211
1212fn log_detailed_reason_uint(reason_node: &InspectNode, reason_details: DetailedReasonUint) {
1213    reason_node.record_string("criterion", reason_details.criterion);
1214    reason_node.record_uint("actual", u64::try_from(reason_details.actual).unwrap_or(u64::MAX));
1215    reason_details.min.map(|min| reason_node.record_uint("min_allowed", min));
1216    reason_details.max.map(|max| reason_node.record_uint("max_allowed", max));
1217}
1218
1219fn log_detailed_reason_float(reason_node: &InspectNode, reason_details: DetailedReasonFloat) {
1220    reason_node.record_string("criterion", reason_details.criterion);
1221    reason_node.record_double("actual", f64::from(reason_details.actual));
1222    reason_details.min.map(|min| reason_node.record_double("min_allowed", f64::from(min)));
1223    reason_details.max.map(|max| reason_node.record_double("max_allowed", f64::from(max)));
1224}
1225
1226fn log_detailed_reason_int(reason_node: &InspectNode, reason_details: DetailedReasonInt) {
1227    reason_node.record_string("criterion", reason_details.criterion);
1228    reason_node.record_int("actual", reason_details.actual);
1229    reason_details.min.map(|min| reason_node.record_int("min_allowed", min));
1230    reason_details.max.map(|max| reason_node.record_int("max_allowed", max));
1231}
1232
1233fn log_reason(reason_node: &InspectNode, contender_name: &'static str, reason: Reason) {
1234    // E.g. "input_pipeline_lib_test::gestures::gesture_arena::tests::utils::StubContender"
1235    // -> "utils::StubContender".
1236    let contender_name = match contender_name.rmatch_indices("::").nth(1) {
1237        Some((i, _matched_substr)) => &contender_name[i + 2..],
1238        None => contender_name,
1239    };
1240    reason_node.record_string("contender", contender_name);
1241    match reason {
1242        Reason::Basic(text) => log_basic_reason(reason_node, text),
1243        Reason::DetailedUint(mismatch_details) => {
1244            log_detailed_reason_uint(reason_node, mismatch_details)
1245        }
1246        Reason::DetailedFloat(mismatch_details) => {
1247            log_detailed_reason_float(reason_node, mismatch_details)
1248        }
1249        Reason::DetailedInt(mismatch_details) => {
1250            log_detailed_reason_int(reason_node, mismatch_details)
1251        }
1252    }
1253}
1254
1255fn log_mismatch_reason(log_entry_node: &InspectNode, contender_name: &'static str, reason: Reason) {
1256    log::debug!("touchpad: {} mismatched: {:?}", contender_name, reason);
1257    log_entry_node.record_child("mismatch_event", |mismatch_event_node| {
1258        log_reason(mismatch_event_node, contender_name, reason);
1259    });
1260}
1261
1262fn log_gesture_start(
1263    log_entry_node: &InspectNode,
1264    recognized_gesture: RecognizedGesture,
1265    num_previous_events: usize,
1266    elapsed_from_first_event: zx::MonotonicDuration,
1267) {
1268    log::debug!("touchpad: recognized start {:?}", recognized_gesture);
1269    log_entry_node.record_child("gesture_start", |gesture_start_node| {
1270        gesture_start_node.record_string("gesture_name", recognized_gesture.to_str());
1271        gesture_start_node.record_int(
1272            "latency_micros",
1273            // Reduce precision, to minimize space.
1274            elapsed_from_first_event.into_micros(),
1275        );
1276        gesture_start_node.record_uint(
1277            "latency_event_count",
1278            u64::try_from(num_previous_events).unwrap_or(u64::MAX),
1279        );
1280    });
1281}
1282
1283fn log_gesture_end(
1284    log_entry_node: &InspectNode,
1285    contender_name: &'static str,
1286    current_gesture: RecognizedGesture,
1287    reason: Reason,
1288    num_events: usize,
1289    elapsed_from_gesture_start: zx::MonotonicDuration,
1290) {
1291    log::debug!("touchpad: recognized end {:?}", current_gesture);
1292    log_entry_node.record_child("gesture_end", |gesture_end_node| {
1293        gesture_end_node.record_string("gesture_name", current_gesture.to_str());
1294        gesture_end_node.record_int(
1295            "duration_micros",
1296            // Reduce precision, to minimize space.
1297            elapsed_from_gesture_start.into_micros(),
1298        );
1299        gesture_end_node.record_uint("event_count", u64::try_from(num_events).unwrap_or(u64::MAX));
1300        log_reason(gesture_end_node, contender_name, reason)
1301    });
1302}
1303
1304#[cfg(test)]
1305mod tests {
1306    mod utils {
1307        use super::super::{
1308            COUNTS_PER_MM, Contender, ContenderFactory, ExamineEventResult, MatchedContender,
1309            PRIMARY_BUTTON, ProcessBufferedEventsResult, ProcessNewEventResult, TouchpadEvent,
1310            VerifyEventResult, Winner, args,
1311        };
1312        use crate::utils::Size;
1313        use crate::{Position, input_device, keyboard_binding, mouse_binding, touch_binding};
1314        use assert_matches::assert_matches;
1315        use fidl_fuchsia_input_report as fidl_input_report;
1316        use maplit::hashset;
1317        use std::cell::{Cell, RefCell};
1318        use std::rc::Rc;
1319
1320        /// The gesture arena is mostly agnostic to the event details. Consequently, most
1321        /// tests can use the same lightly populated touchpad event.
1322        pub(super) fn make_unhandled_touchpad_event() -> input_device::InputEvent {
1323            input_device::InputEvent {
1324                device_event: input_device::InputDeviceEvent::Touchpad(
1325                    touch_binding::TouchpadEvent {
1326                        injector_contacts: vec![],
1327                        pressed_buttons: hashset! {},
1328                    },
1329                ),
1330                device_descriptor: make_touchpad_descriptor(),
1331                event_time: zx::MonotonicInstant::ZERO,
1332                trace_id: None,
1333                handled: input_device::Handled::No,
1334            }
1335        }
1336
1337        /// The gesture arena is mostly agnostic to the event details. Consequently, most
1338        /// tests can use the same lightly populated mouse event.
1339        pub(super) fn make_unhandled_mouse_event() -> input_device::InputEvent {
1340            input_device::InputEvent {
1341                device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
1342                    location: mouse_binding::MouseLocation::Relative(
1343                        mouse_binding::RelativeLocation { millimeters: Position::zero() },
1344                    ),
1345                    wheel_delta_h: None,
1346                    wheel_delta_v: None,
1347                    phase: mouse_binding::MousePhase::Move,
1348                    affected_buttons: hashset! {},
1349                    pressed_buttons: hashset! {},
1350                    is_precision_scroll: None,
1351                    wake_lease: None.into(),
1352                }),
1353                device_descriptor: make_mouse_descriptor(),
1354                event_time: zx::MonotonicInstant::ZERO,
1355                trace_id: None,
1356                handled: input_device::Handled::No,
1357            }
1358        }
1359
1360        /// The gesture arena is mostly agnostic to the event details. Consequently, most
1361        /// tests can use the same lightly populated keyboard event.
1362        pub(super) fn make_unhandled_keyboard_event() -> input_device::InputEvent {
1363            input_device::InputEvent {
1364                device_event: input_device::InputDeviceEvent::Keyboard(
1365                    keyboard_binding::KeyboardEvent::new(
1366                        fidl_fuchsia_input::Key::A,
1367                        fidl_fuchsia_ui_input3::KeyEventType::Pressed,
1368                    ),
1369                ),
1370                device_descriptor: make_keyboard_descriptor(),
1371                event_time: zx::MonotonicInstant::ZERO,
1372                trace_id: None,
1373                handled: input_device::Handled::No,
1374            }
1375        }
1376
1377        pub(super) fn make_touchpad_descriptor() -> input_device::InputDeviceDescriptor {
1378            input_device::InputDeviceDescriptor::Touchpad(touch_binding::TouchpadDeviceDescriptor {
1379                device_id: 1,
1380                contacts: vec![touch_binding::ContactDeviceDescriptor {
1381                    x_range: fidl_input_report::Range { min: 0, max: 10_000 },
1382                    y_range: fidl_input_report::Range { min: 0, max: 10_000 },
1383                    x_unit: fidl_input_report::Unit {
1384                        type_: fidl_input_report::UnitType::Meters,
1385                        exponent: -6,
1386                    },
1387                    y_unit: fidl_input_report::Unit {
1388                        type_: fidl_input_report::UnitType::Meters,
1389                        exponent: -6,
1390                    },
1391                    pressure_range: None,
1392                    width_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
1393                    height_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
1394                }],
1395            })
1396        }
1397
1398        pub(super) fn make_mouse_descriptor() -> input_device::InputDeviceDescriptor {
1399            input_device::InputDeviceDescriptor::Mouse(mouse_binding::MouseDeviceDescriptor {
1400                device_id: 2,
1401                absolute_x_range: Some(fidl_input_report::Range { min: -127, max: 127 }),
1402                absolute_y_range: Some(fidl_input_report::Range { min: -127, max: 127 }),
1403                wheel_v_range: None,
1404                wheel_h_range: None,
1405                buttons: Some(vec![PRIMARY_BUTTON]),
1406                counts_per_mm: COUNTS_PER_MM,
1407            })
1408        }
1409
1410        fn make_keyboard_descriptor() -> input_device::InputDeviceDescriptor {
1411            input_device::InputDeviceDescriptor::Keyboard(
1412                keyboard_binding::KeyboardDeviceDescriptor {
1413                    device_id: 3,
1414                    device_information: fidl_fuchsia_input_report::DeviceInformation {
1415                        vendor_id: Some(0),
1416                        product_id: Some(0),
1417                        version: Some(0),
1418                        polling_rate: Some(0),
1419                        ..Default::default()
1420                    },
1421                    keys: vec![fidl_fuchsia_input::Key::A],
1422                },
1423            )
1424        }
1425
1426        #[derive(Clone, Debug)]
1427        /// Provides the ability to
1428        ///
1429        /// 1. Plumb a fake `Contender` into a `GestureArena`
1430        /// 2. Fake interactions between the two
1431        /// 3. Inspect interactions between the two
1432        ///
1433        /// To plumb the fake, pass a `Clone` of the `StubContender` to
1434        /// `ContenderFactoryOnceOrPanic`. To fake or inspect interactions, call the
1435        /// inherent methods on the struct.
1436        pub(super) struct StubContender {
1437            inner: Rc<RefCell<StubContenderInner>>,
1438            start_from_idle: bool,
1439        }
1440
1441        impl StubContender {
1442            pub(super) fn new() -> Self {
1443                Self {
1444                    inner: Rc::new(RefCell::new(StubContenderInner {
1445                        next_result: None,
1446                        calls_received: 0,
1447                        last_touchpad_event: None,
1448                    })),
1449                    start_from_idle: false,
1450                }
1451            }
1452
1453            pub(super) fn new_start_from_idle() -> Self {
1454                Self {
1455                    inner: Rc::new(RefCell::new(StubContenderInner {
1456                        next_result: None,
1457                        calls_received: 0,
1458                        last_touchpad_event: None,
1459                    })),
1460                    start_from_idle: true,
1461                }
1462            }
1463
1464            /// Set the value to be returned on the next call to `examine_event()`.
1465            /// Aborts if a value is already set, since that suggests that a previously
1466            /// expected call was never made.
1467            pub(super) fn set_next_result(&self, next_result: ExamineEventResult) {
1468                self.assert_next_result_is_none();
1469                self.inner.borrow_mut().next_result = Some(next_result);
1470            }
1471
1472            pub(super) fn assert_next_result_is_none(&self) {
1473                assert_matches!(self.inner.borrow().next_result, None);
1474            }
1475
1476            pub(super) fn calls_received(&self) -> usize {
1477                self.inner.borrow().calls_received
1478            }
1479
1480            pub(super) fn ref_count(&self) -> usize {
1481                Rc::strong_count(&self.inner)
1482            }
1483
1484            pub(super) fn get_last_touchpad_event(&self) -> Option<TouchpadEvent> {
1485                self.inner.borrow_mut().last_touchpad_event.take()
1486            }
1487        }
1488
1489        #[derive(Debug)]
1490        struct StubContenderInner {
1491            next_result: Option<ExamineEventResult>,
1492            calls_received: usize,
1493            last_touchpad_event: Option<TouchpadEvent>,
1494        }
1495
1496        /// A factory that returns `Vec<Box<dyn Contender>>` from `contenders` on the
1497        /// first call, and `panic()`-s on the second call.
1498        ///
1499        /// Useful because
1500        /// a) `GestureArena` requires that the factory be invocable multiple times, BUT
1501        /// b) most of the gesture arena tests don't expect that to happen, SO
1502        /// c) the tests don't have logic to handle that case.
1503        ///
1504        /// To use: pass a lambda which invokes `make_contenders()` to
1505        /// `GestureArena::new_for_test()`.
1506        pub(super) struct ContenderFactoryOnceOrPanic {
1507            contenders: Cell<Option<Vec<Box<dyn Contender>>>>,
1508        }
1509
1510        impl ContenderFactoryOnceOrPanic {
1511            pub(super) fn new(contenders: Vec<Box<dyn Contender>>) -> Self {
1512                Self { contenders: Cell::new(Some(contenders)) }
1513            }
1514
1515            /// `ContenderFactoryOnceOrPanic::new_panic_when_call` will panic when calls
1516            /// `make_contenders`.
1517            pub(super) fn new_panic_when_call() -> Self {
1518                Self { contenders: Cell::new(None) }
1519            }
1520        }
1521
1522        impl ContenderFactory for ContenderFactoryOnceOrPanic {
1523            fn make_contenders(&self) -> Vec<Box<dyn Contender>> {
1524                self.contenders
1525                    .take()
1526                    .expect("`contenders` has been consumed")
1527                    .into_iter()
1528                    .collect()
1529            }
1530        }
1531
1532        impl Contender for StubContender {
1533            fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
1534                let mut inner = self.inner.borrow_mut();
1535                inner.calls_received += 1;
1536                inner.last_touchpad_event = Some(event.clone());
1537                inner.next_result.take().unwrap_or_else(|| {
1538                    panic!("missing `next_result` on call {}", inner.calls_received)
1539                })
1540            }
1541
1542            fn start_from_idle(&self) -> bool {
1543                self.start_from_idle
1544            }
1545        }
1546
1547        #[derive(Clone)]
1548        pub(super) struct ContenderFactoryCalled {
1549            pub called: Rc<RefCell<bool>>,
1550        }
1551
1552        impl ContenderFactoryCalled {
1553            pub(super) fn new() -> Self {
1554                Self { called: Rc::new(RefCell::new(false)) }
1555            }
1556
1557            pub(super) fn was_called(&self) -> bool {
1558                *self.called.borrow()
1559            }
1560        }
1561
1562        impl ContenderFactory for ContenderFactoryCalled {
1563            fn make_contenders(&self) -> Vec<Box<dyn Contender>> {
1564                self.called.replace(true);
1565                vec![]
1566            }
1567        }
1568
1569        // A fake contender that always returns itself. Useful for keeping the
1570        // gesture arena from exiting the matching state. Keeping the arena in
1571        // the matching state eliminates the need for some tests to provide
1572        // return values for `process_buffered_events()`.
1573        #[derive(Debug)]
1574        pub(super) struct ContenderForever {}
1575
1576        impl Contender for ContenderForever {
1577            fn examine_event(self: Box<Self>, _event: &TouchpadEvent) -> ExamineEventResult {
1578                ExamineEventResult::Contender(self)
1579            }
1580        }
1581
1582        #[derive(Clone, Debug)]
1583        pub(super) struct StubMatchedContender {
1584            inner: Rc<RefCell<StubMatchedContenderInner>>,
1585        }
1586
1587        impl StubMatchedContender {
1588            pub(super) fn new() -> Self {
1589                Self {
1590                    inner: Rc::new(RefCell::new(StubMatchedContenderInner {
1591                        next_verify_event_result: None,
1592                        next_process_buffered_events_result: None,
1593                        verify_event_calls_received: 0,
1594                        process_buffered_events_calls_received: 0,
1595                        last_process_buffered_events_args: None,
1596                    })),
1597                }
1598            }
1599
1600            /// Set the value to be returned on the next call to `verify_event()`.
1601            /// Aborts if a value is already set, since that suggests that a previously
1602            /// expected call was never made.
1603            pub(super) fn set_next_verify_event_result(&self, next_result: VerifyEventResult) {
1604                self.assert_next_verify_event_result_is_none();
1605                self.inner.borrow_mut().next_verify_event_result = Some(next_result);
1606            }
1607
1608            fn assert_next_verify_event_result_is_none(&self) {
1609                assert_matches!(self.inner.borrow().next_verify_event_result, None);
1610            }
1611
1612            pub(super) fn verify_event_calls_received(&self) -> usize {
1613                self.inner.borrow().verify_event_calls_received
1614            }
1615
1616            /// Set the value to be returned on the next call to `process_buffered_events()`.
1617            /// Aborts if a value is already set, since that suggests that a previously
1618            /// expected call was never made.
1619            pub(super) fn set_next_process_buffered_events_result(
1620                &self,
1621                next_result: ProcessBufferedEventsResult,
1622            ) {
1623                self.assert_next_process_buffered_events_result_is_none();
1624                self.inner.borrow_mut().next_process_buffered_events_result = Some(next_result);
1625            }
1626
1627            pub(super) fn get_last_processed_buffered_events_args(
1628                &self,
1629            ) -> Option<Vec<TouchpadEvent>> {
1630                self.inner.borrow_mut().last_process_buffered_events_args.take()
1631            }
1632
1633            fn assert_next_process_buffered_events_result_is_none(&self) {
1634                assert_matches!(self.inner.borrow().next_process_buffered_events_result, None);
1635            }
1636
1637            pub(super) fn ref_count(&self) -> usize {
1638                Rc::strong_count(&self.inner)
1639            }
1640        }
1641
1642        #[derive(Debug)]
1643        struct StubMatchedContenderInner {
1644            next_verify_event_result: Option<VerifyEventResult>,
1645            next_process_buffered_events_result: Option<ProcessBufferedEventsResult>,
1646            verify_event_calls_received: usize,
1647            process_buffered_events_calls_received: usize,
1648            last_process_buffered_events_args: Option<Vec<TouchpadEvent>>,
1649        }
1650
1651        impl MatchedContender for StubMatchedContender {
1652            fn verify_event(self: Box<Self>, _event: &TouchpadEvent) -> VerifyEventResult {
1653                let mut inner = self.inner.borrow_mut();
1654                inner.verify_event_calls_received += 1;
1655                inner.next_verify_event_result.take().unwrap_or_else(|| {
1656                    panic!(
1657                        "missing `next_verify_event_result` on call {}",
1658                        inner.verify_event_calls_received
1659                    )
1660                })
1661            }
1662
1663            fn process_buffered_events(
1664                self: Box<Self>,
1665                events: Vec<TouchpadEvent>,
1666            ) -> ProcessBufferedEventsResult {
1667                let mut inner = self.inner.borrow_mut();
1668                inner.last_process_buffered_events_args = Some(events);
1669                inner.process_buffered_events_calls_received += 1;
1670                inner.next_process_buffered_events_result.take().unwrap_or_else(|| {
1671                    panic!(
1672                        "missing `next_process_buffered_events_result` on call {}",
1673                        inner.process_buffered_events_calls_received
1674                    )
1675                })
1676            }
1677        }
1678
1679        #[derive(Clone, Debug)]
1680        pub(super) struct StubWinner {
1681            inner: Rc<RefCell<StubWinnerInner>>,
1682        }
1683
1684        impl StubWinner {
1685            pub(super) fn new() -> Self {
1686                Self {
1687                    inner: Rc::new(RefCell::new(StubWinnerInner {
1688                        next_result: None,
1689                        calls_received: 0,
1690                    })),
1691                }
1692            }
1693
1694            /// Set the value to be returned on the next call to `examine_event()`.
1695            pub(super) fn set_next_result(&self, next_result: ProcessNewEventResult) {
1696                self.inner.borrow_mut().next_result = Some(next_result);
1697            }
1698
1699            pub(super) fn calls_received(&self) -> usize {
1700                self.inner.borrow().calls_received
1701            }
1702        }
1703
1704        #[derive(Debug)]
1705        struct StubWinnerInner {
1706            next_result: Option<ProcessNewEventResult>,
1707            calls_received: usize,
1708        }
1709
1710        impl Winner for StubWinner {
1711            fn process_new_event(self: Box<Self>, _event: TouchpadEvent) -> ProcessNewEventResult {
1712                let mut inner = self.inner.borrow_mut();
1713                inner.calls_received += 1;
1714                inner.next_result.take().unwrap_or_else(|| {
1715                    panic!("missing `next_result` on call {}", inner.calls_received)
1716                })
1717            }
1718        }
1719
1720        impl From<StubContender> for Box<dyn Contender> {
1721            fn from(stub_contender: StubContender) -> Box<dyn Contender> {
1722                Box::new(stub_contender)
1723            }
1724        }
1725
1726        impl From<ContenderForever> for Box<dyn Contender> {
1727            fn from(contender_forever: ContenderForever) -> Box<dyn Contender> {
1728                Box::new(contender_forever)
1729            }
1730        }
1731
1732        impl From<StubMatchedContender> for Box<dyn MatchedContender> {
1733            fn from(stub_matched_contender: StubMatchedContender) -> Box<dyn MatchedContender> {
1734                Box::new(stub_matched_contender)
1735            }
1736        }
1737
1738        impl From<StubWinner> for Box<dyn Winner> {
1739            fn from(stub_winner: StubWinner) -> Box<dyn Winner> {
1740                Box::new(stub_winner)
1741            }
1742        }
1743
1744        pub(super) const TOUCH_CONTACT_INDEX_FINGER: touch_binding::TouchContact =
1745            touch_binding::TouchContact {
1746                id: 0,
1747                position: Position { x: 0.0, y: 0.0 },
1748                pressure: None,
1749                contact_size: Some(Size {
1750                    width: args::MIN_PALM_SIZE_MM / 2.0,
1751                    height: args::MIN_PALM_SIZE_MM / 2.0,
1752                }),
1753            };
1754    }
1755
1756    mod idle_chain_states {
1757        use super::super::{
1758            ExamineEventResult, GestureArena, InputHandler, MutableState,
1759            ProcessBufferedEventsResult, Reason, RecognizedGesture, TouchpadEvent, args,
1760        };
1761        use super::utils::{
1762            ContenderFactoryCalled, ContenderFactoryOnceOrPanic, StubContender,
1763            StubMatchedContender, StubWinner, TOUCH_CONTACT_INDEX_FINGER, make_touchpad_descriptor,
1764            make_unhandled_keyboard_event, make_unhandled_mouse_event,
1765            make_unhandled_touchpad_event,
1766        };
1767        use crate::input_handler::InputHandlerStatus;
1768        use crate::utils::Size;
1769        use crate::{Position, input_device, metrics, touch_binding};
1770        use assert_matches::assert_matches;
1771
1772        use maplit::hashset;
1773        use std::cell::RefCell;
1774        use std::rc::Rc;
1775        use test_case::test_case;
1776
1777        fn make_gesture_arena_with_state(
1778            contender_factory: ContenderFactoryOnceOrPanic,
1779            state: MutableState,
1780        ) -> Rc<GestureArena> {
1781            Rc::new(GestureArena {
1782                contender_factory: Box::new(contender_factory),
1783                mutable_state: RefCell::new(state),
1784                inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
1785                    fuchsia_inspect::Inspector::default().root().create_child("some_key"),
1786                    1,
1787                )),
1788                metrics_logger: metrics::MetricsLogger::default(),
1789                inspect_status: InputHandlerStatus::default(),
1790            })
1791        }
1792
1793        #[test_case(MutableState::Idle; "idle")]
1794        #[test_case(MutableState::Chain; "chain")]
1795        #[fuchsia::test(allow_stalls = false)]
1796        async fn invokes_contender_factory_on_touchpad_event(state: MutableState) {
1797            let contender_factory = Box::new(ContenderFactoryCalled::new());
1798            let arena = Rc::new(GestureArena {
1799                contender_factory: contender_factory.clone(),
1800                mutable_state: RefCell::new(state),
1801                inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
1802                    fuchsia_inspect::Inspector::default().root().create_child("some_key"),
1803                    1,
1804                )),
1805                metrics_logger: metrics::MetricsLogger::default(),
1806                inspect_status: InputHandlerStatus::default(),
1807            });
1808            arena.handle_input_event(make_unhandled_touchpad_event()).await;
1809            assert!(contender_factory.was_called());
1810        }
1811
1812        #[test_case(MutableState::Idle; "idle")]
1813        #[test_case(MutableState::Chain; "chain")]
1814        #[fuchsia::test(allow_stalls = false)]
1815        async fn does_not_invoke_contender_factory_on_mouse_event(state: MutableState) {
1816            let contender_factory = Box::new(ContenderFactoryCalled::new());
1817            let arena = Rc::new(GestureArena {
1818                contender_factory: contender_factory.clone(),
1819                mutable_state: RefCell::new(state),
1820                inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
1821                    fuchsia_inspect::Inspector::default().root().create_child("some_key"),
1822                    1,
1823                )),
1824                metrics_logger: metrics::MetricsLogger::default(),
1825                inspect_status: InputHandlerStatus::default(),
1826            });
1827            arena.handle_input_event(make_unhandled_mouse_event()).await;
1828            assert!(!contender_factory.was_called());
1829        }
1830
1831        #[test_case(MutableState::Idle; "idle")]
1832        #[test_case(MutableState::Chain; "chain")]
1833        #[fuchsia::test(allow_stalls = false)]
1834        async fn does_not_invoke_contender_factory_on_keyboard_event(state: MutableState) {
1835            let contender_factory = Box::new(ContenderFactoryCalled::new());
1836
1837            let arena = Rc::new(GestureArena {
1838                contender_factory: contender_factory.clone(),
1839                mutable_state: RefCell::new(state),
1840                inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
1841                    fuchsia_inspect::Inspector::default().root().create_child("some_key"),
1842                    1,
1843                )),
1844                metrics_logger: metrics::MetricsLogger::default(),
1845                inspect_status: InputHandlerStatus::default(),
1846            });
1847            arena.handle_input_event(make_unhandled_keyboard_event()).await;
1848            assert!(!contender_factory.was_called());
1849        }
1850
1851        #[test_case(MutableState::Idle; "idle")]
1852        #[test_case(MutableState::Chain; "chain")]
1853        #[fuchsia::test(allow_stalls = false)]
1854        async fn calls_examine_event_on_contender(state: MutableState) {
1855            let contender = Box::new(StubContender::new());
1856            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
1857            let arena = make_gesture_arena_with_state(contender_factory, state);
1858            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
1859            arena.handle_input_event(make_unhandled_touchpad_event()).await;
1860            pretty_assertions::assert_eq!(contender.calls_received(), 1);
1861        }
1862
1863        #[fuchsia::test(allow_stalls = false)]
1864        async fn calls_examine_event_on_idle_only_contender_while_idle() {
1865            let contender = Box::new(StubContender::new_start_from_idle());
1866            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
1867            let arena = make_gesture_arena_with_state(contender_factory, MutableState::Idle);
1868            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
1869            arena.handle_input_event(make_unhandled_touchpad_event()).await;
1870            pretty_assertions::assert_eq!(contender.calls_received(), 1);
1871        }
1872
1873        #[fuchsia::test(allow_stalls = false)]
1874        async fn does_not_calls_examine_event_on_idle_only_contender_while_chain() {
1875            let contender = Box::new(StubContender::new_start_from_idle());
1876            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
1877            let arena = make_gesture_arena_with_state(contender_factory, MutableState::Chain);
1878            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
1879            arena.handle_input_event(make_unhandled_touchpad_event()).await;
1880            pretty_assertions::assert_eq!(contender.calls_received(), 0);
1881        }
1882
1883        #[test_case(MutableState::Idle; "idle")]
1884        #[test_case(MutableState::Chain; "chain")]
1885        #[fuchsia::test(allow_stalls = false)]
1886        async fn calls_examine_event_on_all_contenders_even_if_first_matches(state: MutableState) {
1887            let first_contender = Box::new(StubContender::new());
1888            let second_contender = Box::new(StubContender::new());
1889            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![
1890                first_contender.clone(),
1891                second_contender.clone(),
1892            ]);
1893            let arena = make_gesture_arena_with_state(contender_factory, state);
1894            first_contender.set_next_result(ExamineEventResult::MatchedContender(
1895                StubMatchedContender::new().into(),
1896            ));
1897            // Second contender keeps gesture arena in Matching state.
1898            second_contender
1899                .set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
1900            arena.handle_input_event(make_unhandled_touchpad_event()).await;
1901            pretty_assertions::assert_eq!(first_contender.calls_received(), 1);
1902            pretty_assertions::assert_eq!(second_contender.calls_received(), 1);
1903        }
1904
1905        #[test_case(MutableState::Idle; "idle")]
1906        #[test_case(MutableState::Chain; "chain")]
1907        #[fuchsia::test(allow_stalls = false)]
1908        async fn retains_reference_to_replacement_contender(state: MutableState) {
1909            // Create a gesture arena which will instantiate a `StubContender`.
1910            let initial_contender = Box::new(StubContender::new());
1911            let contender_factory =
1912                ContenderFactoryOnceOrPanic::new(vec![initial_contender.clone()]);
1913            let arena = make_gesture_arena_with_state(contender_factory, state);
1914
1915            // Configure `initial_contender` to return a new `StubContender` when
1916            // `examine_event()` is called.
1917            let replacement_contender = StubContender::new();
1918            initial_contender.set_next_result(ExamineEventResult::Contender(
1919                replacement_contender.clone().into(),
1920            ));
1921
1922            // Process a touchpad event. This should cause `arena` to consume the
1923            // `ExamineEventResult` set above.
1924            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
1925
1926            // Verify that the `ExamineEventResult` was, in fact, consumed.
1927            initial_contender.assert_next_result_is_none();
1928
1929            // Finally, verify that `replacement_contender` has two references.
1930            //
1931            // Note that:
1932            // * One of the references is from `replacement_contender`.
1933            // * The second one cannot be from the `ExamineEventResult` above,
1934            //   because the `ExamineEventResult` was consumed.
1935            //
1936            // Hence: the second reference (if it exists) must be in the gesture arena.
1937            pretty_assertions::assert_eq!(replacement_contender.ref_count(), 2);
1938        }
1939
1940        #[test_case(MutableState::Idle; "idle")]
1941        #[test_case(MutableState::Chain; "chain")]
1942        #[fuchsia::test(allow_stalls = false)]
1943        async fn retains_reference_to_matched_contender(state: MutableState) {
1944            // Create a gesture arena which will instantiate a `StubContender`.
1945            let initial_contender = Box::new(StubContender::new());
1946            // Second contender keeps gesture arena in Matching state.
1947            let second_contender = Box::new(StubContender::new());
1948            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![
1949                initial_contender.clone(),
1950                second_contender.clone(),
1951            ]);
1952            let arena = make_gesture_arena_with_state(contender_factory, state);
1953            // Configure `initial_contender` to return a `StubMatchedContender` when
1954            // `examine_event()` is called.
1955            let replacement_contender = StubMatchedContender::new();
1956            initial_contender.set_next_result(ExamineEventResult::MatchedContender(
1957                replacement_contender.clone().into(),
1958            ));
1959            second_contender
1960                .set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
1961
1962            // Process a touchpad event. This should cause `arena` to consume the
1963            // `ExamineEventResult` set above.
1964            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
1965
1966            // Verify that the `ExamineEventResult` was, in fact, consumed.
1967            initial_contender.assert_next_result_is_none();
1968
1969            // Finally, verify that `replacement_contender` has two references.
1970            //
1971            // Note that:
1972            // * One of the references is from `replacement_contender`.
1973            // * The second one cannot be from the `ExamineEventResult` above,
1974            //   because the `ExamineEventResult` was consumed.
1975            //
1976            // Hence: the second reference (if it exists) must be in the gesture arena.
1977            pretty_assertions::assert_eq!(replacement_contender.ref_count(), 2);
1978        }
1979
1980        #[test_case(MutableState::Idle; "idle")]
1981        #[test_case(MutableState::Chain; "chain")]
1982        #[fuchsia::test(allow_stalls = false)]
1983        async fn retains_touchpad_event_when_entering_matching(state: MutableState) {
1984            // Create a gesture arena which will instantiate a `StubContender`.
1985            let initial_contender = Box::new(StubContender::new());
1986            // Second contender keeps gesture arena in Matching state.
1987            let second_contender = Box::new(StubContender::new());
1988            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![
1989                initial_contender.clone(),
1990                second_contender.clone(),
1991            ]);
1992            let arena = make_gesture_arena_with_state(contender_factory, state);
1993            // Create the event which will be sent to the arena.
1994            let touchpad_event = input_device::InputEvent {
1995                event_time: zx::MonotonicInstant::from_nanos(123456),
1996                device_event: input_device::InputDeviceEvent::Touchpad(
1997                    touch_binding::TouchpadEvent {
1998                        injector_contacts: vec![TOUCH_CONTACT_INDEX_FINGER],
1999                        pressed_buttons: hashset! {0},
2000                    },
2001                ),
2002                device_descriptor: make_touchpad_descriptor(),
2003                trace_id: None,
2004                handled: input_device::Handled::No,
2005            };
2006
2007            // Configure `initial_contender` to return a `StubMatchedContender` when
2008            // `examine_event()` is called.
2009            initial_contender.set_next_result(ExamineEventResult::MatchedContender(
2010                StubMatchedContender::new().into(),
2011            ));
2012            second_contender
2013                .set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
2014
2015            // Process `touchpad_event`. Because `initial_contender` returns
2016            // `ExamineEventResult::MatchedContender`, the gesture arena will enter
2017            // the `Matching` state.
2018            arena.clone().handle_input_event(touchpad_event).await;
2019
2020            // Verify that `arena` retained the details of `touchpad_event`.
2021            assert_matches!(
2022                &*arena.mutable_state.borrow(),
2023                MutableState::Matching {
2024                    contenders: _,
2025                    matched_contenders: _,
2026                    buffered_events,
2027                    first_event_timestamp: _,
2028                } => pretty_assertions::assert_eq!(
2029                    buffered_events.as_slice(),
2030                    [TouchpadEvent {
2031                        timestamp: zx::MonotonicInstant::from_nanos(123456),
2032                        pressed_buttons: vec![0],
2033                        contacts: vec![
2034                            touch_binding::TouchContact {
2035                                contact_size: Some(Size{ width: args::MIN_PALM_SIZE_MM / 2000.0, height: args::MIN_PALM_SIZE_MM / 2000.0 }),
2036                                ..TOUCH_CONTACT_INDEX_FINGER
2037                            },
2038                        ],
2039                        filtered_palm_contacts: vec![],
2040                    }]
2041                )
2042            );
2043        }
2044
2045        #[test_case(MutableState::Idle; "idle")]
2046        #[test_case(MutableState::Chain; "chain")]
2047        #[fuchsia::test(allow_stalls = false)]
2048        async fn generates_no_events_on_mismatch_entering_chain(state: MutableState) {
2049            let contender = Box::new(StubContender::new());
2050            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
2051            let arena = make_gesture_arena_with_state(contender_factory, state);
2052            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2053
2054            let touchpad_event = input_device::InputEvent {
2055                event_time: zx::MonotonicInstant::from_nanos(123456),
2056                device_event: input_device::InputDeviceEvent::Touchpad(
2057                    touch_binding::TouchpadEvent {
2058                        injector_contacts: vec![touch_binding::TouchContact {
2059                            id: 1,
2060                            position: Position { x: 0.0, y: 0.0 },
2061                            ..TOUCH_CONTACT_INDEX_FINGER
2062                        }],
2063                        pressed_buttons: hashset! {},
2064                    },
2065                ),
2066                device_descriptor: make_touchpad_descriptor(),
2067                trace_id: None,
2068                handled: input_device::Handled::No,
2069            };
2070
2071            pretty_assertions::assert_eq!(
2072                arena.clone().handle_input_event(touchpad_event).await,
2073                vec![]
2074            );
2075
2076            assert_matches!(*arena.mutable_state.borrow(), MutableState::Chain);
2077        }
2078
2079        #[test_case(MutableState::Idle; "idle")]
2080        #[test_case(MutableState::Chain; "chain")]
2081        #[fuchsia::test(allow_stalls = false)]
2082        async fn generates_no_events_on_mismatch_entering_idle(state: MutableState) {
2083            let contender = Box::new(StubContender::new());
2084            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
2085            let arena = make_gesture_arena_with_state(contender_factory, state);
2086            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2087            pretty_assertions::assert_eq!(
2088                arena.clone().handle_input_event(make_unhandled_touchpad_event()).await,
2089                vec![]
2090            );
2091
2092            assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
2093        }
2094
2095        #[test_case(MutableState::Idle; "idle")]
2096        #[test_case(MutableState::Chain; "chain")]
2097        #[fuchsia::test(allow_stalls = false)]
2098        async fn generates_no_events_when_entering_matching(state: MutableState) {
2099            let contender = Box::new(StubContender::new());
2100            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
2101            let arena = make_gesture_arena_with_state(contender_factory, state);
2102            contender.set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
2103            pretty_assertions::assert_eq!(
2104                arena.handle_input_event(make_unhandled_touchpad_event()).await,
2105                vec![]
2106            );
2107        }
2108
2109        #[test_case(MutableState::Idle; "idle")]
2110        #[test_case(MutableState::Chain; "chain")]
2111        #[fuchsia::test(allow_stalls = false)]
2112        async fn enters_idle_on_mismatch(state: MutableState) {
2113            let contender = Box::new(StubContender::new());
2114            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
2115            let arena = make_gesture_arena_with_state(contender_factory, state);
2116            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2117            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2118            assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
2119        }
2120
2121        #[test_case(MutableState::Idle; "idle")]
2122        #[test_case(MutableState::Chain; "chain")]
2123        #[fuchsia::test(allow_stalls = false)]
2124        async fn enters_matching_on_contender_result(state: MutableState) {
2125            let contender = Box::new(StubContender::new());
2126            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
2127            let arena = make_gesture_arena_with_state(contender_factory, state);
2128            contender.set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
2129            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2130            assert_matches!(*arena.mutable_state.borrow(), MutableState::Matching { .. });
2131        }
2132
2133        #[test_case(MutableState::Idle; "idle")]
2134        #[test_case(MutableState::Chain; "chain")]
2135        #[fuchsia::test(allow_stalls = false)]
2136        async fn enters_matching_on_matched_contender_result(state: MutableState) {
2137            let first_contender = Box::new(StubContender::new());
2138            // Second contender keeps gesture arena in Matching state.
2139            let second_contender = Box::new(StubContender::new());
2140            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![
2141                first_contender.clone(),
2142                second_contender.clone(),
2143            ]);
2144            let arena = make_gesture_arena_with_state(contender_factory, state);
2145
2146            first_contender.set_next_result(ExamineEventResult::MatchedContender(
2147                StubMatchedContender::new().into(),
2148            ));
2149            second_contender
2150                .set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
2151            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2152            assert_matches!(*arena.mutable_state.borrow(), MutableState::Matching { .. });
2153        }
2154
2155        #[test_case(MutableState::Idle; "idle")]
2156        #[test_case(MutableState::Chain; "chain")]
2157        #[fuchsia::test(allow_stalls = false)]
2158        async fn enters_forward_on_only_one_matched_contender_result(state: MutableState) {
2159            let contender = Box::new(StubContender::new());
2160            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
2161            let arena = make_gesture_arena_with_state(contender_factory, state);
2162
2163            let matched_contender = StubMatchedContender::new();
2164            matched_contender.set_next_process_buffered_events_result(
2165                ProcessBufferedEventsResult {
2166                    generated_events: vec![],
2167                    winner: Some(Box::new(StubWinner::new())),
2168                    recognized_gesture: RecognizedGesture::Motion,
2169                },
2170            );
2171            contender
2172                .set_next_result(ExamineEventResult::MatchedContender(matched_contender.into()));
2173            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2174            assert_matches!(*arena.mutable_state.borrow(), MutableState::Forwarding { .. });
2175        }
2176    }
2177
2178    mod matching_state {
2179        use super::super::{
2180            Contender, ContenderFactory, ExamineEventResult, GestureArena, InputHandler,
2181            MouseEvent, MutableState, PRIMARY_BUTTON, ProcessBufferedEventsResult, Reason,
2182            RecognizedGesture, TouchpadEvent, VerifyEventResult,
2183        };
2184        use super::utils::{
2185            ContenderForever, StubContender, StubMatchedContender, StubWinner,
2186            TOUCH_CONTACT_INDEX_FINGER, make_touchpad_descriptor, make_unhandled_keyboard_event,
2187            make_unhandled_mouse_event, make_unhandled_touchpad_event,
2188        };
2189        use crate::input_handler::InputHandlerStatus;
2190        use crate::{Position, input_device, metrics, mouse_binding, touch_binding};
2191        use assert_matches::assert_matches;
2192
2193        use maplit::hashset;
2194        use pretty_assertions::assert_eq;
2195        use std::cell::RefCell;
2196        use std::rc::Rc;
2197        use test_case::test_case;
2198
2199        struct ContenderFactoryWarn {}
2200
2201        impl ContenderFactory for ContenderFactoryWarn {
2202            fn make_contenders(&self) -> Vec<Box<dyn Contender>> {
2203                // Note: printing instead of panic()-ing here yields better
2204                // failure messages from the tests.
2205                eprintln!("factory invoked in matching state");
2206                vec![]
2207            }
2208        }
2209
2210        fn make_matching_arena(
2211            contenders: Vec<StubContender>,
2212            matched_contenders: Vec<StubMatchedContender>,
2213            buffered_events: Vec<TouchpadEvent>,
2214            contender_forever: Option<ContenderForever>,
2215        ) -> Rc<GestureArena> {
2216            Rc::new(GestureArena {
2217                contender_factory: Box::new(ContenderFactoryWarn {}),
2218                mutable_state: RefCell::new(MutableState::Matching {
2219                    contenders: {
2220                        contenders
2221                            .into_iter()
2222                            .map(std::convert::From::<StubContender>::from)
2223                            .chain(
2224                                contender_forever
2225                                    .into_iter()
2226                                    .map(std::convert::From::<ContenderForever>::from),
2227                            )
2228                            .collect()
2229                    },
2230                    matched_contenders: {
2231                        matched_contenders
2232                            .into_iter()
2233                            .map(std::convert::From::<StubMatchedContender>::from)
2234                            .collect()
2235                    },
2236                    first_event_timestamp: zx::MonotonicInstant::ZERO,
2237                    buffered_events,
2238                }),
2239                inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
2240                    fuchsia_inspect::Inspector::default().root().create_child("some_key"),
2241                    1,
2242                )),
2243                metrics_logger: metrics::MetricsLogger::default(),
2244                inspect_status: InputHandlerStatus::default(),
2245            })
2246        }
2247
2248        #[fuchsia::test(allow_stalls = false)]
2249        async fn invokes_examine_and_verify_event_on_touchpad_event() {
2250            let contender = StubContender::new();
2251            let matched_contender = StubMatchedContender::new();
2252            let arena = make_matching_arena(
2253                vec![contender.clone()],
2254                vec![matched_contender.clone()],
2255                vec![],
2256                None,
2257            );
2258            contender.set_next_result(ExamineEventResult::Contender(contender.clone().into()));
2259            matched_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
2260                Reason::Basic("some reason"),
2261            ));
2262            arena.handle_input_event(make_unhandled_touchpad_event()).await;
2263            assert_eq!(contender.calls_received(), 1);
2264            assert_eq!(matched_contender.verify_event_calls_received(), 1);
2265        }
2266
2267        #[fuchsia::test(allow_stalls = false)]
2268        async fn does_not_invoke_examine_or_verify_event_on_mouse_event() {
2269            let contender = StubContender::new();
2270            let matched_contender = StubMatchedContender::new();
2271            let arena = make_matching_arena(
2272                vec![contender.clone()],
2273                vec![matched_contender.clone()],
2274                vec![],
2275                None,
2276            );
2277            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2278            matched_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
2279                Reason::Basic("some reason"),
2280            ));
2281            arena.handle_input_event(make_unhandled_mouse_event()).await;
2282            assert_eq!(contender.calls_received(), 0);
2283            assert_eq!(matched_contender.verify_event_calls_received(), 0);
2284        }
2285
2286        #[fuchsia::test(allow_stalls = false)]
2287        async fn does_not_invoke_examine_or_verify_event_on_keyboard_event() {
2288            let contender = StubContender::new();
2289            let matched_contender = StubMatchedContender::new();
2290            let arena = make_matching_arena(
2291                vec![contender.clone()],
2292                vec![matched_contender.clone()],
2293                vec![],
2294                None,
2295            );
2296            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2297            matched_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
2298                Reason::Basic("some reason"),
2299            ));
2300            arena.handle_input_event(make_unhandled_keyboard_event()).await;
2301            assert_eq!(contender.calls_received(), 0);
2302            assert_eq!(matched_contender.verify_event_calls_received(), 0);
2303        }
2304
2305        #[fuchsia::test(allow_stalls = false)]
2306        async fn does_not_repeat_event_to_matched_contender_returned_by_examine_event() {
2307            let contender = StubContender::new();
2308            let arena = make_matching_arena(
2309                vec![contender.clone()],
2310                vec![],
2311                vec![],
2312                // Make sure that `arena` does not progress to the forwarding state,
2313                // even if the logic _does_ repeat the event.
2314                Some(ContenderForever {}),
2315            );
2316
2317            // Configure `contender` to give a `MatchContender` to `arena`.
2318            let matched_contender = StubMatchedContender::new();
2319            contender.set_next_result(ExamineEventResult::MatchedContender(
2320                matched_contender.clone().into(),
2321            ));
2322
2323            // Set the return value for `matched_contender`. If the implementation
2324            // is buggy, and `verify_event()` is called, having a return value for
2325            // that call makes this test fail at the final assertion, which is easier
2326            // to understand.
2327            matched_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
2328                Reason::Basic("some reason"),
2329            ));
2330
2331            // Send the touchpad event, and validate that the arena did not call
2332            // `verify_event()` on the newly returned `MatchedContender`.
2333            arena.handle_input_event(make_unhandled_touchpad_event()).await;
2334            assert_eq!(matched_contender.verify_event_calls_received(), 0);
2335        }
2336
2337        #[fuchsia::test(allow_stalls = false)]
2338        async fn invokes_examine_event_for_new_event_with_contender_replaced_by_contender() {
2339            // Set up an arena with a `StubContender` that will continue the
2340            // matching process with a new `StubContender`.
2341            let initial_contender = StubContender::new();
2342            let arena = make_matching_arena(vec![initial_contender.clone()], vec![], vec![], None);
2343            let replacement_contender = StubContender::new();
2344            initial_contender.set_next_result(ExamineEventResult::Contender(
2345                replacement_contender.clone().into(),
2346            ));
2347
2348            // Process a touchpad event. This should cause `arena` to replace
2349            // `initial_contender` with `replacement_contender`.
2350            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2351
2352            // Process another touchpad event. This should cause `arena` to invoke
2353            // `examine_event()` on `replacement_contender`.
2354            replacement_contender
2355                .set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2356            arena.handle_input_event(make_unhandled_touchpad_event()).await;
2357
2358            // Verify that `replacement_contender` was called.
2359            assert_eq!(replacement_contender.calls_received(), 1);
2360        }
2361
2362        #[fuchsia::test(allow_stalls = false)]
2363        async fn invokes_verify_event_for_new_event_with_contender_replaced_by_matched_contender() {
2364            // Set up an arena with
2365            // * a `StubContender` that will continue the matching process with
2366            //   a new `StubMatchedContender`
2367            // * a `ContenderForever`, which will keep the arena in the matching
2368            //   state
2369            let initial_contender = StubContender::new();
2370            let arena = make_matching_arena(
2371                vec![initial_contender.clone()],
2372                vec![],
2373                vec![],
2374                Some(ContenderForever {}),
2375            );
2376            let replacement_contender = StubMatchedContender::new();
2377            initial_contender.set_next_result(ExamineEventResult::MatchedContender(
2378                replacement_contender.clone().into(),
2379            ));
2380
2381            // Process a touchpad event. This should cause `arena` to replace
2382            // `initial_contender` with `replacement_contender`.
2383            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2384
2385            // Process another touchpad event. This should cause `arena` to invoke
2386            // `examine_event()` on `replacement_contender`.
2387            replacement_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
2388                Reason::Basic("some reason"),
2389            ));
2390            arena.handle_input_event(make_unhandled_touchpad_event()).await;
2391
2392            // Verify that `replacement_contender` was called.
2393            assert_eq!(replacement_contender.verify_event_calls_received(), 1);
2394        }
2395
2396        #[fuchsia::test(allow_stalls = false)]
2397        async fn invokes_verify_event_for_new_event_with_matched_contender_replaced_by_matched_contender()
2398         {
2399            let matched_contender = StubMatchedContender::new();
2400            let arena = make_matching_arena(
2401                vec![],
2402                vec![matched_contender.clone()],
2403                vec![],
2404                // Ensure that `arena` remains in the matching state. This simplifies
2405                // the test by eliminating the need to provide a response to a
2406                // potential `process_buffered_events()` call.
2407                Some(ContenderForever {}),
2408            );
2409
2410            // Configure `matched_contender` to replace itself with
2411            // `replacement_matched_contender`.
2412            let replacement_matched_contender = StubMatchedContender::new();
2413            matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
2414                replacement_matched_contender.clone().into(),
2415            ));
2416
2417            // Process a touchpad event. This should cause `arena` to retain
2418            // replace `contender` with `replacement_contender`.
2419            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2420
2421            // Set a return value for the expected call on `replacement_contender`.
2422            replacement_matched_contender.set_next_verify_event_result(
2423                VerifyEventResult::Mismatch(Reason::Basic("some reason")),
2424            );
2425
2426            // Process another touchpad event, and verify that `replacement_contender`
2427            // is called.
2428            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2429            assert_eq!(replacement_matched_contender.verify_event_calls_received(), 1);
2430        }
2431
2432        #[fuchsia::test(allow_stalls = false)]
2433        async fn generates_no_events_on_mismatch_entering_idle() {
2434            let contender = StubContender::new();
2435            let arena = make_matching_arena(vec![contender.clone()], vec![], vec![], None);
2436            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2437            assert_eq!(
2438                arena.clone().handle_input_event(make_unhandled_touchpad_event()).await,
2439                vec![]
2440            );
2441            assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
2442        }
2443
2444        #[fuchsia::test(allow_stalls = false)]
2445        async fn generates_no_events_on_mismatch_entering_chain() {
2446            let contender = StubContender::new();
2447            let arena = make_matching_arena(vec![contender.clone()], vec![], vec![], None);
2448            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2449
2450            let touchpad_event = input_device::InputEvent {
2451                event_time: zx::MonotonicInstant::from_nanos(123456),
2452                device_event: input_device::InputDeviceEvent::Touchpad(
2453                    touch_binding::TouchpadEvent {
2454                        injector_contacts: vec![touch_binding::TouchContact {
2455                            id: 1,
2456                            position: Position { x: 0.0, y: 0.0 },
2457                            ..TOUCH_CONTACT_INDEX_FINGER
2458                        }],
2459                        pressed_buttons: hashset! {},
2460                    },
2461                ),
2462                device_descriptor: make_touchpad_descriptor(),
2463                trace_id: None,
2464                handled: input_device::Handled::No,
2465            };
2466
2467            pretty_assertions::assert_eq!(
2468                arena.clone().handle_input_event(touchpad_event).await,
2469                vec![]
2470            );
2471
2472            assert_matches!(*arena.mutable_state.borrow(), MutableState::Chain);
2473        }
2474
2475        #[fuchsia::test(allow_stalls = false)]
2476        async fn generates_no_events_on_contender() {
2477            let contender = StubContender::new();
2478            let arena = make_matching_arena(vec![contender.clone()], vec![], vec![], None);
2479            contender.set_next_result(ExamineEventResult::Contender(contender.clone().into()));
2480            assert_eq!(arena.handle_input_event(make_unhandled_touchpad_event()).await, vec![]);
2481        }
2482
2483        #[fuchsia::test(allow_stalls = false)]
2484        async fn generates_no_events_on_multiple_matched_contenders() {
2485            let first_matched_contender = StubMatchedContender::new();
2486            let second_matched_contender = StubMatchedContender::new();
2487            let arena = make_matching_arena(
2488                vec![],
2489                vec![first_matched_contender.clone(), second_matched_contender.clone()],
2490                vec![],
2491                None,
2492            );
2493            first_matched_contender.set_next_verify_event_result(
2494                VerifyEventResult::MatchedContender(first_matched_contender.clone().into()),
2495            );
2496            second_matched_contender.set_next_verify_event_result(
2497                VerifyEventResult::MatchedContender(second_matched_contender.clone().into()),
2498            );
2499            assert_eq!(arena.handle_input_event(make_unhandled_touchpad_event()).await, vec![]);
2500        }
2501
2502        #[test_case(Some(StubWinner::new()); "with_winner")]
2503        #[test_case(None; "without_winner")]
2504        #[fuchsia::test(allow_stalls = false)]
2505        async fn generates_events_from_process_buffered_events_on_single_matched_contender(
2506            winner: Option<StubWinner>,
2507        ) {
2508            let matched_contender = StubMatchedContender::new();
2509            let arena = make_matching_arena(vec![], vec![matched_contender.clone()], vec![], None);
2510            matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
2511                matched_contender.clone().into(),
2512            ));
2513            matched_contender.set_next_process_buffered_events_result(
2514                ProcessBufferedEventsResult {
2515                    generated_events: vec![
2516                        MouseEvent {
2517                            timestamp: zx::MonotonicInstant::from_nanos(123),
2518                            mouse_data: mouse_binding::MouseEvent {
2519                                location: mouse_binding::MouseLocation::Relative(
2520                                    mouse_binding::RelativeLocation {
2521                                        millimeters: Position::zero(),
2522                                    },
2523                                ),
2524                                wheel_delta_v: None,
2525                                wheel_delta_h: None,
2526                                phase: mouse_binding::MousePhase::Down,
2527                                affected_buttons: hashset! { PRIMARY_BUTTON },
2528                                pressed_buttons: hashset! { PRIMARY_BUTTON },
2529                                is_precision_scroll: None,
2530                                wake_lease: None.into(),
2531                            },
2532                        },
2533                        MouseEvent {
2534                            timestamp: zx::MonotonicInstant::from_nanos(456),
2535                            mouse_data: mouse_binding::MouseEvent {
2536                                location: mouse_binding::MouseLocation::Relative(
2537                                    mouse_binding::RelativeLocation {
2538                                        millimeters: Position::zero(),
2539                                    },
2540                                ),
2541                                wheel_delta_v: None,
2542                                wheel_delta_h: None,
2543                                phase: mouse_binding::MousePhase::Up,
2544                                affected_buttons: hashset! { PRIMARY_BUTTON },
2545                                pressed_buttons: hashset! {},
2546                                is_precision_scroll: None,
2547                                wake_lease: None.into(),
2548                            },
2549                        },
2550                    ],
2551                    winner: winner.map(std::convert::From::<StubWinner>::from),
2552                    recognized_gesture: RecognizedGesture::Motion,
2553                },
2554            );
2555            assert_matches!(
2556                arena
2557                    .handle_input_event(make_unhandled_touchpad_event())
2558                    .await
2559                    .as_slice(),
2560                [
2561                    input_device::InputEvent {
2562                        handled: input_device::Handled::No,
2563                        device_event: input_device::InputDeviceEvent::Mouse(
2564                            mouse_binding::MouseEvent {
2565                                pressed_buttons: first_pressed_buttons, ..
2566                            }
2567                        ),
2568                        ..
2569                    },
2570                    input_device::InputEvent {
2571                        handled: input_device::Handled::No,
2572                        device_event: input_device::InputDeviceEvent::Mouse(
2573                            mouse_binding::MouseEvent {
2574                                pressed_buttons: second_pressed_buttons, ..
2575                            }
2576                        ),
2577                        ..
2578                    },
2579                ] => {
2580                    pretty_assertions::assert_eq!(*first_pressed_buttons, hashset! { PRIMARY_BUTTON });
2581                    pretty_assertions::assert_eq!(*second_pressed_buttons, hashset! {});
2582                }
2583            );
2584        }
2585
2586        #[fuchsia::test(allow_stalls = false)]
2587        async fn passes_all_buffered_events_to_process_buffered_events() {
2588            // Create an arena, and seed it a buffered event (emulating what happens
2589            // as the arena transitions from Idle to Matching).
2590            let contender = StubContender::new();
2591            let matched_contender = StubMatchedContender::new();
2592            let arena = make_matching_arena(
2593                vec![contender.clone()],
2594                vec![matched_contender.clone()],
2595                vec![TouchpadEvent {
2596                    timestamp: zx::MonotonicInstant::from_nanos(123),
2597                    contacts: vec![],
2598                    pressed_buttons: vec![],
2599                    filtered_palm_contacts: vec![],
2600                }],
2601                None,
2602            );
2603
2604            // Send another event to the arena, to exercise the case of an event
2605            // being buffered because the contest continued.
2606            contender.set_next_result(ExamineEventResult::Contender(contender.clone().into()));
2607            matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
2608                matched_contender.clone().into(),
2609            ));
2610            arena
2611                .clone()
2612                .handle_input_event(input_device::InputEvent {
2613                    event_time: zx::MonotonicInstant::from_nanos(456),
2614                    device_event: input_device::InputDeviceEvent::Touchpad(
2615                        touch_binding::TouchpadEvent {
2616                            injector_contacts: vec![],
2617                            pressed_buttons: hashset! {},
2618                        },
2619                    ),
2620                    device_descriptor: make_touchpad_descriptor(),
2621                    trace_id: None,
2622                    handled: input_device::Handled::No,
2623                })
2624                .await;
2625
2626            // Send the event that concludes the contest.
2627            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2628            matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
2629                matched_contender.clone().into(),
2630            ));
2631            matched_contender.set_next_process_buffered_events_result(
2632                ProcessBufferedEventsResult {
2633                    generated_events: vec![],
2634                    winner: None,
2635                    recognized_gesture: RecognizedGesture::Motion,
2636                },
2637            );
2638            arena
2639                .handle_input_event(input_device::InputEvent {
2640                    event_time: zx::MonotonicInstant::from_nanos(789),
2641                    device_event: input_device::InputDeviceEvent::Touchpad(
2642                        touch_binding::TouchpadEvent {
2643                            injector_contacts: vec![],
2644                            pressed_buttons: hashset! {},
2645                        },
2646                    ),
2647                    device_descriptor: make_touchpad_descriptor(),
2648                    trace_id: None,
2649                    handled: input_device::Handled::No,
2650                })
2651                .await;
2652
2653            // Verify that the contender received all three events.
2654            assert_eq!(
2655                matched_contender
2656                    .get_last_processed_buffered_events_args()
2657                    .map(|vec| vec
2658                        .into_iter()
2659                        .map(|event| event.timestamp.into_nanos())
2660                        .collect::<Vec<_>>())
2661                    .as_deref(),
2662                Some([123, 456, 789].as_slice())
2663            );
2664        }
2665
2666        #[fuchsia::test(allow_stalls = false)]
2667        async fn transitions_to_idle_when_sole_contender_does_not_match() {
2668            let contender = StubContender::new();
2669            let arena = make_matching_arena(vec![contender.clone()], vec![], vec![], None);
2670            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2671            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2672            assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
2673        }
2674
2675        #[fuchsia::test(allow_stalls = false)]
2676        async fn transitions_to_idle_when_sole_matched_contender_does_not_match() {
2677            let matched_contender = StubMatchedContender::new();
2678            let arena = make_matching_arena(vec![], vec![matched_contender.clone()], vec![], None);
2679            matched_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
2680                Reason::Basic("some reason"),
2681            ));
2682            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2683            assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
2684        }
2685
2686        #[fuchsia::test(allow_stalls = false)]
2687        async fn remains_in_matching_when_a_contender_remains() {
2688            let contender = StubContender::new();
2689            let arena = make_matching_arena(vec![contender.clone()], vec![], vec![], None);
2690            contender.set_next_result(ExamineEventResult::Contender(contender.clone().into()));
2691            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2692            assert_matches!(*arena.mutable_state.borrow(), MutableState::Matching { .. });
2693        }
2694
2695        #[fuchsia::test(allow_stalls = false)]
2696        async fn remains_in_matching_when_multiple_matched_contenders_remain() {
2697            let matched_contender_a = StubMatchedContender::new();
2698            let matched_contender_b = StubMatchedContender::new();
2699            let arena = make_matching_arena(
2700                vec![],
2701                vec![matched_contender_a.clone(), matched_contender_b.clone()],
2702                vec![],
2703                None,
2704            );
2705            matched_contender_a.set_next_verify_event_result(VerifyEventResult::MatchedContender(
2706                matched_contender_a.clone().into(),
2707            ));
2708            matched_contender_b.set_next_verify_event_result(VerifyEventResult::MatchedContender(
2709                matched_contender_b.clone().into(),
2710            ));
2711            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2712            assert_matches!(*arena.mutable_state.borrow(), MutableState::Matching { .. });
2713        }
2714
2715        #[fuchsia::test(allow_stalls = false)]
2716        async fn transitions_to_idle_when_sole_matched_contender_returns_no_winner() {
2717            let matched_contender = StubMatchedContender::new();
2718            let arena = make_matching_arena(vec![], vec![matched_contender.clone()], vec![], None);
2719            matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
2720                matched_contender.clone().into(),
2721            ));
2722            matched_contender.set_next_process_buffered_events_result(
2723                ProcessBufferedEventsResult {
2724                    generated_events: vec![],
2725                    winner: None,
2726                    recognized_gesture: RecognizedGesture::Motion,
2727                },
2728            );
2729            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2730            assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
2731        }
2732
2733        #[fuchsia::test(allow_stalls = false)]
2734        async fn transitions_to_forwarding_when_sole_matched_contender_returns_a_winner() {
2735            let matched_contender = StubMatchedContender::new();
2736            let arena = make_matching_arena(vec![], vec![matched_contender.clone()], vec![], None);
2737            matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
2738                matched_contender.clone().into(),
2739            ));
2740            matched_contender.set_next_process_buffered_events_result(
2741                ProcessBufferedEventsResult {
2742                    generated_events: vec![],
2743                    winner: Some(StubWinner::new().into()),
2744                    recognized_gesture: RecognizedGesture::Motion,
2745                },
2746            );
2747            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2748            assert_matches!(*arena.mutable_state.borrow(), MutableState::Forwarding { .. });
2749        }
2750    }
2751
2752    mod forwarding_state {
2753        use super::super::{
2754            Contender, EndGestureEvent, ExamineEventResult, GestureArena, InputHandler, MouseEvent,
2755            MutableState, ProcessNewEventResult, Reason, RecognizedGesture, TouchpadEvent,
2756        };
2757        use super::utils::{
2758            ContenderFactoryOnceOrPanic, ContenderForever, StubContender, StubWinner,
2759            TOUCH_CONTACT_INDEX_FINGER, make_touchpad_descriptor, make_unhandled_keyboard_event,
2760            make_unhandled_mouse_event, make_unhandled_touchpad_event,
2761        };
2762        use crate::input_handler::InputHandlerStatus;
2763        use crate::{Position, input_device, metrics, mouse_binding, touch_binding};
2764        use assert_matches::assert_matches;
2765
2766        use maplit::hashset;
2767        use pretty_assertions::assert_eq;
2768        use std::cell::RefCell;
2769        use std::rc::Rc;
2770        use test_case::test_case;
2771
2772        /// Creates an arena in the forwarding state, with
2773        /// a) the given `winner`, and
2774        /// b) an appropriate contender factory
2775        ///
2776        /// If `contender` is `None`, the contender factory will abort on the first
2777        /// call.
2778        ///
2779        /// If `contender` is `Some`, the contender factory will return the content
2780        /// of the `Option` on the first call, and abort on the second call.
2781        ///
2782        /// The former is the common case for the tests. The latter is useful for
2783        /// tests that exercise the arena's handling of an `EndGesture(Some)`.
2784        fn make_forwarding_arena(
2785            winner: StubWinner,
2786            contender: Option<Box<dyn Contender>>,
2787        ) -> Rc<GestureArena> {
2788            let contender_factory = match contender {
2789                Some(c) => Box::new(ContenderFactoryOnceOrPanic::new(vec![c])),
2790                None => Box::new(ContenderFactoryOnceOrPanic::new_panic_when_call()),
2791            };
2792
2793            Rc::new(GestureArena {
2794                contender_factory,
2795                mutable_state: RefCell::new(MutableState::Forwarding {
2796                    winner: winner.into(),
2797                    current_gesture: RecognizedGesture::Motion,
2798                    // Tests that care about `gesture_start_timestamp` should take care
2799                    // to set that value themselves. Default to a value that should cause
2800                    // any test that relies on a good value to fail.
2801                    gesture_start_timestamp: zx::MonotonicInstant::INFINITE_PAST,
2802                    num_events: 0,
2803                }),
2804                inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
2805                    fuchsia_inspect::Inspector::default().root().create_child("some_key"),
2806                    1,
2807                )),
2808                metrics_logger: metrics::MetricsLogger::default(),
2809                inspect_status: InputHandlerStatus::default(),
2810            })
2811        }
2812
2813        #[fuchsia::test(allow_stalls = false)]
2814        async fn invokes_process_new_event_on_touchpad_event() {
2815            let winner = StubWinner::new();
2816            let arena = make_forwarding_arena(winner.clone(), None);
2817            winner.set_next_result(ProcessNewEventResult::EndGesture(
2818                EndGestureEvent::NoEvent,
2819                Reason::Basic("some reason"),
2820            ));
2821            arena.handle_input_event(make_unhandled_touchpad_event()).await;
2822            assert_eq!(winner.calls_received(), 1);
2823        }
2824
2825        #[fuchsia::test(allow_stalls = false)]
2826        async fn does_not_invoke_process_new_event_on_mouse_event() {
2827            let winner = StubWinner::new();
2828            let arena = make_forwarding_arena(winner.clone(), None);
2829            winner.set_next_result(ProcessNewEventResult::EndGesture(
2830                EndGestureEvent::NoEvent,
2831                Reason::Basic("some reason"),
2832            ));
2833            arena.handle_input_event(make_unhandled_mouse_event()).await;
2834            assert_eq!(winner.calls_received(), 0);
2835        }
2836
2837        #[fuchsia::test(allow_stalls = false)]
2838        async fn does_not_invoke_process_new_event_on_keyboard_event() {
2839            let winner = StubWinner::new();
2840            let arena = make_forwarding_arena(winner.clone(), None);
2841            winner.set_next_result(ProcessNewEventResult::EndGesture(
2842                EndGestureEvent::NoEvent,
2843                Reason::Basic("some reason"),
2844            ));
2845            arena.handle_input_event(make_unhandled_keyboard_event()).await;
2846            assert_eq!(winner.calls_received(), 0);
2847        }
2848
2849        #[fuchsia::test(allow_stalls = false)]
2850        async fn invokes_process_new_event_for_multiple_new_events() {
2851            // Create `arena` with `winner` and a `ContenderForever`. The latter
2852            // makes the test fail in a more useful way if the `GestureArena`
2853            // is buggy.
2854            let winner = StubWinner::new();
2855            let arena = make_forwarding_arena(winner.clone(), Some(ContenderForever {}.into()));
2856
2857            // Send two events to `arena`.
2858            winner.set_next_result(ProcessNewEventResult::ContinueGesture(
2859                None,
2860                winner.clone().into(),
2861            ));
2862            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2863            winner.set_next_result(ProcessNewEventResult::ContinueGesture(
2864                None,
2865                winner.clone().into(),
2866            ));
2867            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
2868
2869            // Verify `winner` was called as expected.
2870            assert_eq!(winner.calls_received(), 2);
2871        }
2872
2873        #[fuchsia::test(allow_stalls = false)]
2874        async fn generates_event_on_continue_gesture_with_mouse_event() {
2875            let winner = StubWinner::new();
2876            let arena = make_forwarding_arena(winner.clone(), None);
2877            winner.set_next_result(ProcessNewEventResult::ContinueGesture(
2878                Some(MouseEvent {
2879                    timestamp: zx::MonotonicInstant::from_nanos(123),
2880                    mouse_data: mouse_binding::MouseEvent {
2881                        location: mouse_binding::MouseLocation::Relative(
2882                            mouse_binding::RelativeLocation { millimeters: Position::zero() },
2883                        ),
2884                        wheel_delta_v: None,
2885                        wheel_delta_h: None,
2886                        phase: mouse_binding::MousePhase::Move,
2887                        affected_buttons: hashset! {},
2888                        pressed_buttons: hashset! {},
2889                        is_precision_scroll: None,
2890                        wake_lease: None.into(),
2891                    },
2892                }),
2893                winner.clone().into(),
2894            ));
2895            assert_matches!(
2896                arena.handle_input_event(make_unhandled_touchpad_event()).await.as_slice(),
2897                [
2898                    input_device::InputEvent {
2899                        event_time,
2900                        handled: input_device::Handled::No,
2901                        device_event: input_device::InputDeviceEvent::Mouse(_),
2902                        ..
2903                    },
2904                ] => pretty_assertions::assert_eq!(*event_time, zx::MonotonicInstant::from_nanos(123))
2905            );
2906        }
2907
2908        #[fuchsia::test(allow_stalls = false)]
2909        async fn generates_no_events_on_continue_gesture_without_mouse_event() {
2910            let winner = StubWinner::new();
2911            let arena = make_forwarding_arena(winner.clone(), None);
2912            winner.set_next_result(ProcessNewEventResult::ContinueGesture(
2913                None,
2914                winner.clone().into(),
2915            ));
2916            pretty_assertions::assert_eq!(
2917                arena.handle_input_event(make_unhandled_touchpad_event()).await.as_slice(),
2918                vec![]
2919            );
2920        }
2921
2922        #[fuchsia::test(allow_stalls = false)]
2923        async fn generates_no_events_on_end_gesture_without_touchpad_event() {
2924            let winner = StubWinner::new();
2925            let arena = make_forwarding_arena(winner.clone(), None);
2926            winner.set_next_result(ProcessNewEventResult::EndGesture(
2927                EndGestureEvent::NoEvent,
2928                Reason::Basic("some reason"),
2929            ));
2930            pretty_assertions::assert_eq!(
2931                arena.handle_input_event(make_unhandled_touchpad_event()).await.as_slice(),
2932                vec![]
2933            );
2934        }
2935
2936        #[fuchsia::test(allow_stalls = false)]
2937        async fn generates_no_events_on_end_gesture_with_unconsumed_touchpad_event_entering_idle() {
2938            // Create `arena` with a `StubContender` for processing the unconsumed
2939            // `TouchpadEvent`.
2940            let winner = StubWinner::new();
2941            let contender = StubContender::new();
2942            let arena = make_forwarding_arena(winner.clone(), Some(contender.clone().into()));
2943            winner.set_next_result(ProcessNewEventResult::EndGesture(
2944                EndGestureEvent::UnconsumedEvent(TouchpadEvent {
2945                    contacts: vec![],
2946                    pressed_buttons: vec![],
2947                    timestamp: zx::MonotonicInstant::ZERO,
2948                    filtered_palm_contacts: vec![],
2949                }),
2950                Reason::Basic("some reason"),
2951            ));
2952
2953            // Set a return value for the `examine_event()` call.
2954            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2955
2956            // Verify no events were generated.
2957            pretty_assertions::assert_eq!(
2958                arena.clone().handle_input_event(make_unhandled_touchpad_event()).await.as_slice(),
2959                vec![]
2960            );
2961
2962            // Unconsumed event without contact will invoked in handle_event_while_idle,
2963            // but no contender want this event so stay in Idle state.
2964            assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle { .. });
2965        }
2966
2967        #[fuchsia::test(allow_stalls = false)]
2968        async fn generates_no_events_on_end_gesture_with_unconsumed_touchpad_event_entering_chain()
2969        {
2970            // Create `arena` with a `StubContender` for processing the unconsumed
2971            // `TouchpadEvent`.
2972            let winner = StubWinner::new();
2973            let contender = StubContender::new();
2974            let arena = make_forwarding_arena(winner.clone(), Some(contender.clone().into()));
2975            winner.set_next_result(ProcessNewEventResult::EndGesture(
2976                EndGestureEvent::UnconsumedEvent(TouchpadEvent {
2977                    contacts: vec![touch_binding::TouchContact {
2978                        id: 1,
2979                        position: Position { x: 0.0, y: 0.0 },
2980                        pressure: None,
2981                        contact_size: None,
2982                    }],
2983                    pressed_buttons: vec![],
2984                    timestamp: zx::MonotonicInstant::from_nanos(123456),
2985                    filtered_palm_contacts: vec![],
2986                }),
2987                Reason::Basic("some reason"),
2988            ));
2989
2990            // Set a return value for the `examine_event()` call.
2991            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
2992
2993            let touchpad_event = input_device::InputEvent {
2994                event_time: zx::MonotonicInstant::from_nanos(123456),
2995                device_event: input_device::InputDeviceEvent::Touchpad(
2996                    touch_binding::TouchpadEvent {
2997                        injector_contacts: vec![touch_binding::TouchContact {
2998                            id: 1,
2999                            position: Position { x: 0.0, y: 0.0 },
3000                            ..TOUCH_CONTACT_INDEX_FINGER
3001                        }],
3002                        pressed_buttons: hashset! {},
3003                    },
3004                ),
3005                device_descriptor: make_touchpad_descriptor(),
3006                trace_id: None,
3007                handled: input_device::Handled::No,
3008            };
3009
3010            // Verify no events were generated.
3011            pretty_assertions::assert_eq!(
3012                arena.clone().handle_input_event(touchpad_event).await.as_slice(),
3013                vec![]
3014            );
3015
3016            // Unconsumed event with contact will invoked in handle_event_while_chain,
3017            // but no contender want this event so stay in Chain state.
3018            assert_matches!(*arena.mutable_state.borrow(), MutableState::Chain { .. });
3019        }
3020
3021        #[fuchsia::test(allow_stalls = false)]
3022        async fn generates_event_on_end_gesture_with_touchpad_event() {
3023            // Create `arena` with a `StubContender` for processing the unconsumed
3024            // `TouchpadEvent`.
3025            let winner = StubWinner::new();
3026            let arena = make_forwarding_arena(winner.clone(), None);
3027            let mouse_event = MouseEvent {
3028                timestamp: zx::MonotonicInstant::from_nanos(123),
3029                mouse_data: mouse_binding::MouseEvent {
3030                    location: mouse_binding::MouseLocation::Relative(
3031                        mouse_binding::RelativeLocation { millimeters: Position::zero() },
3032                    ),
3033                    wheel_delta_v: None,
3034                    wheel_delta_h: None,
3035                    phase: mouse_binding::MousePhase::Move,
3036                    affected_buttons: hashset! {},
3037                    pressed_buttons: hashset! {},
3038                    is_precision_scroll: None,
3039                    wake_lease: None.into(),
3040                },
3041            };
3042            winner.set_next_result(ProcessNewEventResult::EndGesture(
3043                EndGestureEvent::GeneratedEvent(mouse_event),
3044                Reason::Basic("some reason"),
3045            ));
3046
3047            // Verify events were generated.
3048            assert_matches!(
3049                arena.handle_input_event(make_unhandled_touchpad_event()).await.as_slice(),
3050                [
3051                    input_device::InputEvent {
3052                        event_time,
3053                        handled: input_device::Handled::No,
3054                        device_event: input_device::InputDeviceEvent::Mouse(_),
3055                        ..
3056                    },
3057                ] => pretty_assertions::assert_eq!(*event_time, zx::MonotonicInstant::from_nanos(123))
3058            );
3059        }
3060
3061        #[test_case(Some(MouseEvent{
3062            timestamp: zx::MonotonicInstant::from_nanos(123),
3063            mouse_data: mouse_binding::MouseEvent {
3064                location: mouse_binding::MouseLocation::Relative(
3065                    mouse_binding::RelativeLocation {
3066                        millimeters: Position::zero(),
3067                    },
3068                ),
3069                wheel_delta_v: None,
3070                wheel_delta_h: None,
3071                phase: mouse_binding::MousePhase::Move,
3072                affected_buttons: hashset! {},
3073                pressed_buttons: hashset! {},
3074                is_precision_scroll: None,
3075                    wake_lease: None.into(),
3076
3077            },
3078        }); "with_mouse_event")]
3079        #[test_case(None; "without_mouse_event")]
3080        #[fuchsia::test(allow_stalls = false)]
3081        async fn remains_in_forwarding_on_continue_gesture(mouse_event: Option<MouseEvent>) {
3082            let winner = StubWinner::new();
3083            let arena = make_forwarding_arena(winner.clone(), None);
3084            winner.set_next_result(ProcessNewEventResult::ContinueGesture(
3085                mouse_event,
3086                winner.clone().into(),
3087            ));
3088            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
3089            assert_matches!(*arena.mutable_state.borrow(), MutableState::Forwarding { .. });
3090        }
3091
3092        #[fuchsia::test(allow_stalls = false)]
3093        async fn transitions_to_idle_on_end_gesture_with_touchpad_event() {
3094            let winner = StubWinner::new();
3095            let arena = make_forwarding_arena(winner.clone(), None);
3096            let mouse_event = MouseEvent {
3097                timestamp: zx::MonotonicInstant::from_nanos(123),
3098                mouse_data: mouse_binding::MouseEvent {
3099                    location: mouse_binding::MouseLocation::Relative(
3100                        mouse_binding::RelativeLocation { millimeters: Position::zero() },
3101                    ),
3102                    wheel_delta_v: None,
3103                    wheel_delta_h: None,
3104                    phase: mouse_binding::MousePhase::Move,
3105                    affected_buttons: hashset! {},
3106                    pressed_buttons: hashset! {},
3107                    is_precision_scroll: None,
3108                    wake_lease: None.into(),
3109                },
3110            };
3111            winner.set_next_result(ProcessNewEventResult::EndGesture(
3112                EndGestureEvent::GeneratedEvent(mouse_event),
3113                Reason::Basic("some reason"),
3114            ));
3115            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
3116            assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle { .. });
3117        }
3118
3119        #[fuchsia::test(allow_stalls = false)]
3120        async fn transitions_to_chain_on_end_gesture_with_touchpad_event() {
3121            let winner = StubWinner::new();
3122            let arena = make_forwarding_arena(winner.clone(), None);
3123            let mouse_event = MouseEvent {
3124                timestamp: zx::MonotonicInstant::from_nanos(123),
3125                mouse_data: mouse_binding::MouseEvent {
3126                    location: mouse_binding::MouseLocation::Relative(
3127                        mouse_binding::RelativeLocation { millimeters: Position::zero() },
3128                    ),
3129                    wheel_delta_v: None,
3130                    wheel_delta_h: None,
3131                    phase: mouse_binding::MousePhase::Move,
3132                    affected_buttons: hashset! {},
3133                    pressed_buttons: hashset! {},
3134                    is_precision_scroll: None,
3135                    wake_lease: None.into(),
3136                },
3137            };
3138            winner.set_next_result(ProcessNewEventResult::EndGesture(
3139                EndGestureEvent::GeneratedEvent(mouse_event),
3140                Reason::Basic("some reason"),
3141            ));
3142            let touchpad_event = input_device::InputEvent {
3143                event_time: zx::MonotonicInstant::from_nanos(123456),
3144                device_event: input_device::InputDeviceEvent::Touchpad(
3145                    touch_binding::TouchpadEvent {
3146                        injector_contacts: vec![touch_binding::TouchContact {
3147                            id: 1,
3148                            position: Position { x: 0.0, y: 0.0 },
3149                            ..TOUCH_CONTACT_INDEX_FINGER
3150                        }],
3151                        pressed_buttons: hashset! {},
3152                    },
3153                ),
3154                device_descriptor: make_touchpad_descriptor(),
3155                trace_id: None,
3156                handled: input_device::Handled::No,
3157            };
3158            arena.clone().handle_input_event(touchpad_event).await;
3159            assert_matches!(*arena.mutable_state.borrow(), MutableState::Chain);
3160        }
3161
3162        #[fuchsia::test(allow_stalls = false)]
3163        async fn transitions_to_idle_on_end_gesture_without_touchpad_event() {
3164            let winner = StubWinner::new();
3165            let arena = make_forwarding_arena(winner.clone(), None);
3166            winner.set_next_result(ProcessNewEventResult::EndGesture(
3167                EndGestureEvent::NoEvent,
3168                Reason::Basic("reason"),
3169            ));
3170            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
3171            assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
3172        }
3173
3174        #[fuchsia::test(allow_stalls = false)]
3175        async fn starts_new_contest_on_end_gesture_with_touchpad_event() {
3176            // Set up an arena in the forwarding state with `winner`, and
3177            // with a factory that will return `contender`. The latter should
3178            // be called at the start of the new contest.
3179            let winner = StubWinner::new();
3180            let contender = StubContender::new();
3181            let arena = make_forwarding_arena(winner.clone(), Some(contender.clone().into()));
3182
3183            // Set up `winner` to end the gesture and return an unconsumed event.
3184            winner.set_next_result(ProcessNewEventResult::EndGesture(
3185                EndGestureEvent::UnconsumedEvent(TouchpadEvent {
3186                    timestamp: zx::MonotonicInstant::ZERO,
3187                    contacts: vec![],
3188                    pressed_buttons: vec![],
3189                    filtered_palm_contacts: vec![],
3190                }),
3191                Reason::Basic("reason"),
3192            ));
3193
3194            // Set up `contender` to reply to the `examine_event()` call.
3195            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
3196
3197            // Send an event into the arena.
3198            arena.handle_input_event(make_unhandled_touchpad_event()).await;
3199
3200            // Verify that the arena started a new contest.
3201            assert_eq!(contender.calls_received(), 1);
3202        }
3203    }
3204
3205    mod touchpad_event_payload {
3206        use super::super::{ExamineEventResult, GestureArena, InputHandler, Reason, args};
3207        use super::utils::{
3208            ContenderFactoryOnceOrPanic, StubContender, TOUCH_CONTACT_INDEX_FINGER,
3209        };
3210        use crate::utils::Size;
3211        use crate::{Position, input_device, metrics, touch_binding};
3212        use assert_matches::assert_matches;
3213        use fidl_fuchsia_input_report::{self as fidl_input_report, UnitType};
3214
3215        use maplit::hashset;
3216        use std::rc::Rc;
3217        use test_case::test_case;
3218        use test_util::assert_near;
3219
3220        fn make_touchpad_descriptor(
3221            units: Vec<(fidl_input_report::Unit, fidl_input_report::Unit)>,
3222        ) -> input_device::InputDeviceDescriptor {
3223            let contacts: Vec<_> = units
3224                .into_iter()
3225                .map(|(x_unit, y_unit)| touch_binding::ContactDeviceDescriptor {
3226                    x_range: fidl_input_report::Range { min: 0, max: 1_000_000 },
3227                    y_range: fidl_input_report::Range { min: 0, max: 1_000_000 },
3228                    x_unit,
3229                    y_unit,
3230                    pressure_range: None,
3231                    width_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
3232                    height_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
3233                })
3234                .collect();
3235            input_device::InputDeviceDescriptor::Touchpad(touch_binding::TouchpadDeviceDescriptor {
3236                device_id: 1,
3237                contacts,
3238            })
3239        }
3240
3241        fn make_unhandled_touchpad_event_with_contacts(
3242            contact_position_units: Vec<(fidl_input_report::Unit, fidl_input_report::Unit)>,
3243            injector_contacts: Vec<touch_binding::TouchContact>,
3244        ) -> input_device::InputEvent {
3245            input_device::InputEvent {
3246                device_event: input_device::InputDeviceEvent::Touchpad(
3247                    touch_binding::TouchpadEvent {
3248                        injector_contacts,
3249                        pressed_buttons: hashset! {},
3250                    },
3251                ),
3252                device_descriptor: make_touchpad_descriptor(contact_position_units),
3253                event_time: zx::MonotonicInstant::ZERO,
3254                trace_id: None,
3255                handled: input_device::Handled::No,
3256            }
3257        }
3258
3259        fn make_unhandled_touchpad_event_with_positions(
3260            contact_position_units: Vec<(fidl_input_report::Unit, fidl_input_report::Unit)>,
3261            positions: Vec<Position>,
3262        ) -> input_device::InputEvent {
3263            let injector_contacts: Vec<_> = positions
3264                .into_iter()
3265                .enumerate()
3266                .map(|(i, position)| touch_binding::TouchContact {
3267                    id: u32::try_from(i).unwrap(),
3268                    position,
3269                    contact_size: None,
3270                    pressure: None,
3271                })
3272                .collect();
3273            make_unhandled_touchpad_event_with_contacts(contact_position_units, injector_contacts)
3274        }
3275
3276        #[test_case(
3277            vec![(
3278                fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
3279                fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
3280            )],
3281            vec![
3282                touch_binding::TouchContact{
3283                    id: 1,
3284                    position: Position { x: 200000.0, y: 100000.0 },
3285                    contact_size: Some(Size {
3286                        width: 2500.0,
3287                        height: 1500.0,
3288                    }),
3289                    pressure: None,
3290                }
3291            ]; "from_micrometers")]
3292        #[test_case(
3293            vec![(
3294                fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -2 },
3295                fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -2 },
3296            )],
3297            vec![
3298                touch_binding::TouchContact{
3299                    id: 1,
3300                    position: Position { x: 20.0, y: 10.0 },
3301                    contact_size: Some(Size {
3302                        width: 0.25,
3303                        height: 0.15,
3304                    }),
3305                    pressure: None,
3306                }
3307            ]; "from_centimeters")]
3308        #[fuchsia::test(allow_stalls = false)]
3309        async fn provides_recognizer_position_size_in_millimeters(
3310            contact_position_units: Vec<(fidl_input_report::Unit, fidl_input_report::Unit)>,
3311            contacts: Vec<touch_binding::TouchContact>,
3312        ) {
3313            let contender = Box::new(StubContender::new());
3314            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
3315            let arena = Rc::new(GestureArena::new_for_test(
3316                Box::new(contender_factory),
3317                &fuchsia_inspect::Inspector::default(),
3318                1,
3319                metrics::MetricsLogger::default(),
3320            ));
3321            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
3322            arena
3323                .handle_input_event(make_unhandled_touchpad_event_with_contacts(
3324                    contact_position_units,
3325                    contacts,
3326                ))
3327                .await;
3328            assert_matches!(
3329                contender.get_last_touchpad_event().unwrap().contacts.as_slice(),
3330                [touch_binding::TouchContact { position, contact_size: Some(size), .. }] => {
3331                    assert_near!(position.x, 200.0, 1.0);
3332                    assert_near!(position.y, 100.0, 1.0);
3333                    assert_near!(size.width, 2.5, 0.1);
3334                    assert_near!(size.height, 1.5, 0.1);
3335                }
3336            );
3337        }
3338
3339        #[test_case(
3340            touch_binding::TouchpadEvent {
3341                injector_contacts: vec![
3342                    touch_binding::TouchContact{
3343                        id: 1,
3344                        position: Position { x: 0.0, y: 0.0 },
3345                        contact_size: Some(Size {
3346                            width: args::MIN_PALM_SIZE_MM,
3347                            height: 0.15,
3348                        }),
3349                        pressure: None,
3350                    }
3351                ],
3352                pressed_buttons: hashset! {},
3353            }; "only palm contact"
3354        )]
3355        #[fuchsia::test(allow_stalls = false)]
3356        async fn ignore_palm_contact(event: touch_binding::TouchpadEvent) {
3357            let contender = Box::new(StubContender::new());
3358            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
3359            let arena = Rc::new(GestureArena::new_for_test(
3360                Box::new(contender_factory),
3361                &fuchsia_inspect::Inspector::default(),
3362                1,
3363                metrics::MetricsLogger::default(),
3364            ));
3365            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
3366
3367            let input_event = input_device::InputEvent {
3368                device_event: input_device::InputDeviceEvent::Touchpad(event),
3369                device_descriptor: make_touchpad_descriptor(vec![(
3370                    fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
3371                    fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
3372                )]),
3373                event_time: zx::MonotonicInstant::ZERO,
3374                trace_id: None,
3375                handled: input_device::Handled::No,
3376            };
3377
3378            arena.handle_input_event(input_event).await;
3379            assert_matches!(contender.get_last_touchpad_event().unwrap().contacts.as_slice(), []);
3380        }
3381
3382        #[test_case(
3383            touch_binding::TouchpadEvent {
3384                injector_contacts: vec![
3385                    TOUCH_CONTACT_INDEX_FINGER,
3386                    touch_binding::TouchContact{
3387                        id: 1,
3388                        position: Position { x: 0.0, y: 0.0 },
3389                        contact_size: Some(Size {
3390                            width: args::MIN_PALM_SIZE_MM,
3391                            height: 0.15,
3392                        }),
3393                        pressure: None,
3394                    }
3395                ],
3396                pressed_buttons: hashset! {},
3397            }, vec![]; "palm contact and finger"
3398        )]
3399        #[fuchsia::test(allow_stalls = false)]
3400        async fn ignore_palm_contact_keep_finger(
3401            event: touch_binding::TouchpadEvent,
3402            expect_buttons: Vec<u8>,
3403        ) {
3404            let contender = Box::new(StubContender::new());
3405            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
3406            let arena = Rc::new(GestureArena::new_for_test(
3407                Box::new(contender_factory),
3408                &fuchsia_inspect::Inspector::default(),
3409                1,
3410                metrics::MetricsLogger::default(),
3411            ));
3412            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
3413
3414            let input_event = input_device::InputEvent {
3415                device_event: input_device::InputDeviceEvent::Touchpad(event),
3416                device_descriptor: make_touchpad_descriptor(vec![(
3417                    fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
3418                    fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
3419                )]),
3420                event_time: zx::MonotonicInstant::ZERO,
3421                trace_id: None,
3422                handled: input_device::Handled::No,
3423            };
3424
3425            arena.handle_input_event(input_event).await;
3426            let got = contender.get_last_touchpad_event().unwrap();
3427            assert_eq!(got.contacts.as_slice(), [TOUCH_CONTACT_INDEX_FINGER]);
3428            assert_eq!(got.pressed_buttons, expect_buttons);
3429        }
3430
3431        #[test_case(
3432            touch_binding::TouchpadEvent {
3433                injector_contacts: vec![
3434                    touch_binding::TouchContact{
3435                        id: 1,
3436                        position: Position { x: 0.0, y: 0.0 },
3437                        contact_size: Some(Size {
3438                            width: args::MIN_PALM_SIZE_MM,
3439                            height: 0.15,
3440                        }),
3441                        pressure: None,
3442                    }
3443                ],
3444                pressed_buttons: hashset! {1},
3445            }; "palm contact"
3446        )]
3447        #[test_case(
3448            touch_binding::TouchpadEvent {
3449                injector_contacts: vec![
3450                    touch_binding::TouchContact{
3451                        id: 1,
3452                        position: Position { x: 0.0, y: 0.0 },
3453                        contact_size: Some(Size {
3454                            width: args::MIN_PALM_SIZE_MM,
3455                            height: 0.15,
3456                        }),
3457                        pressure: None,
3458                    },
3459                    touch_binding::TouchContact{
3460                        id: 2,
3461                        position: Position { x: 5.0, y: 5.0 },
3462                        contact_size: Some(Size {
3463                            width: args::MIN_PALM_SIZE_MM / 2.0,
3464                            height: 0.15,
3465                        }),
3466                        pressure: None,
3467                    },
3468                ],
3469                pressed_buttons: hashset! {1},
3470            }; "palm and finger contact"
3471        )]
3472        #[fuchsia::test(allow_stalls = false)]
3473        async fn skip_palm_detection_when_button_down(event: touch_binding::TouchpadEvent) {
3474            let contender = Box::new(StubContender::new());
3475            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
3476            let arena = Rc::new(GestureArena::new_for_test(
3477                Box::new(contender_factory),
3478                &fuchsia_inspect::Inspector::default(),
3479                1,
3480                metrics::MetricsLogger::default(),
3481            ));
3482            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
3483
3484            let count_of_contact = event.injector_contacts.len();
3485            let input_event = input_device::InputEvent {
3486                device_event: input_device::InputDeviceEvent::Touchpad(event),
3487                device_descriptor: make_touchpad_descriptor(vec![(
3488                    fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
3489                    fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
3490                )]),
3491                event_time: zx::MonotonicInstant::ZERO,
3492                trace_id: None,
3493                handled: input_device::Handled::No,
3494            };
3495
3496            arena.handle_input_event(input_event).await;
3497            assert_eq!(
3498                contender.get_last_touchpad_event().unwrap().contacts.len(),
3499                count_of_contact
3500            );
3501        }
3502
3503        #[test_case(
3504            vec![(
3505                fidl_input_report::Unit{ type_: UnitType::None, exponent: -6 },
3506                fidl_input_report::Unit{ type_: UnitType::None, exponent: -6 },
3507            )],
3508            vec![];
3509            "both units unspecified")]
3510        #[test_case(
3511            vec![(
3512                fidl_input_report::Unit{ type_: UnitType::None, exponent: -6 },
3513                fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
3514            )],
3515            vec![];
3516            "x unit unspecified")]
3517        #[test_case(
3518            vec![(
3519                fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
3520                fidl_input_report::Unit{ type_: UnitType::None, exponent: -6 },
3521            )],
3522            vec![];
3523            "y unit unspecified")]
3524        #[test_case(
3525            vec![(
3526                fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -3 },
3527                fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
3528            )],
3529            vec![];
3530            "mismatched exponents")]
3531        #[test_case(
3532            vec![
3533                (
3534                    fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -3 },
3535                    fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -3 },
3536                ),
3537                (
3538                    fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
3539                    fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
3540                ),
3541            ],
3542            vec![];
3543            "unequal divisors")]
3544        #[fuchsia::test(allow_stalls = false)]
3545        async fn skips_contender_on_bad_descriptor(
3546            contact_position_units: Vec<(fidl_input_report::Unit, fidl_input_report::Unit)>,
3547            positions: Vec<Position>,
3548        ) {
3549            let contender = Box::new(StubContender::new());
3550            let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
3551            let arena = Rc::new(GestureArena::new_for_test(
3552                Box::new(contender_factory),
3553                &fuchsia_inspect::Inspector::default(),
3554                1,
3555                metrics::MetricsLogger::default(),
3556            ));
3557            contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
3558            arena
3559                .handle_input_event(make_unhandled_touchpad_event_with_positions(
3560                    contact_position_units,
3561                    positions,
3562                ))
3563                .await;
3564            assert_eq!(contender.calls_received(), 0);
3565        }
3566    }
3567
3568    mod inspect {
3569        use super::super::{
3570            Contender, ContenderFactory, DetailedReasonFloat, DetailedReasonInt,
3571            DetailedReasonUint, EndGestureEvent, ExamineEventResult, GestureArena, InputHandler,
3572            MouseEvent, ProcessBufferedEventsResult, ProcessNewEventResult, Reason,
3573            RecognizedGesture, TouchpadEvent, args,
3574        };
3575        use super::utils::{
3576            ContenderFactoryOnceOrPanic, StubContender, StubMatchedContender, StubWinner,
3577            make_touchpad_descriptor, make_unhandled_keyboard_event, make_unhandled_mouse_event,
3578            make_unhandled_touchpad_event,
3579        };
3580        use crate::{
3581            Position, Size, input_device, keyboard_binding, metrics, mouse_binding, touch_binding,
3582        };
3583        use assert_matches::assert_matches;
3584        use maplit::hashset;
3585        use std::rc::Rc;
3586        use test_case::test_case;
3587        use {fidl_fuchsia_input_report as fidl_input_report, fuchsia_async as fasync};
3588
3589        struct EmptyContenderFactory {}
3590
3591        impl ContenderFactory for EmptyContenderFactory {
3592            fn make_contenders(&self) -> Vec<Box<dyn crate::gestures::gesture_arena::Contender>> {
3593                vec![]
3594            }
3595        }
3596
3597        #[fuchsia::test]
3598        async fn gesture_arena_initialized_with_inspect_node() {
3599            let inspector = fuchsia_inspect::Inspector::default();
3600            let fake_handlers_node = inspector.root().create_child("input_handlers_node");
3601            let _handler = GestureArena::new_internal(
3602                Box::new(EmptyContenderFactory {}),
3603                &inspector.root(),
3604                2,
3605                &fake_handlers_node,
3606                metrics::MetricsLogger::default(),
3607            );
3608            diagnostics_assertions::assert_data_tree!(inspector, root: {
3609                gestures_event_log: {},
3610                input_handlers_node: {
3611                    gesture_arena: {
3612                        events_received_count: 0u64,
3613                        events_handled_count: 0u64,
3614                        last_received_timestamp_ns: 0u64,
3615                        "fuchsia.inspect.Health": {
3616                            status: "STARTING_UP",
3617                            // Timestamp value is unpredictable and not relevant in this context,
3618                            // so we only assert that the property is present.
3619                            start_timestamp_nanos: diagnostics_assertions::AnyProperty
3620                        },
3621                    }
3622                }
3623            });
3624        }
3625
3626        #[fasync::run_singlethreaded(test)]
3627        async fn gesture_arena_inspect_counts_events() {
3628            let inspector = fuchsia_inspect::Inspector::default();
3629            let fake_handlers_node = inspector.root().create_child("input_handlers_node");
3630            let arena = Rc::new(GestureArena::new_internal(
3631                Box::new(EmptyContenderFactory {}),
3632                &inspector.root(),
3633                1,
3634                &fake_handlers_node,
3635                metrics::MetricsLogger::default(),
3636            ));
3637
3638            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
3639            arena.clone().handle_input_event(make_unhandled_mouse_event()).await;
3640            arena.clone().handle_input_event(make_unhandled_keyboard_event()).await;
3641            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
3642
3643            diagnostics_assertions::assert_data_tree!(inspector, root: contains {
3644                input_handlers_node: {
3645                    gesture_arena: {
3646                        events_received_count: 2u64,
3647                        events_handled_count: 0u64,
3648                        last_received_timestamp_ns: 0u64,
3649                        "fuchsia.inspect.Health": {
3650                            status: "STARTING_UP",
3651                            // Timestamp value is unpredictable and not relevant in this context,
3652                            // so we only assert that the property is present.
3653                            start_timestamp_nanos: diagnostics_assertions::AnyProperty
3654                        },
3655                    }
3656                }
3657            });
3658        }
3659
3660        #[fuchsia::test]
3661        fn logs_to_inspect() {
3662            let mut executor = fasync::TestExecutor::new_with_fake_time();
3663            let basic_mismatch_contender = Box::new(StubContender::new());
3664            let detailed_uint_mismatch_contender = Box::new(StubContender::new());
3665            let detailed_float_mismatch_contender = Box::new(StubContender::new());
3666            let detailed_int_mismatch_contender = Box::new(StubContender::new());
3667            let gesture_matching_contender = Box::new(StubContender::new());
3668            basic_mismatch_contender
3669                .set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
3670            detailed_uint_mismatch_contender.set_next_result(ExamineEventResult::Mismatch(
3671                Reason::DetailedUint(DetailedReasonUint {
3672                    criterion: "num_goats_teleported",
3673                    min: Some(10),
3674                    max: Some(30),
3675                    actual: 42,
3676                }),
3677            ));
3678            detailed_float_mismatch_contender.set_next_result(ExamineEventResult::Mismatch(
3679                Reason::DetailedFloat(DetailedReasonFloat {
3680                    criterion: "teleportation_distance_kilometers",
3681                    min: Some(10.125),
3682                    max: Some(30.5),
3683                    actual: 42.0,
3684                }),
3685            ));
3686            detailed_int_mismatch_contender.set_next_result(ExamineEventResult::Mismatch(
3687                Reason::DetailedInt(DetailedReasonInt {
3688                    criterion: "budget_surplus_trillions",
3689                    min: Some(-10),
3690                    max: Some(1),
3691                    actual: -42,
3692                }),
3693            ));
3694
3695            let inspector = fuchsia_inspect::Inspector::default();
3696            let contender_factory = Box::new(ContenderFactoryOnceOrPanic::new(vec![
3697                basic_mismatch_contender,
3698                detailed_uint_mismatch_contender,
3699                detailed_float_mismatch_contender,
3700                detailed_int_mismatch_contender,
3701                gesture_matching_contender.clone(),
3702            ]));
3703            let arena = Rc::new(GestureArena::new_for_test(
3704                contender_factory,
3705                &inspector,
3706                100,
3707                metrics::MetricsLogger::default(),
3708            ));
3709            let touchpad_descriptor = input_device::InputDeviceDescriptor::Touchpad(
3710                touch_binding::TouchpadDeviceDescriptor {
3711                    device_id: 1,
3712                    contacts: vec![touch_binding::ContactDeviceDescriptor {
3713                        x_range: fidl_input_report::Range { min: 0, max: 10_000 },
3714                        y_range: fidl_input_report::Range { min: 0, max: 10_000 },
3715                        x_unit: fidl_input_report::Unit {
3716                            // Use millimeters to avoid floating-point rounding.
3717                            type_: fidl_input_report::UnitType::Meters,
3718                            exponent: -3,
3719                        },
3720                        y_unit: fidl_input_report::Unit {
3721                            // Use millimeters to avoid floating-point rounding.
3722                            type_: fidl_input_report::UnitType::Meters,
3723                            exponent: -3,
3724                        },
3725                        pressure_range: None,
3726                        width_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
3727                        height_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
3728                    }],
3729                },
3730            );
3731            let keyboard_descriptor = input_device::InputDeviceDescriptor::Keyboard(
3732                keyboard_binding::KeyboardDeviceDescriptor {
3733                    device_id: 2,
3734                    device_information: fidl_fuchsia_input_report::DeviceInformation {
3735                        vendor_id: Some(0),
3736                        product_id: Some(0),
3737                        version: Some(0),
3738                        polling_rate: Some(0),
3739                        ..Default::default()
3740                    },
3741                    keys: vec![fidl_fuchsia_input::Key::A, fidl_fuchsia_input::Key::B],
3742                },
3743            );
3744
3745            // Process a touchpad event without width/height.
3746            let mut handle_event_fut = arena.clone().handle_input_event(input_device::InputEvent {
3747                device_event: input_device::InputDeviceEvent::Touchpad(
3748                    touch_binding::TouchpadEvent {
3749                        injector_contacts: vec![
3750                            touch_binding::TouchContact {
3751                                id: 1u32,
3752                                position: Position { x: 2.0, y: 3.0 },
3753                                contact_size: None,
3754                                pressure: None,
3755                            },
3756                            touch_binding::TouchContact {
3757                                id: 2u32,
3758                                position: Position { x: 40.0, y: 50.0 },
3759                                contact_size: None,
3760                                pressure: None,
3761                            },
3762                        ],
3763                        pressed_buttons: hashset! {1},
3764                    },
3765                ),
3766                device_descriptor: touchpad_descriptor.clone(),
3767                event_time: zx::MonotonicInstant::from_nanos(12_300),
3768                trace_id: None,
3769                handled: input_device::Handled::No,
3770            });
3771            executor.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
3772            gesture_matching_contender
3773                .set_next_result(ExamineEventResult::Contender(gesture_matching_contender.clone()));
3774            assert_matches!(
3775                executor.run_until_stalled(&mut handle_event_fut),
3776                std::task::Poll::Ready(_)
3777            );
3778
3779            // Process a handled key event.
3780            let mut handle_event_fut = arena.clone().handle_input_event(input_device::InputEvent {
3781                device_event: input_device::InputDeviceEvent::Keyboard(
3782                    keyboard_binding::KeyboardEvent::new(
3783                        fidl_fuchsia_input::Key::A,
3784                        fidl_fuchsia_ui_input3::KeyEventType::Pressed,
3785                    ),
3786                ),
3787                device_descriptor: keyboard_descriptor.clone(),
3788                event_time: zx::MonotonicInstant::from_nanos(11_000_000),
3789                trace_id: None,
3790                handled: input_device::Handled::Yes,
3791            });
3792            executor.set_fake_time(fasync::MonotonicInstant::from_nanos(12_000_000));
3793            assert_matches!(
3794                executor.run_until_stalled(&mut handle_event_fut),
3795                std::task::Poll::Ready(_)
3796            );
3797
3798            // Process an unhandled key event.
3799            let mut handle_event_fut = arena.clone().handle_input_event(input_device::InputEvent {
3800                device_event: input_device::InputDeviceEvent::Keyboard(
3801                    keyboard_binding::KeyboardEvent::new(
3802                        fidl_fuchsia_input::Key::B,
3803                        fidl_fuchsia_ui_input3::KeyEventType::Pressed,
3804                    ),
3805                ),
3806                device_descriptor: keyboard_descriptor,
3807                event_time: zx::MonotonicInstant::from_nanos(13_000_000),
3808                trace_id: None,
3809                handled: input_device::Handled::No,
3810            });
3811            executor.set_fake_time(fasync::MonotonicInstant::from_nanos(14_000_000));
3812            assert_matches!(
3813                executor.run_until_stalled(&mut handle_event_fut),
3814                std::task::Poll::Ready(_)
3815            );
3816
3817            // Process a touchpad event with width/height, and end the contest with a match.
3818            let mut handle_event_fut = arena.clone().handle_input_event(input_device::InputEvent {
3819                device_event: input_device::InputDeviceEvent::Touchpad(
3820                    touch_binding::TouchpadEvent {
3821                        injector_contacts: vec![touch_binding::TouchContact {
3822                            id: 1u32,
3823                            position: Position { x: 2.0, y: 3.0 },
3824                            contact_size: Some(Size { width: 3.0, height: 4.0 }),
3825                            pressure: None,
3826                        }],
3827                        pressed_buttons: hashset! {},
3828                    },
3829                ),
3830                device_descriptor: touchpad_descriptor.clone(),
3831                event_time: zx::MonotonicInstant::from_nanos(18_000_000),
3832                trace_id: None,
3833                handled: input_device::Handled::No,
3834            });
3835            let matched_contender = Box::new(StubMatchedContender::new());
3836            matched_contender.set_next_process_buffered_events_result(
3837                ProcessBufferedEventsResult {
3838                    generated_events: vec![],
3839                    winner: None,
3840                    recognized_gesture: RecognizedGesture::Motion,
3841                },
3842            );
3843            gesture_matching_contender
3844                .set_next_result(ExamineEventResult::MatchedContender(matched_contender));
3845            executor.set_fake_time(fasync::MonotonicInstant::from_nanos(19_000_000));
3846            assert_matches!(
3847                executor.run_until_stalled(&mut handle_event_fut),
3848                std::task::Poll::Ready(_)
3849            );
3850
3851            // Uncomment this block to generate a new example for the
3852            // documentation found at the bottom of this file.
3853            /*
3854            {
3855                use fuchsia_inspect::hierarchy::testing::JsonGetter;
3856                println!("{}", inspector.get_pretty_json());
3857            }
3858            */
3859
3860            diagnostics_assertions::assert_data_tree!(@executor executor, inspector, root: {
3861                gestures_event_log: {
3862                    "0": {
3863                        touchpad_event: {
3864                            driver_monotonic_nanos: 12_300i64,
3865                            entry_latency_micros: 9987i64,  // 10_000_000 - 12_300 = 9_987_700 nsec
3866                            pressed_buttons: vec![ 1u64 ],
3867                            contacts: {
3868                                "1": {
3869                                    pos_x_mm: 2.0,
3870                                    pos_y_mm: 3.0,
3871                                },
3872                                "2": {
3873                                    pos_x_mm: 40.0,
3874                                    pos_y_mm: 50.0,
3875                                },
3876                            },
3877                            filtered_palm_contacts: {},
3878                        }
3879                    },
3880                    "1": {
3881                        mismatch_event: {
3882                            contender: "utils::StubContender",
3883                            reason: "some reason",
3884                        }
3885                    },
3886                    "2": {
3887                        mismatch_event: {
3888                            contender: "utils::StubContender",
3889                            criterion: "num_goats_teleported",
3890                            min_allowed: 10u64,
3891                            max_allowed: 30u64,
3892                            actual: 42u64,
3893                        }
3894                    },
3895                    "3": {
3896                        mismatch_event: {
3897                            contender: "utils::StubContender",
3898                            criterion: "teleportation_distance_kilometers",
3899                            min_allowed: 10.125,
3900                            max_allowed: 30.5,
3901                            actual: 42.0,
3902                        }
3903                    },
3904                    "4": {
3905                        mismatch_event: {
3906                            contender: "utils::StubContender",
3907                            criterion: "budget_surplus_trillions",
3908                            min_allowed: -10i64,
3909                            max_allowed: 1i64,
3910                            actual: -42i64,
3911                        }
3912                    },
3913                    "5": {
3914                        key_event: {
3915                            driver_monotonic_nanos: 11_000_000i64,
3916                            entry_latency_micros: 1_000i64,  // 12_000_000 - 11_000_000 = 1_000_00 nsec
3917                        }
3918                    },
3919                    "6": {
3920                        key_event: {
3921                            driver_monotonic_nanos: 13_000_000i64,
3922                            entry_latency_micros: 1_000i64,  // 14_000_000 - 13_000_000 = 1_000_00 nsec
3923                        }
3924                    },
3925                    "7": {
3926                        touchpad_event: {
3927                            driver_monotonic_nanos: 18_000_000i64,
3928                            entry_latency_micros: 1_000i64,  // 19_000_000 - 18_000_000 = 1_000_00 nsec
3929                            pressed_buttons: Vec::<u64>::new(),
3930                            contacts: {
3931                                "1": {
3932                                    pos_x_mm: 2.0,
3933                                    pos_y_mm: 3.0,
3934                                    width_mm: 3.0,
3935                                    height_mm: 4.0,
3936                                },
3937                            },
3938                            filtered_palm_contacts: {},
3939                        }
3940                    },
3941                    "8": {
3942                        gesture_start: {
3943                          gesture_name: "motion",
3944                          latency_event_count: 1u64,
3945                          latency_micros: 17_987i64,  // 18_000_000 - 12_300 = 17_987_700
3946                        }
3947                    },
3948                    "9": {
3949                        gesture_end: {
3950                          gesture_name: "motion",
3951                          contender: "utils::StubMatchedContender",
3952                          event_count: 0u64,
3953                          duration_micros: 0i64,
3954                          reason: "discrete-recognizer",
3955                        }
3956                    }
3957                }
3958            });
3959        }
3960
3961        #[fuchsia::test(allow_stalls = false)]
3962        async fn negative_matching_latency_is_logged_correctly() {
3963            let inspector = fuchsia_inspect::Inspector::default();
3964            let gesture_matching_contender = Box::new(StubContender::new());
3965            let contender_factory = Box::new(ContenderFactoryOnceOrPanic::new(vec![
3966                gesture_matching_contender.clone(),
3967            ]));
3968            let arena = Rc::new(GestureArena::new_for_test(
3969                contender_factory,
3970                &inspector,
3971                100,
3972                metrics::MetricsLogger::default(),
3973            ));
3974
3975            gesture_matching_contender
3976                .set_next_result(ExamineEventResult::Contender(gesture_matching_contender.clone()));
3977            arena
3978                .clone()
3979                .handle_input_event(input_device::InputEvent {
3980                    event_time: zx::MonotonicInstant::from_nanos(15_000),
3981                    ..make_unhandled_touchpad_event()
3982                })
3983                .await;
3984
3985            let matched_contender = Box::new(StubMatchedContender::new());
3986            matched_contender.set_next_process_buffered_events_result(
3987                ProcessBufferedEventsResult {
3988                    generated_events: vec![],
3989                    winner: None,
3990                    recognized_gesture: RecognizedGesture::Motion,
3991                },
3992            );
3993            gesture_matching_contender
3994                .set_next_result(ExamineEventResult::MatchedContender(matched_contender));
3995            arena
3996                .clone()
3997                .handle_input_event(input_device::InputEvent {
3998                    event_time: zx::MonotonicInstant::from_nanos(6_000),
3999                    ..make_unhandled_touchpad_event()
4000                })
4001                .await;
4002
4003            diagnostics_assertions::assert_data_tree!(inspector, root: {
4004                gestures_event_log: {
4005                    "0": contains {},
4006                    "1": contains {},
4007                    "2": {
4008                        gesture_start: {
4009                          gesture_name: diagnostics_assertions::AnyProperty,
4010                          latency_event_count: 1u64,
4011                          latency_micros: -9i64,
4012                        }
4013                    },
4014                    "3": {
4015                        gesture_end: contains {}
4016                    },
4017                }
4018            })
4019        }
4020
4021        struct ContenderFactoryOnceThenEmpty {
4022            contenders: std::cell::Cell<Vec<Box<dyn Contender>>>,
4023        }
4024
4025        impl ContenderFactory for ContenderFactoryOnceThenEmpty {
4026            fn make_contenders(&self) -> Vec<Box<dyn Contender>> {
4027                self.contenders.take()
4028            }
4029        }
4030
4031        #[test_case(EndGestureEvent::NoEvent; "end_gesture_no_event")]
4032        #[test_case(EndGestureEvent::UnconsumedEvent(TouchpadEvent {
4033            timestamp: zx::MonotonicInstant::ZERO,
4034            pressed_buttons: vec![],
4035            contacts: vec![],
4036            filtered_palm_contacts: vec![],
4037        }); "end_gesture_unconsumed_event")]
4038        #[test_case(EndGestureEvent::GeneratedEvent(MouseEvent {
4039            timestamp: zx::MonotonicInstant::ZERO,
4040            mouse_data: mouse_binding::MouseEvent {
4041                location: mouse_binding::MouseLocation::Relative(
4042                    mouse_binding::RelativeLocation {
4043                        millimeters: Position::zero(),
4044                    },
4045                ),
4046                wheel_delta_v: None,
4047                wheel_delta_h: None,
4048                phase: mouse_binding::MousePhase::Move,
4049                affected_buttons: hashset! {},
4050                pressed_buttons: hashset! {},
4051                is_precision_scroll: None,
4052                    wake_lease: None.into(),
4053
4054            },
4055        }); "end_gesture_generated_event")]
4056        #[fuchsia::test(allow_stalls = false)]
4057        async fn multi_event_gesture_is_logged_correctly(end_gesture_event: EndGestureEvent) {
4058            // Set up the arena, and send the first touchpad event.
4059            let inspector = fuchsia_inspect::Inspector::default();
4060            let matching_contender = Box::new(StubContender::new());
4061            let arena = Rc::new(GestureArena::new_for_test(
4062                // In the `UnconsumedEvent` case, the gesture arena will try to start
4063                // a new contest. Hence, `ContenderFactoryOnceOrPanic` isn't appropriate
4064                // here. Nor is `ContenderFactoryOnceOrWarn`, since that would generate
4065                // a spurious warning, making this test harder to debug.
4066                Box::new(ContenderFactoryOnceThenEmpty {
4067                    contenders: std::cell::Cell::new(vec![matching_contender.clone()]),
4068                }),
4069                &inspector,
4070                100,
4071                metrics::MetricsLogger::default(),
4072            ));
4073            matching_contender
4074                .set_next_result(ExamineEventResult::Contender(matching_contender.clone()));
4075            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
4076
4077            // Plumb things up for the next event to trigger the start of a multi-event gesture,
4078            // and send the next event.
4079            let matched_contender = Box::new(StubMatchedContender::new());
4080            let winner = Box::new(StubWinner::new());
4081            matching_contender
4082                .set_next_result(ExamineEventResult::MatchedContender(matched_contender.clone()));
4083            matched_contender.set_next_process_buffered_events_result(
4084                ProcessBufferedEventsResult {
4085                    generated_events: vec![],
4086                    winner: Some(winner.clone()),
4087                    recognized_gesture: RecognizedGesture::Motion,
4088                },
4089            );
4090            winner.set_next_result(ProcessNewEventResult::ContinueGesture(None, winner.clone()));
4091            arena
4092                .clone()
4093                .handle_input_event(input_device::InputEvent {
4094                    device_event: input_device::InputDeviceEvent::Touchpad(
4095                        touch_binding::TouchpadEvent {
4096                            injector_contacts: vec![],
4097                            pressed_buttons: hashset! {},
4098                        },
4099                    ),
4100                    device_descriptor: make_touchpad_descriptor(),
4101                    event_time: zx::MonotonicInstant::from_nanos(123_000),
4102                    trace_id: None,
4103                    handled: input_device::Handled::No,
4104                })
4105                .await;
4106
4107            // Send another event as part of the gesture.
4108            winner.set_next_result(ProcessNewEventResult::ContinueGesture(None, winner.clone()));
4109            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
4110
4111            // Plumb things to end the gesture on the next event, and send that event.
4112            winner.set_next_result(ProcessNewEventResult::EndGesture(
4113                end_gesture_event,
4114                Reason::DetailedUint(DetailedReasonUint {
4115                    criterion: "num_goats_teleported",
4116                    min: Some(10),
4117                    max: Some(30),
4118                    actual: 42,
4119                }),
4120            ));
4121            arena
4122                .clone()
4123                .handle_input_event(input_device::InputEvent {
4124                    device_event: input_device::InputDeviceEvent::Touchpad(
4125                        touch_binding::TouchpadEvent {
4126                            injector_contacts: vec![],
4127                            pressed_buttons: hashset! {},
4128                        },
4129                    ),
4130                    device_descriptor: make_touchpad_descriptor(),
4131                    event_time: zx::MonotonicInstant::from_nanos(456_000),
4132                    trace_id: None,
4133                    handled: input_device::Handled::No,
4134                })
4135                .await;
4136
4137            diagnostics_assertions::assert_data_tree!(inspector, root: {
4138                gestures_event_log: {
4139                    "0": { touchpad_event: contains {} },
4140                    "1": { touchpad_event: contains {} },
4141                    "2": { gesture_start: contains {} },
4142                    "3": { touchpad_event: contains {} },
4143                    "4": { touchpad_event: contains {} },
4144                    "5": {
4145                        gesture_end: contains {
4146                            gesture_name: "motion",
4147                            contender: "utils::StubWinner",
4148                            criterion: "num_goats_teleported",
4149                            min_allowed: 10u64,
4150                            max_allowed: 30u64,
4151                            actual: 42u64,
4152                            duration_micros: 333i64, // 456_000 - 123_000 = 333_000 nsec
4153                            event_count: 2u64,
4154                        },
4155                    },
4156                }
4157            })
4158        }
4159
4160        #[fuchsia::test(allow_stalls = false)]
4161        async fn retains_latest_events_up_to_cap() {
4162            let inspector = fuchsia_inspect::Inspector::default();
4163            let arena = Rc::new(GestureArena::new_for_test(
4164                Box::new(EmptyContenderFactory {}),
4165                &inspector,
4166                2,
4167                metrics::MetricsLogger::default(),
4168            ));
4169            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await; // 0
4170            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await; // 1
4171            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await; // 2
4172            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await; // 3
4173            arena.clone().handle_input_event(make_unhandled_touchpad_event()).await; // 4
4174            diagnostics_assertions::assert_data_tree!(inspector, root: {
4175                gestures_event_log: {
4176                    "3": contains {},
4177                    "4": contains {},
4178                }
4179            })
4180        }
4181
4182        #[fuchsia::test]
4183        fn retains_palm_contacts() {
4184            let mut executor = fasync::TestExecutor::new_with_fake_time();
4185            let inspector = fuchsia_inspect::Inspector::default();
4186            let arena = Rc::new(GestureArena::new_for_test(
4187                Box::new(EmptyContenderFactory {}),
4188                &inspector,
4189                2,
4190                metrics::MetricsLogger::default(),
4191            ));
4192            let mut handle_event_fut = arena.clone().handle_input_event(input_device::InputEvent {
4193                device_event: input_device::InputDeviceEvent::Touchpad(
4194                    touch_binding::TouchpadEvent {
4195                        injector_contacts: vec![touch_binding::TouchContact {
4196                            id: 1u32,
4197                            position: Position { x: 2_000.0, y: 1_000.0 },
4198                            contact_size: Some(Size {
4199                                width: (args::MIN_PALM_SIZE_MM + 0.1) * 1_000.0,
4200                                height: 4_000.0,
4201                            }),
4202                            pressure: None,
4203                        }],
4204                        pressed_buttons: hashset! {},
4205                    },
4206                ),
4207                device_descriptor: make_touchpad_descriptor(),
4208                event_time: zx::MonotonicInstant::ZERO,
4209                trace_id: None,
4210                handled: input_device::Handled::No,
4211            });
4212            executor.set_fake_time(fasync::MonotonicInstant::from_nanos(1_000_000));
4213            assert_matches!(
4214                executor.run_until_stalled(&mut handle_event_fut),
4215                std::task::Poll::Ready(_)
4216            );
4217            diagnostics_assertions::assert_data_tree!(@executor executor, inspector, root: {
4218                gestures_event_log: {
4219                    "0": {
4220                        touchpad_event: {
4221                            driver_monotonic_nanos: 0i64,
4222                            entry_latency_micros: 1_000i64,
4223                            pressed_buttons: Vec::<u64>::new(),
4224                            contacts: {},
4225                            filtered_palm_contacts: {
4226                                "1": {
4227                                    pos_x_mm: 2.0,
4228                                    pos_y_mm: 1.0,
4229                                    width_mm: (args::MIN_PALM_SIZE_MM + 0.1) as f64,
4230                                    height_mm: 4.0,
4231                                },
4232                            },
4233                        },
4234                    },
4235                },
4236            });
4237        }
4238    }
4239}
4240
4241// Example JSON dump of inspect tree generated by `gesture_arena`, from a unit test:
4242//
4243// ```json
4244// {
4245//   "root": {
4246//     "gestures_event_log": {
4247//       "0": {
4248//         "touchpad_event": {
4249//           "driver_monotonic_nanos": 12300,
4250//           "entry_latency_micros": 9987,
4251//           "pressed_buttons": [
4252//             1
4253//           ],
4254//           "contacts": {
4255//             "1": {
4256//               "pos_x_mm": 2.0,
4257//               "pos_y_mm": 3.0
4258//             },
4259//             "2": {
4260//               "pos_x_mm": 40.0,
4261//               "pos_y_mm": 50.0
4262//             }
4263//           }
4264//         }
4265//       },
4266//       "1": {
4267//         "mismatch_event": {
4268//           "contender": "utils::StubContender",
4269//           "reason": "some reason"
4270//         }
4271//       },
4272//       "2": {
4273//         "mismatch_event": {
4274//           "actual": 42,
4275//           "contender": "utils::StubContender",
4276//           "criterion": "num_goats_teleported",
4277//           "max_allowed": 30,
4278//           "min_allowed": 10
4279//         }
4280//       },
4281//       "3": {
4282//         "mismatch_event": {
4283//           "actual": 42.0,
4284//           "contender": "utils::StubContender",
4285//           "criterion": "teleportation_distance_kilometers",
4286//           "max_allowed": 30.5,
4287//           "min_allowed": 10.125
4288//         }
4289//       },
4290//       "4": {
4291//         "mismatch_event": {
4292//           "actual": -42,
4293//           "contender": "utils::StubContender",
4294//           "criterion": "budget_surplus_trillions",
4295//           "max_allowed": 1,
4296//           "min_allowed": -10
4297//         }
4298//       },
4299//       "5": {
4300//         "key_event": {
4301//           "driver_monotonic_nanos": 11000000,
4302//           "entry_latency_micros": 1000
4303//         }
4304//       },
4305//       "6": {
4306//         "key_event": {
4307//           "driver_monotonic_nanos": 13000000,
4308//           "entry_latency_micros": 1000
4309//         }
4310//       },
4311//       "7": {
4312//         "touchpad_event": {
4313//           "driver_monotonic_nanos": 18000000,
4314//           "entry_latency_micros": 1000,
4315//           "pressed_buttons": [],
4316//           "contacts": {
4317//             "1": {
4318//               "height_mm": 4.0,
4319//               "pos_x_mm": 2.0,
4320//               "pos_y_mm": 3.0,
4321//               "width_mm": 3.0
4322//             }
4323//           }
4324//         }
4325//       },
4326//       "8": {
4327//         "gesture_start": {
4328//           "gesture_name": "click",
4329//           "latency_event_count": 1,
4330//           "latency_micros": 17987
4331//         }
4332//       },
4333//       "9": {
4334//         "gesture_end": {
4335//           "contender": "utils::StubMatchedContender",
4336//           "duration_micros": 0,
4337//           "event_count": 0,
4338//           "gesture_name": "click",
4339//           "reason": "discrete-recognizer"
4340//         }
4341//       }
4342//     }
4343//   }
4344// }
4345// ```
4346
4347// Example `iquery` excerpt from a live device:
4348//
4349// ```json5
4350// core/ui/scene_manager:
4351//   metadata:
4352//     filename = fuchsia.inspect.Tree
4353//     component_url = fuchsia-pkg://fuchsia.com/scene_manager#meta/scene_manager.cm
4354//     timestamp = 375999103371
4355//   payload:
4356//     root:
4357//       fuchsia.inspect.Stats:
4358//         allocated_blocks = 9998
4359//         current_size = 163840
4360//         deallocated_blocks = 0
4361//         failed_allocations = 0
4362//         maximum_size = 307200
4363//         total_dynamic_children = 1
4364//       input_pipeline:
4365//         gestures_event_log:
4366//           0:
4367//             key_event:
4368//               driver_monotonic_nanos = 297873226402
4369//               entry_latency_micros = 32908
4370//           1:
4371//             key_event:
4372//               driver_monotonic_nanos = 297955554861
4373//               entry_latency_micros = 1403
4374//           /* ...many entries omitted... */
4375//           150:
4376//             touchpad_event:
4377//               driver_monotonic_nanos = 361816423302
4378//               entry_latency_micros = 14432
4379//               pressed_buttons = []
4380//               contacts:
4381//                 0:
4382//                   height_mm = 2.5840000
4383//                   pos_x_mm = 26.528000
4384//                   pos_y_mm = 23.712999
4385//                   width_mm = 2.9530000
4386//           /* mismatches on u64 properties */
4387//           151:
4388//             mismatch_event:
4389//               actual = 0
4390//               contender = one_finger_drag::InitialContender
4391//               criterion = num_pressed_buttons
4392//               max_allowed = 1
4393//               min_allowed = 1
4394//           152:
4395//             mismatch_event:
4396//               actual = 1
4397//               contender = scroll::InitialContender
4398//               criterion = num_contacts
4399//               max_allowed = 2
4400//               min_allowed = 2
4401//           /* ... many entries omitted ... */
4402//           159:
4403//             touchpad_event:
4404//               driver_monotonic_nanos = 361871136901
4405//               entry_latency_micros = 4745
4406//               pressed_buttons = []
4407//               contacts:
4408//                 0:
4409//                   height_mm = 2.5840000
4410//                   pos_x_mm = 27.162001
4411//                   pos_y_mm = 24.061001
4412//                   width_mm = 2.9530000
4413//           /* mismatches on float properties */
4414//           160:
4415//             mismatch_event:
4416//               actual = 0.723230
4417//               contender = click::UnpressedContender
4418//               criterion = displacement_mm
4419//               max_allowed = 0.500000
4420//           161:
4421//             mismatch_event:
4422//               actual = 0.723230
4423//               contender = primary_tap::FingerContactContender
4424//               criterion = displacement_mm
4425//               max_allowed = 0.500000
4426//           162:
4427//             mismatch_event:
4428//               actual = 0.723230
4429//               contender = secondary_tap::OneFingerContactContender
4430//               criterion = displacement_mm
4431//               max_allowed = 0.500000
4432//           /* gesture start */
4433//           163:
4434//             gesture_start:
4435//               gesture_name = motion
4436//               latency_event_count = 7
4437//               latency_micros = 54713
4438//           /* ... many entries omitted ... */
4439//           295:
4440//             touchpad_event:
4441//               driver_monotonic_nanos = 362903529428
4442//               entry_latency_micros = 3603
4443//               pressed_buttons = []
4444//               contacts:
4445//           /* gesture end */
4446//           296:
4447//             gesture_end:
4448//               actual = 0
4449//               contender = motion::Winner
4450//               criterion = num_contacts
4451//               duration_micros = 1032392
4452//               event_count = 132
4453//               gesture_name = motion
4454//               max_allowed = 1
4455//               min_allowed = 1
4456//           /* ... many entries omitted ... */
4457//           596:
4458//             touchpad_event:
4459//               driver_monotonic_nanos = 370902306135
4460//               entry_latency_micros = 4630
4461//               pressed_buttons = []
4462//               contacts:
4463//                 0:
4464//                   height_mm = 2.5840000
4465//                   pos_x_mm = 76.887001
4466//                   pos_y_mm = 25.962999
4467//                   width_mm = 2.9530000
4468//           /* ... many entries omitted ... */
4469//           752:
4470//             touchpad_event:
4471//               driver_monotonic_nanos = 372106779670
4472//               entry_latency_micros = 4607
4473//               pressed_buttons = []
4474//               contacts:
4475//                 0:
4476//                   height_mm = 3.2300000
4477//                   pos_x_mm = 76.949997
4478//                   pos_y_mm = 26.184999
4479//                   width_mm = 2.9530000
4480//           /* mismatches on i64 properties */
4481//           753:
4482//             mismatch_event:
4483//               actual = 1204473
4484//               contender = primary_tap::FingerContactContender
4485//               criterion = elapsed_time_micros
4486//               max_allowed = 1200000
4487//           754:
4488//             mismatch_event:
4489//               actual = 1204473
4490//               contender = secondary_tap::OneFingerContactContender
4491//               criterion = elapsed_time_micros
4492//               max_allowed = 1200000
4493// ```