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