1 // Copyright (c) 2012 The Chromium 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 
5 #include "chrome/browser/chromeos/login/screens/update_screen.h"
6 
7 #include <algorithm>
8 
9 #include "base/bind.h"
10 #include "base/files/file_util.h"
11 #include "base/i18n/number_formatting.h"
12 #include "base/logging.h"
13 #include "base/metrics/histogram_functions.h"
14 #include "base/threading/thread_restrictions.h"
15 #include "base/time/default_tick_clock.h"
16 #include "base/time/time.h"
17 #include "build/branding_buildflags.h"
18 #include "chrome/browser/chromeos/login/configuration_keys.h"
19 #include "chrome/browser/chromeos/login/error_screens_histogram_helper.h"
20 #include "chrome/browser/chromeos/login/screens/network_error.h"
21 #include "chrome/browser/chromeos/login/wizard_context.h"
22 #include "chrome/browser/chromeos/policy/enrollment_requisition_manager.h"
23 #include "chrome/browser/ui/webui/chromeos/login/update_screen_handler.h"
24 #include "chrome/grit/chromium_strings.h"
25 #include "chrome/grit/generated_resources.h"
26 #include "chromeos/constants/chromeos_features.h"
27 #include "chromeos/network/network_state.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/strings/grit/ui_strings.h"
30 
31 namespace chromeos {
32 
33 namespace {
34 
35 constexpr const char kUserActionAcceptUpdateOverCellular[] =
36     "update-accept-cellular";
37 constexpr const char kUserActionRejectUpdateOverCellular[] =
38     "update-reject-cellular";
39 
40 constexpr const char kUserActionCancelUpdateShortcut[] = "cancel-update";
41 
42 const char kUpdateDeadlineFile[] = "/tmp/update-check-response-deadline";
43 
44 // Time in seconds after which we initiate reboot.
45 constexpr const base::TimeDelta kWaitBeforeRebootTime =
46     base::TimeDelta::FromSeconds(2);
47 
48 // Delay before showing error message if captive portal is detected.
49 // We wait for this delay to let captive portal to perform redirect and show
50 // its login page before error message appears.
51 constexpr const base::TimeDelta kDelayErrorMessage =
52     base::TimeDelta::FromSeconds(10);
53 
54 constexpr const base::TimeDelta kShowDelay =
55     base::TimeDelta::FromMicroseconds(400);
56 
57 // When battery percent is lower and DISCHARGING warn user about it.
58 const double kInsufficientBatteryPercent = 50;
59 
RecordDownloadingTime(base::TimeDelta duration)60 void RecordDownloadingTime(base::TimeDelta duration) {
61   base::UmaHistogramLongTimes("OOBE.UpdateScreen.UpdateDownloadingTime",
62                               duration);
63 }
64 
RecordCheckTime(const base::TimeDelta duration)65 void RecordCheckTime(const base::TimeDelta duration) {
66   base::UmaHistogramLongTimes("OOBE.UpdateScreen.StageTime.Check", duration);
67 }
68 
RecordDownloadTime(const base::TimeDelta duration)69 void RecordDownloadTime(const base::TimeDelta duration) {
70   base::UmaHistogramLongTimes("OOBE.UpdateScreen.StageTime.Download", duration);
71 }
72 
RecordVerifyTime(const base::TimeDelta duration)73 void RecordVerifyTime(const base::TimeDelta duration) {
74   base::UmaHistogramLongTimes("OOBE.UpdateScreen.StageTime.Verify", duration);
75 }
76 
RecordFinalizeTime(const base::TimeDelta duration)77 void RecordFinalizeTime(const base::TimeDelta duration) {
78   base::UmaHistogramLongTimes("OOBE.UpdateScreen.StageTime.Finalize", duration);
79 }
80 
RecordUpdateStages(const base::TimeDelta check_time,const base::TimeDelta download_time,const base::TimeDelta verify_time,const base::TimeDelta finalize_time)81 void RecordUpdateStages(const base::TimeDelta check_time,
82                         const base::TimeDelta download_time,
83                         const base::TimeDelta verify_time,
84                         const base::TimeDelta finalize_time) {
85   RecordCheckTime(check_time);
86   RecordDownloadTime(download_time);
87   RecordVerifyTime(verify_time);
88   RecordFinalizeTime(finalize_time);
89 }
90 
91 }  // anonymous namespace
92 
93 // static
GetResultString(Result result)94 std::string UpdateScreen::GetResultString(Result result) {
95   switch (result) {
96     case Result::UPDATE_NOT_REQUIRED:
97       return "UpdateNotRequired";
98     case Result::UPDATE_ERROR:
99       return "UpdateError";
100     case Result::UPDATE_SKIPPED:
101       return chromeos::BaseScreen::kNotApplicable;
102   }
103 }
104 
UpdateScreen(UpdateView * view,ErrorScreen * error_screen,const ScreenExitCallback & exit_callback)105 UpdateScreen::UpdateScreen(UpdateView* view,
106                            ErrorScreen* error_screen,
107                            const ScreenExitCallback& exit_callback)
108     : BaseScreen(UpdateView::kScreenId, OobeScreenPriority::DEFAULT),
109       view_(view),
110       error_screen_(error_screen),
111       exit_callback_(exit_callback),
112       histogram_helper_(
113           std::make_unique<ErrorScreensHistogramHelper>("Update")),
114       version_updater_(std::make_unique<VersionUpdater>(this)),
115       wait_before_reboot_time_(kWaitBeforeRebootTime),
116       tick_clock_(base::DefaultTickClock::GetInstance()) {
117   if (view_)
118     view_->Bind(this);
119 }
120 
~UpdateScreen()121 UpdateScreen::~UpdateScreen() {
122   if (view_)
123     view_->Unbind();
124 }
125 
OnViewDestroyed(UpdateView * view)126 void UpdateScreen::OnViewDestroyed(UpdateView* view) {
127   if (view_ == view)
128     view_ = nullptr;
129 }
130 
MaybeSkip(WizardContext * context)131 bool UpdateScreen::MaybeSkip(WizardContext* context) {
132   if (context->enrollment_triggered_early) {
133     LOG(WARNING) << "Skip OOBE Update because of enrollment request.";
134     exit_callback_.Run(VersionUpdater::Result::UPDATE_SKIPPED);
135     return true;
136   }
137 
138   if (policy::EnrollmentRequisitionManager::IsRemoraRequisition()) {
139     LOG(WARNING) << "Skip OOBE Update for remora devices.";
140     exit_callback_.Run(VersionUpdater::Result::UPDATE_SKIPPED);
141     return true;
142   }
143 
144   const auto* skip_screen_key = context->configuration.FindKeyOfType(
145       configuration::kUpdateSkipUpdate, base::Value::Type::BOOLEAN);
146   const bool skip_screen = skip_screen_key && skip_screen_key->GetBool();
147 
148   if (skip_screen) {
149     LOG(WARNING) << "Skip OOBE Update because of configuration.";
150     exit_callback_.Run(VersionUpdater::Result::UPDATE_SKIPPED);
151     return true;
152   }
153   return false;
154 }
155 
ShowImpl()156 void UpdateScreen::ShowImpl() {
157   if (chromeos::features::IsBetterUpdateEnabled()) {
158     DCHECK(!power_manager_subscription_);
159     power_manager_subscription_ = std::make_unique<
160         ScopedObserver<PowerManagerClient, PowerManagerClient::Observer>>(this);
161     power_manager_subscription_->Add(PowerManagerClient::Get());
162     PowerManagerClient::Get()->RequestStatusUpdate();
163   }
164 #if !BUILDFLAG(GOOGLE_CHROME_BRANDING)
165   if (view_) {
166     view_->SetCancelUpdateShortcutEnabled(true);
167   }
168 #endif
169   RefreshView(version_updater_->update_info());
170 
171   show_timer_.Start(FROM_HERE, kShowDelay,
172                     base::BindOnce(&UpdateScreen::MakeSureScreenIsShown,
173                                    weak_factory_.GetWeakPtr()));
174 
175   version_updater_->StartNetworkCheck();
176 }
177 
HideImpl()178 void UpdateScreen::HideImpl() {
179   power_manager_subscription_.reset();
180   show_timer_.Stop();
181   if (view_)
182     view_->Hide();
183   is_shown_ = false;
184 }
185 
OnUserAction(const std::string & action_id)186 void UpdateScreen::OnUserAction(const std::string& action_id) {
187   bool is_chrome_branded_build = false;
188 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
189   is_chrome_branded_build = true;
190 #endif
191 
192   if (!is_chrome_branded_build &&
193       action_id == kUserActionCancelUpdateShortcut) {
194     // Skip update UI, usually used only in debug builds/tests.
195     VLOG(1) << "Forced update cancel";
196     ExitUpdate(Result::UPDATE_NOT_REQUIRED);
197   } else if (action_id == kUserActionAcceptUpdateOverCellular) {
198     version_updater_->SetUpdateOverCellularOneTimePermission();
199   } else if (action_id == kUserActionRejectUpdateOverCellular) {
200     version_updater_->RejectUpdateOverCellular();
201     ExitUpdate(Result::UPDATE_ERROR);
202   } else {
203     BaseScreen::OnUserAction(action_id);
204   }
205 }
206 
GetShowTimerForTesting()207 base::OneShotTimer* UpdateScreen::GetShowTimerForTesting() {
208   return &show_timer_;
209 }
210 
GetErrorMessageTimerForTesting()211 base::OneShotTimer* UpdateScreen::GetErrorMessageTimerForTesting() {
212   return &error_message_timer_;
213 }
214 
GetVersionUpdaterForTesting()215 VersionUpdater* UpdateScreen::GetVersionUpdaterForTesting() {
216   return version_updater_.get();
217 }
218 
ExitUpdate(Result result)219 void UpdateScreen::ExitUpdate(Result result) {
220   version_updater_->StartExitUpdate(result);
221 }
222 
OnWaitForRebootTimeElapsed()223 void UpdateScreen::OnWaitForRebootTimeElapsed() {
224   LOG(ERROR) << "Unable to reboot - asking user for a manual reboot.";
225   MakeSureScreenIsShown();
226   if (!view_)
227     return;
228   if (chromeos::features::IsBetterUpdateEnabled()) {
229     view_->SetUIState(UpdateView::UIState::kManualReboot);
230   } else {
231     view_->SetUpdateCompleted(true);
232   }
233 }
234 
PrepareForUpdateCheck()235 void UpdateScreen::PrepareForUpdateCheck() {
236   error_message_timer_.Stop();
237   error_screen_->HideCaptivePortal();
238 
239   connect_request_subscription_.reset();
240   if (version_updater_->update_info().state ==
241       VersionUpdater::State::STATE_ERROR)
242     HideErrorMessage();
243 }
244 
ShowErrorMessage()245 void UpdateScreen::ShowErrorMessage() {
246   LOG(WARNING) << "UpdateScreen::ShowErrorMessage()";
247 
248   error_message_timer_.Stop();
249 
250   is_shown_ = false;
251   show_timer_.Stop();
252 
253   connect_request_subscription_ =
254       error_screen_->RegisterConnectRequestCallback(base::BindRepeating(
255           &UpdateScreen::OnConnectRequested, weak_factory_.GetWeakPtr()));
256   error_screen_->SetUIState(NetworkError::UI_STATE_UPDATE);
257   error_screen_->SetParentScreen(UpdateView::kScreenId);
258   error_screen_->SetHideCallback(base::BindOnce(
259       &UpdateScreen::OnErrorScreenHidden, weak_factory_.GetWeakPtr()));
260   error_screen_->Show(nullptr);
261   histogram_helper_->OnErrorShow(error_screen_->GetErrorState());
262 }
263 
UpdateErrorMessage(const NetworkPortalDetector::CaptivePortalStatus status,const NetworkError::ErrorState & error_state,const std::string & network_name)264 void UpdateScreen::UpdateErrorMessage(
265     const NetworkPortalDetector::CaptivePortalStatus status,
266     const NetworkError::ErrorState& error_state,
267     const std::string& network_name) {
268   error_screen_->SetErrorState(error_state, network_name);
269   if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL) {
270     if (is_first_portal_notification_) {
271       is_first_portal_notification_ = false;
272       error_screen_->FixCaptivePortal();
273     }
274   }
275 }
276 
DelayErrorMessage()277 void UpdateScreen::DelayErrorMessage() {
278   if (error_message_timer_.IsRunning())
279     return;
280 
281   error_message_timer_.Start(FROM_HERE, kDelayErrorMessage, this,
282                              &UpdateScreen::ShowErrorMessage);
283 }
284 
UpdateInfoChanged(const VersionUpdater::UpdateInfo & update_info)285 void UpdateScreen::UpdateInfoChanged(
286     const VersionUpdater::UpdateInfo& update_info) {
287   const update_engine::StatusResult& status = update_info.status;
288   hide_progress_on_exit_ = false;
289   bool need_refresh_view = true;
290   switch (status.current_operation()) {
291     case update_engine::Operation::CHECKING_FOR_UPDATE:
292       if (view_)
293         view_->SetUIState(UpdateView::UIState::kCheckingForUpdate);
294       if (start_update_stage_.is_null())
295         start_update_stage_ = tick_clock_->NowTicks();
296       need_refresh_view = false;
297       break;
298       // Do nothing in these cases, we don't want to notify the user of the
299       // check unless there is an update.
300     case update_engine::Operation::ATTEMPTING_ROLLBACK:
301     case update_engine::Operation::DISABLED:
302     case update_engine::Operation::IDLE:
303       need_refresh_view = false;
304       break;
305     case update_engine::Operation::UPDATE_AVAILABLE:
306       if (view_)
307         view_->SetUIState(UpdateView::UIState::kCheckingForUpdate);
308       if (start_update_stage_.is_null())
309         start_update_stage_ = tick_clock_->NowTicks();
310       MakeSureScreenIsShown();
311       if (!HasCriticalUpdate()) {
312         VLOG(1) << "Non-critical update available: " << status.new_version();
313         hide_progress_on_exit_ = true;
314         ExitUpdate(Result::UPDATE_NOT_REQUIRED);
315       }
316       break;
317     case update_engine::Operation::DOWNLOADING:
318       if (view_)
319         view_->SetUIState(UpdateView::UIState::KUpdateInProgress);
320       SetUpdateStatusMessage(update_info.better_update_progress,
321                              update_info.total_time_left);
322       MakeSureScreenIsShown();
323       if (!is_critical_checked_) {
324         // Because update engine doesn't send UPDATE_STATUS_UPDATE_AVAILABLE we
325         // need to check if update is critical on first downloading
326         // notification.
327         is_critical_checked_ = true;
328         if (!HasCriticalUpdate()) {
329           VLOG(1) << "Non-critical update available: " << status.new_version();
330           hide_progress_on_exit_ = true;
331           ExitUpdate(Result::UPDATE_NOT_REQUIRED);
332         } else {
333           check_time_ = tick_clock_->NowTicks() - start_update_stage_;
334           start_update_stage_ = start_update_downloading_ =
335               tick_clock_->NowTicks();
336           VLOG(1) << "Critical update available: " << status.new_version();
337         }
338       }
339       break;
340     case update_engine::Operation::VERIFYING:
341       if (view_)
342         view_->SetUIState(UpdateView::UIState::KUpdateInProgress);
343       SetUpdateStatusMessage(update_info.better_update_progress,
344                              update_info.total_time_left);
345       // Make sure that VERIFYING and DOWNLOADING stages are recorded correctly.
346       if (download_time_.is_zero()) {
347         download_time_ = tick_clock_->NowTicks() - start_update_stage_;
348         start_update_stage_ = tick_clock_->NowTicks();
349       }
350       MakeSureScreenIsShown();
351       break;
352     case update_engine::Operation::FINALIZING:
353       if (view_)
354         view_->SetUIState(UpdateView::UIState::KUpdateInProgress);
355       SetUpdateStatusMessage(update_info.better_update_progress,
356                              update_info.total_time_left);
357       // Make sure that VERIFYING and FINALIZING stages are recorded correctly.
358       if (verify_time_.is_zero()) {
359         verify_time_ = tick_clock_->NowTicks() - start_update_stage_;
360         start_update_stage_ = tick_clock_->NowTicks();
361       }
362       MakeSureScreenIsShown();
363       break;
364     case update_engine::Operation::NEED_PERMISSION_TO_UPDATE:
365       MakeSureScreenIsShown();
366       break;
367     case update_engine::Operation::UPDATED_NEED_REBOOT:
368       MakeSureScreenIsShown();
369       if (HasCriticalUpdate()) {
370         finalize_time_ = tick_clock_->NowTicks() - start_update_stage_;
371         RecordUpdateStages(check_time_, download_time_, verify_time_,
372                            finalize_time_);
373         RecordDownloadingTime(tick_clock_->NowTicks() -
374                               start_update_downloading_);
375         if (chromeos::features::IsBetterUpdateEnabled()) {
376           ShowRebootInProgress();
377           wait_reboot_timer_.Start(FROM_HERE, wait_before_reboot_time_,
378                                    version_updater_.get(),
379                                    &VersionUpdater::RebootAfterUpdate);
380         } else {
381           version_updater_->RebootAfterUpdate();
382         }
383       } else {
384         hide_progress_on_exit_ = true;
385         ExitUpdate(Result::UPDATE_NOT_REQUIRED);
386       }
387       break;
388     case update_engine::Operation::ERROR:
389     case update_engine::Operation::REPORTING_ERROR_EVENT:
390       // Ignore update errors for non-critical updates to prevent blocking the
391       // user from getting to login screen during OOBE if the pending update is
392       // not critical.
393       if (update_info.is_checking_for_update || !HasCriticalUpdate()) {
394         ExitUpdate(Result::UPDATE_NOT_REQUIRED);
395       } else {
396         ExitUpdate(Result::UPDATE_ERROR);
397       }
398       need_refresh_view = false;
399       break;
400     default:
401       NOTREACHED();
402   }
403   if (chromeos::features::IsBetterUpdateEnabled())
404     UpdateBatteryWarningVisibility();
405   if (need_refresh_view)
406     RefreshView(update_info);
407 }
408 
FinishExitUpdate(Result result)409 void UpdateScreen::FinishExitUpdate(Result result) {
410   if (!start_update_stage_.is_null()) {
411     check_time_ = (check_time_.is_zero())
412                       ? tick_clock_->NowTicks() - start_update_stage_
413                       : check_time_;
414     RecordCheckTime(check_time_);
415   }
416   show_timer_.Stop();
417   exit_callback_.Run(result);
418 }
419 
PowerChanged(const power_manager::PowerSupplyProperties & proto)420 void UpdateScreen::PowerChanged(
421     const power_manager::PowerSupplyProperties& proto) {
422   UpdateBatteryWarningVisibility();
423 }
424 
ShowRebootInProgress()425 void UpdateScreen::ShowRebootInProgress() {
426   MakeSureScreenIsShown();
427   if (view_) {
428     if (chromeos::features::IsBetterUpdateEnabled()) {
429       view_->SetUIState(UpdateView::UIState::kRestartInProgress);
430     } else {
431       view_->SetUpdateCompleted(true);
432     }
433   }
434 }
435 
SetUpdateStatusMessage(int percent,base::TimeDelta time_left)436 void UpdateScreen::SetUpdateStatusMessage(int percent,
437                                           base::TimeDelta time_left) {
438   if (!view_)
439     return;
440   base::string16 time_left_message;
441   if (time_left.InMinutes() == 0) {
442     time_left_message = l10n_util::GetStringFUTF16(
443         IDS_UPDATE_STATUS_SUBTITLE_TIME_LEFT,
444         l10n_util::GetPluralStringFUTF16(IDS_TIME_LONG_SECS,
445                                          time_left.InSeconds()));
446   } else {
447     time_left_message = l10n_util::GetStringFUTF16(
448         IDS_UPDATE_STATUS_SUBTITLE_TIME_LEFT,
449         l10n_util::GetPluralStringFUTF16(IDS_TIME_LONG_MINS,
450                                          time_left.InMinutes()));
451   }
452   view_->SetUpdateStatus(
453       percent,
454       l10n_util::GetStringFUTF16(IDS_UPDATE_STATUS_SUBTITLE_PERCENT,
455                                  base::FormatPercent(percent)),
456       time_left_message);
457 }
458 
UpdateBatteryWarningVisibility()459 void UpdateScreen::UpdateBatteryWarningVisibility() {
460   if (!view_)
461     return;
462   const base::Optional<power_manager::PowerSupplyProperties>& proto =
463       PowerManagerClient::Get()->GetLastStatus();
464   if (!proto.has_value())
465     return;
466   view_->ShowLowBatteryWarningMessage(
467       is_critical_checked_ && HasCriticalUpdate() &&
468       proto->battery_state() ==
469           power_manager::PowerSupplyProperties_BatteryState_DISCHARGING &&
470       proto->battery_percent() < kInsufficientBatteryPercent);
471 }
472 
RefreshView(const VersionUpdater::UpdateInfo & update_info)473 void UpdateScreen::RefreshView(const VersionUpdater::UpdateInfo& update_info) {
474   if (view_) {
475     view_->SetProgress(update_info.progress);
476     view_->SetProgressMessage(update_info.progress_message);
477     view_->SetEstimatedTimeLeft(update_info.estimated_time_left_in_secs);
478     view_->SetShowEstimatedTimeLeft(update_info.show_estimated_time_left);
479     view_->SetShowCurtain(update_info.progress_unavailable ||
480                           hide_progress_on_exit_);
481     view_->SetRequiresPermissionForCellular(
482         update_info.requires_permission_for_cellular);
483   }
484 }
485 
HasCriticalUpdate()486 bool UpdateScreen::HasCriticalUpdate() {
487   if (ignore_update_deadlines_)
488     return true;
489   if (has_critical_update_.has_value())
490     return has_critical_update_.value();
491   has_critical_update_ = true;
492 
493   std::string deadline;
494   // Checking for update flag file causes us to do blocking IO on UI thread.
495   // Temporarily allow it until we fix http://crosbug.com/11106
496   base::ThreadRestrictions::ScopedAllowIO allow_io;
497   base::FilePath update_deadline_file_path(kUpdateDeadlineFile);
498   if (!base::ReadFileToString(update_deadline_file_path, &deadline) ||
499       deadline.empty()) {
500     has_critical_update_ = false;
501     return false;
502   }
503 
504   // TODO(dpolukhin): Analyze file content. Now we can just assume that
505   // if the file exists and not empty, there is critical update.
506   return true;
507 }
508 
MakeSureScreenIsShown()509 void UpdateScreen::MakeSureScreenIsShown() {
510   show_timer_.Stop();
511 
512   if (is_shown_ || !view_)
513     return;
514 
515   is_shown_ = true;
516   histogram_helper_->OnScreenShow();
517 
518   view_->Show();
519 }
520 
HideErrorMessage()521 void UpdateScreen::HideErrorMessage() {
522   LOG(WARNING) << "UpdateScreen::HideErrorMessage()";
523   error_screen_->Hide();
524   histogram_helper_->OnErrorHide();
525 }
526 
OnConnectRequested()527 void UpdateScreen::OnConnectRequested() {
528   if (version_updater_->update_info().state ==
529       VersionUpdater::State::STATE_ERROR) {
530     LOG(WARNING) << "Hiding error message since AP was reselected";
531     version_updater_->StartUpdateCheck();
532   }
533 }
534 
OnErrorScreenHidden()535 void UpdateScreen::OnErrorScreenHidden() {
536   error_screen_->SetParentScreen(OobeScreen::SCREEN_UNKNOWN);
537   Show(context());
538 }
539 
540 }  // namespace chromeos
541