update_screen.cc revision 2385ea399aae016c0806a4f9ef3c9cfe3d2a39df
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 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 StartUpdateCheck(); 277 else 278 UpdateErrorMessage(network, status); 279 } else if (state_ == STATE_FIRST_PORTAL_CHECK) { 280 // In the case of online state immediately proceed to the update 281 // stage. Otherwise, prepare and show error message. 282 if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE) { 283 StartUpdateCheck(); 284 } else { 285 UpdateErrorMessage(network, status); 286 ShowErrorMessage(); 287 } 288 } 289} 290 291void UpdateScreen::StartNetworkCheck() { 292 NetworkPortalDetector* detector = NetworkPortalDetector::GetInstance(); 293 294 // If portal detector is enabled and portal detection before AU is 295 // allowed, initiate network state check. Otherwise, directly 296 // proceed to update. 297 if (!NetworkPortalDetector::IsEnabledInCommandLine() || !detector || 298 !IsBlockingUpdateEnabledInCommandLine()) { 299 StartUpdateCheck(); 300 return; 301 } 302 state_ = STATE_FIRST_PORTAL_CHECK; 303 is_first_detection_notification_ = true; 304 is_first_portal_notification_ = true; 305 detector->AddAndFireObserver(this); 306} 307 308void UpdateScreen::CancelUpdate() { 309 VLOG(1) << "Forced update cancel"; 310 ExitUpdate(REASON_UPDATE_CANCELED); 311} 312 313void UpdateScreen::Show() { 314 is_shown_ = true; 315 if (actor_) { 316 actor_->Show(); 317 actor_->SetProgress(kBeforeUpdateCheckProgress); 318 } 319} 320 321void UpdateScreen::Hide() { 322 if (actor_) 323 actor_->Hide(); 324 is_shown_ = false; 325} 326 327std::string UpdateScreen::GetName() const { 328 return WizardController::kUpdateScreenName; 329} 330 331void UpdateScreen::PrepareToShow() { 332 if (actor_) 333 actor_->PrepareToShow(); 334} 335 336void UpdateScreen::ExitUpdate(UpdateScreen::ExitReason reason) { 337 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); 338 if (NetworkPortalDetector::GetInstance()) 339 NetworkPortalDetector::GetInstance()->RemoveObserver(this); 340 341 switch (reason) { 342 case REASON_UPDATE_CANCELED: 343 get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE); 344 break; 345 case REASON_UPDATE_INIT_FAILED: 346 get_screen_observer()->OnExit( 347 ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE); 348 break; 349 case REASON_UPDATE_NON_CRITICAL: 350 case REASON_UPDATE_ENDED: 351 { 352 UpdateEngineClient* update_engine_client = 353 DBusThreadManager::Get()->GetUpdateEngineClient(); 354 switch (update_engine_client->GetLastStatus().status) { 355 case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE: 356 case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT: 357 case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING: 358 case UpdateEngineClient::UPDATE_STATUS_FINALIZING: 359 case UpdateEngineClient::UPDATE_STATUS_VERIFYING: 360 DCHECK(!HasCriticalUpdate()); 361 // Noncritical update, just exit screen as if there is no update. 362 // no break 363 case UpdateEngineClient::UPDATE_STATUS_IDLE: 364 get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE); 365 break; 366 case UpdateEngineClient::UPDATE_STATUS_ERROR: 367 case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT: 368 get_screen_observer()->OnExit(is_checking_for_update_ ? 369 ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE : 370 ScreenObserver::UPDATE_ERROR_UPDATING); 371 break; 372 default: 373 NOTREACHED(); 374 } 375 } 376 break; 377 default: 378 NOTREACHED(); 379 } 380} 381 382void UpdateScreen::OnWaitForRebootTimeElapsed() { 383 LOG(ERROR) << "Unable to reboot - asking user for a manual reboot."; 384 MakeSureScreenIsShown(); 385 if (actor_) 386 actor_->ShowManualRebootInfo(); 387} 388 389void UpdateScreen::MakeSureScreenIsShown() { 390 if (!is_shown_) 391 get_screen_observer()->ShowCurrentScreen(); 392} 393 394void UpdateScreen::SetRebootCheckDelay(int seconds) { 395 if (seconds <= 0) 396 reboot_timer_.Stop(); 397 DCHECK(!reboot_timer_.IsRunning()); 398 reboot_check_delay_ = seconds; 399} 400 401void UpdateScreen::SetIgnoreIdleStatus(bool ignore_idle_status) { 402 ignore_idle_status_ = ignore_idle_status; 403} 404 405void UpdateScreen::UpdateDownloadingStats( 406 const UpdateEngineClient::Status& status) { 407 if (!actor_) 408 return; 409 base::Time download_current_time = base::Time::Now(); 410 if (download_current_time >= download_last_time_ + kMinTimeStep) { 411 // Estimate downloading rate. 412 double progress_delta = 413 std::max(status.download_progress - download_last_progress_, 0.0); 414 double time_delta = 415 (download_current_time - download_last_time_).InSecondsF(); 416 double download_rate = status.new_size * progress_delta / time_delta; 417 418 download_last_time_ = download_current_time; 419 download_last_progress_ = status.download_progress; 420 421 // Estimate time left. 422 double progress_left = std::max(1.0 - status.download_progress, 0.0); 423 if (!is_download_average_speed_computed_) { 424 download_average_speed_ = download_rate; 425 is_download_average_speed_computed_ = true; 426 } 427 download_average_speed_ = 428 kDownloadSpeedSmoothFactor * download_rate + 429 (1.0 - kDownloadSpeedSmoothFactor) * download_average_speed_; 430 if (download_average_speed_ < kDownloadAverageSpeedDropBound) { 431 time_delta = 432 (download_current_time - download_start_time_).InSecondsF(); 433 download_average_speed_ = 434 status.new_size * 435 (status.download_progress - download_start_progress_) / 436 time_delta; 437 } 438 double work_left = progress_left * status.new_size; 439 double time_left = work_left / download_average_speed_; 440 // |time_left| may be large enough or even +infinity. So we must 441 // |bound possible estimations. 442 time_left = std::min(time_left, kMaxTimeLeft); 443 444 actor_->ShowEstimatedTimeLeft(true); 445 actor_->SetEstimatedTimeLeft( 446 base::TimeDelta::FromSeconds(static_cast<int64>(time_left))); 447 } 448 449 int download_progress = static_cast<int>( 450 status.download_progress * kDownloadProgressIncrement); 451 actor_->SetProgress(kBeforeDownloadProgress + download_progress); 452} 453 454bool UpdateScreen::HasCriticalUpdate() { 455 if (is_ignore_update_deadlines_) 456 return true; 457 458 std::string deadline; 459 // Checking for update flag file causes us to do blocking IO on UI thread. 460 // Temporarily allow it until we fix http://crosbug.com/11106 461 base::ThreadRestrictions::ScopedAllowIO allow_io; 462 base::FilePath update_deadline_file_path(kUpdateDeadlineFile); 463 if (!file_util::ReadFileToString(update_deadline_file_path, &deadline) || 464 deadline.empty()) { 465 return false; 466 } 467 468 // TODO(dpolukhin): Analyze file content. Now we can just assume that 469 // if the file exists and not empty, there is critical update. 470 return true; 471} 472 473void UpdateScreen::OnActorDestroyed(UpdateScreenActor* actor) { 474 if (actor_ == actor) 475 actor_ = NULL; 476} 477 478void UpdateScreen::OnConnectToNetworkRequested( 479 const std::string& service_path) { 480 if (state_ == STATE_ERROR) { 481 LOG(WARNING) << "Hiding error message since AP was reselected"; 482 StartUpdateCheck(); 483 } 484} 485 486ErrorScreen* UpdateScreen::GetErrorScreen() { 487 return get_screen_observer()->GetErrorScreen(); 488} 489 490void UpdateScreen::StartUpdateCheck() { 491 NetworkPortalDetector* detector = NetworkPortalDetector::GetInstance(); 492 if (detector) 493 detector->RemoveObserver(this); 494 if (state_ == STATE_ERROR) 495 HideErrorMessage(); 496 state_ = STATE_UPDATE; 497 DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this); 498 VLOG(1) << "Initiate update check"; 499 DBusThreadManager::Get()->GetUpdateEngineClient()->RequestUpdateCheck( 500 base::Bind(StartUpdateCallback, this)); 501} 502 503void UpdateScreen::ShowErrorMessage() { 504 LOG(WARNING) << "UpdateScreen::ShowErrorMessage()"; 505 state_ = STATE_ERROR; 506 GetErrorScreen()->SetUIState(ErrorScreen::UI_STATE_UPDATE); 507 get_screen_observer()->ShowErrorScreen(); 508} 509 510void UpdateScreen::HideErrorMessage() { 511 LOG(WARNING) << "UpdateScreen::HideErrorMessage()"; 512 get_screen_observer()->HideErrorScreen(this); 513} 514 515void UpdateScreen::UpdateErrorMessage( 516 const NetworkState* network, 517 const NetworkPortalDetector::CaptivePortalStatus status) { 518 switch (status) { 519 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE: 520 NOTREACHED(); 521 break; 522 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN: 523 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE: 524 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_OFFLINE, 525 std::string()); 526 break; 527 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL: 528 DCHECK(network); 529 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PORTAL, 530 network->name()); 531 if (is_first_portal_notification_) { 532 is_first_portal_notification_ = false; 533 GetErrorScreen()->FixCaptivePortal(); 534 } 535 break; 536 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED: 537 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PROXY, 538 std::string()); 539 break; 540 default: 541 NOTREACHED(); 542 break; 543 } 544} 545 546} // namespace chromeos 547