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