input_pipeline/
touch_injector_handler.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![warn(clippy::await_holding_refcell_ref)]
6use crate::input_handler::{InputHandlerStatus, UnhandledInputHandler};
7use crate::utils::{Position, Size};
8use crate::{input_device, metrics, touch_binding};
9use anyhow::{Context, Error, Result};
10use async_trait::async_trait;
11use async_utils::hanging_get::client::HangingGetStream;
12use fidl::endpoints::{create_proxy, Proxy};
13use fidl::AsHandleRef;
14use fuchsia_component::client::connect_to_protocol;
15use fuchsia_inspect::health::Reporter;
16use futures::channel::mpsc;
17use futures::stream::StreamExt;
18use metrics_registry::*;
19use std::cell::RefCell;
20use std::collections::HashMap;
21use std::rc::Rc;
22use {
23    fidl_fuchsia_input_report as fidl_input_report, fidl_fuchsia_ui_input as fidl_ui_input,
24    fidl_fuchsia_ui_pointerinjector as pointerinjector,
25    fidl_fuchsia_ui_pointerinjector_configuration as pointerinjector_config,
26    fidl_fuchsia_ui_policy as fidl_ui_policy, fuchsia_async as fasync,
27};
28
29/// An input handler that parses touch events and forwards them to Scenic through the
30/// fidl_fuchsia_pointerinjector protocols.
31pub struct TouchInjectorHandler {
32    /// The mutable fields of this handler.
33    mutable_state: RefCell<MutableState>,
34
35    /// The scope and coordinate system of injection.
36    /// See fidl_fuchsia_pointerinjector::Context for more details.
37    context_view_ref: fidl_fuchsia_ui_views::ViewRef,
38
39    /// The region where dispatch is attempted for injected events.
40    /// See fidl_fuchsia_pointerinjector::Target for more details.
41    target_view_ref: fidl_fuchsia_ui_views::ViewRef,
42
43    /// The size of the display associated with the touch device, used to convert
44    /// coordinates from the touch input report to device coordinates (which is what
45    /// Scenic expects).
46    display_size: Size,
47
48    /// The FIDL proxy to register new injectors.
49    injector_registry_proxy: pointerinjector::RegistryProxy,
50
51    /// The FIDL proxy used to get configuration details for pointer injection.
52    configuration_proxy: pointerinjector_config::SetupProxy,
53
54    /// The inventory of this handler's Inspect status.
55    pub inspect_status: InputHandlerStatus,
56
57    /// The metrics logger.
58    metrics_logger: metrics::MetricsLogger,
59}
60
61#[derive(Debug)]
62struct MutableState {
63    /// A rectangular region that directs injected events into a target.
64    /// See fidl_fuchsia_pointerinjector::Viewport for more details.
65    viewport: Option<pointerinjector::Viewport>,
66
67    /// The injectors registered with Scenic, indexed by their device ids.
68    injectors: HashMap<u32, pointerinjector::DeviceProxy>,
69
70    /// The touch button listeners, key referenced by proxy channel's raw handle.
71    pub listeners: HashMap<u32, fidl_ui_policy::TouchButtonsListenerProxy>,
72
73    /// The last TouchButtonsEvent sent to all listeners.
74    /// This is used to send new listeners the state of the touchscreen buttons.
75    pub last_button_event: Option<fidl_ui_input::TouchButtonsEvent>,
76
77    pub send_event_task_tracker: LocalTaskTracker,
78}
79
80#[async_trait(?Send)]
81impl UnhandledInputHandler for TouchInjectorHandler {
82    async fn handle_unhandled_input_event(
83        self: Rc<Self>,
84        unhandled_input_event: input_device::UnhandledInputEvent,
85    ) -> Vec<input_device::InputEvent> {
86        fuchsia_trace::duration!(c"input", c"presentation_on_event");
87        match unhandled_input_event {
88            input_device::UnhandledInputEvent {
89                device_event: input_device::InputDeviceEvent::TouchScreen(ref touch_event),
90                device_descriptor:
91                    input_device::InputDeviceDescriptor::TouchScreen(ref touch_device_descriptor),
92                event_time,
93                trace_id,
94            } => {
95                self.inspect_status.count_received_event(input_device::InputEvent::from(
96                    unhandled_input_event.clone(),
97                ));
98                fuchsia_trace::duration!(c"input", c"touch_injector_handler");
99                if let Some(trace_id) = trace_id {
100                    fuchsia_trace::flow_end!(c"input", c"event_in_input_pipeline", trace_id.into());
101                }
102                if touch_event.injector_contacts.values().all(|vec| vec.is_empty()) {
103                    let touch_buttons_event = Self::create_touch_buttons_event(
104                        &touch_event,
105                        event_time,
106                        &touch_device_descriptor,
107                    );
108
109                    // Send the event if the touch buttons are supported.
110                    self.send_event_to_listeners(&touch_buttons_event).await;
111
112                    // Store the sent event.
113                    self.mutable_state.borrow_mut().last_button_event = Some(touch_buttons_event);
114                } else if touch_event.pressed_buttons.is_empty() {
115                    // Create a new injector if this is the first time seeing device_id.
116                    if let Err(e) = self.ensure_injector_registered(&touch_device_descriptor).await
117                    {
118                        self.metrics_logger.log_error(
119                        InputPipelineErrorMetricDimensionEvent::TouchInjectorEnsureInjectorRegisteredFailed,
120                        std::format!("ensure_injector_registered failed: {}", e));
121                    }
122
123                    // Handle the event.
124                    if let Err(e) = self
125                        .send_event_to_scenic(&touch_event, &touch_device_descriptor, event_time)
126                        .await
127                    {
128                        self.metrics_logger.log_error(
129                        InputPipelineErrorMetricDimensionEvent::TouchInjectorSendEventToScenicFailed,
130                        std::format!("send_event_to_scenic failed: {}", e));
131                    }
132                }
133
134                // Consume the input event.
135                self.inspect_status.count_handled_event();
136                vec![input_device::InputEvent::from(unhandled_input_event).into_handled()]
137            }
138            _ => vec![input_device::InputEvent::from(unhandled_input_event)],
139        }
140    }
141
142    fn set_handler_healthy(self: std::rc::Rc<Self>) {
143        self.inspect_status.health_node.borrow_mut().set_ok();
144    }
145
146    fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
147        self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
148    }
149}
150
151impl TouchInjectorHandler {
152    /// Creates a new touch handler that holds touch pointer injectors.
153    /// The caller is expected to spawn a task to continually watch for updates to the viewport.
154    /// Example:
155    /// let handler = TouchInjectorHandler::new(display_size).await?;
156    /// fasync::Task::local(handler.clone().watch_viewport()).detach();
157    ///
158    /// # Parameters
159    /// - `display_size`: The size of the associated touch display.
160    ///
161    /// # Errors
162    /// If unable to connect to pointerinjector protocols.
163    pub async fn new(
164        display_size: Size,
165        input_handlers_node: &fuchsia_inspect::Node,
166        metrics_logger: metrics::MetricsLogger,
167    ) -> Result<Rc<Self>, Error> {
168        let configuration_proxy = connect_to_protocol::<pointerinjector_config::SetupMarker>()?;
169        let injector_registry_proxy = connect_to_protocol::<pointerinjector::RegistryMarker>()?;
170
171        Self::new_handler(
172            configuration_proxy,
173            injector_registry_proxy,
174            display_size,
175            input_handlers_node,
176            metrics_logger,
177        )
178        .await
179    }
180
181    /// Creates a new touch handler that holds touch pointer injectors.
182    /// The caller is expected to spawn a task to continually watch for updates to the viewport.
183    /// Example:
184    /// let handler = TouchInjectorHandler::new_with_config_proxy(config_proxy, display_size).await?;
185    /// fasync::Task::local(handler.clone().watch_viewport()).detach();
186    ///
187    /// # Parameters
188    /// - `configuration_proxy`: A proxy used to get configuration details for pointer
189    ///    injection.
190    /// - `display_size`: The size of the associated touch display.
191    ///
192    /// # Errors
193    /// If unable to get injection view refs from `configuration_proxy`.
194    /// If unable to connect to pointerinjector Registry protocol.
195    pub async fn new_with_config_proxy(
196        configuration_proxy: pointerinjector_config::SetupProxy,
197        display_size: Size,
198        input_handlers_node: &fuchsia_inspect::Node,
199        metrics_logger: metrics::MetricsLogger,
200    ) -> Result<Rc<Self>, Error> {
201        let injector_registry_proxy = connect_to_protocol::<pointerinjector::RegistryMarker>()?;
202        Self::new_handler(
203            configuration_proxy,
204            injector_registry_proxy,
205            display_size,
206            input_handlers_node,
207            metrics_logger,
208        )
209        .await
210    }
211
212    /// Creates a new touch handler that holds touch pointer injectors.
213    /// The caller is expected to spawn a task to continually watch for updates to the viewport.
214    /// Example:
215    /// let handler = TouchInjectorHandler::new_handler(None, None, display_size).await?;
216    /// fasync::Task::local(handler.clone().watch_viewport()).detach();
217    ///
218    /// # Parameters
219    /// - `configuration_proxy`: A proxy used to get configuration details for pointer
220    ///    injection.
221    /// - `injector_registry_proxy`: A proxy used to register new pointer injectors.  If
222    ///    none is provided, connect to protocol routed to this component.
223    /// - `display_size`: The size of the associated touch display.
224    ///
225    /// # Errors
226    /// If unable to get injection view refs from `configuration_proxy`.
227    async fn new_handler(
228        configuration_proxy: pointerinjector_config::SetupProxy,
229        injector_registry_proxy: pointerinjector::RegistryProxy,
230        display_size: Size,
231        input_handlers_node: &fuchsia_inspect::Node,
232        metrics_logger: metrics::MetricsLogger,
233    ) -> Result<Rc<Self>, Error> {
234        // Get the context and target views to inject into.
235        let (context_view_ref, target_view_ref) = configuration_proxy.get_view_refs().await?;
236
237        let inspect_status = InputHandlerStatus::new(
238            input_handlers_node,
239            "touch_injector_handler",
240            /* generates_events */ false,
241        );
242        let handler = Rc::new(Self {
243            mutable_state: RefCell::new(MutableState {
244                viewport: None,
245                injectors: HashMap::new(),
246                listeners: HashMap::new(),
247                last_button_event: None,
248                send_event_task_tracker: LocalTaskTracker::new(),
249            }),
250            context_view_ref,
251            target_view_ref,
252            display_size,
253            injector_registry_proxy,
254            configuration_proxy,
255            inspect_status,
256            metrics_logger,
257        });
258
259        Ok(handler)
260    }
261
262    /// Adds a new pointer injector and tracks it in `self.injectors` if one doesn't exist at
263    /// `touch_descriptor.device_id`.
264    ///
265    /// # Parameters
266    /// - `touch_descriptor`: The descriptor of the new touch device.
267    async fn ensure_injector_registered(
268        self: &Rc<Self>,
269        touch_descriptor: &touch_binding::TouchScreenDeviceDescriptor,
270    ) -> Result<(), anyhow::Error> {
271        if self.mutable_state.borrow().injectors.contains_key(&touch_descriptor.device_id) {
272            return Ok(());
273        }
274
275        // Create a new injector.
276        let (device_proxy, device_server) = create_proxy::<pointerinjector::DeviceMarker>();
277        let context = fuchsia_scenic::duplicate_view_ref(&self.context_view_ref)
278            .context("Failed to duplicate context view ref.")?;
279        let target = fuchsia_scenic::duplicate_view_ref(&self.target_view_ref)
280            .context("Failed to duplicate target view ref.")?;
281        let viewport = self.mutable_state.borrow().viewport.clone();
282        if viewport.is_none() {
283            // An injector without a viewport is not valid. The event will be dropped
284            // since the handler will not have a registered injector to inject into.
285            return Err(anyhow::format_err!(
286                "Received a touch event without a viewport to inject into."
287            ));
288        }
289        let config = pointerinjector::Config {
290            device_id: Some(touch_descriptor.device_id),
291            device_type: Some(pointerinjector::DeviceType::Touch),
292            context: Some(pointerinjector::Context::View(context)),
293            target: Some(pointerinjector::Target::View(target)),
294            viewport,
295            dispatch_policy: Some(pointerinjector::DispatchPolicy::TopHitAndAncestorsInTarget),
296            scroll_v_range: None,
297            scroll_h_range: None,
298            buttons: None,
299            ..Default::default()
300        };
301
302        // Keep track of the injector.
303        self.mutable_state.borrow_mut().injectors.insert(touch_descriptor.device_id, device_proxy);
304
305        // Register the new injector.
306        self.injector_registry_proxy
307            .register(config, device_server)
308            .await
309            .context("Failed to register injector.")?;
310        log::info!("Registered injector with device id {:?}", touch_descriptor.device_id);
311
312        Ok(())
313    }
314
315    /// Sends the given event to Scenic.
316    ///
317    /// # Parameters
318    /// - `touch_event`: The touch event to send to Scenic.
319    /// - `touch_descriptor`: The descriptor for the device that sent the touch event.
320    /// - `event_time`: The time when the event was first recorded.
321    async fn send_event_to_scenic(
322        &self,
323        touch_event: &touch_binding::TouchScreenEvent,
324        touch_descriptor: &touch_binding::TouchScreenDeviceDescriptor,
325        event_time: zx::MonotonicInstant,
326    ) -> Result<(), anyhow::Error> {
327        // The order in which events are sent to clients.
328        let ordered_phases = vec![
329            pointerinjector::EventPhase::Add,
330            pointerinjector::EventPhase::Change,
331            pointerinjector::EventPhase::Remove,
332        ];
333
334        // Make the trace duration end on the call to injector.inject, not the call's return.
335        // The duration should start before the flow_begin is minted in
336        // create_pointer_sample_event, and it should not include the injector.inject() call's
337        // return from await.
338        fuchsia_trace::duration_begin!(c"input", c"touch-inject-into-scenic");
339
340        let mut events: Vec<pointerinjector::Event> = vec![];
341        for phase in ordered_phases {
342            let contacts: Vec<touch_binding::TouchContact> = touch_event
343                .injector_contacts
344                .get(&phase)
345                .map_or(vec![], |contacts| contacts.to_owned());
346            let new_events = contacts.into_iter().map(|contact| {
347                Self::create_pointer_sample_event(
348                    phase,
349                    &contact,
350                    touch_descriptor,
351                    &self.display_size,
352                    event_time,
353                )
354            });
355            events.extend(new_events);
356        }
357
358        let injector =
359            self.mutable_state.borrow().injectors.get(&touch_descriptor.device_id).cloned();
360        if let Some(injector) = injector {
361            let fut = injector.inject(&events);
362            // This trace duration ends before awaiting on the returned future.
363            fuchsia_trace::duration_end!(c"input", c"touch-inject-into-scenic");
364            let _ = fut.await;
365            Ok(())
366        } else {
367            fuchsia_trace::duration_end!(c"input", c"touch-inject-into-scenic");
368            Err(anyhow::format_err!(
369                "No injector found for touch device {}.",
370                touch_descriptor.device_id
371            ))
372        }
373    }
374
375    /// Creates a [`fidl_fuchsia_ui_pointerinjector::Event`] representing the given touch contact.
376    ///
377    /// # Parameters
378    /// - `phase`: The phase of the touch contact.
379    /// - `contact`: The touch contact to create the event for.
380    /// - `touch_descriptor`: The device descriptor for the device that generated the event.
381    /// - `display_size`: The size of the associated touch display.
382    /// - `event_time`: The time in nanoseconds when the event was first recorded.
383    fn create_pointer_sample_event(
384        phase: pointerinjector::EventPhase,
385        contact: &touch_binding::TouchContact,
386        touch_descriptor: &touch_binding::TouchScreenDeviceDescriptor,
387        display_size: &Size,
388        event_time: zx::MonotonicInstant,
389    ) -> pointerinjector::Event {
390        let position =
391            Self::display_coordinate_from_contact(&contact, &touch_descriptor, display_size);
392        let pointer_sample = pointerinjector::PointerSample {
393            pointer_id: Some(contact.id),
394            phase: Some(phase),
395            position_in_viewport: Some([position.x, position.y]),
396            scroll_v: None,
397            scroll_h: None,
398            pressed_buttons: None,
399            ..Default::default()
400        };
401        let data = pointerinjector::Data::PointerSample(pointer_sample);
402
403        let trace_flow_id = fuchsia_trace::Id::random();
404        let event = pointerinjector::Event {
405            timestamp: Some(event_time.into_nanos()),
406            data: Some(data),
407            trace_flow_id: Some(trace_flow_id.into()),
408            ..Default::default()
409        };
410
411        fuchsia_trace::flow_begin!(c"input", c"dispatch_event_to_scenic", trace_flow_id);
412
413        event
414    }
415
416    /// Converts an input event touch to a display coordinate, which is the coordinate space in
417    /// which Scenic handles events.
418    ///
419    /// The display coordinate is calculated by normalizing the contact position to the display
420    /// size. It does not account for the viewport position, which Scenic handles directly.
421    ///
422    /// # Parameters
423    /// - `contact`: The contact to get the display coordinate from.
424    /// - `touch_descriptor`: The device descriptor for the device that generated the event.
425    ///                       This is used to compute the device coordinate.
426    ///
427    /// # Returns
428    /// (x, y) coordinates.
429    fn display_coordinate_from_contact(
430        contact: &touch_binding::TouchContact,
431        touch_descriptor: &touch_binding::TouchScreenDeviceDescriptor,
432        display_size: &Size,
433    ) -> Position {
434        if let Some(contact_descriptor) = touch_descriptor.contacts.first() {
435            // Scale the x position.
436            let x_range: f32 =
437                contact_descriptor.x_range.max as f32 - contact_descriptor.x_range.min as f32;
438            let x_wrt_range: f32 = contact.position.x - contact_descriptor.x_range.min as f32;
439            let x: f32 = (display_size.width * x_wrt_range) / x_range;
440
441            // Scale the y position.
442            let y_range: f32 =
443                contact_descriptor.y_range.max as f32 - contact_descriptor.y_range.min as f32;
444            let y_wrt_range: f32 = contact.position.y - contact_descriptor.y_range.min as f32;
445            let y: f32 = (display_size.height * y_wrt_range) / y_range;
446
447            Position { x, y }
448        } else {
449            return contact.position;
450        }
451    }
452
453    /// Watches for viewport updates from the scene manager.
454    pub async fn watch_viewport(self: Rc<Self>) {
455        let configuration_proxy = self.configuration_proxy.clone();
456        let mut viewport_stream = HangingGetStream::new(
457            configuration_proxy,
458            pointerinjector_config::SetupProxy::watch_viewport,
459        );
460        loop {
461            match viewport_stream.next().await {
462                Some(Ok(new_viewport)) => {
463                    // Update the viewport tracked by this handler.
464                    self.mutable_state.borrow_mut().viewport = Some(new_viewport.clone());
465
466                    // Update Scenic with the latest viewport.
467                    let injectors: Vec<pointerinjector::DeviceProxy> =
468                        self.mutable_state.borrow_mut().injectors.values().cloned().collect();
469                    for injector in injectors {
470                        let events = &[pointerinjector::Event {
471                            timestamp: Some(fuchsia_async::MonotonicInstant::now().into_nanos()),
472                            data: Some(pointerinjector::Data::Viewport(new_viewport.clone())),
473                            trace_flow_id: Some(fuchsia_trace::Id::random().into()),
474                            ..Default::default()
475                        }];
476                        injector.inject(events).await.expect("Failed to inject updated viewport.");
477                    }
478                }
479                Some(Err(e)) => {
480                    self.metrics_logger.log_error(
481                        InputPipelineErrorMetricDimensionEvent::TouchInjectorErrorWhileReadingViewportUpdate,
482                        std::format!("Error while reading viewport update: {}", e));
483                    return;
484                }
485                None => {
486                    self.metrics_logger.log_error(
487                        InputPipelineErrorMetricDimensionEvent::TouchInjectorViewportUpdateStreamTerminatedUnexpectedly,
488                        "Viewport update stream terminated unexpectedly");
489                    return;
490                }
491            }
492        }
493    }
494
495    /// Creates a fidl_ui_input::TouchButtonsEvent from a touch_binding::TouchScreenEvent.
496    ///
497    /// # Parameters
498    /// - `event`: The TouchScreenEvent to create a TouchButtonsEvent from.
499    /// - `event_time`: The time when the event was first recorded.
500    /// - `touch_descriptor`: The descriptor of the new touch device.
501    fn create_touch_buttons_event(
502        event: &touch_binding::TouchScreenEvent,
503        event_time: zx::MonotonicInstant,
504        touch_descriptor: &touch_binding::TouchScreenDeviceDescriptor,
505    ) -> fidl_ui_input::TouchButtonsEvent {
506        let pressed_buttons = match event.pressed_buttons.len() {
507            0 => None,
508            _ => Some(
509                event
510                    .pressed_buttons
511                    .clone()
512                    .into_iter()
513                    .map(|button| match button {
514                        fidl_input_report::TouchButton::Palm => fidl_ui_input::TouchButton::Palm,
515                        fidl_input_report::TouchButton::__SourceBreaking { unknown_ordinal: n } => {
516                            fidl_ui_input::TouchButton::__SourceBreaking {
517                                unknown_ordinal: n as u32,
518                            }
519                        }
520                    })
521                    .collect::<Vec<_>>(),
522            ),
523        };
524        fidl_ui_input::TouchButtonsEvent {
525            event_time: Some(event_time),
526            device_info: Some(fidl_ui_input::TouchDeviceInfo {
527                id: Some(touch_descriptor.device_id),
528                ..Default::default()
529            }),
530            pressed_buttons,
531            ..Default::default()
532        }
533    }
534
535    /// Sends touch button events to touch button listeners.
536    ///
537    /// # Parameters
538    /// - `event`: The event to send to the listeners.
539    async fn send_event_to_listeners(self: &Rc<Self>, event: &fidl_ui_input::TouchButtonsEvent) {
540        let tracker = &self.mutable_state.borrow().send_event_task_tracker;
541
542        for (handle, listener) in &self.mutable_state.borrow().listeners {
543            let weak_handler = Rc::downgrade(&self);
544            let listener_clone = listener.clone();
545            let handle_clone = handle.clone();
546            let event_to_send = event.clone();
547            let fut = async move {
548                match listener_clone.on_event(&event_to_send).await {
549                    Ok(_) => {}
550                    Err(e) => {
551                        if let Some(handler) = weak_handler.upgrade() {
552                            handler.mutable_state.borrow_mut().listeners.remove(&handle_clone);
553                            log::info!(
554                                "Unregistering listener; unable to send TouchButtonsEvent: {:?}",
555                                e
556                            )
557                        }
558                    }
559                }
560            };
561
562            let metrics_logger_clone = self.metrics_logger.clone();
563            tracker.track(metrics_logger_clone, fasync::Task::local(fut));
564        }
565    }
566
567    // Add the listener to the registry.
568    ///
569    /// # Parameters
570    /// - `proxy`: A new listener proxy to send events to.
571    pub async fn register_listener_proxy(
572        self: &Rc<Self>,
573        proxy: fidl_ui_policy::TouchButtonsListenerProxy,
574    ) {
575        self.mutable_state
576            .borrow_mut()
577            .listeners
578            .insert(proxy.as_channel().raw_handle(), proxy.clone());
579
580        // Send the listener the last touch button event.
581        if let Some(event) = &self.mutable_state.borrow().last_button_event {
582            let event_to_send = event.clone();
583            let fut = async move {
584                match proxy.on_event(&event_to_send).await {
585                    Ok(_) => {}
586                    Err(e) => {
587                        log::info!("Failed to send touch buttons event to listener {:?}", e)
588                    }
589                }
590            };
591            let metrics_logger_clone = self.metrics_logger.clone();
592            self.mutable_state
593                .borrow()
594                .send_event_task_tracker
595                .track(metrics_logger_clone, fasync::Task::local(fut));
596        }
597    }
598}
599
600/// Maintains a collection of pending local [`Task`]s, allowing them to be dropped (and cancelled)
601/// en masse.
602#[derive(Debug)]
603pub struct LocalTaskTracker {
604    sender: mpsc::UnboundedSender<fasync::Task<()>>,
605    _receiver_task: fasync::Task<()>,
606}
607
608impl LocalTaskTracker {
609    pub fn new() -> Self {
610        let (sender, receiver) = mpsc::unbounded();
611        let receiver_task = fasync::Task::local(async move {
612            // Drop the tasks as they are completed.
613            receiver.for_each_concurrent(None, |task: fasync::Task<()>| task).await
614        });
615
616        Self { sender, _receiver_task: receiver_task }
617    }
618
619    /// Submits a new task to track.
620    pub fn track(&self, metrics_logger: metrics::MetricsLogger, task: fasync::Task<()>) {
621        match self.sender.unbounded_send(task) {
622            Ok(_) => {}
623            // `Full` should never happen because this is unbounded.
624            // `Disconnected` might happen if the `Service` was dropped. However, it's not clear how
625            // to create such a race condition.
626            Err(e) => {
627                metrics_logger.log_error(
628                    InputPipelineErrorMetricDimensionEvent::TouchFailedToSendTouchScreenEvent,
629                    std::format!("Unexpected {e:?} while pushing task"),
630                );
631            }
632        };
633    }
634}
635
636#[cfg(test)]
637mod tests {
638    use super::*;
639    use crate::input_handler::InputHandler;
640    use crate::testing_utilities::{
641        create_fake_input_event, create_touch_contact, create_touch_pointer_sample_event,
642        create_touch_screen_event, create_touch_screen_event_with_handled, create_touchpad_event,
643        get_touch_screen_device_descriptor,
644    };
645    use assert_matches::assert_matches;
646    use futures::{FutureExt, TryStreamExt};
647    use maplit::hashmap;
648    use pretty_assertions::assert_eq;
649    use std::collections::HashSet;
650    use std::convert::TryFrom as _;
651    use std::ops::Add;
652    use {
653        fidl_fuchsia_input_report as fidl_input_report, fidl_fuchsia_ui_input as fidl_ui_input,
654        fidl_fuchsia_ui_policy as fidl_ui_policy, fuchsia_async as fasync,
655    };
656
657    const TOUCH_ID: u32 = 1;
658    const DISPLAY_WIDTH: f32 = 100.0;
659    const DISPLAY_HEIGHT: f32 = 100.0;
660
661    struct TestFixtures {
662        touch_handler: Rc<TouchInjectorHandler>,
663        device_listener_proxy: fidl_ui_policy::DeviceListenerRegistryProxy,
664        injector_registry_request_stream: pointerinjector::RegistryRequestStream,
665        configuration_request_stream: pointerinjector_config::SetupRequestStream,
666        inspector: fuchsia_inspect::Inspector,
667        _test_node: fuchsia_inspect::Node,
668    }
669
670    fn spawn_device_listener_registry_server(
671        handler: Rc<TouchInjectorHandler>,
672    ) -> fidl_ui_policy::DeviceListenerRegistryProxy {
673        let (device_listener_proxy, mut device_listener_stream) =
674            fidl::endpoints::create_proxy_and_stream::<fidl_ui_policy::DeviceListenerRegistryMarker>(
675            );
676
677        fasync::Task::local(async move {
678            loop {
679                match device_listener_stream.try_next().await {
680                    Ok(Some(
681                        fidl_ui_policy::DeviceListenerRegistryRequest::RegisterTouchButtonsListener {
682                            listener,
683                            responder,
684                        },
685                    )) => {
686                        handler.register_listener_proxy(listener.into_proxy()).await;
687                        let _ = responder.send();
688                    }
689                    Ok(Some(_)) => {
690                        panic!("Unexpected registration");
691                    }
692                    Ok(None) => {
693                        break;
694                    }
695                    Err(e) => {
696                        panic!("Error handling device listener registry request stream: {}", e);
697                    }
698                }
699            }
700        })
701        .detach();
702
703        device_listener_proxy
704    }
705
706    impl TestFixtures {
707        async fn new() -> Self {
708            let inspector = fuchsia_inspect::Inspector::default();
709            let test_node = inspector.root().create_child("test_node");
710            let (configuration_proxy, mut configuration_request_stream) =
711                fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
712            let (injector_registry_proxy, injector_registry_request_stream) =
713                fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
714
715            let touch_handler_fut = TouchInjectorHandler::new_handler(
716                configuration_proxy,
717                injector_registry_proxy,
718                Size { width: DISPLAY_WIDTH, height: DISPLAY_HEIGHT },
719                &test_node,
720                metrics::MetricsLogger::default(),
721            );
722
723            let handle_initial_request_fut = async {
724                match configuration_request_stream.next().await {
725                    Some(Ok(pointerinjector_config::SetupRequest::GetViewRefs {
726                        responder,
727                        ..
728                    })) => {
729                        let context = fuchsia_scenic::ViewRefPair::new()
730                            .expect("Failed to create viewrefpair.")
731                            .view_ref;
732                        let target = fuchsia_scenic::ViewRefPair::new()
733                            .expect("Failed to create viewrefpair.")
734                            .view_ref;
735                        let _ = responder.send(context, target);
736                    }
737                    other => panic!("Expected GetViewRefs request, got {:?}", other),
738                }
739            };
740
741            let (touch_handler_res, _) =
742                futures::future::join(touch_handler_fut, handle_initial_request_fut).await;
743
744            let touch_handler = touch_handler_res.expect("Failed to create touch handler.");
745            let device_listener_proxy =
746                spawn_device_listener_registry_server(touch_handler.clone());
747
748            TestFixtures {
749                touch_handler,
750                device_listener_proxy,
751                injector_registry_request_stream,
752                configuration_request_stream,
753                inspector,
754                _test_node: test_node,
755            }
756        }
757    }
758
759    /// Returns an |input_device::InputDeviceDescriptor::Touchpad|.
760    fn get_touchpad_device_descriptor() -> input_device::InputDeviceDescriptor {
761        input_device::InputDeviceDescriptor::Touchpad(touch_binding::TouchpadDeviceDescriptor {
762            device_id: 1,
763            contacts: vec![touch_binding::ContactDeviceDescriptor {
764                x_range: fidl_input_report::Range { min: 0, max: 100 },
765                y_range: fidl_input_report::Range { min: 0, max: 100 },
766                x_unit: fidl_input_report::Unit {
767                    type_: fidl_input_report::UnitType::Meters,
768                    exponent: -6,
769                },
770                y_unit: fidl_input_report::Unit {
771                    type_: fidl_input_report::UnitType::Meters,
772                    exponent: -6,
773                },
774                pressure_range: None,
775                width_range: None,
776                height_range: None,
777            }],
778        })
779    }
780
781    /// Handles |fidl_fuchsia_pointerinjector::DeviceRequest|s by asserting the `injector_stream`
782    /// gets `expected_event`.
783    async fn handle_device_request_stream(
784        mut injector_stream: pointerinjector::DeviceRequestStream,
785        expected_event: pointerinjector::Event,
786    ) {
787        match injector_stream.next().await {
788            Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => {
789                assert_eq!(events.len(), 1);
790                assert_eq!(events[0].timestamp, expected_event.timestamp);
791                assert_eq!(events[0].data, expected_event.data);
792                responder.send().expect("failed to respond");
793            }
794            Some(Err(e)) => panic!("FIDL error {}", e),
795            None => panic!("Expected another event."),
796        }
797    }
798
799    // Creates a |pointerinjector::Viewport|.
800    fn create_viewport(min: f32, max: f32) -> pointerinjector::Viewport {
801        pointerinjector::Viewport {
802            extents: Some([[min, min], [max, max]]),
803            viewport_to_context_transform: None,
804            ..Default::default()
805        }
806    }
807
808    #[fuchsia::test]
809    async fn events_with_pressed_buttons_are_sent_to_listener() {
810        let fixtures = TestFixtures::new().await;
811        let (listener, mut listener_stream) =
812            fidl::endpoints::create_request_stream::<fidl_ui_policy::TouchButtonsListenerMarker>();
813        fixtures
814            .device_listener_proxy
815            .register_touch_buttons_listener(listener)
816            .await
817            .expect("Failed to register listener.");
818
819        let descriptor = get_touch_screen_device_descriptor();
820        let event_time = zx::MonotonicInstant::get();
821        let input_event = create_touch_screen_event(hashmap! {}, event_time, &descriptor);
822
823        let _ = fixtures.touch_handler.clone().handle_input_event(input_event).await;
824
825        let expected_touch_buttons_event = fidl_ui_input::TouchButtonsEvent {
826            event_time: Some(event_time),
827            device_info: Some(fidl_ui_input::TouchDeviceInfo { id: Some(1), ..Default::default() }),
828            ..Default::default()
829        };
830
831        assert_matches!(
832            listener_stream.next().await,
833            Some(Ok(fidl_ui_policy::TouchButtonsListenerRequest::OnEvent {
834                event,
835                responder,
836            })) => {
837                assert_eq!(event, expected_touch_buttons_event);
838                let _ = responder.send();
839            }
840        );
841    }
842
843    #[fuchsia::test]
844    async fn events_with_contacts_are_not_sent_to_listener() {
845        let fixtures = TestFixtures::new().await;
846        let (listener, mut listener_stream) =
847            fidl::endpoints::create_request_stream::<fidl_ui_policy::TouchButtonsListenerMarker>();
848        fixtures
849            .device_listener_proxy
850            .register_touch_buttons_listener(listener)
851            .await
852            .expect("Failed to register listener.");
853
854        let descriptor = get_touch_screen_device_descriptor();
855        let event_time = zx::MonotonicInstant::get();
856        let contact = create_touch_contact(TOUCH_ID, Position { x: 20.0, y: 40.0 });
857        let input_event = create_touch_screen_event(
858            hashmap! {
859                fidl_ui_input::PointerEventPhase::Add
860                    => vec![contact.clone()],
861            },
862            event_time,
863            &descriptor,
864        );
865
866        let _ = fixtures.touch_handler.clone().handle_input_event(input_event).await;
867
868        assert!(listener_stream.next().now_or_never().is_none());
869    }
870
871    #[fuchsia::test]
872    async fn multiple_listeners_receive_pressed_button_events() {
873        let fixtures = TestFixtures::new().await;
874        let (first_listener, mut first_listener_stream) =
875            fidl::endpoints::create_request_stream::<fidl_ui_policy::TouchButtonsListenerMarker>();
876        let (second_listener, mut second_listener_stream) =
877            fidl::endpoints::create_request_stream::<fidl_ui_policy::TouchButtonsListenerMarker>();
878        fixtures
879            .device_listener_proxy
880            .register_touch_buttons_listener(first_listener)
881            .await
882            .expect("Failed to register listener.");
883        fixtures
884            .device_listener_proxy
885            .register_touch_buttons_listener(second_listener)
886            .await
887            .expect("Failed to register listener.");
888
889        let descriptor = get_touch_screen_device_descriptor();
890        let event_time = zx::MonotonicInstant::get();
891        let input_event = create_touch_screen_event(hashmap! {}, event_time, &descriptor);
892
893        let _ = fixtures.touch_handler.clone().handle_input_event(input_event).await;
894
895        let expected_touch_buttons_event = fidl_ui_input::TouchButtonsEvent {
896            event_time: Some(event_time),
897            device_info: Some(fidl_ui_input::TouchDeviceInfo { id: Some(1), ..Default::default() }),
898            ..Default::default()
899        };
900
901        assert_matches!(
902            first_listener_stream.next().await,
903            Some(Ok(fidl_ui_policy::TouchButtonsListenerRequest::OnEvent {
904                event,
905                responder,
906            })) => {
907                assert_eq!(event, expected_touch_buttons_event);
908                let _ = responder.send();
909            }
910        );
911        assert_matches!(
912            second_listener_stream.next().await,
913            Some(Ok(fidl_ui_policy::TouchButtonsListenerRequest::OnEvent {
914                event,
915                responder,
916            })) => {
917                assert_eq!(event, expected_touch_buttons_event);
918                let _ = responder.send();
919            }
920        );
921    }
922
923    // Tests that TouchInjectorHandler::watch_viewport() tracks viewport updates and notifies
924    // injectors about said updates.
925    #[fuchsia::test]
926    async fn receives_viewport_updates() {
927        let mut fixtures = TestFixtures::new().await;
928
929        // Add an injector.
930        let (injector_device_proxy, mut injector_device_request_stream) =
931            fidl::endpoints::create_proxy_and_stream::<pointerinjector::DeviceMarker>();
932        fixtures
933            .touch_handler
934            .mutable_state
935            .borrow_mut()
936            .injectors
937            .insert(1, injector_device_proxy);
938
939        // This nested block is used to bound the lifetime of `watch_viewport_fut`.
940        {
941            // Request a viewport update.
942            let _watch_viewport_task =
943                fasync::Task::local(fixtures.touch_handler.clone().watch_viewport());
944
945            // Send a viewport update.
946            match fixtures.configuration_request_stream.next().await {
947                Some(Ok(pointerinjector_config::SetupRequest::WatchViewport {
948                    responder, ..
949                })) => {
950                    responder.send(&create_viewport(0.0, 100.0)).expect("Failed to send viewport.");
951                }
952                other => panic!("Received unexpected value: {:?}", other),
953            };
954
955            // Check that the injector received an updated viewport
956            match injector_device_request_stream.next().await {
957                Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => {
958                    assert_eq!(events.len(), 1);
959                    assert!(events[0].data.is_some());
960                    assert_eq!(
961                        events[0].data,
962                        Some(pointerinjector::Data::Viewport(create_viewport(0.0, 100.0)))
963                    );
964                    responder.send().expect("injector stream failed to respond.");
965                }
966                other => panic!("Received unexpected value: {:?}", other),
967            }
968
969            // Request viewport update.
970            // Send viewport update.
971            match fixtures.configuration_request_stream.next().await {
972                Some(Ok(pointerinjector_config::SetupRequest::WatchViewport {
973                    responder, ..
974                })) => {
975                    responder
976                        .send(&create_viewport(100.0, 200.0))
977                        .expect("Failed to send viewport.");
978                }
979                other => panic!("Received unexpected value: {:?}", other),
980            };
981
982            // Check that the injector received an updated viewport
983            match injector_device_request_stream.next().await {
984                Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => {
985                    assert_eq!(events.len(), 1);
986                    assert!(events[0].data.is_some());
987                    assert_eq!(
988                        events[0].data,
989                        Some(pointerinjector::Data::Viewport(create_viewport(100.0, 200.0)))
990                    );
991                    responder.send().expect("injector stream failed to respond.");
992                }
993                other => panic!("Received unexpected value: {:?}", other),
994            }
995        }
996
997        // Check the viewport on the handler is accurate.
998        let expected_viewport = create_viewport(100.0, 200.0);
999        assert_eq!(fixtures.touch_handler.mutable_state.borrow().viewport, Some(expected_viewport));
1000    }
1001
1002    // Tests that an add contact event is dropped without a viewport.
1003    #[fuchsia::test]
1004    async fn add_contact_drops_without_viewport() {
1005        let mut fixtures = TestFixtures::new().await;
1006
1007        // Create touch event.
1008        let event_time = zx::MonotonicInstant::get();
1009        let contact = create_touch_contact(TOUCH_ID, Position { x: 20.0, y: 40.0 });
1010        let descriptor = get_touch_screen_device_descriptor();
1011        let input_event = input_device::UnhandledInputEvent::try_from(create_touch_screen_event(
1012            hashmap! {
1013                fidl_ui_input::PointerEventPhase::Add
1014                    => vec![contact.clone()],
1015            },
1016            event_time,
1017            &descriptor,
1018        ))
1019        .unwrap();
1020
1021        // Clear the viewport that was set during test fixture setup.
1022        fixtures.touch_handler.mutable_state.borrow_mut().viewport = None;
1023
1024        // Try to handle the event.
1025        let _ = fixtures.touch_handler.clone().handle_unhandled_input_event(input_event).await;
1026
1027        // Injector should not receive anything because the handler has no viewport.
1028        assert!(fixtures.injector_registry_request_stream.next().now_or_never().is_none());
1029    }
1030
1031    // Tests that an add contact event is handled correctly with a viewport.
1032    #[fuchsia::test]
1033    async fn add_contact_succeeds_with_viewport() {
1034        let mut fixtures = TestFixtures::new().await;
1035
1036        // Add an injector.
1037        let (injector_device_proxy, mut injector_device_request_stream) =
1038            fidl::endpoints::create_proxy_and_stream::<pointerinjector::DeviceMarker>();
1039        fixtures
1040            .touch_handler
1041            .mutable_state
1042            .borrow_mut()
1043            .injectors
1044            .insert(1, injector_device_proxy);
1045
1046        // Request a viewport update.
1047        let _watch_viewport_task =
1048            fasync::Task::local(fixtures.touch_handler.clone().watch_viewport());
1049
1050        // Send a viewport update.
1051        match fixtures.configuration_request_stream.next().await {
1052            Some(Ok(pointerinjector_config::SetupRequest::WatchViewport { responder, .. })) => {
1053                responder.send(&create_viewport(0.0, 100.0)).expect("Failed to send viewport.");
1054            }
1055            other => panic!("Received unexpected value: {:?}", other),
1056        };
1057
1058        // Check that the injector received an updated viewport
1059        match injector_device_request_stream.next().await {
1060            Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => {
1061                assert_eq!(events.len(), 1);
1062                assert!(events[0].data.is_some());
1063                assert_eq!(
1064                    events[0].data,
1065                    Some(pointerinjector::Data::Viewport(create_viewport(0.0, 100.0)))
1066                );
1067                responder.send().expect("injector stream failed to respond.");
1068            }
1069            other => panic!("Received unexpected value: {:?}", other),
1070        }
1071
1072        // Create touch event.
1073        let event_time = zx::MonotonicInstant::get();
1074        let contact = create_touch_contact(TOUCH_ID, Position { x: 20.0, y: 40.0 });
1075        let descriptor = get_touch_screen_device_descriptor();
1076        let input_event = input_device::UnhandledInputEvent::try_from(create_touch_screen_event(
1077            hashmap! {
1078                fidl_ui_input::PointerEventPhase::Add
1079                    => vec![contact.clone()],
1080            },
1081            event_time,
1082            &descriptor,
1083        ))
1084        .unwrap();
1085
1086        // Handle event.
1087        let handle_event_fut =
1088            fixtures.touch_handler.clone().handle_unhandled_input_event(input_event);
1089
1090        // Declare expected event.
1091        let expected_event = create_touch_pointer_sample_event(
1092            pointerinjector::EventPhase::Add,
1093            &contact,
1094            Position { x: 20.0, y: 40.0 },
1095            event_time,
1096        );
1097
1098        // Await all futures concurrently. If this completes, then the touch event was handled and
1099        // matches `expected_event`.
1100        let device_fut =
1101            handle_device_request_stream(injector_device_request_stream, expected_event);
1102        let (handle_result, _) = futures::future::join(handle_event_fut, device_fut).await;
1103
1104        // No unhandled events.
1105        assert_matches!(
1106            handle_result.as_slice(),
1107            [input_device::InputEvent { handled: input_device::Handled::Yes, .. }]
1108        );
1109    }
1110
1111    // Tests that an add touchpad contact event with viewport is unhandled and not send to scenic.
1112    #[fuchsia::test]
1113    async fn add_touchpad_contact_with_viewport() {
1114        let mut fixtures = TestFixtures::new().await;
1115
1116        // Add an injector.
1117        let (injector_device_proxy, mut injector_device_request_stream) =
1118            fidl::endpoints::create_proxy_and_stream::<pointerinjector::DeviceMarker>();
1119        fixtures
1120            .touch_handler
1121            .mutable_state
1122            .borrow_mut()
1123            .injectors
1124            .insert(1, injector_device_proxy);
1125
1126        // Request a viewport update.
1127        let _watch_viewport_task =
1128            fasync::Task::local(fixtures.touch_handler.clone().watch_viewport());
1129
1130        // Send a viewport update.
1131        match fixtures.configuration_request_stream.next().await {
1132            Some(Ok(pointerinjector_config::SetupRequest::WatchViewport { responder, .. })) => {
1133                responder.send(&create_viewport(0.0, 100.0)).expect("Failed to send viewport.");
1134            }
1135            other => panic!("Received unexpected value: {:?}", other),
1136        };
1137
1138        // Check that the injector received an updated viewport
1139        match injector_device_request_stream.next().await {
1140            Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => {
1141                assert_eq!(events.len(), 1);
1142                assert!(events[0].data.is_some());
1143                assert_eq!(
1144                    events[0].data,
1145                    Some(pointerinjector::Data::Viewport(create_viewport(0.0, 100.0)))
1146                );
1147                responder.send().expect("injector stream failed to respond.");
1148            }
1149            other => panic!("Received unexpected value: {:?}", other),
1150        }
1151
1152        // Create touch event.
1153        let event_time = zx::MonotonicInstant::get();
1154        let contact = create_touch_contact(TOUCH_ID, Position { x: 20.0, y: 40.0 });
1155        let descriptor = get_touchpad_device_descriptor();
1156        let input_event = input_device::UnhandledInputEvent::try_from(create_touchpad_event(
1157            vec![contact.clone()],
1158            HashSet::new(),
1159            event_time,
1160            &descriptor,
1161        ))
1162        .unwrap();
1163
1164        // Handle event.
1165        let handle_event_fut =
1166            fixtures.touch_handler.clone().handle_unhandled_input_event(input_event);
1167
1168        let handle_result = handle_event_fut.await;
1169
1170        // Event is not handled.
1171        assert_matches!(
1172            handle_result.as_slice(),
1173            [input_device::InputEvent { handled: input_device::Handled::No, .. }]
1174        );
1175
1176        // Injector should not receive anything because the handler does not support touchpad yet.
1177        assert!(fixtures.injector_registry_request_stream.next().now_or_never().is_none());
1178    }
1179
1180    #[fuchsia::test(allow_stalls = false)]
1181    async fn touch_injector_handler_initialized_with_inspect_node() {
1182        let fixtures = TestFixtures::new().await;
1183        diagnostics_assertions::assert_data_tree!(fixtures.inspector, root: {
1184            test_node: {
1185                touch_injector_handler: {
1186                    events_received_count: 0u64,
1187                    events_handled_count: 0u64,
1188                    last_received_timestamp_ns: 0u64,
1189                    "fuchsia.inspect.Health": {
1190                        status: "STARTING_UP",
1191                        // Timestamp value is unpredictable and not relevant in this context,
1192                        // so we only assert that the property is present.
1193                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
1194                    },
1195                }
1196            }
1197        });
1198    }
1199
1200    #[fuchsia::test(allow_stalls = false)]
1201    async fn touch_injector_handler_inspect_counts_events() {
1202        let fixtures = TestFixtures::new().await;
1203
1204        let contact = create_touch_contact(TOUCH_ID, Position { x: 20.0, y: 40.0 });
1205        let descriptor = get_touch_screen_device_descriptor();
1206        let event_time1 = zx::MonotonicInstant::get();
1207        let event_time2 = event_time1.add(zx::MonotonicDuration::from_micros(1));
1208        let event_time3 = event_time2.add(zx::MonotonicDuration::from_micros(1));
1209
1210        let input_events = vec![
1211            create_touch_screen_event(
1212                hashmap! {
1213                    fidl_ui_input::PointerEventPhase::Add
1214                        => vec![contact.clone()],
1215                },
1216                event_time1,
1217                &descriptor,
1218            ),
1219            create_touch_screen_event(
1220                hashmap! {
1221                    fidl_ui_input::PointerEventPhase::Move
1222                        => vec![contact.clone()],
1223                },
1224                event_time2,
1225                &descriptor,
1226            ),
1227            // Should not count non-touch input event.
1228            create_fake_input_event(event_time2),
1229            // Should not count received event that has already been handled.
1230            create_touch_screen_event_with_handled(
1231                hashmap! {
1232                    fidl_ui_input::PointerEventPhase::Move
1233                        => vec![contact.clone()],
1234                },
1235                event_time2,
1236                &descriptor,
1237                input_device::Handled::Yes,
1238            ),
1239            create_touch_screen_event(
1240                hashmap! {
1241                    fidl_ui_input::PointerEventPhase::Remove
1242                        => vec![contact.clone()],
1243                },
1244                event_time3,
1245                &descriptor,
1246            ),
1247        ];
1248
1249        for input_event in input_events {
1250            fixtures.touch_handler.clone().handle_input_event(input_event).await;
1251        }
1252
1253        let last_received_event_time: u64 = event_time3.into_nanos().try_into().unwrap();
1254
1255        diagnostics_assertions::assert_data_tree!(fixtures.inspector, root: {
1256            test_node: {
1257                touch_injector_handler: {
1258                    events_received_count: 3u64,
1259                    events_handled_count: 3u64,
1260                    last_received_timestamp_ns: last_received_event_time,
1261                    "fuchsia.inspect.Health": {
1262                        status: "STARTING_UP",
1263                        // Timestamp value is unpredictable and not relevant in this context,
1264                        // so we only assert that the property is present.
1265                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
1266                    },
1267                }
1268            }
1269        });
1270    }
1271}