input_pipeline/
factory_reset_handler.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::consumer_controls_binding::ConsumerControlsEvent;
6use crate::input_handler::{Handler, InputHandlerStatus, UnhandledInputHandler};
7use crate::{input_device, metrics};
8use anyhow::{Context as _, Error, anyhow};
9use async_trait::async_trait;
10use async_utils::hanging_get::server::HangingGet;
11use fidl::endpoints::DiscoverableProtocolMarker as _;
12use fidl_fuchsia_media::AudioRenderUsage2;
13use fidl_fuchsia_media_sounds::{PlaySoundError, PlayerMarker};
14use fidl_fuchsia_recovery::FactoryResetMarker;
15use fidl_fuchsia_recovery_policy::{DeviceRequest, DeviceRequestStream};
16use fidl_fuchsia_recovery_ui::{
17    FactoryResetCountdownRequestStream, FactoryResetCountdownState,
18    FactoryResetCountdownWatchResponder,
19};
20use fuchsia_async::{MonotonicDuration, MonotonicInstant, Task, TimeoutExt, Timer};
21use fuchsia_inspect::health::Reporter;
22use futures::StreamExt;
23use metrics_registry::*;
24use std::cell::RefCell;
25use std::fs::{self, File};
26use std::path::Path;
27use std::rc::Rc;
28use {fidl_fuchsia_input_report as fidl_input_report, fidl_fuchsia_io as fio};
29
30/// FactoryResetState tracks the state of the device through the factory reset
31/// process.
32///
33/// # Values
34/// ## Disallowed
35/// Factory reset of the device is not allowed. This is used to
36/// keep public devices from being reset, such as when being used in kiosk mode.
37///
38/// ### Transitions
39/// Disallowed → Idle
40///
41/// ## Idle
42/// This is the default state for a device when factory resets are allowed but
43/// is not currently being reset.
44///
45/// ### Transitions
46/// Idle → Disallowed
47/// Idle → ButtonCountdown
48///
49/// ## ButtonCountdown
50/// This state represents the fact that the reset button has been pressed and a
51/// countdown has started to verify that the button was pressed intentionally.
52///
53/// ### Transitions
54/// ButtonCountdown → Disallowed
55/// ButtonCountdown → Idle
56/// ButtonCountdown → ResetCountdown
57///
58/// ## ResetCountdown
59/// The button countdown has completed indicating that this was a purposeful
60/// action so a reset countdown is started to give the user a chance to cancel
61/// the factory reset.
62///
63/// ### Transitions
64/// ResetCountdown → Disallowed
65/// ResetCountdown → Idle
66/// ResetCountdown → Resetting
67///
68/// ## Resetting
69/// Once the device is in this state a factory reset is imminent and can no
70/// longer be cancelled.
71#[derive(Clone, Copy, Debug, PartialEq)]
72enum FactoryResetState {
73    Disallowed,
74    Idle,
75    ButtonCountdown { deadline: MonotonicInstant },
76    ResetCountdown { deadline: MonotonicInstant },
77    Resetting,
78}
79
80const FACTORY_RESET_DISALLOWED_PATH: &'static str = "/data/factory_reset_disallowed";
81const FACTORY_RESET_SOUND_PATH: &'static str = "/config/data/chirp-start-tone.wav";
82
83const BUTTON_TIMEOUT: MonotonicDuration = MonotonicDuration::from_millis(500);
84const RESET_TIMEOUT: MonotonicDuration = MonotonicDuration::from_seconds(10);
85/// Maximum length of time to wait for the reset earcon to play (after `RESET_TIMEOUT` elapses).
86const EARCON_TIMEOUT: MonotonicDuration = MonotonicDuration::from_millis(2000);
87
88type NotifyFn = Box<
89    dyn Fn(
90            &(FactoryResetState, metrics::MetricsLogger),
91            FactoryResetCountdownWatchResponder,
92        ) -> bool
93        + Send,
94>;
95type ResetCountdownHangingGet = HangingGet<
96    (FactoryResetState, metrics::MetricsLogger),
97    FactoryResetCountdownWatchResponder,
98    NotifyFn,
99>;
100
101/// A [`FactoryResetHandler`] tracks the state of the consumer control buttons
102/// and starts the factory reset process after appropriate timeouts.
103pub struct FactoryResetHandler {
104    factory_reset_state: RefCell<FactoryResetState>,
105    countdown_hanging_get: RefCell<ResetCountdownHangingGet>,
106
107    /// The inventory of this handler's Inspect status.
108    pub inspect_status: InputHandlerStatus,
109
110    metrics_logger: metrics::MetricsLogger,
111}
112
113/// Uses the `ConsumerControlsEvent` to determine whether the device should
114/// start the Factory Reset process. The driver will turn special button
115/// combinations into a `FactoryReset` signal so this code only needs to
116/// listen for that.
117fn is_reset_requested(event: &ConsumerControlsEvent) -> bool {
118    event.pressed_buttons.iter().any(|button| match button {
119        fidl_input_report::ConsumerControlButton::FactoryReset => true,
120        _ => false,
121    })
122}
123
124impl FactoryResetHandler {
125    /// Creates a new [`FactoryResetHandler`] that listens for the reset button
126    /// and handles timing down and, ultimately, factory resetting the device.
127    pub fn new(
128        input_handlers_node: &fuchsia_inspect::Node,
129        metrics_logger: metrics::MetricsLogger,
130    ) -> Rc<Self> {
131        let initial_state = if Path::new(FACTORY_RESET_DISALLOWED_PATH).exists() {
132            FactoryResetState::Disallowed
133        } else {
134            FactoryResetState::Idle
135        };
136
137        let countdown_hanging_get =
138            FactoryResetHandler::init_hanging_get(initial_state, metrics_logger.clone());
139        let inspect_status = InputHandlerStatus::new(
140            input_handlers_node,
141            "factory_reset_handler",
142            /* generates_events */ false,
143        );
144
145        Rc::new(Self {
146            factory_reset_state: RefCell::new(initial_state),
147            countdown_hanging_get: RefCell::new(countdown_hanging_get),
148            inspect_status,
149            metrics_logger,
150        })
151    }
152
153    /// Handles the request stream for FactoryResetCountdown
154    ///
155    /// # Parameters
156    /// `stream`: The `FactoryResetCountdownRequestStream` to be handled.
157    pub fn handle_factory_reset_countdown_request_stream(
158        self: Rc<Self>,
159        mut stream: FactoryResetCountdownRequestStream,
160    ) -> impl futures::Future<Output = Result<(), Error>> {
161        let subscriber = self.countdown_hanging_get.borrow_mut().new_subscriber();
162
163        async move {
164            while let Some(request_result) = stream.next().await {
165                let watcher = request_result?
166                    .into_watch()
167                    .ok_or_else(|| anyhow!("Failed to get FactoryResetCoundown Watcher"))?;
168                subscriber.register(watcher)?;
169            }
170
171            Ok(())
172        }
173    }
174
175    /// Handles the request stream for fuchsia.recovery.policy.Device
176    ///
177    /// # Parameters
178    /// `stream`: The `DeviceRequestStream` to be handled.
179    pub fn handle_recovery_policy_device_request_stream(
180        self: Rc<Self>,
181        mut stream: DeviceRequestStream,
182    ) -> impl futures::Future<Output = Result<(), Error>> {
183        async move {
184            while let Some(request_result) = stream.next().await {
185                let DeviceRequest::SetIsLocalResetAllowed { allowed, .. } = request_result?;
186                match self.factory_reset_state() {
187                    FactoryResetState::Disallowed if allowed => {
188                        // Update state and delete file
189                        self.set_factory_reset_state(FactoryResetState::Idle);
190                        fs::remove_file(FACTORY_RESET_DISALLOWED_PATH).with_context(|| {
191                            format!("failed to remove {}", FACTORY_RESET_DISALLOWED_PATH)
192                        })?
193                    }
194                    _ if !allowed => {
195                        // Update state and create file
196                        self.set_factory_reset_state(FactoryResetState::Disallowed);
197                        let _: File =
198                            File::create(FACTORY_RESET_DISALLOWED_PATH).with_context(|| {
199                                format!("failed to create {}", FACTORY_RESET_DISALLOWED_PATH)
200                            })?;
201                    }
202                    _ => (),
203                }
204            }
205
206            Ok(())
207        }
208    }
209
210    /// Handles `ConsumerControlEvent`s when the device is in the
211    /// `FactoryResetState::Idle` state
212    async fn handle_allowed_event(self: &Rc<Self>, event: &ConsumerControlsEvent) {
213        if is_reset_requested(event) {
214            if let Err(error) = self.start_button_countdown().await {
215                self.metrics_logger.log_error(
216                    InputPipelineErrorMetricDimensionEvent::FactoryResetFailedToReset,
217                    std::format!("Failed to factory reset device: {:?}", error),
218                );
219            }
220        }
221    }
222
223    /// Handles `ConsumerControlEvent`s when the device is in the
224    /// `FactoryResetState::Disallowed` state
225    fn handle_disallowed_event(self: &Rc<Self>, event: &ConsumerControlsEvent) {
226        if is_reset_requested(event) {
227            self.metrics_logger.log_error(
228                InputPipelineErrorMetricDimensionEvent::FactoryResetNotAllowedReset,
229                "Attempted to factory reset a device that is not allowed to reset",
230            );
231        }
232    }
233
234    /// Handles `ConsumerControlEvent`s when the device is in the
235    /// `FactoryResetState::ButtonCountdown` state
236    fn handle_button_countdown_event(self: &Rc<Self>, event: &ConsumerControlsEvent) {
237        if !is_reset_requested(event) {
238            // Cancel button timeout
239            self.set_factory_reset_state(FactoryResetState::Idle);
240        }
241    }
242
243    /// Handles `ConsumerControlEvent`s when the device is in the
244    /// `FactoryResetState::ResetCountdown` state
245    fn handle_reset_countdown_event(self: &Rc<Self>, event: &ConsumerControlsEvent) {
246        if !is_reset_requested(event) {
247            // Cancel reset timeout
248            self.set_factory_reset_state(FactoryResetState::Idle);
249        }
250    }
251
252    fn init_hanging_get(
253        initial_state: FactoryResetState,
254        metrics_logger: metrics::MetricsLogger,
255    ) -> ResetCountdownHangingGet {
256        let notify_fn: NotifyFn = Box::new(|(state, metrics_logger), responder| {
257            let deadline = match state {
258                FactoryResetState::ResetCountdown { deadline } => {
259                    Some(deadline.into_nanos() as i64)
260                }
261                _ => None,
262            };
263
264            let countdown_state =
265                FactoryResetCountdownState { scheduled_reset_time: deadline, ..Default::default() };
266
267            if responder.send(&countdown_state).is_err() {
268                metrics_logger.log_error(
269                    InputPipelineErrorMetricDimensionEvent::FactoryResetFailedToSendCountdown,
270                    "Failed to send factory reset countdown state",
271                );
272            }
273
274            true
275        });
276
277        ResetCountdownHangingGet::new((initial_state, metrics_logger), notify_fn)
278    }
279
280    /// Sets the state of FactoryResetHandler and notifies watchers of the updated state.
281    fn set_factory_reset_state(self: &Rc<Self>, state: FactoryResetState) {
282        *self.factory_reset_state.borrow_mut() = state;
283        self.countdown_hanging_get
284            .borrow_mut()
285            .new_publisher()
286            .set((state, self.metrics_logger.clone()));
287    }
288
289    fn factory_reset_state(self: &Rc<Self>) -> FactoryResetState {
290        *self.factory_reset_state.borrow()
291    }
292
293    /// Handles waiting for the reset button to be held down long enough to start
294    /// the factory reset countdown.
295    async fn start_button_countdown(self: &Rc<Self>) -> Result<(), Error> {
296        let deadline = MonotonicInstant::after(BUTTON_TIMEOUT);
297        self.set_factory_reset_state(FactoryResetState::ButtonCountdown { deadline });
298
299        // Wait for button timeout
300        Timer::new(MonotonicInstant::after(BUTTON_TIMEOUT)).await;
301
302        // Make sure the buttons are still held
303        match self.factory_reset_state() {
304            FactoryResetState::ButtonCountdown { deadline: state_deadline }
305                if state_deadline == deadline =>
306            {
307                // Proceed with reset.
308                self.start_reset_countdown().await?;
309            }
310            _ => {
311                log::info!("Factory reset request cancelled");
312            }
313        }
314
315        Ok(())
316    }
317
318    /// Handles waiting for the reset countdown to complete before resetting the
319    /// device.
320    async fn start_reset_countdown(self: &Rc<Self>) -> Result<(), Error> {
321        let deadline = MonotonicInstant::after(RESET_TIMEOUT);
322        self.set_factory_reset_state(FactoryResetState::ResetCountdown { deadline });
323
324        // Wait for reset timeout
325        Timer::new(MonotonicInstant::after(RESET_TIMEOUT)).await;
326
327        // Make sure the buttons are still held
328        match self.factory_reset_state() {
329            FactoryResetState::ResetCountdown { deadline: state_deadline }
330                if state_deadline == deadline =>
331            {
332                // Proceed with reset.
333                self.reset().await?;
334            }
335            _ => {
336                log::info!("Factory reset request cancelled");
337            }
338        }
339
340        Ok(())
341    }
342
343    /// Retrieves and plays the sound associated with factory resetting the device.
344    async fn play_reset_sound(self: &Rc<Self>) -> Result<(), Error> {
345        log::debug!("Getting sound");
346        // Get sound
347        let (sound_endpoint, server_end) = fidl::endpoints::create_endpoints::<fio::FileMarker>();
348        let () = fuchsia_fs::file::open_channel_in_namespace(
349            FACTORY_RESET_SOUND_PATH,
350            fuchsia_fs::PERM_READABLE,
351            server_end,
352        )
353        .context("Failed to open factory reset sound file")?;
354
355        log::debug!("Playing sound");
356        // Play sound
357        let sound_player = fuchsia_component::client::connect_to_protocol::<PlayerMarker>()
358            .with_context(|| format!("failed to connect to {}", PlayerMarker::PROTOCOL_NAME))?;
359
360        log::debug!("Connected to player");
361        let sound_id = 0;
362        let _duration: i64 = sound_player
363            .add_sound_from_file(sound_id, sound_endpoint)
364            .await
365            .context("AddSoundFromFile error")?
366            .map_err(zx::Status::from_raw)
367            .context("AddSoundFromFile failed")?;
368        log::debug!("Added sound from file");
369
370        sound_player
371            .play_sound2(sound_id, AudioRenderUsage2::Media)
372            .await
373            .context("PlaySound2 error")?
374            .map_err(|err: PlaySoundError| anyhow!("PlaySound2 failed: {:?}", err))?;
375
376        log::debug!("Played sound");
377
378        Ok(())
379    }
380
381    /// Performs the actual factory reset.
382    async fn reset(self: &Rc<Self>) -> Result<(), Error> {
383        log::info!("Beginning reset sequence");
384        if let Err(error) = self
385            .play_reset_sound()
386            .on_timeout(EARCON_TIMEOUT, || Err(anyhow!("play_reset_sound took too long")))
387            .await
388        {
389            log::warn!("Failed to play reset sound: {:?}", error);
390        }
391
392        // Trigger reset
393        self.set_factory_reset_state(FactoryResetState::Resetting);
394        log::info!("Calling {}.Reset", FactoryResetMarker::PROTOCOL_NAME);
395        let factory_reset = fuchsia_component::client::connect_to_protocol::<FactoryResetMarker>()
396            .with_context(|| {
397                format!("failed to connect to {}", FactoryResetMarker::PROTOCOL_NAME)
398            })?;
399        factory_reset.reset().await.context("failed while calling Reset")?;
400        Ok(())
401    }
402}
403
404impl Handler for FactoryResetHandler {
405    fn set_handler_healthy(self: std::rc::Rc<Self>) {
406        self.inspect_status.health_node.borrow_mut().set_ok();
407    }
408
409    fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
410        self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
411    }
412
413    fn get_name(&self) -> &'static str {
414        "FactoryResetHandler"
415    }
416
417    fn interest(&self) -> Vec<input_device::InputEventType> {
418        vec![input_device::InputEventType::ConsumerControls]
419    }
420}
421
422#[async_trait(?Send)]
423impl UnhandledInputHandler for FactoryResetHandler {
424    /// This InputHandler doesn't consume any input events. It just passes them on to the next handler in the pipeline.
425    /// Since it doesn't need exclusive access to the events this seems like the best way to avoid handlers further
426    /// down the pipeline missing events that they need.
427    async fn handle_unhandled_input_event(
428        self: Rc<Self>,
429        unhandled_input_event: input_device::UnhandledInputEvent,
430    ) -> Vec<input_device::InputEvent> {
431        fuchsia_trace::duration!("input", "factory_reset_handler");
432        match unhandled_input_event {
433            input_device::UnhandledInputEvent {
434                device_event: input_device::InputDeviceEvent::ConsumerControls(ref event),
435                device_descriptor: input_device::InputDeviceDescriptor::ConsumerControls(_),
436                event_time,
437                trace_id: _,
438            } => {
439                fuchsia_trace::duration!("input", "factory_reset_handler[processing]");
440                self.inspect_status.count_received_event(&event_time);
441                match self.factory_reset_state() {
442                    FactoryResetState::Idle => {
443                        let event_clone = event.clone();
444                        Task::local(async move { self.handle_allowed_event(&event_clone).await })
445                            .detach()
446                    }
447                    FactoryResetState::Disallowed => self.handle_disallowed_event(event),
448                    FactoryResetState::ButtonCountdown { deadline: _ } => {
449                        self.handle_button_countdown_event(event)
450                    }
451                    FactoryResetState::ResetCountdown { deadline: _ } => {
452                        self.handle_reset_countdown_event(event)
453                    }
454                    FactoryResetState::Resetting => {
455                        log::warn!("Recieved an input event while factory resetting the device")
456                    }
457                };
458            }
459            _ => {
460                // TODO: b/478249522 - add cobalt logging
461                log::warn!("Unhandled input event: {:?}", unhandled_input_event.get_event_type());
462            }
463        };
464
465        vec![input_device::InputEvent::from(unhandled_input_event)]
466    }
467}
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472    use crate::consumer_controls_binding::ConsumerControlsDeviceDescriptor;
473    use crate::input_handler::InputHandler;
474    use assert_matches::assert_matches;
475    use fidl::endpoints::create_proxy_and_stream;
476    use fidl_fuchsia_recovery_policy::{DeviceMarker, DeviceProxy};
477    use fidl_fuchsia_recovery_ui::{FactoryResetCountdownMarker, FactoryResetCountdownProxy};
478    use fuchsia_async::TestExecutor;
479    use pretty_assertions::assert_eq;
480    use std::pin::pin;
481    use std::task::Poll;
482
483    fn create_factory_reset_countdown_proxy(
484        reset_handler: Rc<FactoryResetHandler>,
485    ) -> FactoryResetCountdownProxy {
486        let (countdown_proxy, countdown_stream) =
487            create_proxy_and_stream::<FactoryResetCountdownMarker>();
488
489        let stream_fut =
490            reset_handler.clone().handle_factory_reset_countdown_request_stream(countdown_stream);
491
492        Task::local(async move {
493            if stream_fut.await.is_err() {
494                log::warn!("Failed to handle factory reset countdown request stream");
495            }
496        })
497        .detach();
498
499        countdown_proxy
500    }
501
502    fn create_recovery_policy_proxy(reset_handler: Rc<FactoryResetHandler>) -> DeviceProxy {
503        let (device_proxy, device_stream) = create_proxy_and_stream::<DeviceMarker>();
504
505        Task::local(async move {
506            if reset_handler
507                .handle_recovery_policy_device_request_stream(device_stream)
508                .await
509                .is_err()
510            {
511                log::warn!("Failed to handle recovery policy device request stream");
512            }
513        })
514        .detach();
515
516        device_proxy
517    }
518
519    fn create_input_device_descriptor() -> input_device::InputDeviceDescriptor {
520        input_device::InputDeviceDescriptor::ConsumerControls(ConsumerControlsDeviceDescriptor {
521            buttons: vec![
522                fidl_input_report::ConsumerControlButton::CameraDisable,
523                fidl_input_report::ConsumerControlButton::FactoryReset,
524                fidl_input_report::ConsumerControlButton::MicMute,
525                fidl_input_report::ConsumerControlButton::Pause,
526                fidl_input_report::ConsumerControlButton::VolumeDown,
527                fidl_input_report::ConsumerControlButton::VolumeUp,
528            ],
529            device_id: 0,
530        })
531    }
532
533    fn create_reset_consumer_controls_event() -> ConsumerControlsEvent {
534        ConsumerControlsEvent::new(
535            vec![fidl_input_report::ConsumerControlButton::FactoryReset],
536            None,
537        )
538    }
539
540    fn create_non_reset_consumer_controls_event() -> ConsumerControlsEvent {
541        ConsumerControlsEvent::new(
542            vec![
543                fidl_input_report::ConsumerControlButton::CameraDisable,
544                fidl_input_report::ConsumerControlButton::MicMute,
545                fidl_input_report::ConsumerControlButton::Pause,
546                fidl_input_report::ConsumerControlButton::VolumeDown,
547                fidl_input_report::ConsumerControlButton::VolumeUp,
548            ],
549            None,
550        )
551    }
552
553    fn create_non_reset_input_event() -> input_device::UnhandledInputEvent {
554        let device_event = input_device::InputDeviceEvent::ConsumerControls(
555            create_non_reset_consumer_controls_event(),
556        );
557
558        input_device::UnhandledInputEvent {
559            device_event,
560            device_descriptor: create_input_device_descriptor(),
561            event_time: zx::MonotonicInstant::get(),
562            trace_id: None,
563        }
564    }
565
566    fn create_reset_input_event() -> input_device::UnhandledInputEvent {
567        let device_event = input_device::InputDeviceEvent::ConsumerControls(
568            create_reset_consumer_controls_event(),
569        );
570
571        input_device::UnhandledInputEvent {
572            device_event,
573            device_descriptor: create_input_device_descriptor(),
574            event_time: zx::MonotonicInstant::get(),
575            trace_id: None,
576        }
577    }
578
579    #[fuchsia::test]
580    async fn is_reset_requested_looks_for_reset_signal() {
581        let reset_event = create_reset_consumer_controls_event();
582        let non_reset_event = create_non_reset_consumer_controls_event();
583
584        assert!(
585            is_reset_requested(&reset_event),
586            "Should reset when the reset signal is received."
587        );
588        assert!(
589            !is_reset_requested(&non_reset_event),
590            "Should only reset when the reset signal is received."
591        );
592    }
593
594    #[fuchsia::test]
595    async fn factory_reset_countdown_listener_gets_initial_state() {
596        let inspector = fuchsia_inspect::Inspector::default();
597        let test_node = inspector.root().create_child("test_node");
598        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
599        let countdown_proxy = create_factory_reset_countdown_proxy(reset_handler.clone());
600        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
601        assert_eq!(reset_state.scheduled_reset_time, None);
602        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
603    }
604
605    #[fuchsia::test]
606    fn factory_reset_countdown_listener_is_notified_on_state_change() -> Result<(), Error> {
607        let mut executor = TestExecutor::new_with_fake_time();
608        let inspector = fuchsia_inspect::Inspector::default();
609        let test_node = inspector.root().create_child("test_node");
610        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
611        let countdown_proxy = create_factory_reset_countdown_proxy(reset_handler.clone());
612
613        let get_countdown_state = |executor: &mut TestExecutor| {
614            let mut fut = countdown_proxy.watch();
615            loop {
616                // NB: cannot call run_singlethreaded on an executor with a fake clock.
617                match executor.run_until_stalled(&mut fut) {
618                    Poll::Pending => continue,
619                    Poll::Ready(state) => {
620                        return state.expect("Failed to get countdown state");
621                    }
622                }
623            }
624        };
625
626        // The initial state should be no scheduled reset time and the
627        // FactoryRestHandler state should be FactoryResetState::Idle
628        let countdown_state = get_countdown_state(&mut executor);
629        let handler_state = reset_handler.factory_reset_state();
630        assert_eq!(countdown_state.scheduled_reset_time, None);
631        assert_eq!(handler_state, FactoryResetState::Idle);
632
633        // Send a reset event
634        let reset_event = create_reset_input_event();
635        let mut handle_input_event_fut =
636            pin!(reset_handler.clone().handle_unhandled_input_event(reset_event));
637        assert_matches!(executor.run_until_stalled(&mut handle_input_event_fut), Poll::Ready(events) => {
638            assert_matches!(events.as_slice(), [input_device::InputEvent { .. }]);
639        });
640
641        // The next state will be FactoryResetState::ButtonCountdown with no scheduled reset
642        let countdown_state = get_countdown_state(&mut executor);
643        let handler_state = reset_handler.factory_reset_state();
644        assert_eq!(countdown_state.scheduled_reset_time, None);
645        assert_matches!(handler_state, FactoryResetState::ButtonCountdown { deadline: _ });
646
647        // Skip ahead 500ms for the ButtonCountdown
648        executor.set_fake_time(MonotonicInstant::after(MonotonicDuration::from_millis(500)));
649        executor.wake_expired_timers();
650
651        // After the ButtonCountdown the reset_handler enters the
652        // FactoryResetState::ResetCountdown state WITH a scheduled reset time.
653        let countdown_state = get_countdown_state(&mut executor);
654        let handler_state = reset_handler.factory_reset_state();
655        assert_matches!(countdown_state.scheduled_reset_time, Some(_));
656        assert_matches!(handler_state, FactoryResetState::ResetCountdown { deadline: _ });
657
658        // Skip ahead 10s for the ResetCountdown
659        executor.set_fake_time(MonotonicInstant::after(MonotonicDuration::from_seconds(10)));
660        executor.wake_expired_timers();
661
662        // After the ResetCountdown the reset_handler enters the
663        // FactoryResetState::Resetting state with no scheduled reset time.
664        let countdown_state = get_countdown_state(&mut executor);
665        let handler_state = reset_handler.factory_reset_state();
666        assert_eq!(countdown_state.scheduled_reset_time, None);
667        assert_eq!(handler_state, FactoryResetState::Resetting);
668
669        Ok(())
670    }
671
672    #[fuchsia::test]
673    async fn recovery_policy_requests_update_reset_handler_state() {
674        let inspector = fuchsia_inspect::Inspector::default();
675        let test_node = inspector.root().create_child("test_node");
676        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
677        let countdown_proxy = create_factory_reset_countdown_proxy(reset_handler.clone());
678
679        // Initial state should be FactoryResetState::Idle with no scheduled reset
680        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
681        assert_eq!(reset_state.scheduled_reset_time, None);
682        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
683
684        // Set FactoryResetState to Disallow
685        let device_proxy = create_recovery_policy_proxy(reset_handler.clone());
686        device_proxy.set_is_local_reset_allowed(false).expect("Failed to set recovery policy");
687
688        // State should now be in Disallow and scheduled_reset_time should be None
689        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
690        assert_eq!(reset_state.scheduled_reset_time, None);
691        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Disallowed);
692
693        // Send reset event
694        let reset_event = create_reset_input_event();
695        reset_handler.clone().handle_unhandled_input_event(reset_event).await;
696
697        // State should still be Disallow
698        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Disallowed);
699
700        // Set the state back to Allow
701        let device_proxy = create_recovery_policy_proxy(reset_handler.clone());
702        device_proxy.set_is_local_reset_allowed(true).expect("Failed to set recovery policy");
703
704        // State should be FactoryResetState::Idle with no scheduled reset
705        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
706        assert_eq!(reset_state.scheduled_reset_time, None);
707        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
708    }
709
710    #[fuchsia::test]
711    fn handle_allowed_event_changes_state_with_reset() {
712        let mut executor = TestExecutor::new();
713
714        let reset_event = create_reset_consumer_controls_event();
715        let inspector = fuchsia_inspect::Inspector::default();
716        let test_node = inspector.root().create_child("test_node");
717        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
718        let countdown_proxy = create_factory_reset_countdown_proxy(reset_handler.clone());
719
720        // Initial state should be FactoryResetState::Idle with no scheduled reset
721        let reset_state = executor
722            .run_singlethreaded(countdown_proxy.watch())
723            .expect("Failed to get countdown state");
724        assert_eq!(reset_state.scheduled_reset_time, None);
725        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
726
727        let handle_allowed_event_fut = reset_handler.handle_allowed_event(&reset_event);
728        futures::pin_mut!(handle_allowed_event_fut);
729        assert_eq!(executor.run_until_stalled(&mut handle_allowed_event_fut), Poll::Pending);
730
731        // This should result in the reset handler entering the ButtonCountdown state
732        assert_matches!(
733            executor.run_singlethreaded(countdown_proxy.watch()),
734            Ok(FactoryResetCountdownState { scheduled_reset_time: None, .. })
735        );
736        assert_matches!(
737            reset_handler.factory_reset_state(),
738            FactoryResetState::ButtonCountdown { deadline: _ }
739        );
740    }
741
742    #[fuchsia::test]
743    async fn handle_allowed_event_wont_change_state_without_reset() {
744        let inspector = fuchsia_inspect::Inspector::default();
745        let test_node = inspector.root().create_child("test_node");
746        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
747        let countdown_proxy = create_factory_reset_countdown_proxy(reset_handler.clone());
748
749        // Initial state should be FactoryResetState::Idle with no scheduled reset
750        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
751        assert_eq!(reset_state.scheduled_reset_time, None);
752        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
753
754        let non_reset_event = create_non_reset_consumer_controls_event();
755        reset_handler.clone().handle_allowed_event(&non_reset_event).await;
756
757        // This should result in the reset handler staying in the Allowed state
758        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
759    }
760
761    #[fuchsia::test]
762    async fn handle_disallowed_event_wont_change_state() {
763        let inspector = fuchsia_inspect::Inspector::default();
764        let test_node = inspector.root().create_child("test_node");
765        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
766        *reset_handler.factory_reset_state.borrow_mut() = FactoryResetState::Disallowed;
767
768        // Calling handle_disallowed_event shouldn't change the state no matter
769        // what the contents of the event are
770        let reset_event = create_reset_consumer_controls_event();
771        reset_handler.handle_disallowed_event(&reset_event);
772        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Disallowed);
773
774        let non_reset_event = create_non_reset_consumer_controls_event();
775        reset_handler.handle_disallowed_event(&non_reset_event);
776        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Disallowed);
777    }
778
779    #[fuchsia::test]
780    async fn handle_button_countdown_event_changes_state_when_reset_no_longer_requested() {
781        let inspector = fuchsia_inspect::Inspector::default();
782        let test_node = inspector.root().create_child("test_node");
783        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
784
785        let deadline = MonotonicInstant::after(BUTTON_TIMEOUT);
786        *reset_handler.factory_reset_state.borrow_mut() =
787            FactoryResetState::ButtonCountdown { deadline };
788
789        // Calling handle_button_countdown_event should reset the handler
790        // to the idle state
791        let non_reset_event = create_non_reset_consumer_controls_event();
792        reset_handler.handle_button_countdown_event(&non_reset_event);
793        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
794    }
795
796    #[fuchsia::test]
797    async fn handle_reset_countdown_event_changes_state_when_reset_no_longer_requested() {
798        let inspector = fuchsia_inspect::Inspector::default();
799        let test_node = inspector.root().create_child("test_node");
800        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
801
802        *reset_handler.factory_reset_state.borrow_mut() =
803            FactoryResetState::ResetCountdown { deadline: MonotonicInstant::now() };
804
805        // Calling handle_reset_countdown_event should reset the handler
806        // to the idle state
807        let non_reset_event = create_non_reset_consumer_controls_event();
808        reset_handler.handle_reset_countdown_event(&non_reset_event);
809        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
810    }
811
812    #[fuchsia::test]
813    async fn factory_reset_disallowed_during_button_countdown() {
814        let inspector = fuchsia_inspect::Inspector::default();
815        let test_node = inspector.root().create_child("test_node");
816        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
817        let countdown_proxy = create_factory_reset_countdown_proxy(reset_handler.clone());
818
819        // Initial state should be FactoryResetState::Idle with no scheduled reset
820        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
821        assert_eq!(reset_state.scheduled_reset_time, None);
822        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
823
824        // Send reset event
825        let reset_event = create_reset_input_event();
826        reset_handler.clone().handle_unhandled_input_event(reset_event).await;
827
828        // State should now be ButtonCountdown and scheduled_reset_time should be None
829        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
830        assert_eq!(reset_state.scheduled_reset_time, None);
831        assert_matches!(
832            reset_handler.factory_reset_state(),
833            FactoryResetState::ButtonCountdown { deadline: _ }
834        );
835
836        // Set FactoryResetState to Disallow
837        let device_proxy = create_recovery_policy_proxy(reset_handler.clone());
838        device_proxy.set_is_local_reset_allowed(false).expect("Failed to set recovery policy");
839
840        // State should now be in Disallow and scheduled_reset_time should be None
841        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
842        assert_eq!(reset_state.scheduled_reset_time, None);
843        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Disallowed);
844    }
845
846    #[fuchsia::test]
847    async fn factory_reset_disallowed_during_reset_countdown() {
848        let inspector = fuchsia_inspect::Inspector::default();
849        let test_node = inspector.root().create_child("test_node");
850        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
851        let countdown_proxy = create_factory_reset_countdown_proxy(reset_handler.clone());
852
853        // Initial state should be FactoryResetState::Idle with no scheduled reset
854        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
855        assert_eq!(reset_state.scheduled_reset_time, None);
856        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
857
858        // Send reset event
859        let reset_event = create_reset_input_event();
860        reset_handler.clone().handle_unhandled_input_event(reset_event).await;
861
862        // State should now be ButtonCountdown and scheduled_reset_time should be None
863        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
864        assert_eq!(reset_state.scheduled_reset_time, None);
865        assert_matches!(
866            reset_handler.factory_reset_state(),
867            FactoryResetState::ButtonCountdown { deadline: _ }
868        );
869
870        // State should now be ResetCountdown and scheduled_reset_time should be Some
871        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
872        assert_matches!(reset_state.scheduled_reset_time, Some(_));
873        assert_matches!(
874            reset_handler.factory_reset_state(),
875            FactoryResetState::ResetCountdown { deadline: _ }
876        );
877
878        // Set FactoryResetState to Disallow
879        let device_proxy = create_recovery_policy_proxy(reset_handler.clone());
880        device_proxy.set_is_local_reset_allowed(false).expect("Failed to set recovery policy");
881
882        // State should now be in Disallow and scheduled_reset_time should be None
883        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
884        assert_eq!(reset_state.scheduled_reset_time, None);
885        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Disallowed);
886    }
887
888    #[fuchsia::test]
889    async fn factory_reset_cancelled_during_button_countdown() {
890        let inspector = fuchsia_inspect::Inspector::default();
891        let test_node = inspector.root().create_child("test_node");
892        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
893        let countdown_proxy = create_factory_reset_countdown_proxy(reset_handler.clone());
894
895        // Initial state should be FactoryResetState::Idle with no scheduled reset
896        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
897        assert_eq!(reset_state.scheduled_reset_time, None);
898        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
899
900        // Send reset event
901        let reset_event = create_reset_input_event();
902        reset_handler.clone().handle_unhandled_input_event(reset_event).await;
903
904        // State should now be ButtonCountdown and scheduled_reset_time should be None
905        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
906        assert_eq!(reset_state.scheduled_reset_time, None);
907        assert_matches!(
908            reset_handler.factory_reset_state(),
909            FactoryResetState::ButtonCountdown { deadline: _ }
910        );
911
912        // Pass in an event to simulate releasing the reset button
913        let non_reset_event = create_non_reset_input_event();
914        reset_handler.clone().handle_unhandled_input_event(non_reset_event).await;
915
916        // State should now be in Idle and scheduled_reset_time should be None
917        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
918        assert_eq!(reset_state.scheduled_reset_time, None);
919        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
920    }
921
922    #[fuchsia::test]
923    async fn factory_reset_cancelled_during_reset_countdown() {
924        let inspector = fuchsia_inspect::Inspector::default();
925        let test_node = inspector.root().create_child("test_node");
926        let reset_handler = FactoryResetHandler::new(&test_node, metrics::MetricsLogger::default());
927        let countdown_proxy = create_factory_reset_countdown_proxy(reset_handler.clone());
928
929        // Initial state should be FactoryResetState::Idle with no scheduled reset
930        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
931        assert_eq!(reset_state.scheduled_reset_time, None);
932        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
933
934        // Send reset event
935        let reset_event = create_reset_input_event();
936        reset_handler.clone().handle_unhandled_input_event(reset_event).await;
937
938        // State should now be ButtonCountdown and scheduled_reset_time should be None
939        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
940        assert_eq!(reset_state.scheduled_reset_time, None);
941        assert_matches!(
942            reset_handler.factory_reset_state(),
943            FactoryResetState::ButtonCountdown { deadline: _ }
944        );
945
946        // State should now be ResetCountdown and scheduled_reset_time should be Some
947        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
948        assert_matches!(reset_state.scheduled_reset_time, Some(_));
949        assert_matches!(
950            reset_handler.factory_reset_state(),
951            FactoryResetState::ResetCountdown { deadline: _ }
952        );
953
954        // Pass in an event to simulate releasing the reset button
955        let non_reset_event = create_non_reset_input_event();
956        reset_handler.clone().handle_unhandled_input_event(non_reset_event).await;
957
958        // State should now be in Idle and scheduled_reset_time should be None
959        let reset_state = countdown_proxy.watch().await.expect("Failed to get countdown state");
960        assert_eq!(reset_state.scheduled_reset_time, None);
961        assert_eq!(reset_handler.factory_reset_state(), FactoryResetState::Idle);
962    }
963
964    #[fuchsia::test]
965    async fn factory_reset_handler_initialized_with_inspect_node() {
966        let inspector = fuchsia_inspect::Inspector::default();
967        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
968        let _handler =
969            FactoryResetHandler::new(&fake_handlers_node, metrics::MetricsLogger::default());
970        diagnostics_assertions::assert_data_tree!(inspector, root: {
971            input_handlers_node: {
972                factory_reset_handler: {
973                    events_received_count: 0u64,
974                    events_handled_count: 0u64,
975                    last_received_timestamp_ns: 0u64,
976                    "fuchsia.inspect.Health": {
977                        status: "STARTING_UP",
978                        // Timestamp value is unpredictable and not relevant in this context,
979                        // so we only assert that the property is present.
980                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
981                    },
982                }
983            }
984        });
985    }
986
987    #[fuchsia::test]
988    async fn factory_reset_handler_inspect_counts_events() {
989        let inspector = fuchsia_inspect::Inspector::default();
990        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
991        let reset_handler =
992            FactoryResetHandler::new(&fake_handlers_node, metrics::MetricsLogger::default());
993
994        // Send reset event
995        let reset_event = create_reset_input_event();
996        reset_handler.clone().handle_unhandled_input_event(reset_event).await;
997
998        // Send handled event that should be ignored.
999        let mut handled_event = input_device::InputEvent::from(create_reset_input_event());
1000        handled_event.handled = input_device::Handled::Yes;
1001        reset_handler.clone().handle_input_event(handled_event).await;
1002
1003        // Send event to simulate releasing the reset button
1004        let non_reset_event = create_non_reset_input_event();
1005        let last_event_timestamp: u64 =
1006            non_reset_event.clone().event_time.into_nanos().try_into().unwrap();
1007        reset_handler.clone().handle_unhandled_input_event(non_reset_event).await;
1008
1009        diagnostics_assertions::assert_data_tree!(inspector, root: {
1010            input_handlers_node: {
1011                factory_reset_handler: {
1012                    events_received_count: 2u64,
1013                    events_handled_count: 0u64,
1014                    last_received_timestamp_ns: last_event_timestamp,
1015                    "fuchsia.inspect.Health": {
1016                        status: "STARTING_UP",
1017                        // Timestamp value is unpredictable and not relevant in this context,
1018                        // so we only assert that the property is present.
1019                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
1020                    },
1021                }
1022            }
1023        });
1024    }
1025}