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