update_screen.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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/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/screens/error_screen.h" 15#include "chrome/browser/chromeos/login/screens/screen_observer.h" 16#include "chrome/browser/chromeos/login/screens/update_screen_actor.h" 17#include "chrome/browser/chromeos/login/startup_utils.h" 18#include "chrome/browser/chromeos/login/wizard_controller.h" 19#include "chromeos/dbus/dbus_thread_manager.h" 20#include "chromeos/network/network_state.h" 21#include "content/public/browser/browser_thread.h" 22 23using content::BrowserThread; 24 25namespace chromeos { 26 27namespace { 28 29// Progress bar stages. Each represents progress bar value 30// at the beginning of each stage. 31// TODO(nkostylev): Base stage progress values on approximate time. 32// TODO(nkostylev): Animate progress during each state. 33const int kBeforeUpdateCheckProgress = 7; 34const int kBeforeDownloadProgress = 14; 35const int kBeforeVerifyingProgress = 74; 36const int kBeforeFinalizingProgress = 81; 37const int kProgressComplete = 100; 38 39// Defines what part of update progress does download part takes. 40const int kDownloadProgressIncrement = 60; 41 42const char kUpdateDeadlineFile[] = "/tmp/update-check-response-deadline"; 43 44// Minimum timestep between two consecutive measurements for the 45// download rate. 46const base::TimeDelta kMinTimeStep = base::TimeDelta::FromSeconds(1); 47 48// Smooth factor that is used for the average downloading speed 49// estimation. 50// avg_speed = smooth_factor * cur_speed + (1.0 - smooth_factor) * avg_speed. 51const double kDownloadSpeedSmoothFactor = 0.1; 52 53// Minumum allowed value for the average downloading speed. 54const double kDownloadAverageSpeedDropBound = 1e-8; 55 56// An upper bound for possible downloading time left estimations. 57const double kMaxTimeLeft = 24 * 60 * 60; 58 59// Invoked from call to RequestUpdateCheck upon completion of the DBus call. 60void StartUpdateCallback(UpdateScreen* screen, 61 UpdateEngineClient::UpdateCheckResult result) { 62 VLOG(1) << "Callback from RequestUpdateCheck, result " << result; 63 if (UpdateScreen::HasInstance(screen)) { 64 if (result == UpdateEngineClient::UPDATE_RESULT_SUCCESS) 65 screen->SetIgnoreIdleStatus(false); 66 else 67 screen->ExitUpdate(UpdateScreen::REASON_UPDATE_INIT_FAILED); 68 } 69} 70 71} // anonymous namespace 72 73// static 74UpdateScreen::InstanceSet& UpdateScreen::GetInstanceSet() { 75 CR_DEFINE_STATIC_LOCAL(std::set<UpdateScreen*>, instance_set, ()); 76 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // not threadsafe. 77 return instance_set; 78} 79 80// static 81bool UpdateScreen::HasInstance(UpdateScreen* inst) { 82 InstanceSet& instance_set = GetInstanceSet(); 83 InstanceSet::iterator found = instance_set.find(inst); 84 return (found != instance_set.end()); 85} 86 87UpdateScreen::UpdateScreen( 88 ScreenObserver* screen_observer, 89 UpdateScreenActor* actor) 90 : WizardScreen(screen_observer), 91 state_(STATE_IDLE), 92 reboot_check_delay_(0), 93 is_checking_for_update_(true), 94 is_downloading_update_(false), 95 is_ignore_update_deadlines_(false), 96 is_shown_(false), 97 ignore_idle_status_(true), 98 actor_(actor), 99 is_first_detection_notification_(true), 100 is_first_portal_notification_(true), 101 weak_factory_(this) { 102 DCHECK(actor_); 103 if (actor_) 104 actor_->SetDelegate(this); 105 GetInstanceSet().insert(this); 106} 107 108UpdateScreen::~UpdateScreen() { 109 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); 110 NetworkPortalDetector::Get()->RemoveObserver(this); 111 GetInstanceSet().erase(this); 112 if (actor_) 113 actor_->SetDelegate(NULL); 114} 115 116void UpdateScreen::UpdateStatusChanged( 117 const UpdateEngineClient::Status& status) { 118 if (!actor_) 119 return; 120 121 if (is_checking_for_update_ && 122 status.status > UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE) { 123 is_checking_for_update_ = false; 124 } 125 if (ignore_idle_status_ && status.status > 126 UpdateEngineClient::UPDATE_STATUS_IDLE) { 127 ignore_idle_status_ = false; 128 } 129 130 switch (status.status) { 131 case UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE: 132 // Do nothing in these cases, we don't want to notify the user of the 133 // check unless there is an update. 134 break; 135 case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE: 136 MakeSureScreenIsShown(); 137 actor_->SetProgress(kBeforeDownloadProgress); 138 actor_->ShowEstimatedTimeLeft(false); 139 if (!HasCriticalUpdate()) { 140 VLOG(1) << "Noncritical update available: " << status.new_version; 141 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 142 } else { 143 VLOG(1) << "Critical update available: " << status.new_version; 144 actor_->SetProgressMessage( 145 UpdateScreenActor::PROGRESS_MESSAGE_UPDATE_AVAILABLE); 146 actor_->ShowProgressMessage(true); 147 actor_->ShowCurtain(false); 148 } 149 break; 150 case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING: 151 { 152 MakeSureScreenIsShown(); 153 if (!is_downloading_update_) { 154 // Because update engine doesn't send UPDATE_STATUS_UPDATE_AVAILABLE 155 // we need to is update critical on first downloading notification. 156 is_downloading_update_ = true; 157 download_start_time_ = download_last_time_ = base::Time::Now(); 158 download_start_progress_ = status.download_progress; 159 download_last_progress_ = status.download_progress; 160 is_download_average_speed_computed_ = false; 161 download_average_speed_ = 0.0; 162 if (!HasCriticalUpdate()) { 163 VLOG(1) << "Non-critical update available: " << status.new_version; 164 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 165 } else { 166 VLOG(1) << "Critical update available: " << status.new_version; 167 actor_->SetProgressMessage( 168 UpdateScreenActor::PROGRESS_MESSAGE_INSTALLING_UPDATE); 169 actor_->ShowProgressMessage(true); 170 actor_->ShowCurtain(false); 171 } 172 } 173 UpdateDownloadingStats(status); 174 } 175 break; 176 case UpdateEngineClient::UPDATE_STATUS_VERIFYING: 177 MakeSureScreenIsShown(); 178 actor_->SetProgress(kBeforeVerifyingProgress); 179 actor_->SetProgressMessage(UpdateScreenActor::PROGRESS_MESSAGE_VERIFYING); 180 actor_->ShowProgressMessage(true); 181 break; 182 case UpdateEngineClient::UPDATE_STATUS_FINALIZING: 183 MakeSureScreenIsShown(); 184 actor_->SetProgress(kBeforeFinalizingProgress); 185 actor_->SetProgressMessage( 186 UpdateScreenActor::PROGRESS_MESSAGE_FINALIZING); 187 actor_->ShowProgressMessage(true); 188 break; 189 case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT: 190 MakeSureScreenIsShown(); 191 actor_->SetProgress(kProgressComplete); 192 actor_->ShowEstimatedTimeLeft(false); 193 if (HasCriticalUpdate()) { 194 actor_->ShowCurtain(false); 195 VLOG(1) << "Initiate reboot after update"; 196 DBusThreadManager::Get()->GetUpdateEngineClient()->RebootAfterUpdate(); 197 reboot_timer_.Start(FROM_HERE, 198 base::TimeDelta::FromSeconds(reboot_check_delay_), 199 this, 200 &UpdateScreen::OnWaitForRebootTimeElapsed); 201 } else { 202 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 203 } 204 break; 205 case UpdateEngineClient::UPDATE_STATUS_ATTEMPTING_ROLLBACK: 206 VLOG(1) << "Attempting rollback"; 207 break; 208 case UpdateEngineClient::UPDATE_STATUS_IDLE: 209 if (ignore_idle_status_) { 210 // It is first IDLE status that is sent before we initiated the check. 211 break; 212 } 213 // else no break 214 case UpdateEngineClient::UPDATE_STATUS_ERROR: 215 case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT: 216 ExitUpdate(REASON_UPDATE_ENDED); 217 break; 218 default: 219 NOTREACHED(); 220 break; 221 } 222} 223 224void UpdateScreen::OnPortalDetectionCompleted( 225 const NetworkState* network, 226 const NetworkPortalDetector::CaptivePortalState& state) { 227 LOG(WARNING) << "UpdateScreen::PortalDetectionCompleted(): " 228 << "network=" << (network ? network->path() : "") << ", " 229 << "state.status=" << state.status << ", " 230 << "state.response_code=" << state.response_code; 231 232 // Wait for the sane detection results. 233 if (network && 234 state.status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN) { 235 return; 236 } 237 238 // Restart portal detection for the first notification about offline state. 239 if ((!network || 240 state.status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE) && 241 is_first_detection_notification_) { 242 is_first_detection_notification_ = false; 243 base::MessageLoop::current()->PostTask( 244 FROM_HERE, 245 base::Bind( 246 base::IgnoreResult(&NetworkPortalDetector::StartDetectionIfIdle), 247 base::Unretained(NetworkPortalDetector::Get()))); 248 return; 249 } 250 is_first_detection_notification_ = false; 251 252 NetworkPortalDetector::CaptivePortalStatus status = state.status; 253 if (state_ == STATE_ERROR) { 254 // In the case of online state hide error message and proceed to 255 // the update stage. Otherwise, update error message content. 256 if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE) 257 StartUpdateCheck(); 258 else 259 UpdateErrorMessage(network, status); 260 } else if (state_ == STATE_FIRST_PORTAL_CHECK) { 261 // In the case of online state immediately proceed to the update 262 // stage. Otherwise, prepare and show error message. 263 if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE) { 264 StartUpdateCheck(); 265 } else { 266 UpdateErrorMessage(network, status); 267 ShowErrorMessage(); 268 } 269 } 270} 271 272void UpdateScreen::StartNetworkCheck() { 273 // If portal detector is enabled and portal detection before AU is 274 // allowed, initiate network state check. Otherwise, directly 275 // proceed to update. 276 if (!NetworkPortalDetector::Get()->IsEnabled()) { 277 StartUpdateCheck(); 278 return; 279 } 280 state_ = STATE_FIRST_PORTAL_CHECK; 281 is_first_detection_notification_ = true; 282 is_first_portal_notification_ = true; 283 NetworkPortalDetector::Get()->AddAndFireObserver(this); 284} 285 286void UpdateScreen::CancelUpdate() { 287 VLOG(1) << "Forced update cancel"; 288 ExitUpdate(REASON_UPDATE_CANCELED); 289} 290 291void UpdateScreen::Show() { 292 is_shown_ = true; 293 if (actor_) { 294 actor_->Show(); 295 actor_->SetProgress(kBeforeUpdateCheckProgress); 296 } 297} 298 299void UpdateScreen::Hide() { 300 if (actor_) 301 actor_->Hide(); 302 is_shown_ = false; 303} 304 305std::string UpdateScreen::GetName() const { 306 return WizardController::kUpdateScreenName; 307} 308 309void UpdateScreen::PrepareToShow() { 310 if (actor_) 311 actor_->PrepareToShow(); 312} 313 314void UpdateScreen::ExitUpdate(UpdateScreen::ExitReason reason) { 315 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); 316 NetworkPortalDetector::Get()->RemoveObserver(this); 317 318 switch (reason) { 319 case REASON_UPDATE_CANCELED: 320 get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE); 321 break; 322 case REASON_UPDATE_INIT_FAILED: 323 get_screen_observer()->OnExit( 324 ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE); 325 break; 326 case REASON_UPDATE_NON_CRITICAL: 327 case REASON_UPDATE_ENDED: 328 { 329 UpdateEngineClient* update_engine_client = 330 DBusThreadManager::Get()->GetUpdateEngineClient(); 331 switch (update_engine_client->GetLastStatus().status) { 332 case UpdateEngineClient::UPDATE_STATUS_ATTEMPTING_ROLLBACK: 333 break; 334 case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE: 335 case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT: 336 case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING: 337 case UpdateEngineClient::UPDATE_STATUS_FINALIZING: 338 case UpdateEngineClient::UPDATE_STATUS_VERIFYING: 339 DCHECK(!HasCriticalUpdate()); 340 // Noncritical update, just exit screen as if there is no update. 341 // no break 342 case UpdateEngineClient::UPDATE_STATUS_IDLE: 343 get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE); 344 break; 345 case UpdateEngineClient::UPDATE_STATUS_ERROR: 346 case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT: 347 get_screen_observer()->OnExit(is_checking_for_update_ ? 348 ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE : 349 ScreenObserver::UPDATE_ERROR_UPDATING); 350 break; 351 default: 352 NOTREACHED(); 353 } 354 } 355 break; 356 default: 357 NOTREACHED(); 358 } 359} 360 361void UpdateScreen::OnWaitForRebootTimeElapsed() { 362 LOG(ERROR) << "Unable to reboot - asking user for a manual reboot."; 363 MakeSureScreenIsShown(); 364 if (actor_) 365 actor_->ShowManualRebootInfo(); 366} 367 368void UpdateScreen::MakeSureScreenIsShown() { 369 if (!is_shown_) 370 get_screen_observer()->ShowCurrentScreen(); 371} 372 373void UpdateScreen::SetRebootCheckDelay(int seconds) { 374 if (seconds <= 0) 375 reboot_timer_.Stop(); 376 DCHECK(!reboot_timer_.IsRunning()); 377 reboot_check_delay_ = seconds; 378} 379 380void UpdateScreen::SetIgnoreIdleStatus(bool ignore_idle_status) { 381 ignore_idle_status_ = ignore_idle_status; 382} 383 384void UpdateScreen::UpdateDownloadingStats( 385 const UpdateEngineClient::Status& status) { 386 if (!actor_) 387 return; 388 base::Time download_current_time = base::Time::Now(); 389 if (download_current_time >= download_last_time_ + kMinTimeStep) { 390 // Estimate downloading rate. 391 double progress_delta = 392 std::max(status.download_progress - download_last_progress_, 0.0); 393 double time_delta = 394 (download_current_time - download_last_time_).InSecondsF(); 395 double download_rate = status.new_size * progress_delta / time_delta; 396 397 download_last_time_ = download_current_time; 398 download_last_progress_ = status.download_progress; 399 400 // Estimate time left. 401 double progress_left = std::max(1.0 - status.download_progress, 0.0); 402 if (!is_download_average_speed_computed_) { 403 download_average_speed_ = download_rate; 404 is_download_average_speed_computed_ = true; 405 } 406 download_average_speed_ = 407 kDownloadSpeedSmoothFactor * download_rate + 408 (1.0 - kDownloadSpeedSmoothFactor) * download_average_speed_; 409 if (download_average_speed_ < kDownloadAverageSpeedDropBound) { 410 time_delta = 411 (download_current_time - download_start_time_).InSecondsF(); 412 download_average_speed_ = 413 status.new_size * 414 (status.download_progress - download_start_progress_) / 415 time_delta; 416 } 417 double work_left = progress_left * status.new_size; 418 double time_left = work_left / download_average_speed_; 419 // |time_left| may be large enough or even +infinity. So we must 420 // |bound possible estimations. 421 time_left = std::min(time_left, kMaxTimeLeft); 422 423 actor_->ShowEstimatedTimeLeft(true); 424 actor_->SetEstimatedTimeLeft( 425 base::TimeDelta::FromSeconds(static_cast<int64>(time_left))); 426 } 427 428 int download_progress = static_cast<int>( 429 status.download_progress * kDownloadProgressIncrement); 430 actor_->SetProgress(kBeforeDownloadProgress + download_progress); 431} 432 433bool UpdateScreen::HasCriticalUpdate() { 434 if (is_ignore_update_deadlines_) 435 return true; 436 437 std::string deadline; 438 // Checking for update flag file causes us to do blocking IO on UI thread. 439 // Temporarily allow it until we fix http://crosbug.com/11106 440 base::ThreadRestrictions::ScopedAllowIO allow_io; 441 base::FilePath update_deadline_file_path(kUpdateDeadlineFile); 442 if (!base::ReadFileToString(update_deadline_file_path, &deadline) || 443 deadline.empty()) { 444 return false; 445 } 446 447 // TODO(dpolukhin): Analyze file content. Now we can just assume that 448 // if the file exists and not empty, there is critical update. 449 return true; 450} 451 452void UpdateScreen::OnActorDestroyed(UpdateScreenActor* actor) { 453 if (actor_ == actor) 454 actor_ = NULL; 455} 456 457void UpdateScreen::OnConnectToNetworkRequested() { 458 if (state_ == STATE_ERROR) { 459 LOG(WARNING) << "Hiding error message since AP was reselected"; 460 StartUpdateCheck(); 461 } 462} 463 464ErrorScreen* UpdateScreen::GetErrorScreen() { 465 return get_screen_observer()->GetErrorScreen(); 466} 467 468void UpdateScreen::StartUpdateCheck() { 469 NetworkPortalDetector::Get()->RemoveObserver(this); 470 if (state_ == STATE_ERROR) 471 HideErrorMessage(); 472 state_ = STATE_UPDATE; 473 DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this); 474 VLOG(1) << "Initiate update check"; 475 DBusThreadManager::Get()->GetUpdateEngineClient()->RequestUpdateCheck( 476 base::Bind(StartUpdateCallback, this)); 477} 478 479void UpdateScreen::ShowErrorMessage() { 480 LOG(WARNING) << "UpdateScreen::ShowErrorMessage()"; 481 state_ = STATE_ERROR; 482 GetErrorScreen()->SetUIState(ErrorScreen::UI_STATE_UPDATE); 483 get_screen_observer()->ShowErrorScreen(); 484} 485 486void UpdateScreen::HideErrorMessage() { 487 LOG(WARNING) << "UpdateScreen::HideErrorMessage()"; 488 get_screen_observer()->HideErrorScreen(this); 489} 490 491void UpdateScreen::UpdateErrorMessage( 492 const NetworkState* network, 493 const NetworkPortalDetector::CaptivePortalStatus status) { 494 switch (status) { 495 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE: 496 NOTREACHED(); 497 break; 498 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN: 499 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE: 500 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_OFFLINE, 501 std::string()); 502 break; 503 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL: 504 DCHECK(network); 505 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PORTAL, 506 network->name()); 507 if (is_first_portal_notification_) { 508 is_first_portal_notification_ = false; 509 GetErrorScreen()->FixCaptivePortal(); 510 } 511 break; 512 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED: 513 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PROXY, 514 std::string()); 515 break; 516 default: 517 NOTREACHED(); 518 break; 519 } 520} 521 522} // namespace chromeos 523