blob: aeee2b4453ee86b3795b734392b10f4b7a639824 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![warn(clippy::await_holding_refcell_ref)]
use {
crate::input_device,
crate::input_handler::UnhandledInputHandler,
crate::touch_binding,
crate::utils::{Position, Size},
anyhow::{Context, Error, Result},
async_trait::async_trait,
async_utils::hanging_get::client::HangingGetStream,
fidl::endpoints::create_proxy,
fidl_fuchsia_ui_pointerinjector as pointerinjector,
fidl_fuchsia_ui_pointerinjector_configuration as pointerinjector_config,
fuchsia_component::client::connect_to_protocol,
fuchsia_syslog::{fx_log_err, fx_log_info},
fuchsia_zircon as zx,
futures::stream::StreamExt,
std::{cell::RefCell, collections::HashMap, option::Option, rc::Rc},
};
/// An input handler that parses touch events and forwards them to Scenic through the
/// fidl_fuchsia_pointerinjector protocols.
#[derive(Debug)]
pub struct TouchInjectorHandler {
/// The mutable fields of this handler.
mutable_state: RefCell<MutableState>,
/// The scope and coordinate system of injection.
/// See fidl_fuchsia_pointerinjector::Context for more details.
context_view_ref: fidl_fuchsia_ui_views::ViewRef,
/// The region where dispatch is attempted for injected events.
/// See fidl_fuchsia_pointerinjector::Target for more details.
target_view_ref: fidl_fuchsia_ui_views::ViewRef,
/// The size of the display associated with the touch device, used to convert
/// coordinates from the touch input report to device coordinates (which is what
/// Scenic expects).
display_size: Size,
/// The FIDL proxy to register new injectors.
injector_registry_proxy: pointerinjector::RegistryProxy,
/// The FIDL proxy used to get configuration details for pointer injection.
configuration_proxy: pointerinjector_config::SetupProxy,
}
#[derive(Debug)]
struct MutableState {
/// A rectangular region that directs injected events into a target.
/// See fidl_fuchsia_pointerinjector::Viewport for more details.
viewport: Option<pointerinjector::Viewport>,
/// The injectors registered with Scenic, indexed by their device ids.
injectors: HashMap<u32, pointerinjector::DeviceProxy>,
}
#[async_trait(?Send)]
impl UnhandledInputHandler for TouchInjectorHandler {
async fn handle_unhandled_input_event(
self: Rc<Self>,
unhandled_input_event: input_device::UnhandledInputEvent,
) -> Vec<input_device::InputEvent> {
fuchsia_trace::duration!("input", "presentation_on_event");
match unhandled_input_event {
input_device::UnhandledInputEvent {
device_event: input_device::InputDeviceEvent::Touch(ref touch_event),
device_descriptor:
input_device::InputDeviceDescriptor::Touch(ref touch_device_descriptor),
event_time,
trace_id,
} => {
fuchsia_trace::flow_end!("input", "report-to-event", trace_id.unwrap_or(0));
// Create a new injector if this is the first time seeing device_id.
if let Err(e) = self.ensure_injector_registered(&touch_device_descriptor).await {
fx_log_err!("{}", e);
}
// Handle the event.
if let Err(e) = self
.send_event_to_scenic(&touch_event, &touch_device_descriptor, event_time)
.await
{
fx_log_err!("{}", e);
}
// Consume the input event.
vec![input_device::InputEvent::from(unhandled_input_event).into_handled()]
}
_ => vec![input_device::InputEvent::from(unhandled_input_event)],
}
}
}
impl TouchInjectorHandler {
/// Creates a new touch handler that holds touch pointer injectors.
/// The caller is expected to spawn a task to continually watch for updates to the viewport.
/// Example:
/// let handler = TouchInjectorHandler::new(display_size).await?;
/// fasync::Task::local(handler.clone().watch_viewport()).detach();
///
/// # Parameters
/// - `display_size`: The size of the associated touch display.
///
/// # Errors
/// If unable to connect to pointerinjector protocols.
pub async fn new(display_size: Size) -> Result<Rc<Self>, Error> {
let configuration_proxy = connect_to_protocol::<pointerinjector_config::SetupMarker>()?;
let injector_registry_proxy = connect_to_protocol::<pointerinjector::RegistryMarker>()?;
Self::new_handler(configuration_proxy, injector_registry_proxy, display_size).await
}
/// Creates a new touch handler that holds touch pointer injectors.
/// The caller is expected to spawn a task to continually watch for updates to the viewport.
/// Example:
/// let handler = TouchInjectorHandler::new_with_config_proxy(config_proxy, display_size).await?;
/// fasync::Task::local(handler.clone().watch_viewport()).detach();
///
/// # Parameters
/// - `configuration_proxy`: A proxy used to get configuration details for pointer
/// injection.
/// - `display_size`: The size of the associated touch display.
///
/// # Errors
/// If unable to get injection view refs from `configuration_proxy`.
/// If unable to connect to pointerinjector Registry protocol.
pub async fn new_with_config_proxy(
configuration_proxy: pointerinjector_config::SetupProxy,
display_size: Size,
) -> Result<Rc<Self>, Error> {
let injector_registry_proxy = connect_to_protocol::<pointerinjector::RegistryMarker>()?;
Self::new_handler(configuration_proxy, injector_registry_proxy, display_size).await
}
/// Creates a new touch handler that holds touch pointer injectors.
/// The caller is expected to spawn a task to continually watch for updates to the viewport.
/// Example:
/// let handler = TouchInjectorHandler::new_handler(None, None, display_size).await?;
/// fasync::Task::local(handler.clone().watch_viewport()).detach();
///
/// # Parameters
/// - `configuration_proxy`: A proxy used to get configuration details for pointer
/// injection.
/// - `injector_registry_proxy`: A proxy used to register new pointer injectors. If
/// none is provided, connect to protocol routed to this component.
/// - `display_size`: The size of the associated touch display.
///
/// # Errors
/// If unable to get injection view refs from `configuration_proxy`.
async fn new_handler(
configuration_proxy: pointerinjector_config::SetupProxy,
injector_registry_proxy: pointerinjector::RegistryProxy,
display_size: Size,
) -> Result<Rc<Self>, Error> {
// Get the context and target views to inject into.
let (context_view_ref, target_view_ref) = configuration_proxy.get_view_refs().await?;
let handler = Rc::new(Self {
mutable_state: RefCell::new(MutableState { viewport: None, injectors: HashMap::new() }),
context_view_ref,
target_view_ref,
display_size,
injector_registry_proxy,
configuration_proxy,
});
Ok(handler)
}
/// Adds a new pointer injector and tracks it in `self.injectors` if one doesn't exist at
/// `touch_descriptor.device_id`.
///
/// # Parameters
/// - `touch_descriptor`: The descriptor of the new touch device.
async fn ensure_injector_registered(
self: &Rc<Self>,
touch_descriptor: &touch_binding::TouchDeviceDescriptor,
) -> Result<(), anyhow::Error> {
if self.mutable_state.borrow().injectors.contains_key(&touch_descriptor.device_id) {
return Ok(());
}
// Create a new injector.
let (device_proxy, device_server) = create_proxy::<pointerinjector::DeviceMarker>()
.context("Failed to create DeviceProxy.")?;
let context = fuchsia_scenic::duplicate_view_ref(&self.context_view_ref)
.context("Failed to duplicate context view ref.")?;
let target = fuchsia_scenic::duplicate_view_ref(&self.target_view_ref)
.context("Failed to duplicate target view ref.")?;
let viewport = self.mutable_state.borrow().viewport.clone();
if viewport.is_none() {
// An injector without a viewport is not valid. The event will be dropped
// since the handler will not have a registered injector to inject into.
return Err(anyhow::format_err!(
"Received a touch event without a viewport to inject into."
));
}
let config = pointerinjector::Config {
device_id: Some(touch_descriptor.device_id),
device_type: Some(pointerinjector::DeviceType::Touch),
context: Some(pointerinjector::Context::View(context)),
target: Some(pointerinjector::Target::View(target)),
viewport,
dispatch_policy: Some(pointerinjector::DispatchPolicy::TopHitAndAncestorsInTarget),
scroll_v_range: None,
scroll_h_range: None,
buttons: None,
..pointerinjector::Config::EMPTY
};
// Keep track of the injector.
self.mutable_state.borrow_mut().injectors.insert(touch_descriptor.device_id, device_proxy);
// Register the new injector.
self.injector_registry_proxy
.register(config, device_server)
.await
.context("Failed to register injector.")?;
fx_log_info!("Registered injector with device id {:?}", touch_descriptor.device_id);
Ok(())
}
/// Sends the given event to Scenic.
///
/// # Parameters
/// - `touch_event`: The touch event to send to Scenic.
/// - `touch_descriptor`: The descriptor for the device that sent the touch event.
/// - `event_time`: The time in nanoseconds when the event was first recorded.
async fn send_event_to_scenic(
&self,
touch_event: &touch_binding::TouchEvent,
touch_descriptor: &touch_binding::TouchDeviceDescriptor,
event_time: zx::Time,
) -> Result<(), anyhow::Error> {
// The order in which events are sent to clients.
let ordered_phases = vec![
pointerinjector::EventPhase::Add,
pointerinjector::EventPhase::Change,
pointerinjector::EventPhase::Remove,
];
// Make the trace duration end on the call to injector.inject, not the call's return.
// The duration should start before the flow_begin is minted in
// create_pointer_sample_event, and it should not include the injector.inject() call's
// return from await.
fuchsia_trace::duration_begin!("input", "touch-inject-into-scenic");
let mut events: Vec<pointerinjector::Event> = vec![];
for phase in ordered_phases {
let contacts: Vec<touch_binding::TouchContact> = touch_event
.injector_contacts
.get(&phase)
.map_or(vec![], |contacts| contacts.to_owned());
let new_events = contacts.into_iter().map(|contact| {
Self::create_pointer_sample_event(
phase,
&contact,
touch_descriptor,
&self.display_size,
event_time,
)
});
events.extend(new_events);
}
let injector =
self.mutable_state.borrow().injectors.get(&touch_descriptor.device_id).cloned();
if let Some(injector) = injector {
let events_to_send = &mut events.into_iter();
let fut = injector.inject(events_to_send);
// This trace duration ends before awaiting on the returned future.
fuchsia_trace::duration_end!("input", "touch-inject-into-scenic");
let _ = fut.await;
Ok(())
} else {
fuchsia_trace::duration_end!("input", "touch-inject-into-scenic");
Err(anyhow::format_err!(
"No injector found for touch device {}.",
touch_descriptor.device_id
))
}
}
/// Creates a [`fidl_fuchsia_ui_pointerinjector::Event`] representing the given touch contact.
///
/// # Parameters
/// - `phase`: The phase of the touch contact.
/// - `contact`: The touch contact to create the event for.
/// - `touch_descriptor`: The device descriptor for the device that generated the event.
/// - `display_size`: The size of the associated touch display.
/// - `event_time`: The time in nanoseconds when the event was first recorded.
fn create_pointer_sample_event(
phase: pointerinjector::EventPhase,
contact: &touch_binding::TouchContact,
touch_descriptor: &touch_binding::TouchDeviceDescriptor,
display_size: &Size,
event_time: zx::Time,
) -> pointerinjector::Event {
let position =
Self::display_coordinate_from_contact(&contact, &touch_descriptor, display_size);
let pointer_sample = pointerinjector::PointerSample {
pointer_id: Some(contact.id),
phase: Some(phase),
position_in_viewport: Some([position.x, position.y]),
scroll_v: None,
scroll_h: None,
pressed_buttons: None,
..pointerinjector::PointerSample::EMPTY
};
let data = pointerinjector::Data::PointerSample(pointer_sample);
let trace_flow_id = fuchsia_trace::generate_nonce();
let event = pointerinjector::Event {
timestamp: Some(event_time.into_nanos()),
data: Some(data),
trace_flow_id: Some(trace_flow_id),
..pointerinjector::Event::EMPTY
};
fuchsia_trace::flow_begin!("input", "dispatch_event_to_scenic", trace_flow_id);
event
}
/// Converts an input event touch to a display coordinate, which is the coordinate space in
/// which Scenic handles events.
///
/// The display coordinate is calculated by normalizing the contact position to the display
/// size. It does not account for the viewport position, which Scenic handles directly.
///
/// # Parameters
/// - `contact`: The contact to get the display coordinate from.
/// - `touch_descriptor`: The device descriptor for the device that generated the event.
/// This is used to compute the device coordinate.
///
/// # Returns
/// (x, y) coordinates.
fn display_coordinate_from_contact(
contact: &touch_binding::TouchContact,
touch_descriptor: &touch_binding::TouchDeviceDescriptor,
display_size: &Size,
) -> Position {
if let Some(contact_descriptor) = touch_descriptor.contacts.first() {
// Scale the x position.
let x_range: f32 =
contact_descriptor.x_range.max as f32 - contact_descriptor.x_range.min as f32;
let x_wrt_range: f32 = contact.position.x - contact_descriptor.x_range.min as f32;
let x: f32 = (display_size.width * x_wrt_range) / x_range;
// Scale the y position.
let y_range: f32 =
contact_descriptor.y_range.max as f32 - contact_descriptor.y_range.min as f32;
let y_wrt_range: f32 = contact.position.y - contact_descriptor.y_range.min as f32;
let y: f32 = (display_size.height * y_wrt_range) / y_range;
Position { x, y }
} else {
return contact.position;
}
}
/// Watches for viewport updates from the scene manager.
pub async fn watch_viewport(self: Rc<Self>) {
let configuration_proxy = self.configuration_proxy.clone();
let mut viewport_stream = HangingGetStream::new(
configuration_proxy,
pointerinjector_config::SetupProxy::watch_viewport,
);
loop {
match viewport_stream.next().await {
Some(Ok(new_viewport)) => {
// Update the viewport tracked by this handler.
self.mutable_state.borrow_mut().viewport = Some(new_viewport.clone());
// Update Scenic with the latest viewport.
let injectors: Vec<pointerinjector::DeviceProxy> =
self.mutable_state.borrow_mut().injectors.values().cloned().collect();
for injector in injectors {
let events = &mut vec![pointerinjector::Event {
timestamp: Some(fuchsia_async::Time::now().into_nanos()),
data: Some(pointerinjector::Data::Viewport(new_viewport.clone())),
trace_flow_id: Some(fuchsia_trace::generate_nonce()),
..pointerinjector::Event::EMPTY
}]
.into_iter();
injector.inject(events).await.expect("Failed to inject updated viewport.");
}
}
Some(Err(e)) => {
fx_log_err!("Error while reading viewport update: {}", e);
return;
}
None => {
fx_log_err!("Viewport update stream terminated unexpectedly");
return;
}
}
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::testing_utilities::{
create_touch_contact, create_touch_event, create_touch_pointer_sample_event,
},
assert_matches::assert_matches,
fidl_fuchsia_input_report as fidl_input_report, fidl_fuchsia_ui_input as fidl_ui_input,
fuchsia_async as fasync, fuchsia_zircon as zx,
futures::StreamExt,
maplit::hashmap,
pretty_assertions::assert_eq,
std::convert::TryFrom as _,
};
const TOUCH_ID: u32 = 1;
const DISPLAY_WIDTH: f32 = 100.0;
const DISPLAY_HEIGHT: f32 = 100.0;
/// Returns an |input_device::InputDeviceDescriptor::TouchDescriptor|.
fn get_touch_device_descriptor() -> input_device::InputDeviceDescriptor {
input_device::InputDeviceDescriptor::Touch(touch_binding::TouchDeviceDescriptor {
device_id: 1,
contacts: vec![touch_binding::ContactDeviceDescriptor {
x_range: fidl_input_report::Range { min: 0, max: 100 },
y_range: fidl_input_report::Range { min: 0, max: 100 },
pressure_range: None,
width_range: None,
height_range: None,
}],
})
}
/// Handles |fidl_fuchsia_pointerinjector_configuration::SetupRequest::GetViewRefs|.
async fn handle_configuration_request_stream(
stream: &mut pointerinjector_config::SetupRequestStream,
) {
if let Some(Ok(request)) = stream.next().await {
match request {
pointerinjector_config::SetupRequest::GetViewRefs { responder, .. } => {
let mut context = fuchsia_scenic::ViewRefPair::new()
.expect("Failed to create viewrefpair.")
.view_ref;
let mut target = fuchsia_scenic::ViewRefPair::new()
.expect("Failed to create viewrefpair.")
.view_ref;
let _ = responder.send(&mut context, &mut target);
}
_ => {}
};
}
}
/// Handles |fidl_fuchsia_pointerinjector::DeviceRequest|s by asserting the `injector_stream`
/// gets `expected_event`.
async fn handle_device_request_stream(
mut injector_stream: pointerinjector::DeviceRequestStream,
expected_event: pointerinjector::Event,
) {
match injector_stream.next().await {
Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => {
assert_eq!(events.len(), 1);
assert_eq!(events[0].timestamp, expected_event.timestamp);
assert_eq!(events[0].data, expected_event.data);
responder.send().expect("failed to respond");
}
Some(Err(e)) => panic!("FIDL error {}", e),
None => panic!("Expected another event."),
}
}
// Creates a |pointerinjector::Viewport|.
fn create_viewport(min: f32, max: f32) -> pointerinjector::Viewport {
pointerinjector::Viewport {
extents: Some([[min, min], [max, max]]),
viewport_to_context_transform: None,
..pointerinjector::Viewport::EMPTY
}
}
// Tests that TouchInjectorHandler::watch_viewport() tracks viewport updates and notifies
// injectors about said updates.
#[fuchsia::test]
fn receives_viewport_updates() {
let mut exec = fasync::TestExecutor::new().expect("executor needed");
// Create touch handler.
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>()
.expect("Failed to create pointerinjector Setup proxy and stream.");
let (injector_registry_proxy, _injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>()
.expect("Failed to create pointerinjector Registry proxy and stream.");
let touch_handler_fut = TouchInjectorHandler::new_handler(
configuration_proxy,
injector_registry_proxy,
Size { width: DISPLAY_WIDTH, height: DISPLAY_HEIGHT },
);
let config_request_stream_fut =
handle_configuration_request_stream(&mut configuration_request_stream);
let (touch_handler_res, _) = exec.run_singlethreaded(futures::future::join(
touch_handler_fut,
config_request_stream_fut,
));
let touch_handler = touch_handler_res.expect("Failed to create touch handler.");
// Add an injector.
let (injector_device_proxy, mut injector_device_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::DeviceMarker>()
.expect("Failed to create pointerinjector Registry proxy and stream.");
touch_handler.mutable_state.borrow_mut().injectors.insert(1, injector_device_proxy);
// This nested block is used to bound the lifetime of `watch_viewport_fut`.
{
// Request a viewport update.
let watch_viewport_fut = touch_handler.clone().watch_viewport();
futures::pin_mut!(watch_viewport_fut);
assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending());
// Send a viewport update.
match exec.run_singlethreaded(&mut configuration_request_stream.next()) {
Some(Ok(pointerinjector_config::SetupRequest::WatchViewport {
responder, ..
})) => {
responder.send(create_viewport(0.0, 100.0)).expect("Failed to send viewport.");
}
other => panic!("Received unexpected value: {:?}", other),
};
assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending());
// Check that the injector received an updated viewport
exec.run_singlethreaded(async {
match injector_device_request_stream.next().await {
Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => {
assert_eq!(events.len(), 1);
assert!(events[0].data.is_some());
assert_eq!(
events[0].data,
Some(pointerinjector::Data::Viewport(create_viewport(0.0, 100.0)))
);
responder.send().expect("injector stream failed to respond.");
}
other => panic!("Received unexpected value: {:?}", other),
}
});
// Request viewport update.
assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending());
// Send viewport update.
match exec.run_singlethreaded(&mut configuration_request_stream.next()) {
Some(Ok(pointerinjector_config::SetupRequest::WatchViewport {
responder, ..
})) => {
responder
.send(create_viewport(100.0, 200.0))
.expect("Failed to send viewport.");
}
other => panic!("Received unexpected value: {:?}", other),
};
// Process viewport update.
assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending());
// Check that the injector received an updated viewport
exec.run_singlethreaded(async {
match injector_device_request_stream.next().await {
Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => {
assert_eq!(events.len(), 1);
assert!(events[0].data.is_some());
assert_eq!(
events[0].data,
Some(pointerinjector::Data::Viewport(create_viewport(100.0, 200.0)))
);
responder.send().expect("injector stream failed to respond.");
}
other => panic!("Received unexpected value: {:?}", other),
}
});
}
// Check the viewport on the handler is accurate.
let expected_viewport = create_viewport(100.0, 200.0);
assert_eq!(touch_handler.mutable_state.borrow().viewport, Some(expected_viewport));
}
// Tests that an add contact event is dropped without a viewport.
#[fuchsia::test]
fn add_contact_drops_without_viewport() {
let mut exec = fasync::TestExecutor::new().expect("executor needed");
// Set up fidl streams.
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>()
.expect("Failed to create pointerinjector Setup proxy and stream.");
let (injector_registry_proxy, mut injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>()
.expect("Failed to create pointerinjector Registry proxy and stream.");
let config_request_stream_fut =
handle_configuration_request_stream(&mut configuration_request_stream);
// Create TouchInjectorHandler.
let touch_handler_fut = TouchInjectorHandler::new_handler(
configuration_proxy,
injector_registry_proxy,
Size { width: DISPLAY_WIDTH, height: DISPLAY_HEIGHT },
);
let (touch_handler_res, _) = exec.run_singlethreaded(futures::future::join(
touch_handler_fut,
config_request_stream_fut,
));
let touch_handler = touch_handler_res.expect("Failed to create touch handler.");
// Create touch event.
let event_time = zx::Time::get_monotonic();
let contact = create_touch_contact(TOUCH_ID, Position { x: 20.0, y: 40.0 });
let descriptor = get_touch_device_descriptor();
let input_event = input_device::UnhandledInputEvent::try_from(create_touch_event(
hashmap! {
fidl_ui_input::PointerEventPhase::Add
=> vec![contact.clone()],
},
event_time,
&descriptor,
))
.unwrap();
// Try to handle the event.
// Subtle: We handle the event on a clone of the handler because the call consumes the
// handler, whose reference to `injectory_registry_proxy` is needed to keep
// `injector_registry_request_stream` alive.
let mut handle_event_fut = touch_handler.clone().handle_unhandled_input_event(input_event);
let _ = exec.run_until_stalled(&mut handle_event_fut);
// Injector should not receive anything because the handler has no viewport.
let mut ir_fut = injector_registry_request_stream.next();
assert_matches!(exec.run_until_stalled(&mut ir_fut), futures::task::Poll::Pending);
}
// Tests that an add contact event is handled correctly with a viewport.
#[fuchsia::test]
fn add_contact_succeeds_with_viewport() {
let mut exec = fasync::TestExecutor::new().expect("executor needed");
// Create touch handler.
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>()
.expect("Failed to create pointerinjector Setup proxy and stream.");
let (injector_registry_proxy, _injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>()
.expect("Failed to create pointerinjector Registry proxy and stream.");
let touch_handler_fut = TouchInjectorHandler::new_handler(
configuration_proxy,
injector_registry_proxy,
Size { width: DISPLAY_WIDTH, height: DISPLAY_HEIGHT },
);
let config_request_stream_fut =
handle_configuration_request_stream(&mut configuration_request_stream);
let (touch_handler_res, _) = exec.run_singlethreaded(futures::future::join(
touch_handler_fut,
config_request_stream_fut,
));
let touch_handler = touch_handler_res.expect("Failed to create touch handler.");
// Add an injector.
let (injector_device_proxy, mut injector_device_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::DeviceMarker>()
.expect("Failed to create pointerinjector Registry proxy and stream.");
touch_handler.mutable_state.borrow_mut().injectors.insert(1, injector_device_proxy);
// Request a viewport update.
let watch_viewport_fut = fasync::Task::local(touch_handler.clone().watch_viewport());
futures::pin_mut!(watch_viewport_fut);
assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending());
// Send a viewport update.
match exec.run_singlethreaded(&mut configuration_request_stream.next()) {
Some(Ok(pointerinjector_config::SetupRequest::WatchViewport { responder, .. })) => {
responder.send(create_viewport(0.0, 100.0)).expect("Failed to send viewport.");
}
other => panic!("Received unexpected value: {:?}", other),
};
assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending());
// Check that the injector received an updated viewport
exec.run_singlethreaded(async {
match injector_device_request_stream.next().await {
Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => {
assert_eq!(events.len(), 1);
assert!(events[0].data.is_some());
assert_eq!(
events[0].data,
Some(pointerinjector::Data::Viewport(create_viewport(0.0, 100.0)))
);
responder.send().expect("injector stream failed to respond.");
}
other => panic!("Received unexpected value: {:?}", other),
}
});
// Create touch event.
let event_time = zx::Time::get_monotonic();
let contact = create_touch_contact(TOUCH_ID, Position { x: 20.0, y: 40.0 });
let descriptor = get_touch_device_descriptor();
let input_event = input_device::UnhandledInputEvent::try_from(create_touch_event(
hashmap! {
fidl_ui_input::PointerEventPhase::Add
=> vec![contact.clone()],
},
event_time,
&descriptor,
))
.unwrap();
// Handle event.
let handle_event_fut = touch_handler.clone().handle_unhandled_input_event(input_event);
// Declare expected event.
let expected_event = create_touch_pointer_sample_event(
pointerinjector::EventPhase::Add,
&contact,
Position { x: 20.0, y: 40.0 },
event_time,
);
// Await all futures concurrently. If this completes, then the touch event was handled and
// matches `expected_event`.
let device_fut =
handle_device_request_stream(injector_device_request_stream, expected_event);
let (handle_result, _) =
exec.run_singlethreaded(futures::future::join(handle_event_fut, device_fut));
// No unhandled events.
assert_matches!(
handle_result.as_slice(),
[input_device::InputEvent { handled: input_device::Handled::Yes, .. }]
);
}
}