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