use super::{
args, motion, one_finger_button, primary_tap, scroll, secondary_button, secondary_tap,
};
use crate::input_handler::{InputHandler, InputHandlerStatus};
use crate::utils::Size;
use crate::{input_device, mouse_binding, touch_binding};
use anyhow::{format_err, Context, Error};
use async_trait::async_trait;
use core::cell::RefCell;
use fidl_fuchsia_input_report as fidl_input_report;
use fuchsia_inspect::health::Reporter;
use fuchsia_inspect::{ArrayProperty, Node as InspectNode};
use fuchsia_inspect_contrib::nodes::BoundedListNode;
use std::any::Any;
use std::fmt::Debug;
struct GestureArenaInitialContenders {}
impl ContenderFactory for GestureArenaInitialContenders {
fn make_contenders(&self) -> Vec<Box<dyn Contender>> {
vec![
Box::new(motion::InitialContender {
min_movement_in_mm: args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
}),
Box::new(primary_tap::InitialContender {
max_finger_displacement_in_mm: args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
max_time_elapsed: args::TAP_TIMEOUT,
}),
Box::new(secondary_tap::InitialContender {
max_finger_displacement_in_mm: args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
max_time_elapsed: args::TAP_TIMEOUT,
}),
Box::new(scroll::InitialContender {
motion_threshold_in_mm: args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
min_movement_in_mm: args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
max_movement_in_mm: args::MAX_SPURIOUS_TO_INTENTIONAL_SCROLL_THRESHOLD_MM,
limit_tangent_for_direction: args::MAX_SCROLL_DIRECTION_SKEW_DEGREES
.to_radians()
.tan(),
}),
Box::new(one_finger_button::InitialContender {
spurious_to_intentional_motion_threshold_mm:
args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
spurious_to_intentional_motion_threshold_button_change_mm:
args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
button_change_state_timeout: args::BUTTON_CHANGE_STATE_TIMEOUT,
}),
Box::new(secondary_button::InitialContender {
spurious_to_intentional_motion_threshold_mm:
args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
spurious_to_intentional_motion_threshold_button_change_mm:
args::SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
button_change_state_timeout: args::BUTTON_CHANGE_STATE_TIMEOUT,
}),
]
}
}
pub fn make_input_handler(
inspect_node: &InspectNode,
input_handlers_node: &InspectNode,
) -> std::rc::Rc<dyn crate::input_handler::InputHandler> {
tracing::info!("touchpad: created input handler");
std::rc::Rc::new(GestureArena::new_internal(
Box::new(GestureArenaInitialContenders {}),
inspect_node,
MAX_TOUCHPAD_EVENT_LOG_ENTRIES,
input_handlers_node,
))
}
pub(super) const PRIMARY_BUTTON: mouse_binding::MouseButton = 1;
pub(super) const SECONDARY_BUTTON: mouse_binding::MouseButton = 2;
#[derive(Debug, Clone, PartialEq)]
pub(super) struct TouchpadEvent {
pub(super) timestamp: zx::MonotonicInstant,
pub(super) pressed_buttons: Vec<u8>,
pub(super) contacts: Vec<touch_binding::TouchContact>,
pub(super) filtered_palm_contacts: Vec<touch_binding::TouchContact>,
}
#[derive(Debug, PartialEq)]
pub(super) struct MouseEvent {
pub(super) timestamp: zx::MonotonicInstant,
pub(super) mouse_data: mouse_binding::MouseEvent,
}
#[derive(Debug)]
pub(super) struct DetailedReasonUint {
pub(super) criterion: &'static str,
pub(super) min: Option<u64>,
pub(super) max: Option<u64>,
pub(super) actual: usize,
}
#[derive(Debug)]
pub(super) struct DetailedReasonFloat {
pub(super) criterion: &'static str,
pub(super) min: Option<f32>,
pub(super) max: Option<f32>,
pub(super) actual: f32,
}
#[derive(Debug)]
pub(super) struct DetailedReasonInt {
pub(super) criterion: &'static str,
pub(super) min: Option<i64>,
pub(super) max: Option<i64>,
pub(super) actual: i64,
}
#[derive(Debug)]
pub(super) enum Reason {
Basic(&'static str),
DetailedUint(DetailedReasonUint),
DetailedFloat(DetailedReasonFloat),
DetailedInt(DetailedReasonInt),
}
#[derive(Debug)]
pub(super) enum ExamineEventResult {
Contender(Box<dyn Contender>),
MatchedContender(Box<dyn MatchedContender>),
Mismatch(Reason),
}
pub(super) trait Contender: std::fmt::Debug + AsAny {
fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult;
fn get_type_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
fn start_from_idle(&self) -> bool {
false
}
}
pub trait AsAny {
#[allow(dead_code)] fn as_any(&self) -> &dyn Any;
}
impl<T: Any> AsAny for T {
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Debug)]
pub(super) enum VerifyEventResult {
MatchedContender(Box<dyn MatchedContender>),
Mismatch(Reason),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) enum RecognizedGesture {
_Unrecognized,
PrimaryTap,
SecondaryTap,
Motion,
Scroll,
OneButtonDown,
SecondaryButtonDown,
}
#[derive(Debug)]
pub(super) struct ProcessBufferedEventsResult {
pub(super) generated_events: Vec<MouseEvent>,
pub(super) winner: Option<Box<dyn Winner>>,
pub(super) recognized_gesture: RecognizedGesture, }
pub(super) trait MatchedContender: std::fmt::Debug + AsAny {
fn verify_event(self: Box<Self>, event: &TouchpadEvent) -> VerifyEventResult;
fn process_buffered_events(
self: Box<Self>,
events: Vec<TouchpadEvent>,
) -> ProcessBufferedEventsResult;
fn get_type_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
}
#[derive(Debug, PartialEq)]
pub(super) enum EndGestureEvent {
#[allow(dead_code)]
GeneratedEvent(MouseEvent),
UnconsumedEvent(TouchpadEvent),
NoEvent,
}
#[derive(Debug)]
pub(super) enum ProcessNewEventResult {
ContinueGesture(Option<MouseEvent>, Box<dyn Winner>),
EndGesture(EndGestureEvent, Reason),
}
pub(super) trait Winner: std::fmt::Debug {
fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult;
fn get_type_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
}
const MAX_TOUCHPAD_EVENT_LOG_ENTRIES: usize = 1250; #[derive(Debug)]
enum MutableState {
Idle,
Chain,
Matching {
contenders: Vec<Box<dyn Contender>>,
matched_contenders: Vec<Box<dyn MatchedContender>>,
first_event_timestamp: zx::MonotonicInstant,
buffered_events: Vec<TouchpadEvent>,
},
Forwarding {
winner: Box<dyn Winner>,
current_gesture: RecognizedGesture,
gesture_start_timestamp: zx::MonotonicInstant,
num_events: usize,
},
Invalid,
}
#[derive(Debug, PartialEq)]
enum StateName {
Idle,
Chain,
Matching,
Forwarding,
Invalid,
}
trait ContenderFactory {
fn make_contenders(&self) -> Vec<Box<dyn Contender>>;
}
pub(super) struct GestureArena {
contender_factory: Box<dyn ContenderFactory>,
mutable_state: RefCell<MutableState>,
inspect_log: RefCell<BoundedListNode>,
inspect_status: InputHandlerStatus,
}
impl GestureArena {
#[cfg(test)]
fn new_for_test(
contender_factory: Box<dyn ContenderFactory>,
inspector: &fuchsia_inspect::Inspector,
max_inspect_log_entries: usize,
) -> GestureArena {
let test_node = inspector.root().create_child("test_node");
Self::new_internal(contender_factory, inspector.root(), max_inspect_log_entries, &test_node)
}
fn new_internal(
contender_factory: Box<dyn ContenderFactory>,
inspect_node: &InspectNode,
max_inspect_log_entries: usize,
input_handlers_node: &InspectNode,
) -> GestureArena {
let inspect_status = InputHandlerStatus::new(
input_handlers_node,
"gesture_arena",
true,
);
GestureArena {
contender_factory,
mutable_state: RefCell::new(MutableState::Idle),
inspect_log: RefCell::new(BoundedListNode::new(
inspect_node.create_child("gestures_event_log"),
max_inspect_log_entries,
)),
inspect_status,
}
}
#[cfg(test)]
pub(super) fn has_buffered_events(self: std::rc::Rc<Self>) -> bool {
match &*self.mutable_state.borrow() {
MutableState::Matching { buffered_events, .. } => buffered_events.len() > 0,
_ => false,
}
}
}
impl TouchpadEvent {
fn log_inspect(&self, log_entry_node: &InspectNode) {
let touchpad_event_node = log_entry_node.create_child("touchpad_event");
let pressed_buttons_node =
touchpad_event_node.create_uint_array("pressed_buttons", self.pressed_buttons.len());
self.pressed_buttons.iter().enumerate().for_each(|(i, &button_id)| {
pressed_buttons_node.set(i, button_id);
});
log_common(&touchpad_event_node, self.timestamp);
touchpad_event_node.record(pressed_buttons_node);
touchpad_event_node.record_child("contacts", |contact_set_node| {
self.contacts.iter().for_each(|contact| {
contact_set_node.record_child(contact.id.to_string(), |contact_node| {
contact_node.record_double("pos_x_mm", f64::from(contact.position.x));
contact_node.record_double("pos_y_mm", f64::from(contact.position.y));
if let Some(contact_size) = contact.contact_size {
contact_node.record_double("width_mm", f64::from(contact_size.width));
contact_node.record_double("height_mm", f64::from(contact_size.height));
}
})
})
});
touchpad_event_node.record_child("filtered_palm_contacts", |contact_set_node| {
self.filtered_palm_contacts.iter().for_each(|contact| {
contact_set_node.record_child(contact.id.to_string(), |contact_node| {
contact_node.record_double("pos_x_mm", f64::from(contact.position.x));
contact_node.record_double("pos_y_mm", f64::from(contact.position.y));
if let Some(contact_size) = contact.contact_size {
contact_node.record_double("width_mm", f64::from(contact_size.width));
contact_node.record_double("height_mm", f64::from(contact_size.height));
}
})
})
});
log_entry_node.record(touchpad_event_node);
}
}
impl RecognizedGesture {
fn to_str(&self) -> &'static str {
match self {
RecognizedGesture::_Unrecognized => "_unrecognized",
RecognizedGesture::PrimaryTap => "primary_tap",
RecognizedGesture::SecondaryTap => "secondary_tap",
RecognizedGesture::Motion => "motion",
RecognizedGesture::Scroll => "scroll",
RecognizedGesture::OneButtonDown => "one_button_down",
RecognizedGesture::SecondaryButtonDown => "secondary_button_down",
}
}
}
fn log_common(inspect_node: &InspectNode, driver_timestamp: zx::MonotonicInstant) {
inspect_node.record_int("driver_monotonic_nanos", driver_timestamp.into_nanos());
inspect_node.record_int(
"entry_latency_micros",
(fuchsia_async::MonotonicInstant::now().into_zx() - driver_timestamp).into_micros(),
);
}
impl MutableState {
fn get_state_name(&self) -> StateName {
match self {
Self::Idle => StateName::Idle,
Self::Chain => StateName::Chain,
Self::Matching { .. } => StateName::Matching,
Self::Forwarding { .. } => StateName::Forwarding,
Self::Invalid => StateName::Invalid,
}
}
}
fn parse_touchpad_event(
event_time: &zx::MonotonicInstant,
touchpad_event: &touch_binding::TouchpadEvent,
touchpad_descriptor: &touch_binding::TouchpadDeviceDescriptor,
) -> Result<TouchpadEvent, Error> {
let position_divisor =
get_position_divisor_to_mm(touchpad_descriptor).context("failed to compute divisor")?;
Ok(TouchpadEvent {
timestamp: *event_time,
pressed_buttons: touchpad_event.pressed_buttons.iter().copied().collect::<Vec<_>>(),
contacts: touchpad_event
.injector_contacts
.iter()
.map(|contact| touch_binding::TouchContact {
position: contact.position / position_divisor,
contact_size: match contact.contact_size {
Some(size) => Some(Size {
width: size.width / position_divisor,
height: size.height / position_divisor,
}),
None => None,
},
..*contact
})
.collect(),
filtered_palm_contacts: vec![],
})
}
fn filter_palm_contact(touchpad_event: TouchpadEvent) -> TouchpadEvent {
if touchpad_event.pressed_buttons.len() > 0 {
return touchpad_event;
}
let (contacts, filtered_palm_contacts) = touchpad_event.contacts.into_iter().fold(
(
Vec::<touch_binding::TouchContact>::default(),
Vec::<touch_binding::TouchContact>::default(),
),
|mut out, contact| {
match contact.contact_size {
Some(size) => {
if size.width < args::MIN_PALM_SIZE_MM && size.height < args::MIN_PALM_SIZE_MM {
out.0.push(contact);
} else {
out.1.push(contact);
}
}
None => {
out.0.push(contact);
}
}
out
},
);
TouchpadEvent { contacts, filtered_palm_contacts, ..touchpad_event }
}
const COUNTS_PER_MM: u32 = 12;
impl std::convert::From<MouseEvent> for input_device::InputEvent {
fn from(mouse_event: MouseEvent) -> input_device::InputEvent {
input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Mouse(mouse_event.mouse_data),
device_descriptor: input_device::InputDeviceDescriptor::Mouse(
mouse_binding::MouseDeviceDescriptor {
device_id: u32::MAX / 2,
absolute_x_range: Some(fidl_input_report::Range { min: -127, max: 127 }),
absolute_y_range: Some(fidl_input_report::Range { min: -127, max: 127 }),
wheel_v_range: Some(fidl_input_report::Axis {
range: fidl_input_report::Range { min: -65535, max: 65535 },
unit: fidl_input_report::Unit {
type_: fidl_fuchsia_input_report::UnitType::Other,
exponent: 0,
},
}),
wheel_h_range: None,
buttons: Some(vec![PRIMARY_BUTTON, SECONDARY_BUTTON]),
counts_per_mm: COUNTS_PER_MM,
},
),
event_time: mouse_event.timestamp,
handled: input_device::Handled::No,
trace_id: None,
}
}
}
impl GestureArena {
fn handle_touchpad_event(
self: std::rc::Rc<Self>,
event_time: &zx::MonotonicInstant,
touchpad_event: &touch_binding::TouchpadEvent,
device_descriptor: &touch_binding::TouchpadDeviceDescriptor,
) -> Result<Vec<input_device::InputEvent>, Error> {
let touchpad_event = parse_touchpad_event(event_time, touchpad_event, device_descriptor)
.context("dropping touchpad event")?;
let touchpad_event = filter_palm_contact(touchpad_event);
self.inspect_log.borrow_mut().add_entry(|node| {
touchpad_event.log_inspect(node);
});
let old_state_name = self.mutable_state.borrow().get_state_name();
let (new_state, generated_events) = match self.mutable_state.replace(MutableState::Invalid)
{
MutableState::Idle => self.handle_event_while_idle(touchpad_event),
MutableState::Chain => self.handle_event_while_chain(touchpad_event),
MutableState::Matching {
contenders,
matched_contenders,
first_event_timestamp,
buffered_events,
} => self.handle_event_while_matching(
contenders,
matched_contenders,
first_event_timestamp,
buffered_events,
touchpad_event,
),
MutableState::Forwarding {
winner,
current_gesture,
gesture_start_timestamp,
num_events,
} => self.handle_event_while_forwarding(
winner,
current_gesture,
gesture_start_timestamp,
num_events + 1,
touchpad_event,
),
MutableState::Invalid => {
unreachable!();
}
};
tracing::debug!("gesture_arena: {:?} -> {:?}", old_state_name, new_state.get_state_name());
self.mutable_state.replace(new_state);
Ok(generated_events.into_iter().map(input_device::InputEvent::from).collect())
}
fn handle_event_while_idle(&self, new_event: TouchpadEvent) -> (MutableState, Vec<MouseEvent>) {
let (contenders, matched_contenders) = self
.contender_factory
.make_contenders()
.into_iter()
.map(|contender| (contender.get_type_name(), contender.examine_event(&new_event)))
.fold(
(vec![], vec![]),
|(mut contenders, mut matched_contenders), (type_name, examine_result)| {
match examine_result {
ExamineEventResult::Contender(contender) => {
contenders.push(contender);
}
ExamineEventResult::MatchedContender(matched_contender) => {
matched_contenders.push(matched_contender);
}
ExamineEventResult::Mismatch(mismatch_data) => {
self.inspect_log.borrow_mut().add_entry(|node| {
log_mismatch_reason(node, type_name, mismatch_data);
});
}
}
(contenders, matched_contenders)
},
);
let event_timestamp = new_event.timestamp;
self.transit_based_on_contenders_matched_contenders(
vec![],
new_event,
event_timestamp,
contenders,
matched_contenders,
)
}
fn handle_event_while_chain(
&self,
new_event: TouchpadEvent,
) -> (MutableState, Vec<MouseEvent>) {
let (contenders, matched_contenders) = self
.contender_factory
.make_contenders()
.into_iter()
.filter(|contender| !contender.start_from_idle())
.map(|contender| (contender.get_type_name(), contender.examine_event(&new_event)))
.fold(
(vec![], vec![]),
|(mut contenders, mut matched_contenders), (type_name, examine_result)| {
match examine_result {
ExamineEventResult::Contender(contender) => {
contenders.push(contender);
}
ExamineEventResult::MatchedContender(matched_contender) => {
matched_contenders.push(matched_contender);
}
ExamineEventResult::Mismatch(mismatch_data) => {
self.inspect_log.borrow_mut().add_entry(|node| {
log_mismatch_reason(node, type_name, mismatch_data);
});
}
}
(contenders, matched_contenders)
},
);
let event_timestamp = new_event.timestamp;
self.transit_based_on_contenders_matched_contenders(
vec![],
new_event,
event_timestamp,
contenders,
matched_contenders,
)
}
fn handle_event_while_matching(
&self,
contenders: Vec<Box<dyn Contender>>,
matched_contenders: Vec<Box<dyn MatchedContender>>,
first_event_timestamp: zx::MonotonicInstant,
buffered_events: Vec<TouchpadEvent>,
new_event: TouchpadEvent,
) -> (MutableState, Vec<MouseEvent>) {
let matched_contenders = matched_contenders
.into_iter()
.map(|matched_contender| {
(matched_contender.get_type_name(), matched_contender.verify_event(&new_event))
})
.fold(vec![], |mut matched_contenders, (type_name, verify_result)| {
match verify_result {
VerifyEventResult::MatchedContender(m) => {
matched_contenders.push(m);
}
VerifyEventResult::Mismatch(mismatch_data) => {
self.inspect_log.borrow_mut().add_entry(|node| {
log_mismatch_reason(node, type_name, mismatch_data);
});
}
}
matched_contenders
});
let (contenders, matched_contenders) = contenders
.into_iter()
.map(|contender| (contender.get_type_name(), contender.examine_event(&new_event)))
.fold(
(vec![], matched_contenders),
|(mut contenders, mut matched_contenders), (type_name, examine_result)| {
match examine_result {
ExamineEventResult::Contender(contender) => {
contenders.push(contender);
}
ExamineEventResult::MatchedContender(matched_contender) => {
matched_contenders.push(matched_contender);
}
ExamineEventResult::Mismatch(mismatch_data) => {
self.inspect_log.borrow_mut().add_entry(|node| {
log_mismatch_reason(node, type_name, mismatch_data);
});
}
}
(contenders, matched_contenders)
},
);
self.transit_based_on_contenders_matched_contenders(
buffered_events,
new_event,
first_event_timestamp,
contenders,
matched_contenders,
)
}
fn transit_based_on_contenders_matched_contenders(
&self,
buffered_events: Vec<TouchpadEvent>,
new_event: TouchpadEvent,
first_event_timestamp: zx::MonotonicInstant,
contenders: Vec<Box<dyn Contender>>,
matched_contenders: Vec<Box<dyn MatchedContender>>,
) -> (MutableState, Vec<MouseEvent>) {
let has_finger_contact = new_event.contacts.len() > 0;
let new_event_timestamp = new_event.timestamp;
match (
u8::try_from(contenders.len()).unwrap_or(u8::MAX),
u8::try_from(matched_contenders.len()).unwrap_or(u8::MAX),
) {
(0, 0) => {
if has_finger_contact {
(MutableState::Chain, vec![])
} else {
(MutableState::Idle, vec![])
}
}
(0, 1) => {
let num_previous_events = buffered_events.len();
let mut buffered_events = buffered_events;
buffered_events.push(new_event);
let matched_contender = {
let mut matched_contenders = matched_contenders;
matched_contenders.remove(0)
};
let type_name = matched_contender.get_type_name();
let ProcessBufferedEventsResult { generated_events, winner, recognized_gesture } =
matched_contender.process_buffered_events(buffered_events);
self.inspect_log.borrow_mut().add_entry(|node| {
log_gesture_start(
node,
recognized_gesture,
num_previous_events,
new_event_timestamp - first_event_timestamp,
);
});
match winner {
Some(winner) => (
MutableState::Forwarding {
winner,
current_gesture: recognized_gesture,
gesture_start_timestamp: new_event_timestamp,
num_events: 0,
},
generated_events,
),
None => {
self.inspect_log.borrow_mut().add_entry(|node| {
log_gesture_end(
node,
type_name,
recognized_gesture,
Reason::Basic("discrete-recognizer"),
0,
zx::MonotonicDuration::from_nanos(0),
);
});
if has_finger_contact {
(MutableState::Chain, generated_events)
} else {
(MutableState::Idle, generated_events)
}
}
}
}
(1.., _) | (_, 2..) => {
let mut buffered_events = buffered_events;
buffered_events.push(new_event);
(
MutableState::Matching {
contenders,
matched_contenders,
first_event_timestamp,
buffered_events,
},
vec![],
)
}
}
}
fn handle_event_while_forwarding(
&self,
winner: Box<dyn Winner>,
current_gesture: RecognizedGesture,
gesture_start_timestamp: zx::MonotonicInstant,
num_events: usize,
new_event: TouchpadEvent,
) -> (MutableState, Vec<MouseEvent>) {
let type_name = winner.get_type_name();
let has_finger_contact = new_event.contacts.len() > 0;
let new_event_timestamp = new_event.timestamp;
match winner.process_new_event(new_event) {
ProcessNewEventResult::ContinueGesture(generated_event, winner) => (
MutableState::Forwarding {
winner,
current_gesture,
gesture_start_timestamp,
num_events,
},
generated_event.into_iter().collect(),
),
ProcessNewEventResult::EndGesture(
EndGestureEvent::GeneratedEvent(generated_event),
reason,
) => {
tracing::debug!("touchpad: {} ended: {:?}", type_name, reason);
self.inspect_log.borrow_mut().add_entry(|node| {
log_gesture_end(
node,
type_name,
current_gesture,
reason,
num_events,
new_event_timestamp - gesture_start_timestamp,
);
});
if has_finger_contact {
(MutableState::Chain, vec![generated_event])
} else {
(MutableState::Idle, vec![generated_event])
}
}
ProcessNewEventResult::EndGesture(
EndGestureEvent::UnconsumedEvent(unconsumed_event),
reason,
) => {
tracing::debug!("touchpad: {} ended: {:?}", type_name, reason);
self.inspect_log.borrow_mut().add_entry(|node| {
log_gesture_end(
node,
type_name,
current_gesture,
reason,
num_events,
new_event_timestamp - gesture_start_timestamp,
);
});
if unconsumed_event.contacts.len() > 0 {
self.handle_event_while_chain(unconsumed_event)
} else {
self.handle_event_while_idle(unconsumed_event)
}
}
ProcessNewEventResult::EndGesture(EndGestureEvent::NoEvent, reason) => {
tracing::debug!("touchpad: {} ended: {:?}", type_name, reason);
self.inspect_log.borrow_mut().add_entry(|node| {
log_gesture_end(
node,
type_name,
current_gesture,
reason,
num_events,
new_event_timestamp - gesture_start_timestamp,
);
});
if has_finger_contact {
(MutableState::Chain, vec![])
} else {
(MutableState::Idle, vec![])
}
}
}
}
#[allow(dead_code)] pub(super) fn mutable_state_to_str(&self) -> String {
match &*self.mutable_state.borrow() {
MutableState::Idle => format!("touchpad: Idle"),
MutableState::Chain => format!("touchpad: Chain"),
MutableState::Matching {
contenders,
matched_contenders,
first_event_timestamp,
buffered_events,
} => {
format!(
"touchpad: Matching {{ \
contenders: [ {} ], \
matched_contenders: [ {} ], \
first_event_timestamp_nanos: {}, \
n_buffered_events: {} \
}}",
contenders.iter().fold(String::new(), |accum, item| {
accum + &format!("{}, ", item.get_type_name())
}),
matched_contenders.iter().fold(String::new(), |accum, item| {
accum + &format!("{}, ", item.get_type_name())
}),
first_event_timestamp.into_nanos(),
buffered_events.len()
)
}
MutableState::Forwarding {
winner,
current_gesture,
gesture_start_timestamp,
num_events,
} => {
format!(
"touchpad: Forwarding {{ \
winner: {}, \
current_gesture: {:?}, \
gesture_start_timestamp_nanos: {}, \
num_events: {} \
}}",
winner.get_type_name(),
current_gesture,
gesture_start_timestamp.into_nanos(),
num_events
)
}
MutableState::Invalid => format!("touchpad: Invalid"),
}
}
#[allow(dead_code)] fn log_mutable_state(&self) {
tracing::info!("{}", self.mutable_state_to_str());
}
}
#[async_trait(?Send)]
impl InputHandler for GestureArena {
async fn handle_input_event(
self: std::rc::Rc<Self>,
input_event: input_device::InputEvent,
) -> Vec<input_device::InputEvent> {
match input_event {
input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touchpad(ref touchpad_event),
ref event_time,
device_descriptor:
input_device::InputDeviceDescriptor::Touchpad(ref touchpad_descriptor),
handled: input_device::Handled::No,
..
} => {
self.inspect_status
.count_received_event(input_device::InputEvent::from(input_event.clone()));
match self.handle_touchpad_event(event_time, touchpad_event, touchpad_descriptor) {
Ok(r) => r,
Err(e) => {
tracing::error!("{}", e);
vec![input_event]
}
}
}
input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(_),
event_time,
..
} => {
self.inspect_log.borrow_mut().add_entry(|node| {
log_keyboard_event_timestamp(node, event_time);
});
vec![input_event]
}
_ => {
vec![input_event]
}
}
}
fn set_handler_healthy(self: std::rc::Rc<Self>) {
self.inspect_status.health_node.borrow_mut().set_ok();
}
fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
}
}
fn get_position_divisor_to_mm(
touchpad_descriptor: &touch_binding::TouchpadDeviceDescriptor,
) -> Result<f32, Error> {
const EXPONENT_MILLIS: i32 = -3;
let divisors =
touchpad_descriptor.contacts.iter().enumerate().map(|(i, contact_descriptor)| {
match (contact_descriptor.x_unit, contact_descriptor.y_unit) {
(
fidl_input_report::Unit {
type_: fidl_input_report::UnitType::Meters,
exponent: exponent_x,
},
fidl_input_report::Unit {
type_: fidl_input_report::UnitType::Meters,
exponent: exponent_y,
},
) => {
if exponent_x == exponent_y {
Ok(f32::powi(10.0, EXPONENT_MILLIS - exponent_x))
} else {
Err(format!(
"contact {}: mismatched exponents x={}, y={}",
i, exponent_x, exponent_y
))
}
}
(
fidl_input_report::Unit { type_: x_unit_type, .. },
fidl_input_report::Unit { type_: fidl_input_report::UnitType::Meters, .. },
) => Err(format!(
"contact {}: expected x-unit-type of Meters but got {:?}",
i, x_unit_type
)),
(
fidl_input_report::Unit { type_: fidl_input_report::UnitType::Meters, .. },
fidl_input_report::Unit { type_: y_unit_type, .. },
) => Err(format!(
"contact {}: expected y-unit-type of Meters but got {:?}",
i, y_unit_type
)),
(
fidl_input_report::Unit { type_: x_unit_type, .. },
fidl_input_report::Unit { type_: y_unit_type, .. },
) => Err(format!(
"contact {}: expected x and y unit-types of Meters but got x={:?} and y={:?}",
i, x_unit_type, y_unit_type
)),
}
});
let (divisors, errors): (Vec<_>, Vec<_>) =
divisors.fold((vec![], vec![]), |(mut divisors, mut errors), divisor| {
match divisor {
Ok(d) => divisors.push(d),
Err(e) => errors.push(e),
};
(divisors, errors)
});
if !errors.is_empty() {
return Err(format_err!(errors
.into_iter()
.fold(String::new(), |prev_err_msgs, this_err_msg| prev_err_msgs
+ &this_err_msg
+ ", ")));
}
let first_divisor = match divisors.first() {
Some(&divisor) => divisor,
None => return Err(format_err!("no contact descriptors!")),
};
if divisors.iter().any(|&divisor| divisor != first_divisor) {
return Err(format_err!(divisors
.iter()
.enumerate()
.filter(|(_i, &divisor)| divisor != first_divisor)
.map(|(i, divisor)| format!(
"contact {} has a different divisor than the first contact ({:?} != {:?})",
i, divisor, first_divisor,
))
.fold(String::new(), |prev_err_msgs, this_err_msg| prev_err_msgs
+ &this_err_msg
+ ", ")));
}
Ok(first_divisor)
}
fn log_keyboard_event_timestamp(
log_entry_node: &InspectNode,
driver_timestamp: zx::MonotonicInstant,
) {
log_entry_node.record_child("key_event", |key_event_node| {
log_common(key_event_node, driver_timestamp);
});
}
fn log_basic_reason(reason_node: &InspectNode, text: &'static str) {
reason_node.record_string("reason", text);
}
fn log_detailed_reason_uint(reason_node: &InspectNode, reason_details: DetailedReasonUint) {
reason_node.record_string("criterion", reason_details.criterion);
reason_node.record_uint("actual", u64::try_from(reason_details.actual).unwrap_or(u64::MAX));
reason_details.min.map(|min| reason_node.record_uint("min_allowed", min));
reason_details.max.map(|max| reason_node.record_uint("max_allowed", max));
}
fn log_detailed_reason_float(reason_node: &InspectNode, reason_details: DetailedReasonFloat) {
reason_node.record_string("criterion", reason_details.criterion);
reason_node.record_double("actual", f64::from(reason_details.actual));
reason_details.min.map(|min| reason_node.record_double("min_allowed", f64::from(min)));
reason_details.max.map(|max| reason_node.record_double("max_allowed", f64::from(max)));
}
fn log_detailed_reason_int(reason_node: &InspectNode, reason_details: DetailedReasonInt) {
reason_node.record_string("criterion", reason_details.criterion);
reason_node.record_int("actual", reason_details.actual);
reason_details.min.map(|min| reason_node.record_int("min_allowed", min));
reason_details.max.map(|max| reason_node.record_int("max_allowed", max));
}
fn log_reason(reason_node: &InspectNode, contender_name: &'static str, reason: Reason) {
let contender_name = match contender_name.rmatch_indices("::").nth(1) {
Some((i, _matched_substr)) => &contender_name[i + 2..],
None => contender_name,
};
reason_node.record_string("contender", contender_name);
match reason {
Reason::Basic(text) => log_basic_reason(reason_node, text),
Reason::DetailedUint(mismatch_details) => {
log_detailed_reason_uint(reason_node, mismatch_details)
}
Reason::DetailedFloat(mismatch_details) => {
log_detailed_reason_float(reason_node, mismatch_details)
}
Reason::DetailedInt(mismatch_details) => {
log_detailed_reason_int(reason_node, mismatch_details)
}
}
}
fn log_mismatch_reason(log_entry_node: &InspectNode, contender_name: &'static str, reason: Reason) {
tracing::debug!("touchpad: {} mismatched: {:?}", contender_name, reason);
log_entry_node.record_child("mismatch_event", |mismatch_event_node| {
log_reason(mismatch_event_node, contender_name, reason);
});
}
fn log_gesture_start(
log_entry_node: &InspectNode,
recognized_gesture: RecognizedGesture,
num_previous_events: usize,
elapsed_from_first_event: zx::MonotonicDuration,
) {
tracing::debug!("touchpad: recognized start {:?}", recognized_gesture);
log_entry_node.record_child("gesture_start", |gesture_start_node| {
gesture_start_node.record_string("gesture_name", recognized_gesture.to_str());
gesture_start_node.record_int(
"latency_micros",
elapsed_from_first_event.into_micros(),
);
gesture_start_node.record_uint(
"latency_event_count",
u64::try_from(num_previous_events).unwrap_or(u64::MAX),
);
});
}
fn log_gesture_end(
log_entry_node: &InspectNode,
contender_name: &'static str,
current_gesture: RecognizedGesture,
reason: Reason,
num_events: usize,
elapsed_from_gesture_start: zx::MonotonicDuration,
) {
tracing::debug!("touchpad: recognized end {:?}", current_gesture);
log_entry_node.record_child("gesture_end", |gesture_end_node| {
gesture_end_node.record_string("gesture_name", current_gesture.to_str());
gesture_end_node.record_int(
"duration_micros",
elapsed_from_gesture_start.into_micros(),
);
gesture_end_node.record_uint("event_count", u64::try_from(num_events).unwrap_or(u64::MAX));
log_reason(gesture_end_node, contender_name, reason)
});
}
#[cfg(test)]
mod tests {
mod utils {
use super::super::{
args, Contender, ContenderFactory, ExamineEventResult, MatchedContender,
ProcessBufferedEventsResult, ProcessNewEventResult, TouchpadEvent, VerifyEventResult,
Winner, COUNTS_PER_MM, PRIMARY_BUTTON,
};
use crate::utils::Size;
use crate::{input_device, keyboard_binding, mouse_binding, touch_binding, Position};
use assert_matches::assert_matches;
use fidl_fuchsia_input_report as fidl_input_report;
use maplit::hashset;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
pub(super) fn make_unhandled_touchpad_event() -> input_device::InputEvent {
input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![],
pressed_buttons: hashset! {},
},
),
device_descriptor: make_touchpad_descriptor(),
event_time: zx::MonotonicInstant::ZERO,
trace_id: None,
handled: input_device::Handled::No,
}
}
pub(super) fn make_unhandled_mouse_event() -> input_device::InputEvent {
input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(
mouse_binding::RelativeLocation { millimeters: Position::zero() },
),
wheel_delta_h: None,
wheel_delta_v: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: hashset! {},
pressed_buttons: hashset! {},
is_precision_scroll: None,
}),
device_descriptor: make_mouse_descriptor(),
event_time: zx::MonotonicInstant::ZERO,
trace_id: None,
handled: input_device::Handled::No,
}
}
pub(super) fn make_unhandled_keyboard_event() -> input_device::InputEvent {
input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(
keyboard_binding::KeyboardEvent::new(
fidl_fuchsia_input::Key::A,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
),
),
device_descriptor: make_keyboard_descriptor(),
event_time: zx::MonotonicInstant::ZERO,
trace_id: None,
handled: input_device::Handled::No,
}
}
pub(super) fn make_touchpad_descriptor() -> input_device::InputDeviceDescriptor {
input_device::InputDeviceDescriptor::Touchpad(touch_binding::TouchpadDeviceDescriptor {
device_id: 1,
contacts: vec![touch_binding::ContactDeviceDescriptor {
x_range: fidl_input_report::Range { min: 0, max: 10_000 },
y_range: fidl_input_report::Range { min: 0, max: 10_000 },
x_unit: fidl_input_report::Unit {
type_: fidl_input_report::UnitType::Meters,
exponent: -6,
},
y_unit: fidl_input_report::Unit {
type_: fidl_input_report::UnitType::Meters,
exponent: -6,
},
pressure_range: None,
width_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
height_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
}],
})
}
pub(super) fn make_mouse_descriptor() -> input_device::InputDeviceDescriptor {
input_device::InputDeviceDescriptor::Mouse(mouse_binding::MouseDeviceDescriptor {
device_id: 2,
absolute_x_range: Some(fidl_input_report::Range { min: -127, max: 127 }),
absolute_y_range: Some(fidl_input_report::Range { min: -127, max: 127 }),
wheel_v_range: None,
wheel_h_range: None,
buttons: Some(vec![PRIMARY_BUTTON]),
counts_per_mm: COUNTS_PER_MM,
})
}
fn make_keyboard_descriptor() -> input_device::InputDeviceDescriptor {
input_device::InputDeviceDescriptor::Keyboard(
keyboard_binding::KeyboardDeviceDescriptor {
device_id: 3,
device_information: fidl_fuchsia_input_report::DeviceInformation {
vendor_id: Some(0),
product_id: Some(0),
version: Some(0),
polling_rate: Some(0),
..Default::default()
},
keys: vec![fidl_fuchsia_input::Key::A],
},
)
}
#[derive(Clone, Debug)]
pub(super) struct StubContender {
inner: Rc<RefCell<StubContenderInner>>,
start_from_idle: bool,
}
impl StubContender {
pub(super) fn new() -> Self {
Self {
inner: Rc::new(RefCell::new(StubContenderInner {
next_result: None,
calls_received: 0,
last_touchpad_event: None,
})),
start_from_idle: false,
}
}
pub(super) fn new_start_from_idle() -> Self {
Self {
inner: Rc::new(RefCell::new(StubContenderInner {
next_result: None,
calls_received: 0,
last_touchpad_event: None,
})),
start_from_idle: true,
}
}
pub(super) fn set_next_result(&self, next_result: ExamineEventResult) {
self.assert_next_result_is_none();
self.inner.borrow_mut().next_result = Some(next_result);
}
pub(super) fn assert_next_result_is_none(&self) {
assert_matches!(self.inner.borrow().next_result, None);
}
pub(super) fn calls_received(&self) -> usize {
self.inner.borrow().calls_received
}
pub(super) fn ref_count(&self) -> usize {
Rc::strong_count(&self.inner)
}
pub(super) fn get_last_touchpad_event(&self) -> Option<TouchpadEvent> {
self.inner.borrow_mut().last_touchpad_event.take()
}
}
#[derive(Debug)]
struct StubContenderInner {
next_result: Option<ExamineEventResult>,
calls_received: usize,
last_touchpad_event: Option<TouchpadEvent>,
}
pub(super) struct ContenderFactoryOnceOrPanic {
contenders: Cell<Option<Vec<Box<dyn Contender>>>>,
}
impl ContenderFactoryOnceOrPanic {
pub(super) fn new(contenders: Vec<Box<dyn Contender>>) -> Self {
Self { contenders: Cell::new(Some(contenders)) }
}
pub(super) fn new_panic_when_call() -> Self {
Self { contenders: Cell::new(None) }
}
}
impl ContenderFactory for ContenderFactoryOnceOrPanic {
fn make_contenders(&self) -> Vec<Box<dyn Contender>> {
self.contenders
.take()
.expect("`contenders` has been consumed")
.into_iter()
.collect()
}
}
impl Contender for StubContender {
fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
let mut inner = self.inner.borrow_mut();
inner.calls_received += 1;
inner.last_touchpad_event = Some(event.clone());
inner.next_result.take().unwrap_or_else(|| {
panic!("missing `next_result` on call {}", inner.calls_received)
})
}
fn start_from_idle(&self) -> bool {
self.start_from_idle
}
}
#[derive(Clone)]
pub(super) struct ContenderFactoryCalled {
pub called: Rc<RefCell<bool>>,
}
impl ContenderFactoryCalled {
pub(super) fn new() -> Self {
Self { called: Rc::new(RefCell::new(false)) }
}
pub(super) fn was_called(&self) -> bool {
*self.called.borrow()
}
}
impl ContenderFactory for ContenderFactoryCalled {
fn make_contenders(&self) -> Vec<Box<dyn Contender>> {
self.called.replace(true);
vec![]
}
}
#[derive(Debug)]
pub(super) struct ContenderForever {}
impl Contender for ContenderForever {
fn examine_event(self: Box<Self>, _event: &TouchpadEvent) -> ExamineEventResult {
ExamineEventResult::Contender(self)
}
}
#[derive(Clone, Debug)]
pub(super) struct StubMatchedContender {
inner: Rc<RefCell<StubMatchedContenderInner>>,
}
impl StubMatchedContender {
pub(super) fn new() -> Self {
Self {
inner: Rc::new(RefCell::new(StubMatchedContenderInner {
next_verify_event_result: None,
next_process_buffered_events_result: None,
verify_event_calls_received: 0,
process_buffered_events_calls_received: 0,
last_process_buffered_events_args: None,
})),
}
}
pub(super) fn set_next_verify_event_result(&self, next_result: VerifyEventResult) {
self.assert_next_verify_event_result_is_none();
self.inner.borrow_mut().next_verify_event_result = Some(next_result);
}
fn assert_next_verify_event_result_is_none(&self) {
assert_matches!(self.inner.borrow().next_verify_event_result, None);
}
pub(super) fn verify_event_calls_received(&self) -> usize {
self.inner.borrow().verify_event_calls_received
}
pub(super) fn set_next_process_buffered_events_result(
&self,
next_result: ProcessBufferedEventsResult,
) {
self.assert_next_process_buffered_events_result_is_none();
self.inner.borrow_mut().next_process_buffered_events_result = Some(next_result);
}
pub(super) fn get_last_processed_buffered_events_args(
&self,
) -> Option<Vec<TouchpadEvent>> {
self.inner.borrow_mut().last_process_buffered_events_args.take()
}
fn assert_next_process_buffered_events_result_is_none(&self) {
assert_matches!(self.inner.borrow().next_process_buffered_events_result, None);
}
pub(super) fn ref_count(&self) -> usize {
Rc::strong_count(&self.inner)
}
}
#[derive(Debug)]
struct StubMatchedContenderInner {
next_verify_event_result: Option<VerifyEventResult>,
next_process_buffered_events_result: Option<ProcessBufferedEventsResult>,
verify_event_calls_received: usize,
process_buffered_events_calls_received: usize,
last_process_buffered_events_args: Option<Vec<TouchpadEvent>>,
}
impl MatchedContender for StubMatchedContender {
fn verify_event(self: Box<Self>, _event: &TouchpadEvent) -> VerifyEventResult {
let mut inner = self.inner.borrow_mut();
inner.verify_event_calls_received += 1;
inner.next_verify_event_result.take().unwrap_or_else(|| {
panic!(
"missing `next_verify_event_result` on call {}",
inner.verify_event_calls_received
)
})
}
fn process_buffered_events(
self: Box<Self>,
events: Vec<TouchpadEvent>,
) -> ProcessBufferedEventsResult {
let mut inner = self.inner.borrow_mut();
inner.last_process_buffered_events_args = Some(events);
inner.process_buffered_events_calls_received += 1;
inner.next_process_buffered_events_result.take().unwrap_or_else(|| {
panic!(
"missing `next_process_buffered_events_result` on call {}",
inner.process_buffered_events_calls_received
)
})
}
}
#[derive(Clone, Debug)]
pub(super) struct StubWinner {
inner: Rc<RefCell<StubWinnerInner>>,
}
impl StubWinner {
pub(super) fn new() -> Self {
Self {
inner: Rc::new(RefCell::new(StubWinnerInner {
next_result: None,
calls_received: 0,
})),
}
}
pub(super) fn set_next_result(&self, next_result: ProcessNewEventResult) {
self.inner.borrow_mut().next_result = Some(next_result);
}
pub(super) fn calls_received(&self) -> usize {
self.inner.borrow().calls_received
}
}
#[derive(Debug)]
struct StubWinnerInner {
next_result: Option<ProcessNewEventResult>,
calls_received: usize,
}
impl Winner for StubWinner {
fn process_new_event(self: Box<Self>, _event: TouchpadEvent) -> ProcessNewEventResult {
let mut inner = self.inner.borrow_mut();
inner.calls_received += 1;
inner.next_result.take().unwrap_or_else(|| {
panic!("missing `next_result` on call {}", inner.calls_received)
})
}
}
impl From<StubContender> for Box<dyn Contender> {
fn from(stub_contender: StubContender) -> Box<dyn Contender> {
Box::new(stub_contender)
}
}
impl From<ContenderForever> for Box<dyn Contender> {
fn from(contender_forever: ContenderForever) -> Box<dyn Contender> {
Box::new(contender_forever)
}
}
impl From<StubMatchedContender> for Box<dyn MatchedContender> {
fn from(stub_matched_contender: StubMatchedContender) -> Box<dyn MatchedContender> {
Box::new(stub_matched_contender)
}
}
impl From<StubWinner> for Box<dyn Winner> {
fn from(stub_winner: StubWinner) -> Box<dyn Winner> {
Box::new(stub_winner)
}
}
pub(super) const TOUCH_CONTACT_INDEX_FINGER: touch_binding::TouchContact =
touch_binding::TouchContact {
id: 0,
position: Position { x: 0.0, y: 0.0 },
pressure: None,
contact_size: Some(Size {
width: args::MIN_PALM_SIZE_MM / 2.0,
height: args::MIN_PALM_SIZE_MM / 2.0,
}),
};
}
mod idle_chain_states {
use super::super::{
args, ExamineEventResult, GestureArena, InputHandler, MutableState,
ProcessBufferedEventsResult, Reason, RecognizedGesture, TouchpadEvent,
};
use super::utils::{
make_touchpad_descriptor, make_unhandled_keyboard_event, make_unhandled_mouse_event,
make_unhandled_touchpad_event, ContenderFactoryCalled, ContenderFactoryOnceOrPanic,
StubContender, StubMatchedContender, StubWinner, TOUCH_CONTACT_INDEX_FINGER,
};
use crate::input_handler::InputHandlerStatus;
use crate::utils::Size;
use crate::{input_device, touch_binding, Position};
use assert_matches::assert_matches;
use maplit::hashset;
use std::cell::RefCell;
use std::rc::Rc;
use test_case::test_case;
fn make_gesture_arena_with_state(
contender_factory: ContenderFactoryOnceOrPanic,
state: MutableState,
) -> Rc<GestureArena> {
Rc::new(GestureArena {
contender_factory: Box::new(contender_factory),
mutable_state: RefCell::new(state),
inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
fuchsia_inspect::Inspector::default().root().create_child("some_key"),
1,
)),
inspect_status: InputHandlerStatus::default(),
})
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn invokes_contender_factory_on_touchpad_event(state: MutableState) {
let contender_factory = Box::new(ContenderFactoryCalled::new());
let arena = Rc::new(GestureArena {
contender_factory: contender_factory.clone(),
mutable_state: RefCell::new(state),
inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
fuchsia_inspect::Inspector::default().root().create_child("some_key"),
1,
)),
inspect_status: InputHandlerStatus::default(),
});
arena.handle_input_event(make_unhandled_touchpad_event()).await;
assert!(contender_factory.was_called());
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn does_not_invoke_contender_factory_on_mouse_event(state: MutableState) {
let contender_factory = Box::new(ContenderFactoryCalled::new());
let arena = Rc::new(GestureArena {
contender_factory: contender_factory.clone(),
mutable_state: RefCell::new(state),
inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
fuchsia_inspect::Inspector::default().root().create_child("some_key"),
1,
)),
inspect_status: InputHandlerStatus::default(),
});
arena.handle_input_event(make_unhandled_mouse_event()).await;
assert!(!contender_factory.was_called());
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn does_not_invoke_contender_factory_on_keyboard_event(state: MutableState) {
let contender_factory = Box::new(ContenderFactoryCalled::new());
let arena = Rc::new(GestureArena {
contender_factory: contender_factory.clone(),
mutable_state: RefCell::new(state),
inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
fuchsia_inspect::Inspector::default().root().create_child("some_key"),
1,
)),
inspect_status: InputHandlerStatus::default(),
});
arena.handle_input_event(make_unhandled_keyboard_event()).await;
assert!(!contender_factory.was_called());
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn calls_examine_event_on_contender(state: MutableState) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = make_gesture_arena_with_state(contender_factory, state);
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
arena.handle_input_event(make_unhandled_touchpad_event()).await;
pretty_assertions::assert_eq!(contender.calls_received(), 1);
}
#[fuchsia::test(allow_stalls = false)]
async fn calls_examine_event_on_idle_only_contender_while_idle() {
let contender = Box::new(StubContender::new_start_from_idle());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = make_gesture_arena_with_state(contender_factory, MutableState::Idle);
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
arena.handle_input_event(make_unhandled_touchpad_event()).await;
pretty_assertions::assert_eq!(contender.calls_received(), 1);
}
#[fuchsia::test(allow_stalls = false)]
async fn does_not_calls_examine_event_on_idle_only_contender_while_chain() {
let contender = Box::new(StubContender::new_start_from_idle());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = make_gesture_arena_with_state(contender_factory, MutableState::Chain);
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
arena.handle_input_event(make_unhandled_touchpad_event()).await;
pretty_assertions::assert_eq!(contender.calls_received(), 0);
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn calls_examine_event_on_all_contenders_even_if_first_matches(state: MutableState) {
let first_contender = Box::new(StubContender::new());
let second_contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![
first_contender.clone(),
second_contender.clone(),
]);
let arena = make_gesture_arena_with_state(contender_factory, state);
first_contender.set_next_result(ExamineEventResult::MatchedContender(
StubMatchedContender::new().into(),
));
second_contender
.set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
arena.handle_input_event(make_unhandled_touchpad_event()).await;
pretty_assertions::assert_eq!(first_contender.calls_received(), 1);
pretty_assertions::assert_eq!(second_contender.calls_received(), 1);
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn retains_reference_to_replacement_contender(state: MutableState) {
let initial_contender = Box::new(StubContender::new());
let contender_factory =
ContenderFactoryOnceOrPanic::new(vec![initial_contender.clone()]);
let arena = make_gesture_arena_with_state(contender_factory, state);
let replacement_contender = StubContender::new();
initial_contender.set_next_result(ExamineEventResult::Contender(
replacement_contender.clone().into(),
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
initial_contender.assert_next_result_is_none();
pretty_assertions::assert_eq!(replacement_contender.ref_count(), 2);
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn retains_reference_to_matched_contender(state: MutableState) {
let initial_contender = Box::new(StubContender::new());
let second_contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![
initial_contender.clone(),
second_contender.clone(),
]);
let arena = make_gesture_arena_with_state(contender_factory, state);
let replacement_contender = StubMatchedContender::new();
initial_contender.set_next_result(ExamineEventResult::MatchedContender(
replacement_contender.clone().into(),
));
second_contender
.set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
initial_contender.assert_next_result_is_none();
pretty_assertions::assert_eq!(replacement_contender.ref_count(), 2);
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn retains_touchpad_event_when_entering_matching(state: MutableState) {
let initial_contender = Box::new(StubContender::new());
let second_contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![
initial_contender.clone(),
second_contender.clone(),
]);
let arena = make_gesture_arena_with_state(contender_factory, state);
let touchpad_event = input_device::InputEvent {
event_time: zx::MonotonicInstant::from_nanos(123456),
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![TOUCH_CONTACT_INDEX_FINGER],
pressed_buttons: hashset! {0},
},
),
device_descriptor: make_touchpad_descriptor(),
trace_id: None,
handled: input_device::Handled::No,
};
initial_contender.set_next_result(ExamineEventResult::MatchedContender(
StubMatchedContender::new().into(),
));
second_contender
.set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
arena.clone().handle_input_event(touchpad_event).await;
assert_matches!(
&*arena.mutable_state.borrow(),
MutableState::Matching {
contenders: _,
matched_contenders: _,
buffered_events,
first_event_timestamp: _,
} => pretty_assertions::assert_eq!(
buffered_events.as_slice(),
[TouchpadEvent {
timestamp: zx::MonotonicInstant::from_nanos(123456),
pressed_buttons: vec![0],
contacts: vec![
touch_binding::TouchContact {
contact_size: Some(Size{ width: args::MIN_PALM_SIZE_MM / 2000.0, height: args::MIN_PALM_SIZE_MM / 2000.0 }),
..TOUCH_CONTACT_INDEX_FINGER
},
],
filtered_palm_contacts: vec![],
}]
)
);
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn generates_no_events_on_mismatch_entering_chain(state: MutableState) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = make_gesture_arena_with_state(contender_factory, state);
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
let touchpad_event = input_device::InputEvent {
event_time: zx::MonotonicInstant::from_nanos(123456),
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![touch_binding::TouchContact {
id: 1,
position: Position { x: 0.0, y: 0.0 },
..TOUCH_CONTACT_INDEX_FINGER
}],
pressed_buttons: hashset! {},
},
),
device_descriptor: make_touchpad_descriptor(),
trace_id: None,
handled: input_device::Handled::No,
};
pretty_assertions::assert_eq!(
arena.clone().handle_input_event(touchpad_event).await,
vec![]
);
assert_matches!(*arena.mutable_state.borrow(), MutableState::Chain);
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn generates_no_events_on_mismatch_entering_idle(state: MutableState) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = make_gesture_arena_with_state(contender_factory, state);
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
pretty_assertions::assert_eq!(
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await,
vec![]
);
assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn generates_no_events_when_entering_matching(state: MutableState) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = make_gesture_arena_with_state(contender_factory, state);
contender.set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
pretty_assertions::assert_eq!(
arena.handle_input_event(make_unhandled_touchpad_event()).await,
vec![]
);
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn enters_idle_on_mismatch(state: MutableState) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = make_gesture_arena_with_state(contender_factory, state);
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn enters_matching_on_contender_result(state: MutableState) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = make_gesture_arena_with_state(contender_factory, state);
contender.set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Matching { .. });
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn enters_matching_on_matched_contender_result(state: MutableState) {
let first_contender = Box::new(StubContender::new());
let second_contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![
first_contender.clone(),
second_contender.clone(),
]);
let arena = make_gesture_arena_with_state(contender_factory, state);
first_contender.set_next_result(ExamineEventResult::MatchedContender(
StubMatchedContender::new().into(),
));
second_contender
.set_next_result(ExamineEventResult::Contender(StubContender::new().into()));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Matching { .. });
}
#[test_case(MutableState::Idle; "idle")]
#[test_case(MutableState::Chain; "chain")]
#[fuchsia::test(allow_stalls = false)]
async fn enters_forward_on_only_one_matched_contender_result(state: MutableState) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = make_gesture_arena_with_state(contender_factory, state);
let matched_contender = StubMatchedContender::new();
matched_contender.set_next_process_buffered_events_result(
ProcessBufferedEventsResult {
generated_events: vec![],
winner: Some(Box::new(StubWinner::new())),
recognized_gesture: RecognizedGesture::Motion,
},
);
contender
.set_next_result(ExamineEventResult::MatchedContender(matched_contender.into()));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Forwarding { .. });
}
}
mod matching_state {
use super::super::{
Contender, ContenderFactory, ExamineEventResult, GestureArena, InputHandler,
MouseEvent, MutableState, ProcessBufferedEventsResult, Reason, RecognizedGesture,
TouchpadEvent, VerifyEventResult, PRIMARY_BUTTON,
};
use super::utils::{
make_touchpad_descriptor, make_unhandled_keyboard_event, make_unhandled_mouse_event,
make_unhandled_touchpad_event, ContenderForever, StubContender, StubMatchedContender,
StubWinner, TOUCH_CONTACT_INDEX_FINGER,
};
use crate::input_handler::InputHandlerStatus;
use crate::{input_device, mouse_binding, touch_binding, Position};
use assert_matches::assert_matches;
use maplit::hashset;
use pretty_assertions::assert_eq;
use std::cell::RefCell;
use std::rc::Rc;
use test_case::test_case;
struct ContenderFactoryWarn {}
impl ContenderFactory for ContenderFactoryWarn {
fn make_contenders(&self) -> Vec<Box<dyn Contender>> {
eprintln!("factory invoked in matching state");
vec![]
}
}
fn make_matching_arena(
contenders: Vec<StubContender>,
matched_contenders: Vec<StubMatchedContender>,
buffered_events: Vec<TouchpadEvent>,
contender_forever: Option<ContenderForever>,
) -> Rc<GestureArena> {
Rc::new(GestureArena {
contender_factory: Box::new(ContenderFactoryWarn {}),
mutable_state: RefCell::new(MutableState::Matching {
contenders: {
contenders
.into_iter()
.map(std::convert::From::<StubContender>::from)
.chain(
contender_forever
.into_iter()
.map(std::convert::From::<ContenderForever>::from),
)
.collect()
},
matched_contenders: {
matched_contenders
.into_iter()
.map(std::convert::From::<StubMatchedContender>::from)
.collect()
},
first_event_timestamp: zx::MonotonicInstant::ZERO,
buffered_events,
}),
inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
fuchsia_inspect::Inspector::default().root().create_child("some_key"),
1,
)),
inspect_status: InputHandlerStatus::default(),
})
}
#[fuchsia::test(allow_stalls = false)]
async fn invokes_examine_and_verify_event_on_touchpad_event() {
let contender = StubContender::new();
let matched_contender = StubMatchedContender::new();
let arena = make_matching_arena(
vec![contender.clone()],
vec![matched_contender.clone()],
vec![],
None,
);
contender.set_next_result(ExamineEventResult::Contender(contender.clone().into()));
matched_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
Reason::Basic("some reason"),
));
arena.handle_input_event(make_unhandled_touchpad_event()).await;
assert_eq!(contender.calls_received(), 1);
assert_eq!(matched_contender.verify_event_calls_received(), 1);
}
#[fuchsia::test(allow_stalls = false)]
async fn does_not_invoke_examine_or_verify_event_on_mouse_event() {
let contender = StubContender::new();
let matched_contender = StubMatchedContender::new();
let arena = make_matching_arena(
vec![contender.clone()],
vec![matched_contender.clone()],
vec![],
None,
);
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
matched_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
Reason::Basic("some reason"),
));
arena.handle_input_event(make_unhandled_mouse_event()).await;
assert_eq!(contender.calls_received(), 0);
assert_eq!(matched_contender.verify_event_calls_received(), 0);
}
#[fuchsia::test(allow_stalls = false)]
async fn does_not_invoke_examine_or_verify_event_on_keyboard_event() {
let contender = StubContender::new();
let matched_contender = StubMatchedContender::new();
let arena = make_matching_arena(
vec![contender.clone()],
vec![matched_contender.clone()],
vec![],
None,
);
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
matched_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
Reason::Basic("some reason"),
));
arena.handle_input_event(make_unhandled_keyboard_event()).await;
assert_eq!(contender.calls_received(), 0);
assert_eq!(matched_contender.verify_event_calls_received(), 0);
}
#[fuchsia::test(allow_stalls = false)]
async fn does_not_repeat_event_to_matched_contender_returned_by_examine_event() {
let contender = StubContender::new();
let arena = make_matching_arena(
vec![contender.clone()],
vec![],
vec![],
Some(ContenderForever {}),
);
let matched_contender = StubMatchedContender::new();
contender.set_next_result(ExamineEventResult::MatchedContender(
matched_contender.clone().into(),
));
matched_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
Reason::Basic("some reason"),
));
arena.handle_input_event(make_unhandled_touchpad_event()).await;
assert_eq!(matched_contender.verify_event_calls_received(), 0);
}
#[fuchsia::test(allow_stalls = false)]
async fn invokes_examine_event_for_new_event_with_contender_replaced_by_contender() {
let initial_contender = StubContender::new();
let arena = make_matching_arena(vec![initial_contender.clone()], vec![], vec![], None);
let replacement_contender = StubContender::new();
initial_contender.set_next_result(ExamineEventResult::Contender(
replacement_contender.clone().into(),
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
replacement_contender
.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
arena.handle_input_event(make_unhandled_touchpad_event()).await;
assert_eq!(replacement_contender.calls_received(), 1);
}
#[fuchsia::test(allow_stalls = false)]
async fn invokes_verify_event_for_new_event_with_contender_replaced_by_matched_contender() {
let initial_contender = StubContender::new();
let arena = make_matching_arena(
vec![initial_contender.clone()],
vec![],
vec![],
Some(ContenderForever {}),
);
let replacement_contender = StubMatchedContender::new();
initial_contender.set_next_result(ExamineEventResult::MatchedContender(
replacement_contender.clone().into(),
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
replacement_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
Reason::Basic("some reason"),
));
arena.handle_input_event(make_unhandled_touchpad_event()).await;
assert_eq!(replacement_contender.verify_event_calls_received(), 1);
}
#[fuchsia::test(allow_stalls = false)]
async fn invokes_verify_event_for_new_event_with_matched_contender_replaced_by_matched_contender(
) {
let matched_contender = StubMatchedContender::new();
let arena = make_matching_arena(
vec![],
vec![matched_contender.clone()],
vec![],
Some(ContenderForever {}),
);
let replacement_matched_contender = StubMatchedContender::new();
matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
replacement_matched_contender.clone().into(),
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
replacement_matched_contender.set_next_verify_event_result(
VerifyEventResult::Mismatch(Reason::Basic("some reason")),
);
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_eq!(replacement_matched_contender.verify_event_calls_received(), 1);
}
#[fuchsia::test(allow_stalls = false)]
async fn generates_no_events_on_mismatch_entering_idle() {
let contender = StubContender::new();
let arena = make_matching_arena(vec![contender.clone()], vec![], vec![], None);
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
assert_eq!(
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await,
vec![]
);
assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
}
#[fuchsia::test(allow_stalls = false)]
async fn generates_no_events_on_mismatch_entering_chain() {
let contender = StubContender::new();
let arena = make_matching_arena(vec![contender.clone()], vec![], vec![], None);
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
let touchpad_event = input_device::InputEvent {
event_time: zx::MonotonicInstant::from_nanos(123456),
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![touch_binding::TouchContact {
id: 1,
position: Position { x: 0.0, y: 0.0 },
..TOUCH_CONTACT_INDEX_FINGER
}],
pressed_buttons: hashset! {},
},
),
device_descriptor: make_touchpad_descriptor(),
trace_id: None,
handled: input_device::Handled::No,
};
pretty_assertions::assert_eq!(
arena.clone().handle_input_event(touchpad_event).await,
vec![]
);
assert_matches!(*arena.mutable_state.borrow(), MutableState::Chain);
}
#[fuchsia::test(allow_stalls = false)]
async fn generates_no_events_on_contender() {
let contender = StubContender::new();
let arena = make_matching_arena(vec![contender.clone()], vec![], vec![], None);
contender.set_next_result(ExamineEventResult::Contender(contender.clone().into()));
assert_eq!(arena.handle_input_event(make_unhandled_touchpad_event()).await, vec![]);
}
#[fuchsia::test(allow_stalls = false)]
async fn generates_no_events_on_multiple_matched_contenders() {
let first_matched_contender = StubMatchedContender::new();
let second_matched_contender = StubMatchedContender::new();
let arena = make_matching_arena(
vec![],
vec![first_matched_contender.clone(), second_matched_contender.clone()],
vec![],
None,
);
first_matched_contender.set_next_verify_event_result(
VerifyEventResult::MatchedContender(first_matched_contender.clone().into()),
);
second_matched_contender.set_next_verify_event_result(
VerifyEventResult::MatchedContender(second_matched_contender.clone().into()),
);
assert_eq!(arena.handle_input_event(make_unhandled_touchpad_event()).await, vec![]);
}
#[test_case(Some(StubWinner::new()); "with_winner")]
#[test_case(None; "without_winner")]
#[fuchsia::test(allow_stalls = false)]
async fn generates_events_from_process_buffered_events_on_single_matched_contender(
winner: Option<StubWinner>,
) {
let matched_contender = StubMatchedContender::new();
let arena = make_matching_arena(vec![], vec![matched_contender.clone()], vec![], None);
matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
matched_contender.clone().into(),
));
matched_contender.set_next_process_buffered_events_result(
ProcessBufferedEventsResult {
generated_events: vec![
MouseEvent {
timestamp: zx::MonotonicInstant::from_nanos(123),
mouse_data: mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(
mouse_binding::RelativeLocation {
millimeters: Position::zero(),
},
),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Down,
affected_buttons: hashset! { PRIMARY_BUTTON },
pressed_buttons: hashset! { PRIMARY_BUTTON },
is_precision_scroll: None,
},
},
MouseEvent {
timestamp: zx::MonotonicInstant::from_nanos(456),
mouse_data: mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(
mouse_binding::RelativeLocation {
millimeters: Position::zero(),
},
),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Up,
affected_buttons: hashset! { PRIMARY_BUTTON },
pressed_buttons: hashset! {},
is_precision_scroll: None,
},
},
],
winner: winner.map(std::convert::From::<StubWinner>::from),
recognized_gesture: RecognizedGesture::Motion,
},
);
assert_matches!(
arena
.handle_input_event(make_unhandled_touchpad_event())
.await
.as_slice(),
[
input_device::InputEvent {
handled: input_device::Handled::No,
device_event: input_device::InputDeviceEvent::Mouse(
mouse_binding::MouseEvent {
pressed_buttons: first_pressed_buttons, ..
}
),
..
},
input_device::InputEvent {
handled: input_device::Handled::No,
device_event: input_device::InputDeviceEvent::Mouse(
mouse_binding::MouseEvent {
pressed_buttons: second_pressed_buttons, ..
}
),
..
},
] => {
pretty_assertions::assert_eq!(*first_pressed_buttons, hashset! { PRIMARY_BUTTON });
pretty_assertions::assert_eq!(*second_pressed_buttons, hashset! {});
}
);
}
#[fuchsia::test(allow_stalls = false)]
async fn passes_all_buffered_events_to_process_buffered_events() {
let contender = StubContender::new();
let matched_contender = StubMatchedContender::new();
let arena = make_matching_arena(
vec![contender.clone()],
vec![matched_contender.clone()],
vec![TouchpadEvent {
timestamp: zx::MonotonicInstant::from_nanos(123),
contacts: vec![],
pressed_buttons: vec![],
filtered_palm_contacts: vec![],
}],
None,
);
contender.set_next_result(ExamineEventResult::Contender(contender.clone().into()));
matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
matched_contender.clone().into(),
));
arena
.clone()
.handle_input_event(input_device::InputEvent {
event_time: zx::MonotonicInstant::from_nanos(456),
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![],
pressed_buttons: hashset! {},
},
),
device_descriptor: make_touchpad_descriptor(),
trace_id: None,
handled: input_device::Handled::No,
})
.await;
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
matched_contender.clone().into(),
));
matched_contender.set_next_process_buffered_events_result(
ProcessBufferedEventsResult {
generated_events: vec![],
winner: None,
recognized_gesture: RecognizedGesture::Motion,
},
);
arena
.handle_input_event(input_device::InputEvent {
event_time: zx::MonotonicInstant::from_nanos(789),
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![],
pressed_buttons: hashset! {},
},
),
device_descriptor: make_touchpad_descriptor(),
trace_id: None,
handled: input_device::Handled::No,
})
.await;
assert_eq!(
matched_contender
.get_last_processed_buffered_events_args()
.map(|vec| vec
.into_iter()
.map(|event| event.timestamp.into_nanos())
.collect::<Vec<_>>())
.as_deref(),
Some([123, 456, 789].as_slice())
);
}
#[fuchsia::test(allow_stalls = false)]
async fn transitions_to_idle_when_sole_contender_does_not_match() {
let contender = StubContender::new();
let arena = make_matching_arena(vec![contender.clone()], vec![], vec![], None);
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
}
#[fuchsia::test(allow_stalls = false)]
async fn transitions_to_idle_when_sole_matched_contender_does_not_match() {
let matched_contender = StubMatchedContender::new();
let arena = make_matching_arena(vec![], vec![matched_contender.clone()], vec![], None);
matched_contender.set_next_verify_event_result(VerifyEventResult::Mismatch(
Reason::Basic("some reason"),
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
}
#[fuchsia::test(allow_stalls = false)]
async fn remains_in_matching_when_a_contender_remains() {
let contender = StubContender::new();
let arena = make_matching_arena(vec![contender.clone()], vec![], vec![], None);
contender.set_next_result(ExamineEventResult::Contender(contender.clone().into()));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Matching { .. });
}
#[fuchsia::test(allow_stalls = false)]
async fn remains_in_matching_when_multiple_matched_contenders_remain() {
let matched_contender_a = StubMatchedContender::new();
let matched_contender_b = StubMatchedContender::new();
let arena = make_matching_arena(
vec![],
vec![matched_contender_a.clone(), matched_contender_b.clone()],
vec![],
None,
);
matched_contender_a.set_next_verify_event_result(VerifyEventResult::MatchedContender(
matched_contender_a.clone().into(),
));
matched_contender_b.set_next_verify_event_result(VerifyEventResult::MatchedContender(
matched_contender_b.clone().into(),
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Matching { .. });
}
#[fuchsia::test(allow_stalls = false)]
async fn transitions_to_idle_when_sole_matched_contender_returns_no_winner() {
let matched_contender = StubMatchedContender::new();
let arena = make_matching_arena(vec![], vec![matched_contender.clone()], vec![], None);
matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
matched_contender.clone().into(),
));
matched_contender.set_next_process_buffered_events_result(
ProcessBufferedEventsResult {
generated_events: vec![],
winner: None,
recognized_gesture: RecognizedGesture::Motion,
},
);
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
}
#[fuchsia::test(allow_stalls = false)]
async fn transitions_to_forwarding_when_sole_matched_contender_returns_a_winner() {
let matched_contender = StubMatchedContender::new();
let arena = make_matching_arena(vec![], vec![matched_contender.clone()], vec![], None);
matched_contender.set_next_verify_event_result(VerifyEventResult::MatchedContender(
matched_contender.clone().into(),
));
matched_contender.set_next_process_buffered_events_result(
ProcessBufferedEventsResult {
generated_events: vec![],
winner: Some(StubWinner::new().into()),
recognized_gesture: RecognizedGesture::Motion,
},
);
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Forwarding { .. });
}
}
mod forwarding_state {
use super::super::{
Contender, EndGestureEvent, ExamineEventResult, GestureArena, InputHandler, MouseEvent,
MutableState, ProcessNewEventResult, Reason, RecognizedGesture, TouchpadEvent,
};
use super::utils::{
make_touchpad_descriptor, make_unhandled_keyboard_event, make_unhandled_mouse_event,
make_unhandled_touchpad_event, ContenderFactoryOnceOrPanic, ContenderForever,
StubContender, StubWinner, TOUCH_CONTACT_INDEX_FINGER,
};
use crate::input_handler::InputHandlerStatus;
use crate::{input_device, mouse_binding, touch_binding, Position};
use assert_matches::assert_matches;
use maplit::hashset;
use pretty_assertions::assert_eq;
use std::cell::RefCell;
use std::rc::Rc;
use test_case::test_case;
fn make_forwarding_arena(
winner: StubWinner,
contender: Option<Box<dyn Contender>>,
) -> Rc<GestureArena> {
let contender_factory = match contender {
Some(c) => Box::new(ContenderFactoryOnceOrPanic::new(vec![c])),
None => Box::new(ContenderFactoryOnceOrPanic::new_panic_when_call()),
};
Rc::new(GestureArena {
contender_factory,
mutable_state: RefCell::new(MutableState::Forwarding {
winner: winner.into(),
current_gesture: RecognizedGesture::Motion,
gesture_start_timestamp: zx::MonotonicInstant::INFINITE_PAST,
num_events: 0,
}),
inspect_log: RefCell::new(fuchsia_inspect_contrib::nodes::BoundedListNode::new(
fuchsia_inspect::Inspector::default().root().create_child("some_key"),
1,
)),
inspect_status: InputHandlerStatus::default(),
})
}
#[fuchsia::test(allow_stalls = false)]
async fn invokes_process_new_event_on_touchpad_event() {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), None);
winner.set_next_result(ProcessNewEventResult::EndGesture(
EndGestureEvent::NoEvent,
Reason::Basic("some reason"),
));
arena.handle_input_event(make_unhandled_touchpad_event()).await;
assert_eq!(winner.calls_received(), 1);
}
#[fuchsia::test(allow_stalls = false)]
async fn does_not_invoke_process_new_event_on_mouse_event() {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), None);
winner.set_next_result(ProcessNewEventResult::EndGesture(
EndGestureEvent::NoEvent,
Reason::Basic("some reason"),
));
arena.handle_input_event(make_unhandled_mouse_event()).await;
assert_eq!(winner.calls_received(), 0);
}
#[fuchsia::test(allow_stalls = false)]
async fn does_not_invoke_process_new_event_on_keyboard_event() {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), None);
winner.set_next_result(ProcessNewEventResult::EndGesture(
EndGestureEvent::NoEvent,
Reason::Basic("some reason"),
));
arena.handle_input_event(make_unhandled_keyboard_event()).await;
assert_eq!(winner.calls_received(), 0);
}
#[fuchsia::test(allow_stalls = false)]
async fn invokes_process_new_event_for_multiple_new_events() {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), Some(ContenderForever {}.into()));
winner.set_next_result(ProcessNewEventResult::ContinueGesture(
None,
winner.clone().into(),
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
winner.set_next_result(ProcessNewEventResult::ContinueGesture(
None,
winner.clone().into(),
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_eq!(winner.calls_received(), 2);
}
#[fuchsia::test(allow_stalls = false)]
async fn generates_event_on_continue_gesture_with_mouse_event() {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), None);
winner.set_next_result(ProcessNewEventResult::ContinueGesture(
Some(MouseEvent {
timestamp: zx::MonotonicInstant::from_nanos(123),
mouse_data: mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(
mouse_binding::RelativeLocation { millimeters: Position::zero() },
),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: hashset! {},
pressed_buttons: hashset! {},
is_precision_scroll: None,
},
}),
winner.clone().into(),
));
assert_matches!(
arena.handle_input_event(make_unhandled_touchpad_event()).await.as_slice(),
[
input_device::InputEvent {
event_time,
handled: input_device::Handled::No,
device_event: input_device::InputDeviceEvent::Mouse(_),
..
},
] => pretty_assertions::assert_eq!(*event_time, zx::MonotonicInstant::from_nanos(123))
);
}
#[fuchsia::test(allow_stalls = false)]
async fn generates_no_events_on_continue_gesture_without_mouse_event() {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), None);
winner.set_next_result(ProcessNewEventResult::ContinueGesture(
None,
winner.clone().into(),
));
pretty_assertions::assert_eq!(
arena.handle_input_event(make_unhandled_touchpad_event()).await.as_slice(),
vec![]
);
}
#[fuchsia::test(allow_stalls = false)]
async fn generates_no_events_on_end_gesture_without_touchpad_event() {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), None);
winner.set_next_result(ProcessNewEventResult::EndGesture(
EndGestureEvent::NoEvent,
Reason::Basic("some reason"),
));
pretty_assertions::assert_eq!(
arena.handle_input_event(make_unhandled_touchpad_event()).await.as_slice(),
vec![]
);
}
#[fuchsia::test(allow_stalls = false)]
async fn generates_no_events_on_end_gesture_with_unconsumed_touchpad_event_entering_idle() {
let winner = StubWinner::new();
let contender = StubContender::new();
let arena = make_forwarding_arena(winner.clone(), Some(contender.clone().into()));
winner.set_next_result(ProcessNewEventResult::EndGesture(
EndGestureEvent::UnconsumedEvent(TouchpadEvent {
contacts: vec![],
pressed_buttons: vec![],
timestamp: zx::MonotonicInstant::ZERO,
filtered_palm_contacts: vec![],
}),
Reason::Basic("some reason"),
));
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
pretty_assertions::assert_eq!(
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await.as_slice(),
vec![]
);
assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle { .. });
}
#[fuchsia::test(allow_stalls = false)]
async fn generates_no_events_on_end_gesture_with_unconsumed_touchpad_event_entering_chain()
{
let winner = StubWinner::new();
let contender = StubContender::new();
let arena = make_forwarding_arena(winner.clone(), Some(contender.clone().into()));
winner.set_next_result(ProcessNewEventResult::EndGesture(
EndGestureEvent::UnconsumedEvent(TouchpadEvent {
contacts: vec![touch_binding::TouchContact {
id: 1,
position: Position { x: 0.0, y: 0.0 },
pressure: None,
contact_size: None,
}],
pressed_buttons: vec![],
timestamp: zx::MonotonicInstant::from_nanos(123456),
filtered_palm_contacts: vec![],
}),
Reason::Basic("some reason"),
));
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
let touchpad_event = input_device::InputEvent {
event_time: zx::MonotonicInstant::from_nanos(123456),
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![touch_binding::TouchContact {
id: 1,
position: Position { x: 0.0, y: 0.0 },
..TOUCH_CONTACT_INDEX_FINGER
}],
pressed_buttons: hashset! {},
},
),
device_descriptor: make_touchpad_descriptor(),
trace_id: None,
handled: input_device::Handled::No,
};
pretty_assertions::assert_eq!(
arena.clone().handle_input_event(touchpad_event).await.as_slice(),
vec![]
);
assert_matches!(*arena.mutable_state.borrow(), MutableState::Chain { .. });
}
#[fuchsia::test(allow_stalls = false)]
async fn generates_event_on_end_gesture_with_touchpad_event() {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), None);
let mouse_event = MouseEvent {
timestamp: zx::MonotonicInstant::from_nanos(123),
mouse_data: mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(
mouse_binding::RelativeLocation { millimeters: Position::zero() },
),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: hashset! {},
pressed_buttons: hashset! {},
is_precision_scroll: None,
},
};
winner.set_next_result(ProcessNewEventResult::EndGesture(
EndGestureEvent::GeneratedEvent(mouse_event),
Reason::Basic("some reason"),
));
assert_matches!(
arena.handle_input_event(make_unhandled_touchpad_event()).await.as_slice(),
[
input_device::InputEvent {
event_time,
handled: input_device::Handled::No,
device_event: input_device::InputDeviceEvent::Mouse(_),
..
},
] => pretty_assertions::assert_eq!(*event_time, zx::MonotonicInstant::from_nanos(123))
);
}
#[test_case(Some(MouseEvent{
timestamp: zx::MonotonicInstant::from_nanos(123),
mouse_data: mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(
mouse_binding::RelativeLocation {
millimeters: Position::zero(),
},
),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: hashset! {},
pressed_buttons: hashset! {},
is_precision_scroll: None,
},
}); "with_mouse_event")]
#[test_case(None; "without_mouse_event")]
#[fuchsia::test(allow_stalls = false)]
async fn remains_in_forwarding_on_continue_gesture(mouse_event: Option<MouseEvent>) {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), None);
winner.set_next_result(ProcessNewEventResult::ContinueGesture(
mouse_event,
winner.clone().into(),
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Forwarding { .. });
}
#[fuchsia::test(allow_stalls = false)]
async fn transitions_to_idle_on_end_gesture_with_touchpad_event() {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), None);
let mouse_event = MouseEvent {
timestamp: zx::MonotonicInstant::from_nanos(123),
mouse_data: mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(
mouse_binding::RelativeLocation { millimeters: Position::zero() },
),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: hashset! {},
pressed_buttons: hashset! {},
is_precision_scroll: None,
},
};
winner.set_next_result(ProcessNewEventResult::EndGesture(
EndGestureEvent::GeneratedEvent(mouse_event),
Reason::Basic("some reason"),
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle { .. });
}
#[fuchsia::test(allow_stalls = false)]
async fn transitions_to_chain_on_end_gesture_with_touchpad_event() {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), None);
let mouse_event = MouseEvent {
timestamp: zx::MonotonicInstant::from_nanos(123),
mouse_data: mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(
mouse_binding::RelativeLocation { millimeters: Position::zero() },
),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: hashset! {},
pressed_buttons: hashset! {},
is_precision_scroll: None,
},
};
winner.set_next_result(ProcessNewEventResult::EndGesture(
EndGestureEvent::GeneratedEvent(mouse_event),
Reason::Basic("some reason"),
));
let touchpad_event = input_device::InputEvent {
event_time: zx::MonotonicInstant::from_nanos(123456),
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![touch_binding::TouchContact {
id: 1,
position: Position { x: 0.0, y: 0.0 },
..TOUCH_CONTACT_INDEX_FINGER
}],
pressed_buttons: hashset! {},
},
),
device_descriptor: make_touchpad_descriptor(),
trace_id: None,
handled: input_device::Handled::No,
};
arena.clone().handle_input_event(touchpad_event).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Chain);
}
#[fuchsia::test(allow_stalls = false)]
async fn transitions_to_idle_on_end_gesture_without_touchpad_event() {
let winner = StubWinner::new();
let arena = make_forwarding_arena(winner.clone(), None);
winner.set_next_result(ProcessNewEventResult::EndGesture(
EndGestureEvent::NoEvent,
Reason::Basic("reason"),
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
assert_matches!(*arena.mutable_state.borrow(), MutableState::Idle);
}
#[fuchsia::test(allow_stalls = false)]
async fn starts_new_contest_on_end_gesture_with_touchpad_event() {
let winner = StubWinner::new();
let contender = StubContender::new();
let arena = make_forwarding_arena(winner.clone(), Some(contender.clone().into()));
winner.set_next_result(ProcessNewEventResult::EndGesture(
EndGestureEvent::UnconsumedEvent(TouchpadEvent {
timestamp: zx::MonotonicInstant::ZERO,
contacts: vec![],
pressed_buttons: vec![],
filtered_palm_contacts: vec![],
}),
Reason::Basic("reason"),
));
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
arena.handle_input_event(make_unhandled_touchpad_event()).await;
assert_eq!(contender.calls_received(), 1);
}
}
mod touchpad_event_payload {
use super::super::{args, ExamineEventResult, GestureArena, InputHandler, Reason};
use super::utils::{
ContenderFactoryOnceOrPanic, StubContender, TOUCH_CONTACT_INDEX_FINGER,
};
use crate::utils::Size;
use crate::{input_device, touch_binding, Position};
use assert_matches::assert_matches;
use fidl_fuchsia_input_report::{self as fidl_input_report, UnitType};
use maplit::hashset;
use std::rc::Rc;
use test_case::test_case;
use test_util::assert_near;
fn make_touchpad_descriptor(
units: Vec<(fidl_input_report::Unit, fidl_input_report::Unit)>,
) -> input_device::InputDeviceDescriptor {
let contacts: Vec<_> = units
.into_iter()
.map(|(x_unit, y_unit)| touch_binding::ContactDeviceDescriptor {
x_range: fidl_input_report::Range { min: 0, max: 1_000_000 },
y_range: fidl_input_report::Range { min: 0, max: 1_000_000 },
x_unit,
y_unit,
pressure_range: None,
width_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
height_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
})
.collect();
input_device::InputDeviceDescriptor::Touchpad(touch_binding::TouchpadDeviceDescriptor {
device_id: 1,
contacts,
})
}
fn make_unhandled_touchpad_event_with_contacts(
contact_position_units: Vec<(fidl_input_report::Unit, fidl_input_report::Unit)>,
injector_contacts: Vec<touch_binding::TouchContact>,
) -> input_device::InputEvent {
input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts,
pressed_buttons: hashset! {},
},
),
device_descriptor: make_touchpad_descriptor(contact_position_units),
event_time: zx::MonotonicInstant::ZERO,
trace_id: None,
handled: input_device::Handled::No,
}
}
fn make_unhandled_touchpad_event_with_positions(
contact_position_units: Vec<(fidl_input_report::Unit, fidl_input_report::Unit)>,
positions: Vec<Position>,
) -> input_device::InputEvent {
let injector_contacts: Vec<_> = positions
.into_iter()
.enumerate()
.map(|(i, position)| touch_binding::TouchContact {
id: u32::try_from(i).unwrap(),
position,
contact_size: None,
pressure: None,
})
.collect();
make_unhandled_touchpad_event_with_contacts(contact_position_units, injector_contacts)
}
#[test_case(
vec![(
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
)],
vec![
touch_binding::TouchContact{
id: 1,
position: Position { x: 200000.0, y: 100000.0 },
contact_size: Some(Size {
width: 2500.0,
height: 1500.0,
}),
pressure: None,
}
]; "from_micrometers")]
#[test_case(
vec![(
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -2 },
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -2 },
)],
vec![
touch_binding::TouchContact{
id: 1,
position: Position { x: 20.0, y: 10.0 },
contact_size: Some(Size {
width: 0.25,
height: 0.15,
}),
pressure: None,
}
]; "from_centimeters")]
#[fuchsia::test(allow_stalls = false)]
async fn provides_recognizer_position_size_in_millimeters(
contact_position_units: Vec<(fidl_input_report::Unit, fidl_input_report::Unit)>,
contacts: Vec<touch_binding::TouchContact>,
) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = Rc::new(GestureArena::new_for_test(
Box::new(contender_factory),
&fuchsia_inspect::Inspector::default(),
1,
));
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
arena
.handle_input_event(make_unhandled_touchpad_event_with_contacts(
contact_position_units,
contacts,
))
.await;
assert_matches!(
contender.get_last_touchpad_event().unwrap().contacts.as_slice(),
[touch_binding::TouchContact { position, contact_size: Some(size), .. }] => {
assert_near!(position.x, 200.0, 1.0);
assert_near!(position.y, 100.0, 1.0);
assert_near!(size.width, 2.5, 0.1);
assert_near!(size.height, 1.5, 0.1);
}
);
}
#[test_case(
touch_binding::TouchpadEvent {
injector_contacts: vec![
touch_binding::TouchContact{
id: 1,
position: Position { x: 0.0, y: 0.0 },
contact_size: Some(Size {
width: args::MIN_PALM_SIZE_MM,
height: 0.15,
}),
pressure: None,
}
],
pressed_buttons: hashset! {},
}; "only palm contact"
)]
#[fuchsia::test(allow_stalls = false)]
async fn ignore_palm_contact(event: touch_binding::TouchpadEvent) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = Rc::new(GestureArena::new_for_test(
Box::new(contender_factory),
&fuchsia_inspect::Inspector::default(),
1,
));
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
let input_event = input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touchpad(event),
device_descriptor: make_touchpad_descriptor(vec![(
fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
)]),
event_time: zx::MonotonicInstant::ZERO,
trace_id: None,
handled: input_device::Handled::No,
};
arena.handle_input_event(input_event).await;
assert_matches!(contender.get_last_touchpad_event().unwrap().contacts.as_slice(), []);
}
#[test_case(
touch_binding::TouchpadEvent {
injector_contacts: vec![
TOUCH_CONTACT_INDEX_FINGER,
touch_binding::TouchContact{
id: 1,
position: Position { x: 0.0, y: 0.0 },
contact_size: Some(Size {
width: args::MIN_PALM_SIZE_MM,
height: 0.15,
}),
pressure: None,
}
],
pressed_buttons: hashset! {},
}, vec![]; "palm contact and finger"
)]
#[fuchsia::test(allow_stalls = false)]
async fn ignore_palm_contact_keep_finger(
event: touch_binding::TouchpadEvent,
expect_buttons: Vec<u8>,
) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = Rc::new(GestureArena::new_for_test(
Box::new(contender_factory),
&fuchsia_inspect::Inspector::default(),
1,
));
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
let input_event = input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touchpad(event),
device_descriptor: make_touchpad_descriptor(vec![(
fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
)]),
event_time: zx::MonotonicInstant::ZERO,
trace_id: None,
handled: input_device::Handled::No,
};
arena.handle_input_event(input_event).await;
let got = contender.get_last_touchpad_event().unwrap();
assert_eq!(got.contacts.as_slice(), [TOUCH_CONTACT_INDEX_FINGER]);
assert_eq!(got.pressed_buttons, expect_buttons);
}
#[test_case(
touch_binding::TouchpadEvent {
injector_contacts: vec![
touch_binding::TouchContact{
id: 1,
position: Position { x: 0.0, y: 0.0 },
contact_size: Some(Size {
width: args::MIN_PALM_SIZE_MM,
height: 0.15,
}),
pressure: None,
}
],
pressed_buttons: hashset! {1},
}; "palm contact"
)]
#[test_case(
touch_binding::TouchpadEvent {
injector_contacts: vec![
touch_binding::TouchContact{
id: 1,
position: Position { x: 0.0, y: 0.0 },
contact_size: Some(Size {
width: args::MIN_PALM_SIZE_MM,
height: 0.15,
}),
pressure: None,
},
touch_binding::TouchContact{
id: 2,
position: Position { x: 5.0, y: 5.0 },
contact_size: Some(Size {
width: args::MIN_PALM_SIZE_MM / 2.0,
height: 0.15,
}),
pressure: None,
},
],
pressed_buttons: hashset! {1},
}; "palm and finger contact"
)]
#[fuchsia::test(allow_stalls = false)]
async fn skip_palm_detection_when_button_down(event: touch_binding::TouchpadEvent) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = Rc::new(GestureArena::new_for_test(
Box::new(contender_factory),
&fuchsia_inspect::Inspector::default(),
1,
));
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
let count_of_contact = event.injector_contacts.len();
let input_event = input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touchpad(event),
device_descriptor: make_touchpad_descriptor(vec![(
fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
fidl_input_report::Unit { type_: UnitType::Meters, exponent: -3 },
)]),
event_time: zx::MonotonicInstant::ZERO,
trace_id: None,
handled: input_device::Handled::No,
};
arena.handle_input_event(input_event).await;
assert_eq!(
contender.get_last_touchpad_event().unwrap().contacts.len(),
count_of_contact
);
}
#[test_case(
vec![(
fidl_input_report::Unit{ type_: UnitType::None, exponent: -6 },
fidl_input_report::Unit{ type_: UnitType::None, exponent: -6 },
)],
vec![];
"both units unspecified")]
#[test_case(
vec![(
fidl_input_report::Unit{ type_: UnitType::None, exponent: -6 },
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
)],
vec![];
"x unit unspecified")]
#[test_case(
vec![(
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
fidl_input_report::Unit{ type_: UnitType::None, exponent: -6 },
)],
vec![];
"y unit unspecified")]
#[test_case(
vec![(
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -3 },
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
)],
vec![];
"mismatched exponents")]
#[test_case(
vec![
(
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -3 },
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -3 },
),
(
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
fidl_input_report::Unit{ type_: UnitType::Meters, exponent: -6 },
),
],
vec![];
"unequal divisors")]
#[fuchsia::test(allow_stalls = false)]
async fn skips_contender_on_bad_descriptor(
contact_position_units: Vec<(fidl_input_report::Unit, fidl_input_report::Unit)>,
positions: Vec<Position>,
) {
let contender = Box::new(StubContender::new());
let contender_factory = ContenderFactoryOnceOrPanic::new(vec![contender.clone()]);
let arena = Rc::new(GestureArena::new_for_test(
Box::new(contender_factory),
&fuchsia_inspect::Inspector::default(),
1,
));
contender.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
arena
.handle_input_event(make_unhandled_touchpad_event_with_positions(
contact_position_units,
positions,
))
.await;
assert_eq!(contender.calls_received(), 0);
}
}
mod inspect {
use super::super::{
args, Contender, ContenderFactory, DetailedReasonFloat, DetailedReasonInt,
DetailedReasonUint, EndGestureEvent, ExamineEventResult, GestureArena, InputHandler,
MouseEvent, ProcessBufferedEventsResult, ProcessNewEventResult, Reason,
RecognizedGesture, TouchpadEvent,
};
use super::utils::{
make_touchpad_descriptor, make_unhandled_keyboard_event, make_unhandled_mouse_event,
make_unhandled_touchpad_event, ContenderFactoryOnceOrPanic, StubContender,
StubMatchedContender, StubWinner,
};
use crate::{input_device, keyboard_binding, mouse_binding, touch_binding, Position, Size};
use assert_matches::assert_matches;
use maplit::hashset;
use std::rc::Rc;
use test_case::test_case;
use {fidl_fuchsia_input_report as fidl_input_report, fuchsia_async as fasync};
struct EmptyContenderFactory {}
impl ContenderFactory for EmptyContenderFactory {
fn make_contenders(&self) -> Vec<Box<dyn crate::gestures::gesture_arena::Contender>> {
vec![]
}
}
#[fuchsia::test]
fn gesture_arena_initialized_with_inspect_node() {
let inspector = fuchsia_inspect::Inspector::default();
let fake_handlers_node = inspector.root().create_child("input_handlers_node");
let _handler = GestureArena::new_internal(
Box::new(EmptyContenderFactory {}),
&inspector.root(),
2,
&fake_handlers_node,
);
diagnostics_assertions::assert_data_tree!(inspector, root: {
gestures_event_log: {},
input_handlers_node: {
gesture_arena: {
events_received_count: 0u64,
events_handled_count: 0u64,
last_received_timestamp_ns: 0u64,
"fuchsia.inspect.Health": {
status: "STARTING_UP",
start_timestamp_nanos: diagnostics_assertions::AnyProperty
},
}
}
});
}
#[fasync::run_singlethreaded(test)]
async fn gesture_arena_inspect_counts_events() {
let inspector = fuchsia_inspect::Inspector::default();
let fake_handlers_node = inspector.root().create_child("input_handlers_node");
let arena = Rc::new(GestureArena::new_internal(
Box::new(EmptyContenderFactory {}),
&inspector.root(),
1,
&fake_handlers_node,
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
arena.clone().handle_input_event(make_unhandled_mouse_event()).await;
arena.clone().handle_input_event(make_unhandled_keyboard_event()).await;
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
diagnostics_assertions::assert_data_tree!(inspector, root: contains {
input_handlers_node: {
gesture_arena: {
events_received_count: 2u64,
events_handled_count: 0u64,
last_received_timestamp_ns: 0u64,
"fuchsia.inspect.Health": {
status: "STARTING_UP",
start_timestamp_nanos: diagnostics_assertions::AnyProperty
},
}
}
});
}
#[fuchsia::test]
fn logs_to_inspect() {
let mut executor = fasync::TestExecutor::new_with_fake_time();
let basic_mismatch_contender = Box::new(StubContender::new());
let detailed_uint_mismatch_contender = Box::new(StubContender::new());
let detailed_float_mismatch_contender = Box::new(StubContender::new());
let detailed_int_mismatch_contender = Box::new(StubContender::new());
let gesture_matching_contender = Box::new(StubContender::new());
basic_mismatch_contender
.set_next_result(ExamineEventResult::Mismatch(Reason::Basic("some reason")));
detailed_uint_mismatch_contender.set_next_result(ExamineEventResult::Mismatch(
Reason::DetailedUint(DetailedReasonUint {
criterion: "num_goats_teleported",
min: Some(10),
max: Some(30),
actual: 42,
}),
));
detailed_float_mismatch_contender.set_next_result(ExamineEventResult::Mismatch(
Reason::DetailedFloat(DetailedReasonFloat {
criterion: "teleportation_distance_kilometers",
min: Some(10.125),
max: Some(30.5),
actual: 42.0,
}),
));
detailed_int_mismatch_contender.set_next_result(ExamineEventResult::Mismatch(
Reason::DetailedInt(DetailedReasonInt {
criterion: "budget_surplus_trillions",
min: Some(-10),
max: Some(1),
actual: -42,
}),
));
let inspector = fuchsia_inspect::Inspector::default();
let contender_factory = Box::new(ContenderFactoryOnceOrPanic::new(vec![
basic_mismatch_contender,
detailed_uint_mismatch_contender,
detailed_float_mismatch_contender,
detailed_int_mismatch_contender,
gesture_matching_contender.clone(),
]));
let arena = Rc::new(GestureArena::new_for_test(contender_factory, &inspector, 100));
let touchpad_descriptor = input_device::InputDeviceDescriptor::Touchpad(
touch_binding::TouchpadDeviceDescriptor {
device_id: 1,
contacts: vec![touch_binding::ContactDeviceDescriptor {
x_range: fidl_input_report::Range { min: 0, max: 10_000 },
y_range: fidl_input_report::Range { min: 0, max: 10_000 },
x_unit: fidl_input_report::Unit {
type_: fidl_input_report::UnitType::Meters,
exponent: -3,
},
y_unit: fidl_input_report::Unit {
type_: fidl_input_report::UnitType::Meters,
exponent: -3,
},
pressure_range: None,
width_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
height_range: Some(fidl_input_report::Range { min: 0, max: 10_000 }),
}],
},
);
let keyboard_descriptor = input_device::InputDeviceDescriptor::Keyboard(
keyboard_binding::KeyboardDeviceDescriptor {
device_id: 2,
device_information: fidl_fuchsia_input_report::DeviceInformation {
vendor_id: Some(0),
product_id: Some(0),
version: Some(0),
polling_rate: Some(0),
..Default::default()
},
keys: vec![fidl_fuchsia_input::Key::A, fidl_fuchsia_input::Key::B],
},
);
let mut handle_event_fut = arena.clone().handle_input_event(input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![
touch_binding::TouchContact {
id: 1u32,
position: Position { x: 2.0, y: 3.0 },
contact_size: None,
pressure: None,
},
touch_binding::TouchContact {
id: 2u32,
position: Position { x: 40.0, y: 50.0 },
contact_size: None,
pressure: None,
},
],
pressed_buttons: hashset! {1},
},
),
device_descriptor: touchpad_descriptor.clone(),
event_time: zx::MonotonicInstant::from_nanos(12_300),
trace_id: None,
handled: input_device::Handled::No,
});
executor.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
gesture_matching_contender
.set_next_result(ExamineEventResult::Contender(gesture_matching_contender.clone()));
assert_matches!(
executor.run_until_stalled(&mut handle_event_fut),
std::task::Poll::Ready(_)
);
let mut handle_event_fut = arena.clone().handle_input_event(input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(
keyboard_binding::KeyboardEvent::new(
fidl_fuchsia_input::Key::A,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
),
),
device_descriptor: keyboard_descriptor.clone(),
event_time: zx::MonotonicInstant::from_nanos(11_000_000),
trace_id: None,
handled: input_device::Handled::Yes,
});
executor.set_fake_time(fasync::MonotonicInstant::from_nanos(12_000_000));
assert_matches!(
executor.run_until_stalled(&mut handle_event_fut),
std::task::Poll::Ready(_)
);
let mut handle_event_fut = arena.clone().handle_input_event(input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(
keyboard_binding::KeyboardEvent::new(
fidl_fuchsia_input::Key::B,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
),
),
device_descriptor: keyboard_descriptor,
event_time: zx::MonotonicInstant::from_nanos(13_000_000),
trace_id: None,
handled: input_device::Handled::No,
});
executor.set_fake_time(fasync::MonotonicInstant::from_nanos(14_000_000));
assert_matches!(
executor.run_until_stalled(&mut handle_event_fut),
std::task::Poll::Ready(_)
);
let mut handle_event_fut = arena.clone().handle_input_event(input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![touch_binding::TouchContact {
id: 1u32,
position: Position { x: 2.0, y: 3.0 },
contact_size: Some(Size { width: 3.0, height: 4.0 }),
pressure: None,
}],
pressed_buttons: hashset! {},
},
),
device_descriptor: touchpad_descriptor.clone(),
event_time: zx::MonotonicInstant::from_nanos(18_000_000),
trace_id: None,
handled: input_device::Handled::No,
});
let matched_contender = Box::new(StubMatchedContender::new());
matched_contender.set_next_process_buffered_events_result(
ProcessBufferedEventsResult {
generated_events: vec![],
winner: None,
recognized_gesture: RecognizedGesture::Motion,
},
);
gesture_matching_contender
.set_next_result(ExamineEventResult::MatchedContender(matched_contender));
executor.set_fake_time(fasync::MonotonicInstant::from_nanos(19_000_000));
assert_matches!(
executor.run_until_stalled(&mut handle_event_fut),
std::task::Poll::Ready(_)
);
diagnostics_assertions::assert_data_tree!(inspector, root: {
gestures_event_log: {
"0": {
touchpad_event: {
driver_monotonic_nanos: 12_300i64,
entry_latency_micros: 9987i64, pressed_buttons: vec![ 1u64 ],
contacts: {
"1": {
pos_x_mm: 2.0,
pos_y_mm: 3.0,
},
"2": {
pos_x_mm: 40.0,
pos_y_mm: 50.0,
},
},
filtered_palm_contacts: {},
}
},
"1": {
mismatch_event: {
contender: "utils::StubContender",
reason: "some reason",
}
},
"2": {
mismatch_event: {
contender: "utils::StubContender",
criterion: "num_goats_teleported",
min_allowed: 10u64,
max_allowed: 30u64,
actual: 42u64,
}
},
"3": {
mismatch_event: {
contender: "utils::StubContender",
criterion: "teleportation_distance_kilometers",
min_allowed: 10.125,
max_allowed: 30.5,
actual: 42.0,
}
},
"4": {
mismatch_event: {
contender: "utils::StubContender",
criterion: "budget_surplus_trillions",
min_allowed: -10i64,
max_allowed: 1i64,
actual: -42i64,
}
},
"5": {
key_event: {
driver_monotonic_nanos: 11_000_000i64,
entry_latency_micros: 1_000i64, }
},
"6": {
key_event: {
driver_monotonic_nanos: 13_000_000i64,
entry_latency_micros: 1_000i64, }
},
"7": {
touchpad_event: {
driver_monotonic_nanos: 18_000_000i64,
entry_latency_micros: 1_000i64, pressed_buttons: Vec::<u64>::new(),
contacts: {
"1": {
pos_x_mm: 2.0,
pos_y_mm: 3.0,
width_mm: 3.0,
height_mm: 4.0,
},
},
filtered_palm_contacts: {},
}
},
"8": {
gesture_start: {
gesture_name: "motion",
latency_event_count: 1u64,
latency_micros: 17_987i64, }
},
"9": {
gesture_end: {
gesture_name: "motion",
contender: "utils::StubMatchedContender",
event_count: 0u64,
duration_micros: 0i64,
reason: "discrete-recognizer",
}
}
}
});
}
#[fuchsia::test(allow_stalls = false)]
async fn negative_matching_latency_is_logged_correctly() {
let inspector = fuchsia_inspect::Inspector::default();
let gesture_matching_contender = Box::new(StubContender::new());
let contender_factory =
Box::new(ContenderFactoryOnceOrPanic::new(
vec![gesture_matching_contender.clone()],
));
let arena = Rc::new(GestureArena::new_for_test(contender_factory, &inspector, 100));
gesture_matching_contender
.set_next_result(ExamineEventResult::Contender(gesture_matching_contender.clone()));
arena
.clone()
.handle_input_event(input_device::InputEvent {
event_time: zx::MonotonicInstant::from_nanos(15_000),
..make_unhandled_touchpad_event()
})
.await;
let matched_contender = Box::new(StubMatchedContender::new());
matched_contender.set_next_process_buffered_events_result(
ProcessBufferedEventsResult {
generated_events: vec![],
winner: None,
recognized_gesture: RecognizedGesture::Motion,
},
);
gesture_matching_contender
.set_next_result(ExamineEventResult::MatchedContender(matched_contender));
arena
.clone()
.handle_input_event(input_device::InputEvent {
event_time: zx::MonotonicInstant::from_nanos(6_000),
..make_unhandled_touchpad_event()
})
.await;
diagnostics_assertions::assert_data_tree!(inspector, root: {
gestures_event_log: {
"0": contains {},
"1": contains {},
"2": {
gesture_start: {
gesture_name: diagnostics_assertions::AnyProperty,
latency_event_count: 1u64,
latency_micros: -9i64,
}
},
"3": {
gesture_end: contains {}
},
}
})
}
struct ContenderFactoryOnceThenEmpty {
contenders: std::cell::Cell<Vec<Box<dyn Contender>>>,
}
impl ContenderFactory for ContenderFactoryOnceThenEmpty {
fn make_contenders(&self) -> Vec<Box<dyn Contender>> {
self.contenders.take()
}
}
#[test_case(EndGestureEvent::NoEvent; "end_gesture_no_event")]
#[test_case(EndGestureEvent::UnconsumedEvent(TouchpadEvent {
timestamp: zx::MonotonicInstant::ZERO,
pressed_buttons: vec![],
contacts: vec![],
filtered_palm_contacts: vec![],
}); "end_gesture_unconsumed_event")]
#[test_case(EndGestureEvent::GeneratedEvent(MouseEvent {
timestamp: zx::MonotonicInstant::ZERO,
mouse_data: mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(
mouse_binding::RelativeLocation {
millimeters: Position::zero(),
},
),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: hashset! {},
pressed_buttons: hashset! {},
is_precision_scroll: None,
},
}); "end_gesture_generated_event")]
#[fuchsia::test(allow_stalls = false)]
async fn multi_event_gesture_is_logged_correctly(end_gesture_event: EndGestureEvent) {
let inspector = fuchsia_inspect::Inspector::default();
let matching_contender = Box::new(StubContender::new());
let arena = Rc::new(GestureArena::new_for_test(
Box::new(ContenderFactoryOnceThenEmpty {
contenders: std::cell::Cell::new(vec![matching_contender.clone()]),
}),
&inspector,
100,
));
matching_contender
.set_next_result(ExamineEventResult::Contender(matching_contender.clone()));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
let matched_contender = Box::new(StubMatchedContender::new());
let winner = Box::new(StubWinner::new());
matching_contender
.set_next_result(ExamineEventResult::MatchedContender(matched_contender.clone()));
matched_contender.set_next_process_buffered_events_result(
ProcessBufferedEventsResult {
generated_events: vec![],
winner: Some(winner.clone()),
recognized_gesture: RecognizedGesture::Motion,
},
);
winner.set_next_result(ProcessNewEventResult::ContinueGesture(None, winner.clone()));
arena
.clone()
.handle_input_event(input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![],
pressed_buttons: hashset! {},
},
),
device_descriptor: make_touchpad_descriptor(),
event_time: zx::MonotonicInstant::from_nanos(123_000),
trace_id: None,
handled: input_device::Handled::No,
})
.await;
winner.set_next_result(ProcessNewEventResult::ContinueGesture(None, winner.clone()));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await;
winner.set_next_result(ProcessNewEventResult::EndGesture(
end_gesture_event,
Reason::DetailedUint(DetailedReasonUint {
criterion: "num_goats_teleported",
min: Some(10),
max: Some(30),
actual: 42,
}),
));
arena
.clone()
.handle_input_event(input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![],
pressed_buttons: hashset! {},
},
),
device_descriptor: make_touchpad_descriptor(),
event_time: zx::MonotonicInstant::from_nanos(456_000),
trace_id: None,
handled: input_device::Handled::No,
})
.await;
diagnostics_assertions::assert_data_tree!(inspector, root: {
gestures_event_log: {
"0": { touchpad_event: contains {} },
"1": { touchpad_event: contains {} },
"2": { gesture_start: contains {} },
"3": { touchpad_event: contains {} },
"4": { touchpad_event: contains {} },
"5": {
gesture_end: contains {
gesture_name: "motion",
contender: "utils::StubWinner",
criterion: "num_goats_teleported",
min_allowed: 10u64,
max_allowed: 30u64,
actual: 42u64,
duration_micros: 333i64, event_count: 2u64,
},
},
}
})
}
#[fuchsia::test(allow_stalls = false)]
async fn retains_latest_events_up_to_cap() {
let inspector = fuchsia_inspect::Inspector::default();
let arena = Rc::new(GestureArena::new_for_test(
Box::new(EmptyContenderFactory {}),
&inspector,
2,
));
arena.clone().handle_input_event(make_unhandled_touchpad_event()).await; arena.clone().handle_input_event(make_unhandled_touchpad_event()).await; arena.clone().handle_input_event(make_unhandled_touchpad_event()).await; arena.clone().handle_input_event(make_unhandled_touchpad_event()).await; arena.clone().handle_input_event(make_unhandled_touchpad_event()).await; diagnostics_assertions::assert_data_tree!(inspector, root: {
gestures_event_log: {
"3": contains {},
"4": contains {},
}
})
}
#[fuchsia::test]
fn retains_palm_contacts() {
let mut executor = fasync::TestExecutor::new_with_fake_time();
let inspector = fuchsia_inspect::Inspector::default();
let arena = Rc::new(GestureArena::new_for_test(
Box::new(EmptyContenderFactory {}),
&inspector,
2,
));
let mut handle_event_fut = arena.clone().handle_input_event(input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touchpad(
touch_binding::TouchpadEvent {
injector_contacts: vec![touch_binding::TouchContact {
id: 1u32,
position: Position { x: 2_000.0, y: 1_000.0 },
contact_size: Some(Size {
width: (args::MIN_PALM_SIZE_MM + 0.1) * 1_000.0,
height: 4_000.0,
}),
pressure: None,
}],
pressed_buttons: hashset! {},
},
),
device_descriptor: make_touchpad_descriptor(),
event_time: zx::MonotonicInstant::ZERO,
trace_id: None,
handled: input_device::Handled::No,
});
executor.set_fake_time(fasync::MonotonicInstant::from_nanos(1_000_000));
assert_matches!(
executor.run_until_stalled(&mut handle_event_fut),
std::task::Poll::Ready(_)
);
diagnostics_assertions::assert_data_tree!(inspector, root: {
gestures_event_log: {
"0": {
touchpad_event: {
driver_monotonic_nanos: 0i64,
entry_latency_micros: 1_000i64,
pressed_buttons: Vec::<u64>::new(),
contacts: {},
filtered_palm_contacts: {
"1": {
pos_x_mm: 2.0,
pos_y_mm: 1.0,
width_mm: (args::MIN_PALM_SIZE_MM + 0.1) as f64,
height_mm: 4.0,
},
},
},
},
},
});
}
}
}