update_screen.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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/command_line.h" 11#include "base/file_util.h" 12#include "base/logging.h" 13#include "base/message_loop/message_loop.h" 14#include "base/threading/thread_restrictions.h" 15#include "chrome/browser/chromeos/login/screens/error_screen.h" 16#include "chrome/browser/chromeos/login/screens/screen_observer.h" 17#include "chrome/browser/chromeos/login/screens/update_screen_actor.h" 18#include "chrome/browser/chromeos/login/startup_utils.h" 19#include "chrome/browser/chromeos/login/wizard_controller.h" 20#include "chromeos/chromeos_switches.h" 21#include "chromeos/dbus/dbus_thread_manager.h" 22#include "chromeos/network/network_state.h" 23#include "content/public/browser/browser_thread.h" 24 25using content::BrowserThread; 26 27namespace chromeos { 28 29namespace { 30 31// Progress bar stages. Each represents progress bar value 32// at the beginning of each stage. 33// TODO(nkostylev): Base stage progress values on approximate time. 34// TODO(nkostylev): Animate progress during each state. 35const int kBeforeUpdateCheckProgress = 7; 36const int kBeforeDownloadProgress = 14; 37const int kBeforeVerifyingProgress = 74; 38const int kBeforeFinalizingProgress = 81; 39const int kProgressComplete = 100; 40 41// Defines what part of update progress does download part takes. 42const int kDownloadProgressIncrement = 60; 43 44const char kUpdateDeadlineFile[] = "/tmp/update-check-response-deadline"; 45 46// Minimum timestep between two consecutive measurements for the 47// download rate. 48const base::TimeDelta kMinTimeStep = base::TimeDelta::FromSeconds(1); 49 50// Smooth factor that is used for the average downloading speed 51// estimation. 52// avg_speed = smooth_factor * cur_speed + (1.0 - smooth_factor) * avg_speed. 53const double kDownloadSpeedSmoothFactor = 0.1; 54 55// Minumum allowed value for the average downloading speed. 56const double kDownloadAverageSpeedDropBound = 1e-8; 57 58// An upper bound for possible downloading time left estimations. 59const double kMaxTimeLeft = 24 * 60 * 60; 60 61// Invoked from call to RequestUpdateCheck upon completion of the DBus call. 62void StartUpdateCallback(UpdateScreen* screen, 63 UpdateEngineClient::UpdateCheckResult result) { 64 VLOG(1) << "Callback from RequestUpdateCheck, result " << result; 65 if (UpdateScreen::HasInstance(screen)) { 66 if (result == UpdateEngineClient::UPDATE_RESULT_SUCCESS) 67 screen->SetIgnoreIdleStatus(false); 68 else 69 screen->ExitUpdate(UpdateScreen::REASON_UPDATE_INIT_FAILED); 70 } 71} 72 73} // anonymous namespace 74 75// static 76UpdateScreen::InstanceSet& UpdateScreen::GetInstanceSet() { 77 CR_DEFINE_STATIC_LOCAL(std::set<UpdateScreen*>, instance_set, ()); 78 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // not threadsafe. 79 return instance_set; 80} 81 82// static 83bool UpdateScreen::HasInstance(UpdateScreen* inst) { 84 InstanceSet& instance_set = GetInstanceSet(); 85 InstanceSet::iterator found = instance_set.find(inst); 86 return (found != instance_set.end()); 87} 88 89UpdateScreen::UpdateScreen( 90 ScreenObserver* screen_observer, 91 UpdateScreenActor* actor) 92 : WizardScreen(screen_observer), 93 state_(STATE_IDLE), 94 reboot_check_delay_(0), 95 is_checking_for_update_(true), 96 is_downloading_update_(false), 97 is_ignore_update_deadlines_(false), 98 is_shown_(false), 99 ignore_idle_status_(true), 100 actor_(actor), 101 is_first_detection_notification_(true), 102 is_first_portal_notification_(true), 103 weak_factory_(this) { 104 DCHECK(actor_); 105 if (actor_) 106 actor_->SetDelegate(this); 107 GetInstanceSet().insert(this); 108} 109 110UpdateScreen::~UpdateScreen() { 111 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); 112 NetworkPortalDetector::Get()->RemoveObserver(this); 113 GetInstanceSet().erase(this); 114 if (actor_) 115 actor_->SetDelegate(NULL); 116} 117 118void UpdateScreen::UpdateStatusChanged( 119 const UpdateEngineClient::Status& status) { 120 if (!actor_) 121 return; 122 123 if (is_checking_for_update_ && 124 status.status > UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE) { 125 is_checking_for_update_ = false; 126 } 127 if (ignore_idle_status_ && status.status > 128 UpdateEngineClient::UPDATE_STATUS_IDLE) { 129 ignore_idle_status_ = false; 130 } 131 132 switch (status.status) { 133 case UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE: 134 // Do nothing in these cases, we don't want to notify the user of the 135 // check unless there is an update. 136 break; 137 case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE: 138 MakeSureScreenIsShown(); 139 actor_->SetProgress(kBeforeDownloadProgress); 140 actor_->ShowEstimatedTimeLeft(false); 141 if (!HasCriticalUpdate()) { 142 VLOG(1) << "Noncritical update available: " << status.new_version; 143 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 144 } else { 145 VLOG(1) << "Critical update available: " << status.new_version; 146 actor_->SetProgressMessage( 147 UpdateScreenActor::PROGRESS_MESSAGE_UPDATE_AVAILABLE); 148 actor_->ShowProgressMessage(true); 149 actor_->ShowCurtain(false); 150 } 151 break; 152 case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING: 153 { 154 MakeSureScreenIsShown(); 155 if (!is_downloading_update_) { 156 // Because update engine doesn't send UPDATE_STATUS_UPDATE_AVAILABLE 157 // we need to is update critical on first downloading notification. 158 is_downloading_update_ = true; 159 download_start_time_ = download_last_time_ = base::Time::Now(); 160 download_start_progress_ = status.download_progress; 161 download_last_progress_ = status.download_progress; 162 is_download_average_speed_computed_ = false; 163 download_average_speed_ = 0.0; 164 if (!HasCriticalUpdate()) { 165 VLOG(1) << "Non-critical update available: " << status.new_version; 166 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 167 } else { 168 VLOG(1) << "Critical update available: " << status.new_version; 169 actor_->SetProgressMessage( 170 UpdateScreenActor::PROGRESS_MESSAGE_INSTALLING_UPDATE); 171 actor_->ShowProgressMessage(true); 172 actor_->ShowCurtain(false); 173 } 174 } 175 UpdateDownloadingStats(status); 176 } 177 break; 178 case UpdateEngineClient::UPDATE_STATUS_VERIFYING: 179 MakeSureScreenIsShown(); 180 actor_->SetProgress(kBeforeVerifyingProgress); 181 actor_->SetProgressMessage(UpdateScreenActor::PROGRESS_MESSAGE_VERIFYING); 182 actor_->ShowProgressMessage(true); 183 break; 184 case UpdateEngineClient::UPDATE_STATUS_FINALIZING: 185 MakeSureScreenIsShown(); 186 actor_->SetProgress(kBeforeFinalizingProgress); 187 actor_->SetProgressMessage( 188 UpdateScreenActor::PROGRESS_MESSAGE_FINALIZING); 189 actor_->ShowProgressMessage(true); 190 break; 191 case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT: 192 MakeSureScreenIsShown(); 193 // Make sure that first OOBE stage won't be shown after reboot. 194 StartupUtils::MarkOobeCompleted(); 195 actor_->SetProgress(kProgressComplete); 196 actor_->ShowEstimatedTimeLeft(false); 197 if (HasCriticalUpdate()) { 198 actor_->ShowCurtain(false); 199 VLOG(1) << "Initiate reboot after update"; 200 DBusThreadManager::Get()->GetUpdateEngineClient()->RebootAfterUpdate(); 201 reboot_timer_.Start(FROM_HERE, 202 base::TimeDelta::FromSeconds(reboot_check_delay_), 203 this, 204 &UpdateScreen::OnWaitForRebootTimeElapsed); 205 } else { 206 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 207 } 208 break; 209 case UpdateEngineClient::UPDATE_STATUS_IDLE: 210 if (ignore_idle_status_) { 211 // It is first IDLE status that is sent before we initiated the check. 212 break; 213 } 214 // else no break 215 216 case UpdateEngineClient::UPDATE_STATUS_ERROR: 217 case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT: 218 ExitUpdate(REASON_UPDATE_ENDED); 219 break; 220 default: 221 NOTREACHED(); 222 break; 223 } 224} 225 226void UpdateScreen::OnPortalDetectionCompleted( 227 const NetworkState* network, 228 const NetworkPortalDetector::CaptivePortalState& state) { 229 LOG(WARNING) << "UpdateScreen::PortalDetectionCompleted(): " 230 << "network=" << (network ? network->path() : "") << ", " 231 << "state.status=" << state.status << ", " 232 << "state.response_code=" << state.response_code; 233 234 // Wait for the sane detection results. 235 if (network && 236 state.status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN) { 237 return; 238 } 239 240 // Restart portal detection for the first notification about offline state. 241 if ((!network || 242 state.status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE) && 243 is_first_detection_notification_) { 244 is_first_detection_notification_ = false; 245 base::MessageLoop::current()->PostTask( 246 FROM_HERE, 247 base::Bind( 248 base::IgnoreResult(&NetworkPortalDetector::StartDetectionIfIdle), 249 base::Unretained(NetworkPortalDetector::Get()))); 250 return; 251 } 252 is_first_detection_notification_ = false; 253 254 NetworkPortalDetector::CaptivePortalStatus status = state.status; 255 if (state_ == STATE_ERROR) { 256 // In the case of online state hide error message and proceed to 257 // the update stage. Otherwise, update error message content. 258 if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE) 259 StartUpdateCheck(); 260 else 261 UpdateErrorMessage(network, status); 262 } else if (state_ == STATE_FIRST_PORTAL_CHECK) { 263 // In the case of online state immediately proceed to the update 264 // stage. Otherwise, prepare and show error message. 265 if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE) { 266 StartUpdateCheck(); 267 } else { 268 UpdateErrorMessage(network, status); 269 ShowErrorMessage(); 270 } 271 } 272} 273 274void UpdateScreen::StartNetworkCheck() { 275 // If portal detector is enabled and portal detection before AU is 276 // allowed, initiate network state check. Otherwise, directly 277 // proceed to update. 278 if (!NetworkPortalDetector::Get()->IsEnabled()) { 279 StartUpdateCheck(); 280 return; 281 } 282 state_ = STATE_FIRST_PORTAL_CHECK; 283 is_first_detection_notification_ = true; 284 is_first_portal_notification_ = true; 285 NetworkPortalDetector::Get()->AddAndFireObserver(this); 286} 287 288void UpdateScreen::CancelUpdate() { 289 VLOG(1) << "Forced update cancel"; 290 ExitUpdate(REASON_UPDATE_CANCELED); 291} 292 293void UpdateScreen::Show() { 294 is_shown_ = true; 295 if (actor_) { 296 actor_->Show(); 297 actor_->SetProgress(kBeforeUpdateCheckProgress); 298 } 299} 300 301void UpdateScreen::Hide() { 302 if (actor_) 303 actor_->Hide(); 304 is_shown_ = false; 305} 306 307std::string UpdateScreen::GetName() const { 308 return WizardController::kUpdateScreenName; 309} 310 311void UpdateScreen::PrepareToShow() { 312 if (actor_) 313 actor_->PrepareToShow(); 314} 315 316void UpdateScreen::ExitUpdate(UpdateScreen::ExitReason reason) { 317 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); 318 NetworkPortalDetector::Get()->RemoveObserver(this); 319 320 switch (reason) { 321 case REASON_UPDATE_CANCELED: 322 get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE); 323 break; 324 case REASON_UPDATE_INIT_FAILED: 325 get_screen_observer()->OnExit( 326 ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE); 327 break; 328 case REASON_UPDATE_NON_CRITICAL: 329 case REASON_UPDATE_ENDED: 330 { 331 UpdateEngineClient* update_engine_client = 332 DBusThreadManager::Get()->GetUpdateEngineClient(); 333 switch (update_engine_client->GetLastStatus().status) { 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 const std::string& service_path) { 459 if (state_ == STATE_ERROR) { 460 LOG(WARNING) << "Hiding error message since AP was reselected"; 461 StartUpdateCheck(); 462 } 463} 464 465ErrorScreen* UpdateScreen::GetErrorScreen() { 466 return get_screen_observer()->GetErrorScreen(); 467} 468 469void UpdateScreen::StartUpdateCheck() { 470 NetworkPortalDetector::Get()->RemoveObserver(this); 471 if (state_ == STATE_ERROR) 472 HideErrorMessage(); 473 state_ = STATE_UPDATE; 474 DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this); 475 VLOG(1) << "Initiate update check"; 476 DBusThreadManager::Get()->GetUpdateEngineClient()->RequestUpdateCheck( 477 base::Bind(StartUpdateCallback, this)); 478} 479 480void UpdateScreen::ShowErrorMessage() { 481 LOG(WARNING) << "UpdateScreen::ShowErrorMessage()"; 482 state_ = STATE_ERROR; 483 GetErrorScreen()->SetUIState(ErrorScreen::UI_STATE_UPDATE); 484 get_screen_observer()->ShowErrorScreen(); 485} 486 487void UpdateScreen::HideErrorMessage() { 488 LOG(WARNING) << "UpdateScreen::HideErrorMessage()"; 489 get_screen_observer()->HideErrorScreen(this); 490} 491 492void UpdateScreen::UpdateErrorMessage( 493 const NetworkState* network, 494 const NetworkPortalDetector::CaptivePortalStatus status) { 495 switch (status) { 496 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE: 497 NOTREACHED(); 498 break; 499 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN: 500 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE: 501 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_OFFLINE, 502 std::string()); 503 break; 504 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL: 505 DCHECK(network); 506 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PORTAL, 507 network->name()); 508 if (is_first_portal_notification_) { 509 is_first_portal_notification_ = false; 510 GetErrorScreen()->FixCaptivePortal(); 511 } 512 break; 513 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED: 514 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PROXY, 515 std::string()); 516 break; 517 default: 518 NOTREACHED(); 519 break; 520 } 521} 522 523} // namespace chromeos 524