Skip to main content

input_pipeline/
interaction_state_handler.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4#![cfg(fuchsia_api_level_at_least = "HEAD")]
5use crate::input_handler::{Handler, InputHandlerStatus, UnhandledInputHandler};
6use crate::{input_device, metrics};
7use anyhow::{Context, Error};
8use async_trait::async_trait;
9use async_utils::hanging_get::server::{HangingGet, Publisher, Subscriber};
10use fidl_fuchsia_input_interaction::{
11    NotifierRequest, NotifierRequestStream, NotifierWatchStateResponder, State,
12};
13use fidl_fuchsia_power_system::{ActivityGovernorMarker, ActivityGovernorProxy};
14use fuchsia_async::{Task, Timer};
15use fuchsia_component::client::connect_to_protocol;
16
17use fuchsia_inspect::health::Reporter;
18use futures::StreamExt;
19use metrics_registry::InputPipelineErrorMetricDimensionEvent;
20use std::cell::{Cell, RefCell};
21use std::rc::Rc;
22
23struct LeaseHolder {
24    activity_governor: ActivityGovernorProxy,
25    wake_lease: Option<zx::EventPair>,
26}
27
28impl LeaseHolder {
29    async fn new(activity_governor: ActivityGovernorProxy) -> Result<Self, Error> {
30        let wake_lease = activity_governor
31            .take_wake_lease("scene_manager")
32            .await
33            .context("cannot get wake lease from SAG")?;
34        log::info!("InteractionStateHandler created a wake lease during initialization.");
35
36        Ok(Self { activity_governor, wake_lease: Some(wake_lease) })
37    }
38
39    async fn create_lease(&mut self) -> Result<(), Error> {
40        if self.wake_lease.is_some() {
41            log::warn!(
42                "InteractionStateHandler already held a wake lease when trying to create one, please investigate."
43            );
44            return Ok(());
45        }
46
47        let wake_lease = self
48            .activity_governor
49            .take_wake_lease("scene_manager")
50            .await
51            .context("cannot get wake lease from SAG")?;
52        self.wake_lease = Some(wake_lease);
53        log::info!(
54            "InteractionStateHandler created a wake lease due to receiving recent user input."
55        );
56
57        Ok(())
58    }
59
60    fn drop_lease(&mut self) {
61        if let Some(lease) = self.wake_lease.take() {
62            log::info!(
63                "InteractionStateHandler is dropping the wake lease due to not receiving any recent user input."
64            );
65            std::mem::drop(lease);
66        } else {
67            log::warn!(
68                "InteractionStateHandler was not holding a wake lease when trying to drop one, please investigate."
69            );
70        }
71    }
72
73    #[cfg(test)]
74    fn is_holding_lease(&self) -> bool {
75        self.wake_lease.is_some()
76    }
77}
78
79pub type NotifyFn = Box<dyn Fn(&State, NotifierWatchStateResponder) -> bool>;
80pub type InteractionStatePublisher = Publisher<State, NotifierWatchStateResponder, NotifyFn>;
81pub type InteractionStateSubscriber = Subscriber<State, NotifierWatchStateResponder, NotifyFn>;
82type InteractionHangingGet = HangingGet<State, NotifierWatchStateResponder, NotifyFn>;
83
84/// An [`InteractionStateHandler`] tracks the state of user input interaction.
85pub struct InteractionStateHandler {
86    // When `idle_threshold_ms` has transpired since the last user input
87    // interaction, the user interaction state will transition from active to idle.
88    idle_threshold_ms: zx::MonotonicDuration,
89
90    // The task holding the timer-based idle transition after last user input.
91    idle_transition_task: Cell<Option<Task<()>>>,
92
93    // The event time of the last user input interaction.
94    last_event_time: RefCell<zx::MonotonicInstant>,
95
96    // To support power management, the caller must provide `Some` value for
97    // `lease_holder`. The existence of a `LeaseHolder` implies power framework
98    // availability in the platform.
99    lease_holder: Option<Rc<RefCell<LeaseHolder>>>,
100
101    // The publisher used to set active/idle state with hanging-get subscribers.
102    state_publisher: InteractionStatePublisher,
103
104    /// The inventory of this handler's Inspect status.
105    pub inspect_status: InputHandlerStatus,
106
107    /// The metrics logger.
108    metrics_logger: metrics::MetricsLogger,
109
110    // TODO(b/443729860): Remove these temporary feature flags once enabled.
111    enable_button_baton_passing: bool,
112    enable_mouse_baton_passing: bool,
113    enable_touch_baton_passing: bool,
114}
115
116impl InteractionStateHandler {
117    /// Creates a new [`InteractionStateHandler`] that listens for user input
118    /// input interactions and notifies clients of interaction state changes.
119    pub async fn new(
120        idle_threshold_ms: zx::MonotonicDuration,
121        input_handlers_node: &fuchsia_inspect::Node,
122        metrics_logger: metrics::MetricsLogger,
123        state_publisher: InteractionStatePublisher,
124        suspend_enabled: bool,
125        enable_button_baton_passing: bool,
126        enable_mouse_baton_passing: bool,
127        enable_touch_baton_passing: bool,
128    ) -> Rc<Self> {
129        log::info!(
130            "InteractionStateHandler is initialized with idle_threshold_ms: {:?}",
131            idle_threshold_ms.into_millis()
132        );
133
134        let inspect_status =
135            InputHandlerStatus::new(input_handlers_node, "interaction_state_handler", false);
136
137        let lease_holder = match suspend_enabled {
138            true => {
139                let activity_governor = connect_to_protocol::<ActivityGovernorMarker>()
140                    .expect("connect to fuchsia.power.system.ActivityGovernor");
141                match LeaseHolder::new(activity_governor).await {
142                    Ok(holder) => Some(Rc::new(RefCell::new(holder))),
143                    Err(e) => {
144                        log::error!(
145                            "Unable to integrate with power, system may incorrectly enter suspend: {:?}",
146                            e
147                        );
148                        None
149                    }
150                }
151            }
152            false => None,
153        };
154
155        Rc::new(Self::new_internal(
156            idle_threshold_ms,
157            zx::MonotonicInstant::get(),
158            metrics_logger,
159            lease_holder,
160            inspect_status,
161            state_publisher,
162            enable_button_baton_passing,
163            enable_mouse_baton_passing,
164            enable_touch_baton_passing,
165        ))
166    }
167
168    #[cfg(test)]
169    /// Sets the initial idleness timer relative to fake time at 0 for tests.
170    async fn new_for_test(
171        idle_threshold_ms: zx::MonotonicDuration,
172        metrics_logger: metrics::MetricsLogger,
173        lease_holder: Option<Rc<RefCell<LeaseHolder>>>,
174        state_publisher: InteractionStatePublisher,
175    ) -> Rc<Self> {
176        fuchsia_async::TestExecutor::advance_to(zx::MonotonicInstant::ZERO.into()).await;
177
178        let inspector = fuchsia_inspect::Inspector::default();
179        let test_node = inspector.root().create_child("test_node");
180        let inspect_status = InputHandlerStatus::new(
181            &test_node,
182            "interaction_state_handler",
183            /* generates_events */ false,
184        );
185        Rc::new(Self::new_internal(
186            idle_threshold_ms,
187            zx::MonotonicInstant::ZERO,
188            metrics_logger,
189            lease_holder,
190            inspect_status,
191            state_publisher,
192            /* enable_button_baton_passing */ false,
193            /* enable_mouse_baton_passing */ false,
194            /* enable_touch_baton_passing */ false,
195        ))
196    }
197
198    fn new_internal(
199        idle_threshold_ms: zx::MonotonicDuration,
200        initial_timestamp: zx::MonotonicInstant,
201        metrics_logger: metrics::MetricsLogger,
202        lease_holder: Option<Rc<RefCell<LeaseHolder>>>,
203        inspect_status: InputHandlerStatus,
204        state_publisher: InteractionStatePublisher,
205        enable_button_baton_passing: bool,
206        enable_mouse_baton_passing: bool,
207        enable_touch_baton_passing: bool,
208    ) -> Self {
209        let task = Self::create_idle_transition_task(
210            initial_timestamp + idle_threshold_ms,
211            state_publisher.clone(),
212            lease_holder.clone(),
213        );
214
215        Self {
216            idle_threshold_ms,
217            idle_transition_task: Cell::new(Some(task)),
218            last_event_time: RefCell::new(initial_timestamp),
219            lease_holder,
220            metrics_logger,
221            state_publisher,
222            inspect_status,
223            enable_button_baton_passing,
224            enable_mouse_baton_passing,
225            enable_touch_baton_passing,
226        }
227    }
228
229    async fn transition_to_active(
230        state_publisher: &InteractionStatePublisher,
231        lease_holder: &Option<Rc<RefCell<LeaseHolder>>>,
232    ) {
233        if let Some(holder) = lease_holder {
234            if let Err(e) = holder.borrow_mut().create_lease().await {
235                log::warn!(
236                    "Unable to create lease, system may incorrectly go into suspend: {:?}",
237                    e
238                );
239            };
240        }
241        state_publisher.set(State::Active);
242    }
243
244    fn create_idle_transition_task(
245        timeout: zx::MonotonicInstant,
246        state_publisher: InteractionStatePublisher,
247        lease_holder: Option<Rc<RefCell<LeaseHolder>>>,
248    ) -> Task<()> {
249        Task::local(async move {
250            Timer::new(timeout).await;
251            lease_holder.and_then(|holder| Some(holder.borrow_mut().drop_lease()));
252            state_publisher.set(State::Idle);
253        })
254    }
255
256    async fn transition_to_idle_after_new_time(&self, event_time: zx::MonotonicInstant) {
257        if *self.last_event_time.borrow() > event_time {
258            return;
259        }
260
261        *self.last_event_time.borrow_mut() = event_time;
262        if let Some(t) = self.idle_transition_task.take() {
263            // If the task returns a completed output, we can assume the
264            // state has transitioned to Idle.
265            if let Some(()) = t.abort().await {
266                Self::transition_to_active(&self.state_publisher, &self.lease_holder).await;
267            }
268        }
269
270        self.idle_transition_task.set(Some(Self::create_idle_transition_task(
271            event_time + self.idle_threshold_ms,
272            self.state_publisher.clone(),
273            self.lease_holder.clone(),
274        )));
275    }
276
277    #[cfg(test)]
278    fn is_holding_lease(&self) -> bool {
279        if let Some(holder) = &self.lease_holder {
280            return holder.borrow().is_holding_lease();
281        }
282
283        false
284    }
285}
286
287/// Handles the request stream for fuchsia.input.interaction.Notifier.
288///
289/// # Parameters
290/// `stream`: The `NotifierRequestStream` to be handled.
291pub async fn handle_interaction_notifier_request_stream(
292    mut stream: NotifierRequestStream,
293    subscriber: InteractionStateSubscriber,
294) -> Result<(), Error> {
295    while let Some(notifier_request) = stream.next().await {
296        let NotifierRequest::WatchState { responder } = notifier_request?;
297        subscriber.register(responder)?;
298    }
299
300    Ok(())
301}
302
303pub fn init_interaction_hanging_get() -> InteractionHangingGet {
304    let notify_fn: NotifyFn = Box::new(|state, responder| {
305        if responder.send(*state).is_err() {
306            log::info!("Failed to send user input interaction state");
307        }
308
309        true
310    });
311
312    let initial_state = State::Active;
313    InteractionHangingGet::new(initial_state, notify_fn)
314}
315
316impl Handler for InteractionStateHandler {
317    fn set_handler_healthy(self: std::rc::Rc<Self>) {
318        self.inspect_status.health_node.borrow_mut().set_ok();
319    }
320
321    fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
322        self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
323    }
324
325    fn get_name(&self) -> &'static str {
326        "InteractionStateHandler"
327    }
328
329    fn interest(&self) -> Vec<input_device::InputEventType> {
330        let mut interest = vec![];
331        if !self.enable_button_baton_passing {
332            interest.push(input_device::InputEventType::ConsumerControls);
333        }
334        if !self.enable_mouse_baton_passing {
335            interest.push(input_device::InputEventType::Mouse);
336        }
337        if !self.enable_touch_baton_passing {
338            interest.push(input_device::InputEventType::TouchScreen);
339        }
340        interest
341    }
342}
343
344#[async_trait(?Send)]
345impl UnhandledInputHandler for InteractionStateHandler {
346    /// This InputHandler doesn't consume any input events.
347    /// It just passes them on to the next handler in the pipeline.
348    async fn handle_unhandled_input_event(
349        self: Rc<Self>,
350        unhandled_input_event: input_device::UnhandledInputEvent,
351    ) -> Vec<input_device::InputEvent> {
352        fuchsia_trace::duration!("input", "interaction_state_handler");
353        let trace_id = unhandled_input_event.trace_id.unwrap_or_else(|| 0.into());
354        fuchsia_trace::flow_step!("input", "event_in_input_pipeline", trace_id);
355
356        match unhandled_input_event.device_event {
357            input_device::InputDeviceEvent::ConsumerControls(..) => {
358                if self.enable_button_baton_passing {
359                    self.metrics_logger.log_error(
360                        InputPipelineErrorMetricDimensionEvent::HandlerReceivedUninterestedEvent,
361                        "Button event with baton passing".to_string(),
362                    );
363                } else {
364                    fuchsia_trace::duration!("input", "interaction_state_handler[processing]");
365                    // Clamp the time to now so that clients cannot send events far off
366                    // in the future to keep the system always active.
367                    // Note: We use the global executor to get the current time instead
368                    // of the kernel so that we do not unnecessarily clamp
369                    // test-injected times.
370                    let event_time = unhandled_input_event.event_time.clamp(
371                        zx::MonotonicInstant::ZERO,
372                        fuchsia_async::MonotonicInstant::now().into_zx(),
373                    );
374
375                    self.inspect_status.count_received_event(&event_time);
376                    self.transition_to_idle_after_new_time(event_time).await;
377                }
378            }
379            input_device::InputDeviceEvent::Mouse(..) => {
380                if self.enable_mouse_baton_passing {
381                    self.metrics_logger.log_error(
382                        InputPipelineErrorMetricDimensionEvent::HandlerReceivedUninterestedEvent,
383                        "Mouse event with baton passing".to_string(),
384                    );
385                } else {
386                    fuchsia_trace::duration!("input", "interaction_state_handler[processing]");
387                    // Clamp the time to now so that clients cannot send events far off
388                    // in the future to keep the system always active.
389                    // Note: We use the global executor to get the current time instead
390                    // of the kernel so that we do not unnecessarily clamp
391                    // test-injected times.
392                    let event_time = unhandled_input_event.event_time.clamp(
393                        zx::MonotonicInstant::ZERO,
394                        fuchsia_async::MonotonicInstant::now().into_zx(),
395                    );
396
397                    self.inspect_status.count_received_event(&event_time);
398                    self.transition_to_idle_after_new_time(event_time).await;
399                }
400            }
401            input_device::InputDeviceEvent::TouchScreen(..) => {
402                if self.enable_touch_baton_passing {
403                    self.metrics_logger.log_error(
404                        InputPipelineErrorMetricDimensionEvent::HandlerReceivedUninterestedEvent,
405                        "Touch event with baton passing".to_string(),
406                    );
407                } else {
408                    fuchsia_trace::duration!("input", "interaction_state_handler[processing]");
409                    // Clamp the time to now so that clients cannot send events far off
410                    // in the future to keep the system always active.
411                    // Note: We use the global executor to get the current time instead
412                    // of the kernel so that we do not unnecessarily clamp
413                    // test-injected times.
414                    let event_time = unhandled_input_event.event_time.clamp(
415                        zx::MonotonicInstant::ZERO,
416                        fuchsia_async::MonotonicInstant::now().into_zx(),
417                    );
418
419                    self.inspect_status.count_received_event(&event_time);
420                    self.transition_to_idle_after_new_time(event_time).await;
421                }
422            }
423            _ => {
424                self.metrics_logger.log_error(
425                    InputPipelineErrorMetricDimensionEvent::HandlerReceivedUninterestedEvent,
426                    std::format!(
427                        "uninterested input event: {:?}",
428                        unhandled_input_event.get_event_type()
429                    ),
430                );
431            }
432        }
433
434        vec![input_device::InputEvent::from(unhandled_input_event)]
435    }
436}
437
438#[cfg(test)]
439mod tests {
440    use super::*;
441    use crate::mouse_binding;
442    use crate::testing_utilities::{
443        consumer_controls_device_descriptor, create_consumer_controls_event, create_mouse_event,
444        create_touch_contact, create_touch_screen_event, get_mouse_device_descriptor,
445        get_touch_screen_device_descriptor,
446    };
447    use crate::utils::Position;
448    use assert_matches::assert_matches;
449    use async_utils::hanging_get::client::HangingGetStream;
450    use fidl::endpoints::create_proxy_and_stream;
451    use fidl_fuchsia_input_interaction::{NotifierMarker, NotifierProxy};
452    use fidl_fuchsia_power_system::{ActivityGovernorMarker, ActivityGovernorRequest};
453    use fidl_fuchsia_ui_input::PointerEventPhase;
454    use fuchsia_async::TestExecutor;
455    use futures::pin_mut;
456    use maplit::hashmap;
457    use std::collections::HashSet;
458    use std::task::Poll;
459    use test_case::test_case;
460
461    const ACTIVITY_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(5000);
462
463    async fn create_interaction_state_handler_and_notifier_proxy(
464        suspend_enabled: bool,
465    ) -> (Rc<InteractionStateHandler>, NotifierProxy) {
466        let mut interaction_hanging_get = init_interaction_hanging_get();
467
468        let (notifier_proxy, notifier_stream) = create_proxy_and_stream::<NotifierMarker>();
469        let stream_fut = handle_interaction_notifier_request_stream(
470            notifier_stream,
471            interaction_hanging_get.new_subscriber(),
472        );
473
474        Task::local(async move {
475            if stream_fut.await.is_err() {
476                panic!("Failed to handle notifier request stream");
477            }
478        })
479        .detach();
480
481        let lease_holder = match suspend_enabled {
482            true => {
483                let holder = LeaseHolder::new(fake_activity_governor_server())
484                    .await
485                    .expect("create lease holder for test");
486                Some(Rc::new(RefCell::new(holder)))
487            }
488            false => None,
489        };
490
491        (
492            InteractionStateHandler::new_for_test(
493                ACTIVITY_TIMEOUT,
494                metrics::MetricsLogger::default(),
495                lease_holder,
496                interaction_hanging_get.new_publisher(),
497            )
498            .await,
499            notifier_proxy,
500        )
501    }
502
503    fn fake_activity_governor_server() -> ActivityGovernorProxy {
504        let (proxy, mut stream) = create_proxy_and_stream::<ActivityGovernorMarker>();
505        Task::local(async move {
506            while let Some(request) = stream.next().await {
507                match request {
508                    Ok(ActivityGovernorRequest::TakeWakeLease { responder, .. }) => {
509                        let (_, fake_wake_lease) = zx::EventPair::create();
510                        responder.send(fake_wake_lease).expect("failed to send fake wake lease");
511                    }
512                    Ok(unexpected) => {
513                        log::warn!(
514                            "Unexpected request {unexpected:?} serving fuchsia.power.system.ActivityGovernor"
515                        );
516                    }
517                    Err(e) => {
518                        log::warn!(
519                            "Error serving fuchsia.power.system.ActivityGovernor: {:?}",
520                            e
521                        );
522                    }
523                }
524            }
525        })
526        .detach();
527
528        proxy
529    }
530
531    #[test_case(true; "Suspend enabled")]
532    #[test_case(false; "Suspend disabled")]
533    #[fuchsia::test(allow_stalls = false)]
534    async fn notifier_sends_initial_state(suspend_enabled: bool) {
535        let (interaction_state_handler, notifier_proxy) =
536            create_interaction_state_handler_and_notifier_proxy(suspend_enabled).await;
537        let state = notifier_proxy.watch_state().await.expect("Failed to get interaction state");
538        assert_eq!(state, State::Active);
539        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
540    }
541
542    #[test_case(true; "Suspend enabled")]
543    #[test_case(false; "Suspend disabled")]
544    #[fuchsia::test]
545    fn notifier_sends_idle_state_after_timeout(suspend_enabled: bool) -> Result<(), Error> {
546        let mut executor = TestExecutor::new_with_fake_time();
547
548        let handler_and_proxy_fut =
549            create_interaction_state_handler_and_notifier_proxy(suspend_enabled);
550        pin_mut!(handler_and_proxy_fut);
551        let handler_and_proxy_res = executor.run_until_stalled(&mut handler_and_proxy_fut);
552        let (interaction_state_handler, notifier_proxy) = match handler_and_proxy_res {
553            Poll::Ready((handler, proxy)) => (handler, proxy),
554            _ => panic!("Unable to create interaction state handler and proxy"),
555        };
556
557        // Initial state is active.
558        let mut watch_state_stream =
559            HangingGetStream::new(notifier_proxy, NotifierProxy::watch_state);
560        let state_fut = watch_state_stream.next();
561        pin_mut!(state_fut);
562        let initial_state = executor.run_until_stalled(&mut state_fut);
563        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Active))));
564        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
565
566        // Skip ahead by the activity timeout.
567        executor.set_fake_time(fuchsia_async::MonotonicInstant::after(ACTIVITY_TIMEOUT));
568
569        // State transitions to Idle.
570        let idle_state_fut = watch_state_stream.next();
571        pin_mut!(idle_state_fut);
572        let initial_state = executor.run_until_stalled(&mut idle_state_fut);
573        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Idle))));
574        assert_eq!(interaction_state_handler.is_holding_lease(), false);
575
576        Ok(())
577    }
578
579    #[test_case(true; "Suspend enabled")]
580    #[test_case(false; "Suspend disabled")]
581    #[fuchsia::test]
582    fn interaction_state_handler_drops_first_timer_on_activity(
583        suspend_enabled: bool,
584    ) -> Result<(), Error> {
585        // This test does the following:
586        //   - Start an InteractionStateHandler, whose initial timeout is set to
587        //     ACTIVITY_TIMEOUT.
588        //   - Send an activity at time ACTIVITY_TIMEOUT / 2.
589        //   - Observe that after ACTIVITY_TIMEOUT transpires, the initial
590        //     timeout to transition to idle state _does not_ fire, as we
591        //     expect it to be replaced by a new timeout in response to the
592        //     injected activity.
593        //   - Observe that after ACTIVITY_TIMEOUT * 1.5 transpires, the second
594        //     timeout to transition to idle state _does_ fire.
595        // Because division will round to 0, odd-number timeouts could cause an
596        // incorrect implementation to still pass the test. In order to catch
597        // these cases, we first assert that ACTIVITY_TIMEOUT is an even number.
598        assert_eq!(ACTIVITY_TIMEOUT.into_nanos() % 2, 0);
599
600        let mut executor = TestExecutor::new_with_fake_time();
601
602        let handler_and_proxy_fut =
603            create_interaction_state_handler_and_notifier_proxy(suspend_enabled);
604        pin_mut!(handler_and_proxy_fut);
605        let handler_and_proxy_res = executor.run_until_stalled(&mut handler_and_proxy_fut);
606        let (interaction_state_handler, notifier_proxy) = match handler_and_proxy_res {
607            Poll::Ready((handler, proxy)) => (handler, proxy),
608            _ => panic!("Unable to create interaction state handler and proxy"),
609        };
610
611        // Initial state is active.
612        let mut watch_state_stream =
613            HangingGetStream::new(notifier_proxy, NotifierProxy::watch_state);
614        let state_fut = watch_state_stream.next();
615        pin_mut!(state_fut);
616        let initial_state = executor.run_until_stalled(&mut state_fut);
617        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Active))));
618        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
619
620        // Skip ahead by half the activity timeout.
621        executor.set_fake_time(fuchsia_async::MonotonicInstant::after(ACTIVITY_TIMEOUT / 2));
622
623        // Send an input event, replacing the initial idleness timer.
624        let input_event =
625            input_device::UnhandledInputEvent::try_from(create_consumer_controls_event(
626                vec![fidl_fuchsia_input_report::ConsumerControlButton::Power],
627                zx::MonotonicInstant::from(fuchsia_async::MonotonicInstant::after(
628                    ACTIVITY_TIMEOUT / 2,
629                )),
630                &consumer_controls_device_descriptor(),
631            ))
632            .unwrap();
633
634        let mut handle_event_fut =
635            interaction_state_handler.clone().handle_unhandled_input_event(input_event);
636        let handle_result = executor.run_until_stalled(&mut handle_event_fut);
637        assert!(handle_result.is_ready());
638
639        // Skip ahead by half the activity timeout.
640        executor.set_fake_time(fuchsia_async::MonotonicInstant::after(ACTIVITY_TIMEOUT / 2));
641
642        // Initial state does not change.
643        let watch_state_fut = watch_state_stream.next();
644        pin_mut!(watch_state_fut);
645        let watch_state_res = executor.run_until_stalled(&mut watch_state_fut);
646        assert_matches!(watch_state_res, Poll::Pending);
647        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
648
649        // Skip ahead by half the activity timeout.
650        executor.set_fake_time(fuchsia_async::MonotonicInstant::after(ACTIVITY_TIMEOUT / 2));
651
652        // Interaction state does change.
653        let watch_state_res = executor.run_until_stalled(&mut watch_state_fut);
654        assert_matches!(watch_state_res, Poll::Ready(Some(Ok(State::Idle))));
655        assert_eq!(interaction_state_handler.is_holding_lease(), false);
656
657        Ok(())
658    }
659
660    #[test_case(true; "Suspend enabled")]
661    #[test_case(false; "Suspend disabled")]
662    #[fuchsia::test]
663    fn interaction_state_handler_drops_late_activities(suspend_enabled: bool) -> Result<(), Error> {
664        let mut executor = TestExecutor::new_with_fake_time();
665
666        let handler_and_proxy_fut =
667            create_interaction_state_handler_and_notifier_proxy(suspend_enabled);
668        pin_mut!(handler_and_proxy_fut);
669        let handler_and_proxy_res = executor.run_until_stalled(&mut handler_and_proxy_fut);
670        let (interaction_state_handler, notifier_proxy) = match handler_and_proxy_res {
671            Poll::Ready((handler, proxy)) => (handler, proxy),
672            _ => panic!("Unable to create interaction state handler and proxy"),
673        };
674
675        // Initial state is active.
676        let mut watch_state_stream =
677            HangingGetStream::new(notifier_proxy, NotifierProxy::watch_state);
678        let state_fut = watch_state_stream.next();
679        pin_mut!(state_fut);
680        let watch_state_res = executor.run_until_stalled(&mut state_fut);
681        assert_matches!(watch_state_res, Poll::Ready(Some(Ok(State::Active))));
682        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
683
684        // Skip ahead by half the activity timeout.
685        executor.set_fake_time(fuchsia_async::MonotonicInstant::after(ACTIVITY_TIMEOUT / 2));
686
687        // Send an input event, replacing the initial idleness timer.
688        let input_event =
689            input_device::UnhandledInputEvent::try_from(create_consumer_controls_event(
690                vec![fidl_fuchsia_input_report::ConsumerControlButton::Power],
691                zx::MonotonicInstant::from(fuchsia_async::MonotonicInstant::after(
692                    ACTIVITY_TIMEOUT / 2,
693                )),
694                &consumer_controls_device_descriptor(),
695            ))
696            .unwrap();
697
698        let mut handle_event_fut =
699            interaction_state_handler.clone().handle_unhandled_input_event(input_event);
700        let handle_result = executor.run_until_stalled(&mut handle_event_fut);
701        assert!(handle_result.is_ready());
702
703        // Skip ahead by half the activity timeout.
704        executor.set_fake_time(fuchsia_async::MonotonicInstant::after(ACTIVITY_TIMEOUT / 2));
705
706        // Send an input event with an earlier event time.
707        let input_event =
708            input_device::UnhandledInputEvent::try_from(create_consumer_controls_event(
709                vec![fidl_fuchsia_input_report::ConsumerControlButton::Power],
710                zx::MonotonicInstant::ZERO,
711                &consumer_controls_device_descriptor(),
712            ))
713            .unwrap();
714
715        let mut handle_event_fut =
716            interaction_state_handler.clone().handle_unhandled_input_event(input_event);
717        let handle_result = executor.run_until_stalled(&mut handle_event_fut);
718        assert!(handle_result.is_ready());
719
720        // Initial task does not transition to idle, nor does one from the
721        // "earlier" activity that was received later.
722        let watch_state_fut = watch_state_stream.next();
723        pin_mut!(watch_state_fut);
724        let initial_state = executor.run_until_stalled(&mut watch_state_fut);
725        assert_matches!(initial_state, Poll::Pending);
726        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
727
728        // Skip ahead by half the activity timeout.
729        executor.set_fake_time(fuchsia_async::MonotonicInstant::after(ACTIVITY_TIMEOUT / 2));
730
731        // Interaction state does change.
732        let watch_state_res = executor.run_until_stalled(&mut watch_state_fut);
733        assert_matches!(watch_state_res, Poll::Ready(Some(Ok(State::Idle))));
734        assert_eq!(interaction_state_handler.is_holding_lease(), false);
735
736        Ok(())
737    }
738
739    #[test_case(true; "Suspend enabled")]
740    #[test_case(false; "Suspend disabled")]
741    #[fuchsia::test]
742    fn notifier_sends_active_state_with_button_input_event(
743        suspend_enabled: bool,
744    ) -> Result<(), Error> {
745        let mut executor = TestExecutor::new_with_fake_time();
746
747        let handler_and_proxy_fut =
748            create_interaction_state_handler_and_notifier_proxy(suspend_enabled);
749        pin_mut!(handler_and_proxy_fut);
750        let handler_and_proxy_res = executor.run_until_stalled(&mut handler_and_proxy_fut);
751        let (interaction_state_handler, notifier_proxy) = match handler_and_proxy_res {
752            Poll::Ready((handler, proxy)) => (handler, proxy),
753            _ => panic!("Unable to create interaction state handler and proxy"),
754        };
755
756        // Initial state is active.
757        let mut watch_state_stream =
758            HangingGetStream::new(notifier_proxy, NotifierProxy::watch_state);
759        let state_fut = watch_state_stream.next();
760        pin_mut!(state_fut);
761        let initial_state = executor.run_until_stalled(&mut state_fut);
762        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Active))));
763        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
764
765        // Skip ahead by the activity timeout.
766        executor.set_fake_time(fuchsia_async::MonotonicInstant::after(ACTIVITY_TIMEOUT));
767
768        // State transitions to Idle.
769        let idle_state_fut = watch_state_stream.next();
770        pin_mut!(idle_state_fut);
771        let initial_state = executor.run_until_stalled(&mut idle_state_fut);
772        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Idle))));
773        assert_eq!(interaction_state_handler.is_holding_lease(), false);
774
775        // Send an input event.
776        let input_event =
777            input_device::UnhandledInputEvent::try_from(create_consumer_controls_event(
778                vec![fidl_fuchsia_input_report::ConsumerControlButton::Power],
779                zx::MonotonicInstant::get(),
780                &consumer_controls_device_descriptor(),
781            ))
782            .unwrap();
783
784        let mut handle_event_fut =
785            interaction_state_handler.clone().handle_unhandled_input_event(input_event);
786        let handle_result = executor.run_until_stalled(&mut handle_event_fut);
787
788        // Event is not handled.
789        match handle_result {
790            Poll::Ready(res) => assert_matches!(
791                res.as_slice(),
792                [input_device::InputEvent { handled: input_device::Handled::No, .. }]
793            ),
794            x => panic!("expected Ready from handle_unhandled_input_event, got {:?}", x),
795        };
796
797        // State transitions to Active.
798        let active_state_fut = watch_state_stream.next();
799        pin_mut!(active_state_fut);
800        let initial_state = executor.run_until_stalled(&mut active_state_fut);
801        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Active))));
802        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
803
804        Ok(())
805    }
806
807    #[test_case(true; "Suspend enabled")]
808    #[test_case(false; "Suspend disabled")]
809    #[fuchsia::test]
810    fn notifier_sends_active_state_with_mouse_input_event(
811        suspend_enabled: bool,
812    ) -> Result<(), Error> {
813        let mut executor = TestExecutor::new_with_fake_time();
814
815        let handler_and_proxy_fut =
816            create_interaction_state_handler_and_notifier_proxy(suspend_enabled);
817        pin_mut!(handler_and_proxy_fut);
818        let handler_and_proxy_res = executor.run_until_stalled(&mut handler_and_proxy_fut);
819        let (interaction_state_handler, notifier_proxy) = match handler_and_proxy_res {
820            Poll::Ready((handler, proxy)) => (handler, proxy),
821            _ => panic!("Unable to create interaction state handler and proxy"),
822        };
823
824        // Initial state is active.
825        let mut watch_state_stream =
826            HangingGetStream::new(notifier_proxy, NotifierProxy::watch_state);
827        let state_fut = watch_state_stream.next();
828        pin_mut!(state_fut);
829        let initial_state = executor.run_until_stalled(&mut state_fut);
830        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Active))));
831        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
832
833        // Skip ahead by the activity timeout.
834        executor.set_fake_time(fuchsia_async::MonotonicInstant::after(ACTIVITY_TIMEOUT));
835
836        // State transitions to Idle.
837        let idle_state_fut = watch_state_stream.next();
838        pin_mut!(idle_state_fut);
839        let initial_state = executor.run_until_stalled(&mut idle_state_fut);
840        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Idle))));
841        assert_eq!(interaction_state_handler.is_holding_lease(), false);
842
843        // Send an input event.
844        let input_event = input_device::UnhandledInputEvent::try_from(create_mouse_event(
845            mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }),
846            None, /* wheel_delta_v */
847            None, /* wheel_delta_h */
848            None, /* is_precision_scroll */
849            mouse_binding::MousePhase::Down,
850            HashSet::new(),
851            HashSet::new(),
852            zx::MonotonicInstant::get(),
853            &get_mouse_device_descriptor(),
854        ))
855        .unwrap();
856
857        let mut handle_event_fut =
858            interaction_state_handler.clone().handle_unhandled_input_event(input_event);
859        let handle_result = executor.run_until_stalled(&mut handle_event_fut);
860
861        // Event is not handled.
862        match handle_result {
863            Poll::Ready(res) => assert_matches!(
864                res.as_slice(),
865                [input_device::InputEvent { handled: input_device::Handled::No, .. }]
866            ),
867            x => panic!("expected Ready from handle_unhandled_input_event, got {:?}", x),
868        };
869
870        // State transitions to Active.
871        let active_state_fut = watch_state_stream.next();
872        pin_mut!(active_state_fut);
873        let initial_state = executor.run_until_stalled(&mut active_state_fut);
874        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Active))));
875        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
876
877        Ok(())
878    }
879
880    #[test_case(true; "Suspend enabled")]
881    #[test_case(false; "Suspend disabled")]
882    #[fuchsia::test]
883    fn notifier_sends_active_state_with_touch_input_event(
884        suspend_enabled: bool,
885    ) -> Result<(), Error> {
886        let mut executor = TestExecutor::new_with_fake_time();
887
888        let handler_and_proxy_fut =
889            create_interaction_state_handler_and_notifier_proxy(suspend_enabled);
890        pin_mut!(handler_and_proxy_fut);
891        let handler_and_proxy_res = executor.run_until_stalled(&mut handler_and_proxy_fut);
892        let (interaction_state_handler, notifier_proxy) = match handler_and_proxy_res {
893            Poll::Ready((handler, proxy)) => (handler, proxy),
894            _ => panic!("Unable to create interaction state handler and proxy"),
895        };
896
897        // Initial state is active.
898        let mut watch_state_stream =
899            HangingGetStream::new(notifier_proxy, NotifierProxy::watch_state);
900        let state_fut = watch_state_stream.next();
901        pin_mut!(state_fut);
902        let initial_state = executor.run_until_stalled(&mut state_fut);
903        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Active))));
904        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
905
906        // Skip ahead by the activity timeout.
907        executor.set_fake_time(fuchsia_async::MonotonicInstant::after(ACTIVITY_TIMEOUT));
908
909        // State transitions to Idle.
910        let idle_state_fut = watch_state_stream.next();
911        pin_mut!(idle_state_fut);
912        let initial_state = executor.run_until_stalled(&mut idle_state_fut);
913        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Idle))));
914        assert_eq!(interaction_state_handler.is_holding_lease(), false);
915
916        // Send an input event.
917        const TOUCH_ID: u32 = 1;
918        let contact = create_touch_contact(TOUCH_ID, Position { x: 0.0, y: 0.0 });
919        let input_event = input_device::UnhandledInputEvent::try_from(create_touch_screen_event(
920            hashmap! {
921                PointerEventPhase::Add
922                    => vec![contact.clone()],
923            },
924            zx::MonotonicInstant::get(),
925            &get_touch_screen_device_descriptor(),
926        ))
927        .unwrap();
928
929        let mut handle_event_fut =
930            interaction_state_handler.clone().handle_unhandled_input_event(input_event);
931        let handle_result = executor.run_until_stalled(&mut handle_event_fut);
932
933        // Event is not handled.
934        match handle_result {
935            Poll::Ready(res) => assert_matches!(
936                res.as_slice(),
937                [input_device::InputEvent { handled: input_device::Handled::No, .. }]
938            ),
939            x => panic!("expected Ready from handle_unhandled_input_event, got {:?}", x),
940        };
941
942        // State transitions to Active.
943        let active_state_fut = watch_state_stream.next();
944        pin_mut!(active_state_fut);
945        let initial_state = executor.run_until_stalled(&mut active_state_fut);
946        assert_matches!(initial_state, Poll::Ready(Some(Ok(State::Active))));
947        assert_eq!(interaction_state_handler.is_holding_lease(), suspend_enabled);
948
949        Ok(())
950    }
951}