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