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