Skip to main content

recovery_ui/
screens.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::check_network::CheckNetworkViewAssistant;
6#[allow(unused)]
7use crate::constants::constants::{
8    ScreenSplit, ICON_FACTORY_RESET, ICON_REINSTALL_SOFTWARE, IMAGE_DEFAULT_SIZE,
9    IMAGE_DEVICE_CONNECT, IMAGE_DEVICE_INSTALL, IMAGE_DEVICE_UPDATING, IMAGE_DEVICE_UPDATING_SIZE,
10    IMAGE_PHONE_HOME_APP, IMAGE_PHONE_HOME_APP_SIZE, IMAGE_RESET_FAILED,
11};
12use crate::font;
13use crate::generic_view::{ButtonInfo, GenericSplitViewAssistant};
14use crate::keyboard::KeyboardViewAssistant;
15use crate::network::NetworkViewAssistant;
16use crate::proxy_view_assistant::ProxyMessages;
17use crate::text_field::TextVisibility;
18use carnelian::{make_message, AppSender, MessageTarget, ViewAssistantPtr, ViewKey};
19use recovery_util::ota::state_machine::DataSharingConsent::Allow;
20use recovery_util::ota::state_machine::{
21    DataSharingConsent, Event, Operation, State, StateHandler,
22};
23use recovery_util::wlan::NetworkInfo;
24
25pub struct Screens {
26    app_sender: AppSender,
27    view_key: ViewKey,
28}
29
30impl Screens {
31    pub fn new(app_sender: AppSender, view_key: ViewKey) -> Self {
32        Self { app_sender, view_key }
33    }
34
35    pub fn initial_screen(&self) -> ViewAssistantPtr {
36        self.home()
37    }
38
39    fn state(&mut self, state: &State) {
40        let new_view = match state {
41            State::Home => self.home(),
42            State::FactoryReset => self.progress(Operation::FactoryDataReset, 0),
43            State::FactoryResetConfirm => self.factory_reset_confirm(),
44            State::Failed(operation, error) => self.failed(operation, error),
45            State::Reinstall => self.reinstall(),
46            State::Rebooting(_) => self.rebooting(),
47            State::GetWiFiNetworks => self.select_wifi(&Vec::new()),
48            State::SelectWiFi(networks) => self.select_wifi(networks),
49            State::EnterWiFi => {
50                self.user_entry("Enter Network Name".to_string(), TextVisibility::Always)
51            }
52            State::EnterPassword(network) => self.user_entry(
53                format!("Enter Password for {} ", network),
54                TextVisibility::Toggleable(false),
55            ),
56            State::Connecting(network, password) => self.connecting(network, password),
57            State::ConnectionFailed(network, password) => self.connection_failed(network, password),
58            State::ReinstallConfirm { desired: user_data_sharing_consent, reported } => {
59                self.reinstall_confirm(user_data_sharing_consent.clone(), reported.clone())
60            }
61            State::ExecuteReinstall => self.progress(Operation::Reinstall, 0),
62            State::ReinstallRunning { progress, .. } => {
63                // TODO(b/245415603): report actual progress values
64                // Clamping progress to between 0-93%. The progress reports currently arrive as fake timed events
65                // A device staying at 93% indicates OTA is still running
66                let mapped_progess: f32 = (*progress as f32) * 0.93;
67                self.progress(Operation::Reinstall, mapped_progess as u8)
68            }
69            State::FinalizeReinstall(_) => {
70                // TODO(b/258285426): Evaluate UI impact of uploading metrics
71                // Marking progress as 95 percent to indicate metrics uploading and reboot is in progress.
72                self.progress(Operation::Reinstall, 95)
73            }
74        };
75        self.app_sender.queue_message(
76            MessageTarget::View(self.view_key),
77            make_message(ProxyMessages::ReplaceViewAssistant(Some(new_view))),
78        );
79    }
80
81    fn factory_reset_confirm(&self) -> ViewAssistantPtr {
82        let view_assistant_ptr = Box::new(
83            GenericSplitViewAssistant::new(
84                self.app_sender.clone(),
85                self.view_key,
86                ScreenSplit::None,
87                Some("Confirm factory reset".to_string()),
88                Some(
89                    "• This will clear your data from the device and can't be undone\n\
90                     • Upon completion, the device will automatically restart"
91                        .to_string(),
92                ),
93                None,
94                Some(vec![
95                    ButtonInfo::new("Cancel", None, false, true, Event::Cancel),
96                    ButtonInfo::new(
97                        "Start Factory Reset",
98                        Some(ICON_FACTORY_RESET),
99                        false,
100                        false,
101                        Event::StartFactoryReset,
102                    ),
103                ]),
104                None,
105                None,
106                Some(IMAGE_DEVICE_UPDATING),
107                Some(IMAGE_DEVICE_UPDATING_SIZE),
108            )
109            .unwrap(),
110        );
111        view_assistant_ptr
112    }
113
114    fn progress(&self, operation: Operation, percent: u8) -> ViewAssistantPtr {
115        let title = match operation {
116            // Factory resets are currently fast operations. If they take longer progress can be added here.
117            Operation::FactoryDataReset => "Resetting".to_string(),
118            Operation::Reinstall => format!("Updating {}%", percent),
119        };
120        let content = match operation {
121            Operation::FactoryDataReset => "• Resetting user data\n\
122            • Upon completion, the\n   \
123              device will automatically\n   \
124              restart"
125                .to_string(),
126            Operation::Reinstall => "• This may take several minutes\n\
127            • Upon completion, the\n   \
128              device will automatically\n   \
129              restart"
130                .to_string(),
131        };
132        let view_assistant_ptr = Box::new(
133            GenericSplitViewAssistant::new(
134                self.app_sender.clone(),
135                self.view_key,
136                ScreenSplit::Even,
137                Some(title),
138                Some(content),
139                None,
140                None,
141                None,
142                None,
143                Some(IMAGE_DEVICE_UPDATING),
144                Some(IMAGE_DEVICE_UPDATING_SIZE),
145            )
146            .unwrap(),
147        );
148        view_assistant_ptr
149    }
150
151    fn home(&self) -> ViewAssistantPtr {
152        let view_assistant_ptr = Box::new(
153            GenericSplitViewAssistant::new(
154                self.app_sender.clone(),
155                self.view_key,
156                ScreenSplit::Wide,
157                Some("Factory reset".to_string()),
158                Some("• A factory reset can fix a device problem\n• This will clear your data from the device and can't be undone".to_string()),
159                Some("Still having issues after factory reset?".to_string()),
160                Some(vec![ButtonInfo::new("Start factory reset", Some(ICON_FACTORY_RESET), false, false, Event::StartFactoryReset)]),
161                Some(vec![ButtonInfo::new("Try another option", None, true, true, Event::TryAnotherWay)]),
162                None,
163                None,
164                None,
165            )
166                .unwrap(),
167        );
168        view_assistant_ptr
169    }
170
171    fn failed(&self, operation: &Operation, error: &Option<String>) -> ViewAssistantPtr {
172        let text1 = match operation {
173            Operation::FactoryDataReset => "Reset failed",
174            Operation::Reinstall => "Recovery failed",
175        };
176        let error = if let Some(error) = error { error.clone() } else { "".to_string() };
177        let text2 = match operation {
178            Operation::FactoryDataReset => "An error occurred while\nrecovering".to_string(),
179            Operation::Reinstall => error + "\n\nPlease reconnect and try again.",
180        };
181        let view_assistant_ptr = Box::new(
182            GenericSplitViewAssistant::new(
183                self.app_sender.clone(),
184                self.view_key,
185                ScreenSplit::Even,
186                Some(text1.to_string()),
187                Some(text2),
188                None,
189                Some(vec![
190                    ButtonInfo::new("Cancel", None, false, true, Event::Cancel),
191                    ButtonInfo::new("Try again", None, false, false, Event::TryAgain),
192                ]),
193                None,
194                None,
195                Some(IMAGE_RESET_FAILED),
196                None,
197            )
198            .unwrap(),
199        );
200        view_assistant_ptr
201    }
202
203    fn rebooting(&self) -> ViewAssistantPtr {
204        let view_assistant_ptr = Box::new(
205            GenericSplitViewAssistant::new(
206                self.app_sender.clone(),
207                self.view_key,
208                ScreenSplit::Even,
209                Some("Rebooting...".to_string()),
210                Some("Your device will now reboot.".to_string()),
211                None,
212                None,
213                None,
214                None,
215                Some(IMAGE_DEVICE_UPDATING),
216                None,
217            )
218            .unwrap(),
219        );
220        view_assistant_ptr
221    }
222
223    fn reinstall(&self) -> ViewAssistantPtr {
224        let view_assistant_ptr = Box::new(
225            GenericSplitViewAssistant::new(
226                self.app_sender.clone(),
227                self.view_key,
228                ScreenSplit::Wide,
229                Some("Reinstall Software".to_string()),
230                Some(
231                    "• Install the latest software to fix bugs and update the system\n\
232                     • This will clear your data from the device and can't be undone\n\
233                     • You'll need a Wi-Fi connection to proceed"
234                        .to_string(),
235                ),
236                None,
237                Some(vec![
238                    ButtonInfo::new("Cancel", None, false, true, Event::Cancel),
239                    ButtonInfo::new(
240                        "Reinstall Software",
241                        Some(ICON_REINSTALL_SOFTWARE),
242                        false,
243                        false,
244                        Event::Reinstall,
245                    ),
246                ]),
247                Some(vec![ButtonInfo::new("Reboot", None, true, true, Event::Reboot)]),
248                None,
249                None,
250                None,
251            )
252            .unwrap(),
253        );
254        view_assistant_ptr
255    }
256
257    fn select_wifi(&self, networks: &Vec<NetworkInfo>) -> ViewAssistantPtr {
258        let network_view = Box::new(
259            NetworkViewAssistant::new(self.app_sender.clone(), self.view_key, networks.to_vec())
260                .unwrap(),
261        );
262        network_view
263    }
264
265    fn user_entry(&self, text: String, privacy: TextVisibility) -> ViewAssistantPtr {
266        let mut keyboard = Box::new(
267            KeyboardViewAssistant::new(
268                self.app_sender.clone(),
269                self.view_key,
270                font::get_default_font_face().clone(),
271            )
272            .unwrap(),
273        );
274        keyboard.set_field_name(text);
275        keyboard.set_text_field(String::new());
276        keyboard.set_privacy(privacy);
277        keyboard
278    }
279
280    fn connecting(&self, _network: &String, _password: &String) -> ViewAssistantPtr {
281        let view_assistant_ptr = Box::new(
282            GenericSplitViewAssistant::new(
283                self.app_sender.clone(),
284                self.view_key,
285                ScreenSplit::Even,
286                Some("Connecting Wi-Fi".to_string()),
287                Some("Sit tight. This may take a few\nseconds.".to_string()),
288                None,
289                Some(vec![ButtonInfo::new("Cancel", None, false, true, Event::Cancel)]),
290                None,
291                None,
292                Some(IMAGE_DEVICE_CONNECT),
293                Some(IMAGE_DEFAULT_SIZE),
294            )
295            .unwrap(),
296        );
297        view_assistant_ptr
298    }
299
300    fn connection_failed(&self, network: &String, password: &String) -> ViewAssistantPtr {
301        let title_text = "Check Wi-Fi Network".to_string();
302        let body_text = format!(
303            "Incorrect password for Wi-Fi “{}”\n\
304            Try again or choose a different Wi-Fi network.",
305            network
306        );
307        let view_assistant_ptr = Box::new(
308            CheckNetworkViewAssistant::new(
309                self.app_sender.clone(),
310                self.view_key,
311                title_text,
312                body_text,
313                network.into(),
314                password.into(),
315                vec![
316                    ButtonInfo::new("Cancel", None, false, true, Event::Cancel),
317                    ButtonInfo::new("Choose network", None, false, true, Event::ChooseNetwork),
318                    ButtonInfo::new("Try Again", None, false, false, Event::TryAgain),
319                ],
320            )
321            .unwrap(),
322        );
323        view_assistant_ptr
324    }
325
326    fn reinstall_confirm(
327        &self,
328        _desired: DataSharingConsent,
329        reported: DataSharingConsent,
330    ) -> ViewAssistantPtr {
331        let next_privacy_state = match reported {
332            DataSharingConsent::Unknown => Allow,
333            _ => reported.toggle(),
334        };
335        let view_assistant_ptr = Box::new(
336            GenericSplitViewAssistant::new(
337                self.app_sender.clone(),
338                self.view_key,
339                ScreenSplit::Even,
340                Some("Reinstall Software".to_string()),
341                // TODO(b/260539609 Reinstall timing hint text needs an update
342                Some(
343                    "• This will clear your data from the\n  \
344                       device and can't be undone\n\
345                     • It may take several minutes\n\
346                     • Upon completion, the device will\n  \
347                       automatically restart"
348                        .to_string(),
349                ),
350                None,
351                Some(vec![
352                    ButtonInfo::new("Cancel", None, false, true, Event::Cancel),
353                    ButtonInfo::new("Reinstall Now", None, false, false, Event::Reinstall),
354                ]),
355                None,
356                Some(vec![ButtonInfo::new(
357                    "Permission",
358                    None,
359                    false,
360                    reported == Allow, // Unknown and DontAllow show metrics as disabled
361                    Event::SendReports(next_privacy_state),
362                )]),
363                Some(IMAGE_DEVICE_INSTALL),
364                Some(IMAGE_DEFAULT_SIZE),
365            )
366            .unwrap(),
367        );
368        view_assistant_ptr
369    }
370}
371
372impl StateHandler for Screens {
373    fn handle_state(&mut self, state: State) {
374        self.state(&state);
375    }
376}