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