Skip to main content

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