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.
5 #include "chrome/browser/chromeos/login/screens/update_screen.h"
10 #include "base/files/file_util.h"
11 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/threading/thread_restrictions.h"
14 #include "chrome/browser/chromeos/login/error_screens_histogram_helper.h"
15 #include "chrome/browser/chromeos/login/screen_manager.h"
16 #include "chrome/browser/chromeos/login/screens/base_screen_delegate.h"
17 #include "chrome/browser/chromeos/login/screens/error_screen.h"
18 #include "chrome/browser/chromeos/login/screens/network_error.h"
19 #include "chrome/browser/chromeos/login/screens/update_view.h"
20 #include "chrome/browser/chromeos/login/startup_utils.h"
21 #include "chrome/browser/chromeos/login/wizard_controller.h"
22 #include "chrome/grit/chromium_strings.h"
23 #include "chrome/grit/generated_resources.h"
24 #include "chromeos/dbus/dbus_thread_manager.h"
25 #include "chromeos/network/network_state.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "ui/base/l10n/l10n_util.h"
29 using content::BrowserThread
;
30 using pairing_chromeos::HostPairingController
;
36 // If reboot didn't happen, ask user to reboot device manually.
37 const int kWaitForRebootTimeSec
= 3;
39 // Progress bar stages. Each represents progress bar value
40 // at the beginning of each stage.
41 // TODO(nkostylev): Base stage progress values on approximate time.
42 // TODO(nkostylev): Animate progress during each state.
43 const int kBeforeUpdateCheckProgress
= 7;
44 const int kBeforeDownloadProgress
= 14;
45 const int kBeforeVerifyingProgress
= 74;
46 const int kBeforeFinalizingProgress
= 81;
47 const int kProgressComplete
= 100;
49 // Defines what part of update progress does download part takes.
50 const int kDownloadProgressIncrement
= 60;
52 const char kUpdateDeadlineFile
[] = "/tmp/update-check-response-deadline";
54 // Minimum timestep between two consecutive measurements for the
56 const base::TimeDelta kMinTimeStep
= base::TimeDelta::FromSeconds(1);
58 // Smooth factor that is used for the average downloading speed
60 // avg_speed = smooth_factor * cur_speed + (1.0 - smooth_factor) * avg_speed.
61 const double kDownloadSpeedSmoothFactor
= 0.1;
63 // Minumum allowed value for the average downloading speed.
64 const double kDownloadAverageSpeedDropBound
= 1e-8;
66 // An upper bound for possible downloading time left estimations.
67 const double kMaxTimeLeft
= 24 * 60 * 60;
69 // Delay before showing error message if captive portal is detected.
70 // We wait for this delay to let captive portal to perform redirect and show
71 // its login page before error message appears.
72 const int kDelayErrorMessageSec
= 10;
74 // Invoked from call to RequestUpdateCheck upon completion of the DBus call.
75 void StartUpdateCallback(UpdateScreen
* screen
,
76 UpdateEngineClient::UpdateCheckResult result
) {
77 VLOG(1) << "Callback from RequestUpdateCheck, result " << result
;
78 if (UpdateScreen::HasInstance(screen
)) {
79 if (result
== UpdateEngineClient::UPDATE_RESULT_SUCCESS
)
80 screen
->SetIgnoreIdleStatus(false);
82 screen
->ExitUpdate(UpdateScreen::REASON_UPDATE_INIT_FAILED
);
86 } // anonymous namespace
89 UpdateScreen::InstanceSet
& UpdateScreen::GetInstanceSet() {
90 CR_DEFINE_STATIC_LOCAL(std::set
<UpdateScreen
*>, instance_set
, ());
91 DCHECK_CURRENTLY_ON(BrowserThread::UI
); // not threadsafe.
96 bool UpdateScreen::HasInstance(UpdateScreen
* inst
) {
97 InstanceSet
& instance_set
= GetInstanceSet();
98 InstanceSet::iterator found
= instance_set
.find(inst
);
99 return (found
!= instance_set
.end());
103 UpdateScreen
* UpdateScreen::Get(ScreenManager
* manager
) {
104 return static_cast<UpdateScreen
*>(
105 manager
->GetScreen(WizardController::kUpdateScreenName
));
108 UpdateScreen::UpdateScreen(BaseScreenDelegate
* base_screen_delegate
,
110 HostPairingController
* remora_controller
)
111 : UpdateModel(base_screen_delegate
),
113 reboot_check_delay_(kWaitForRebootTimeSec
),
114 is_checking_for_update_(true),
115 is_downloading_update_(false),
116 is_ignore_update_deadlines_(false),
118 ignore_idle_status_(true),
120 remora_controller_(remora_controller
),
121 is_first_detection_notification_(true),
122 is_first_portal_notification_(true),
123 histogram_helper_(new ErrorScreensHistogramHelper("Update")),
124 weak_factory_(this) {
128 GetInstanceSet().insert(this);
131 UpdateScreen::~UpdateScreen() {
135 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
136 NetworkPortalDetector::Get()->RemoveObserver(this);
137 GetInstanceSet().erase(this);
140 void UpdateScreen::UpdateStatusChanged(
141 const UpdateEngineClient::Status
& status
) {
142 if (is_checking_for_update_
&&
143 status
.status
> UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE
) {
144 is_checking_for_update_
= false;
146 if (ignore_idle_status_
&& status
.status
>
147 UpdateEngineClient::UPDATE_STATUS_IDLE
) {
148 ignore_idle_status_
= false;
151 switch (status
.status
) {
152 case UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE
:
153 // Do nothing in these cases, we don't want to notify the user of the
154 // check unless there is an update.
155 SetHostPairingControllerStatus(
156 HostPairingController::UPDATE_STATUS_UPDATING
);
158 case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE
:
159 MakeSureScreenIsShown();
161 .SetInteger(kContextKeyProgress
, kBeforeDownloadProgress
)
162 .SetBoolean(kContextKeyShowEstimatedTimeLeft
, false);
163 if (!HasCriticalUpdate()) {
164 VLOG(1) << "Noncritical update available: " << status
.new_version
;
165 ExitUpdate(REASON_UPDATE_NON_CRITICAL
);
167 VLOG(1) << "Critical update available: " << status
.new_version
;
169 .SetString(kContextKeyProgressMessage
,
170 l10n_util::GetStringUTF16(IDS_UPDATE_AVAILABLE
))
171 .SetBoolean(kContextKeyShowProgressMessage
, true)
172 .SetBoolean(kContextKeyShowCurtain
, false);
175 case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING
:
177 MakeSureScreenIsShown();
178 if (!is_downloading_update_
) {
179 // Because update engine doesn't send UPDATE_STATUS_UPDATE_AVAILABLE
180 // we need to is update critical on first downloading notification.
181 is_downloading_update_
= true;
182 download_start_time_
= download_last_time_
= base::Time::Now();
183 download_start_progress_
= status
.download_progress
;
184 download_last_progress_
= status
.download_progress
;
185 is_download_average_speed_computed_
= false;
186 download_average_speed_
= 0.0;
187 if (!HasCriticalUpdate()) {
188 VLOG(1) << "Non-critical update available: " << status
.new_version
;
189 ExitUpdate(REASON_UPDATE_NON_CRITICAL
);
191 VLOG(1) << "Critical update available: " << status
.new_version
;
193 .SetString(kContextKeyProgressMessage
,
194 l10n_util::GetStringUTF16(IDS_INSTALLING_UPDATE
))
195 .SetBoolean(kContextKeyShowProgressMessage
, true)
196 .SetBoolean(kContextKeyShowCurtain
, false);
199 UpdateDownloadingStats(status
);
202 case UpdateEngineClient::UPDATE_STATUS_VERIFYING
:
203 MakeSureScreenIsShown();
205 .SetInteger(kContextKeyProgress
, kBeforeVerifyingProgress
)
206 .SetString(kContextKeyProgressMessage
,
207 l10n_util::GetStringUTF16(IDS_UPDATE_VERIFYING
))
208 .SetBoolean(kContextKeyShowProgressMessage
, true);
210 case UpdateEngineClient::UPDATE_STATUS_FINALIZING
:
211 MakeSureScreenIsShown();
213 .SetInteger(kContextKeyProgress
, kBeforeFinalizingProgress
)
214 .SetString(kContextKeyProgressMessage
,
215 l10n_util::GetStringUTF16(IDS_UPDATE_FINALIZING
))
216 .SetBoolean(kContextKeyShowProgressMessage
, true);
218 case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT
:
219 MakeSureScreenIsShown();
221 .SetInteger(kContextKeyProgress
, kProgressComplete
)
222 .SetBoolean(kContextKeyShowEstimatedTimeLeft
, false);
223 if (HasCriticalUpdate()) {
224 GetContextEditor().SetBoolean(kContextKeyShowCurtain
, false);
225 VLOG(1) << "Initiate reboot after update";
226 SetHostPairingControllerStatus(
227 HostPairingController::UPDATE_STATUS_REBOOTING
);
228 DBusThreadManager::Get()->GetUpdateEngineClient()->RebootAfterUpdate();
229 reboot_timer_
.Start(FROM_HERE
,
230 base::TimeDelta::FromSeconds(reboot_check_delay_
),
232 &UpdateScreen::OnWaitForRebootTimeElapsed
);
234 ExitUpdate(REASON_UPDATE_NON_CRITICAL
);
237 case UpdateEngineClient::UPDATE_STATUS_ATTEMPTING_ROLLBACK
:
238 VLOG(1) << "Attempting rollback";
240 case UpdateEngineClient::UPDATE_STATUS_IDLE
:
241 if (ignore_idle_status_
) {
242 // It is first IDLE status that is sent before we initiated the check.
246 case UpdateEngineClient::UPDATE_STATUS_ERROR
:
247 case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT
:
248 ExitUpdate(REASON_UPDATE_ENDED
);
256 void UpdateScreen::OnPortalDetectionCompleted(
257 const NetworkState
* network
,
258 const NetworkPortalDetector::CaptivePortalState
& state
) {
259 LOG(WARNING
) << "UpdateScreen::OnPortalDetectionCompleted(): "
260 << "network=" << (network
? network
->path() : "") << ", "
261 << "state.status=" << state
.status
<< ", "
262 << "state.response_code=" << state
.response_code
;
264 // Wait for the sane detection results.
266 state
.status
== NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN
) {
270 // Restart portal detection for the first notification about offline state.
272 state
.status
== NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE
) &&
273 is_first_detection_notification_
) {
274 is_first_detection_notification_
= false;
275 base::MessageLoop::current()->PostTask(
278 base::IgnoreResult(&NetworkPortalDetector::StartDetectionIfIdle
),
279 base::Unretained(NetworkPortalDetector::Get())));
282 is_first_detection_notification_
= false;
284 NetworkPortalDetector::CaptivePortalStatus status
= state
.status
;
285 if (state_
== STATE_ERROR
) {
286 // In the case of online state hide error message and proceed to
287 // the update stage. Otherwise, update error message content.
288 if (status
== NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE
)
291 UpdateErrorMessage(network
, status
);
292 } else if (state_
== STATE_FIRST_PORTAL_CHECK
) {
293 // In the case of online state immediately proceed to the update
294 // stage. Otherwise, prepare and show error message.
295 if (status
== NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE
) {
298 UpdateErrorMessage(network
, status
);
300 if (status
== NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL
)
308 void UpdateScreen::StartNetworkCheck() {
309 // If portal detector is enabled and portal detection before AU is
310 // allowed, initiate network state check. Otherwise, directly
311 // proceed to update.
312 if (!NetworkPortalDetector::Get()->IsEnabled()) {
316 state_
= STATE_FIRST_PORTAL_CHECK
;
317 is_first_detection_notification_
= true;
318 is_first_portal_notification_
= true;
319 NetworkPortalDetector::Get()->AddAndFireObserver(this);
322 void UpdateScreen::PrepareToShow() {
326 view_
->PrepareToShow();
329 void UpdateScreen::Show() {
331 histogram_helper_
->OnScreenShow();
333 #if !defined(OFFICIAL_BUILD)
334 GetContextEditor().SetBoolean(kContextKeyCancelUpdateShortcutEnabled
, true);
336 GetContextEditor().SetInteger(kContextKeyProgress
,
337 kBeforeUpdateCheckProgress
);
343 void UpdateScreen::Hide() {
349 void UpdateScreen::Initialize(::login::ScreenContext
* context
) {
350 UpdateModel::Initialize(context
);
353 void UpdateScreen::OnViewDestroyed(UpdateView
* view
) {
358 void UpdateScreen::OnUserAction(const std::string
& action_id
) {
359 #if !defined(OFFICIAL_BUILD)
360 if (action_id
== kUserActionCancelUpdateShortcut
)
364 BaseScreen::OnUserAction(action_id
);
367 void UpdateScreen::OnContextKeyUpdated(
368 const ::login::ScreenContext::KeyType
& key
) {
369 UpdateModel::OnContextKeyUpdated(key
);
372 void UpdateScreen::ExitUpdate(UpdateScreen::ExitReason reason
) {
373 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
374 NetworkPortalDetector::Get()->RemoveObserver(this);
375 SetHostPairingControllerStatus(HostPairingController::UPDATE_STATUS_UPDATED
);
379 case REASON_UPDATE_CANCELED
:
380 Finish(BaseScreenDelegate::UPDATE_NOUPDATE
);
382 case REASON_UPDATE_INIT_FAILED
:
383 Finish(BaseScreenDelegate::UPDATE_ERROR_CHECKING_FOR_UPDATE
);
385 case REASON_UPDATE_NON_CRITICAL
:
386 case REASON_UPDATE_ENDED
:
388 UpdateEngineClient
* update_engine_client
=
389 DBusThreadManager::Get()->GetUpdateEngineClient();
390 switch (update_engine_client
->GetLastStatus().status
) {
391 case UpdateEngineClient::UPDATE_STATUS_ATTEMPTING_ROLLBACK
:
393 case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE
:
394 case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT
:
395 case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING
:
396 case UpdateEngineClient::UPDATE_STATUS_FINALIZING
:
397 case UpdateEngineClient::UPDATE_STATUS_VERIFYING
:
398 DCHECK(!HasCriticalUpdate());
399 // Noncritical update, just exit screen as if there is no update.
401 case UpdateEngineClient::UPDATE_STATUS_IDLE
:
402 Finish(BaseScreenDelegate::UPDATE_NOUPDATE
);
404 case UpdateEngineClient::UPDATE_STATUS_ERROR
:
405 case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT
:
406 Finish(is_checking_for_update_
407 ? BaseScreenDelegate::UPDATE_ERROR_CHECKING_FOR_UPDATE
408 : BaseScreenDelegate::UPDATE_ERROR_UPDATING
);
420 void UpdateScreen::OnWaitForRebootTimeElapsed() {
421 LOG(ERROR
) << "Unable to reboot - asking user for a manual reboot.";
422 MakeSureScreenIsShown();
423 GetContextEditor().SetString(kContextKeyUpdateMessage
,
424 l10n_util::GetStringUTF16(IDS_UPDATE_COMPLETED
));
427 void UpdateScreen::MakeSureScreenIsShown() {
429 get_base_screen_delegate()->ShowCurrentScreen();
432 void UpdateScreen::SetIgnoreIdleStatus(bool ignore_idle_status
) {
433 ignore_idle_status_
= ignore_idle_status
;
436 void UpdateScreen::CancelUpdate() {
437 VLOG(1) << "Forced update cancel";
438 ExitUpdate(REASON_UPDATE_CANCELED
);
441 void UpdateScreen::UpdateDownloadingStats(
442 const UpdateEngineClient::Status
& status
) {
443 base::Time download_current_time
= base::Time::Now();
444 if (download_current_time
>= download_last_time_
+ kMinTimeStep
) {
445 // Estimate downloading rate.
446 double progress_delta
=
447 std::max(status
.download_progress
- download_last_progress_
, 0.0);
449 (download_current_time
- download_last_time_
).InSecondsF();
450 double download_rate
= status
.new_size
* progress_delta
/ time_delta
;
452 download_last_time_
= download_current_time
;
453 download_last_progress_
= status
.download_progress
;
455 // Estimate time left.
456 double progress_left
= std::max(1.0 - status
.download_progress
, 0.0);
457 if (!is_download_average_speed_computed_
) {
458 download_average_speed_
= download_rate
;
459 is_download_average_speed_computed_
= true;
461 download_average_speed_
=
462 kDownloadSpeedSmoothFactor
* download_rate
+
463 (1.0 - kDownloadSpeedSmoothFactor
) * download_average_speed_
;
464 if (download_average_speed_
< kDownloadAverageSpeedDropBound
) {
466 (download_current_time
- download_start_time_
).InSecondsF();
467 download_average_speed_
=
469 (status
.download_progress
- download_start_progress_
) /
472 double work_left
= progress_left
* status
.new_size
;
473 double time_left
= work_left
/ download_average_speed_
;
474 // |time_left| may be large enough or even +infinity. So we must
475 // |bound possible estimations.
476 time_left
= std::min(time_left
, kMaxTimeLeft
);
479 .SetBoolean(kContextKeyShowEstimatedTimeLeft
, true)
480 .SetInteger(kContextKeyEstimatedTimeLeftSec
,
481 static_cast<int>(time_left
));
484 int download_progress
= static_cast<int>(
485 status
.download_progress
* kDownloadProgressIncrement
);
486 GetContextEditor().SetInteger(kContextKeyProgress
,
487 kBeforeDownloadProgress
+ download_progress
);
490 bool UpdateScreen::HasCriticalUpdate() {
491 if (is_ignore_update_deadlines_
)
494 std::string deadline
;
495 // Checking for update flag file causes us to do blocking IO on UI thread.
496 // Temporarily allow it until we fix http://crosbug.com/11106
497 base::ThreadRestrictions::ScopedAllowIO allow_io
;
498 base::FilePath
update_deadline_file_path(kUpdateDeadlineFile
);
499 if (!base::ReadFileToString(update_deadline_file_path
, &deadline
) ||
504 // TODO(dpolukhin): Analyze file content. Now we can just assume that
505 // if the file exists and not empty, there is critical update.
509 ErrorScreen
* UpdateScreen::GetErrorScreen() {
510 return get_base_screen_delegate()->GetErrorScreen();
513 void UpdateScreen::StartUpdateCheck() {
514 error_message_timer_
.Stop();
515 GetErrorScreen()->HideCaptivePortal();
517 NetworkPortalDetector::Get()->RemoveObserver(this);
518 connect_request_subscription_
.reset();
519 if (state_
== STATE_ERROR
)
521 state_
= STATE_UPDATE
;
522 DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this);
523 VLOG(1) << "Initiate update check";
524 DBusThreadManager::Get()->GetUpdateEngineClient()->RequestUpdateCheck(
525 base::Bind(StartUpdateCallback
, this));
528 void UpdateScreen::ShowErrorMessage() {
529 LOG(WARNING
) << "UpdateScreen::ShowErrorMessage()";
531 error_message_timer_
.Stop();
533 state_
= STATE_ERROR
;
534 connect_request_subscription_
=
535 GetErrorScreen()->RegisterConnectRequestCallback(base::Bind(
536 &UpdateScreen::OnConnectRequested
, base::Unretained(this)));
537 GetErrorScreen()->SetUIState(NetworkError::UI_STATE_UPDATE
);
538 get_base_screen_delegate()->ShowErrorScreen();
539 histogram_helper_
->OnErrorShow(GetErrorScreen()->GetErrorState());
542 void UpdateScreen::HideErrorMessage() {
543 LOG(WARNING
) << "UpdateScreen::HideErrorMessage()";
544 get_base_screen_delegate()->HideErrorScreen(this);
545 histogram_helper_
->OnErrorHide();
548 void UpdateScreen::UpdateErrorMessage(
549 const NetworkState
* network
,
550 const NetworkPortalDetector::CaptivePortalStatus status
) {
552 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE
:
555 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN
:
556 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE
:
557 GetErrorScreen()->SetErrorState(NetworkError::ERROR_STATE_OFFLINE
,
560 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL
:
562 GetErrorScreen()->SetErrorState(NetworkError::ERROR_STATE_PORTAL
,
564 if (is_first_portal_notification_
) {
565 is_first_portal_notification_
= false;
566 GetErrorScreen()->FixCaptivePortal();
569 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED
:
570 GetErrorScreen()->SetErrorState(NetworkError::ERROR_STATE_PROXY
,
579 void UpdateScreen::SetHostPairingControllerStatus(
580 HostPairingController::UpdateStatus update_status
) {
581 if (remora_controller_
) {
582 remora_controller_
->OnUpdateStatusChanged(update_status
);
586 void UpdateScreen::DelayErrorMessage() {
587 if (error_message_timer_
.IsRunning())
590 state_
= STATE_ERROR
;
591 error_message_timer_
.Start(
592 FROM_HERE
, base::TimeDelta::FromSeconds(kDelayErrorMessageSec
), this,
593 &UpdateScreen::ShowErrorMessage
);
596 base::OneShotTimer
<UpdateScreen
>&
597 UpdateScreen::GetErrorMessageTimerForTesting() {
598 return error_message_timer_
;
601 void UpdateScreen::OnConnectRequested() {
602 if (state_
== STATE_ERROR
) {
603 LOG(WARNING
) << "Hiding error message since AP was reselected";
608 } // namespace chromeos