update_screen.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/cros/network_library.h" 16#include "chrome/browser/chromeos/login/screens/error_screen.h" 17#include "chrome/browser/chromeos/login/screens/screen_observer.h" 18#include "chrome/browser/chromeos/login/screens/update_screen_actor.h" 19#include "chrome/browser/chromeos/login/startup_utils.h" 20#include "chrome/browser/chromeos/login/wizard_controller.h" 21#include "chromeos/chromeos_switches.h" 22#include "chromeos/dbus/dbus_thread_manager.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_portal_notification_(true), 115 weak_factory_(this) { 116 DCHECK(actor_); 117 if (actor_) 118 actor_->SetDelegate(this); 119 GetInstanceSet().insert(this); 120} 121 122UpdateScreen::~UpdateScreen() { 123 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); 124 GetInstanceSet().erase(this); 125 if (actor_) 126 actor_->SetDelegate(NULL); 127} 128 129void UpdateScreen::UpdateStatusChanged( 130 const UpdateEngineClient::Status& status) { 131 if (!actor_) 132 return; 133 134 if (is_checking_for_update_ && 135 status.status > UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE) { 136 is_checking_for_update_ = false; 137 } 138 if (ignore_idle_status_ && status.status > 139 UpdateEngineClient::UPDATE_STATUS_IDLE) { 140 ignore_idle_status_ = false; 141 } 142 143 switch (status.status) { 144 case UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE: 145 // Do nothing in these cases, we don't want to notify the user of the 146 // check unless there is an update. 147 break; 148 case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE: 149 MakeSureScreenIsShown(); 150 actor_->SetProgress(kBeforeDownloadProgress); 151 actor_->ShowEstimatedTimeLeft(false); 152 if (!HasCriticalUpdate()) { 153 LOG(INFO) << "Noncritical update available: " 154 << status.new_version; 155 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 156 } else { 157 LOG(INFO) << "Critical update available: " 158 << status.new_version; 159 actor_->SetProgressMessage( 160 UpdateScreenActor::PROGRESS_MESSAGE_UPDATE_AVAILABLE); 161 actor_->ShowProgressMessage(true); 162 actor_->ShowCurtain(false); 163 } 164 break; 165 case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING: 166 { 167 MakeSureScreenIsShown(); 168 if (!is_downloading_update_) { 169 // Because update engine doesn't send UPDATE_STATUS_UPDATE_AVAILABLE 170 // we need to is update critical on first downloading notification. 171 is_downloading_update_ = true; 172 download_start_time_ = download_last_time_ = base::Time::Now(); 173 download_start_progress_ = status.download_progress; 174 download_last_progress_ = status.download_progress; 175 is_download_average_speed_computed_ = false; 176 download_average_speed_ = 0.0; 177 if (!HasCriticalUpdate()) { 178 LOG(INFO) << "Non-critical update available: " 179 << status.new_version; 180 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 181 } else { 182 LOG(INFO) << "Critical update available: " 183 << status.new_version; 184 actor_->SetProgressMessage( 185 UpdateScreenActor::PROGRESS_MESSAGE_INSTALLING_UPDATE); 186 actor_->ShowProgressMessage(true); 187 actor_->ShowCurtain(false); 188 } 189 } 190 UpdateDownloadingStats(status); 191 } 192 break; 193 case UpdateEngineClient::UPDATE_STATUS_VERIFYING: 194 MakeSureScreenIsShown(); 195 actor_->SetProgress(kBeforeVerifyingProgress); 196 actor_->SetProgressMessage(UpdateScreenActor::PROGRESS_MESSAGE_VERIFYING); 197 actor_->ShowProgressMessage(true); 198 break; 199 case UpdateEngineClient::UPDATE_STATUS_FINALIZING: 200 MakeSureScreenIsShown(); 201 actor_->SetProgress(kBeforeFinalizingProgress); 202 actor_->SetProgressMessage( 203 UpdateScreenActor::PROGRESS_MESSAGE_FINALIZING); 204 actor_->ShowProgressMessage(true); 205 break; 206 case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT: 207 MakeSureScreenIsShown(); 208 // Make sure that first OOBE stage won't be shown after reboot. 209 StartupUtils::MarkOobeCompleted(); 210 actor_->SetProgress(kProgressComplete); 211 actor_->ShowEstimatedTimeLeft(false); 212 if (HasCriticalUpdate()) { 213 actor_->ShowCurtain(false); 214 VLOG(1) << "Initiate reboot after update"; 215 DBusThreadManager::Get()->GetUpdateEngineClient()->RebootAfterUpdate(); 216 reboot_timer_.Start(FROM_HERE, 217 base::TimeDelta::FromSeconds(reboot_check_delay_), 218 this, 219 &UpdateScreen::OnWaitForRebootTimeElapsed); 220 } else { 221 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 222 } 223 break; 224 case UpdateEngineClient::UPDATE_STATUS_IDLE: 225 if (ignore_idle_status_) { 226 // It is first IDLE status that is sent before we initiated the check. 227 break; 228 } 229 // else no break 230 231 case UpdateEngineClient::UPDATE_STATUS_ERROR: 232 case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT: 233 ExitUpdate(REASON_UPDATE_ENDED); 234 break; 235 default: 236 NOTREACHED(); 237 break; 238 } 239} 240 241void UpdateScreen::OnPortalDetectionCompleted( 242 const Network* network, 243 const NetworkPortalDetector::CaptivePortalState& state) { 244 // Wait for the sane portal detection results. 245 if (!network || 246 state.status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN) { 247 return; 248 } 249 LOG(WARNING) << "UpdateScreen::OnPortalDetectionCompleted(): " 250 << "network=" << network->service_path() << ", " 251 << "state.status=" << state.status << ", " 252 << "state.response_code=" << state.response_code; 253 NetworkPortalDetector::CaptivePortalStatus status = state.status; 254 if (state_ == STATE_ERROR) { 255 // In the case of online state hide error message and proceed to 256 // the update stage. Otherwise, update error message content. 257 if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE) { 258 HideErrorMessage(); 259 StartUpdateCheck(); 260 } else { 261 UpdateErrorMessage(network, status); 262 } 263 } else if (state_ == STATE_FIRST_PORTAL_CHECK) { 264 // In the case of online state immediately proceed to the update 265 // stage. Otherwise, prepare and show error message. 266 if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE) { 267 StartUpdateCheck(); 268 } else { 269 UpdateErrorMessage(network, status); 270 ShowErrorMessage(); 271 } 272 } 273} 274 275void UpdateScreen::StartNetworkCheck() { 276 NetworkPortalDetector* detector = NetworkPortalDetector::GetInstance(); 277 278 // If portal detector is enabled and portal detection before AU is 279 // allowed, initiate network state check. Otherwise, directly 280 // proceed to update. 281 if (!NetworkPortalDetector::IsEnabledInCommandLine() || !detector || 282 !IsBlockingUpdateEnabledInCommandLine()) { 283 StartUpdateCheck(); 284 return; 285 } 286 state_ = STATE_FIRST_PORTAL_CHECK; 287 detector->AddAndFireObserver(this); 288} 289 290void UpdateScreen::CancelUpdate() { 291 VLOG(1) << "Forced update cancel"; 292 ExitUpdate(REASON_UPDATE_CANCELED); 293} 294 295void UpdateScreen::Show() { 296 is_shown_ = true; 297 if (actor_) { 298 actor_->Show(); 299 actor_->SetProgress(kBeforeUpdateCheckProgress); 300 } 301} 302 303void UpdateScreen::Hide() { 304 if (actor_) 305 actor_->Hide(); 306 is_shown_ = false; 307} 308 309std::string UpdateScreen::GetName() const { 310 return WizardController::kUpdateScreenName; 311} 312 313void UpdateScreen::PrepareToShow() { 314 if (actor_) 315 actor_->PrepareToShow(); 316} 317 318void UpdateScreen::ExitUpdate(UpdateScreen::ExitReason reason) { 319 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); 320 if (NetworkPortalDetector::GetInstance()) 321 NetworkPortalDetector::GetInstance()->RemoveObserver(this); 322 323 switch (reason) { 324 case REASON_UPDATE_CANCELED: 325 get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE); 326 break; 327 case REASON_UPDATE_INIT_FAILED: 328 get_screen_observer()->OnExit( 329 ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE); 330 break; 331 case REASON_UPDATE_NON_CRITICAL: 332 case REASON_UPDATE_ENDED: 333 { 334 UpdateEngineClient* update_engine_client = 335 DBusThreadManager::Get()->GetUpdateEngineClient(); 336 switch (update_engine_client->GetLastStatus().status) { 337 case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE: 338 case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT: 339 case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING: 340 case UpdateEngineClient::UPDATE_STATUS_FINALIZING: 341 case UpdateEngineClient::UPDATE_STATUS_VERIFYING: 342 DCHECK(!HasCriticalUpdate()); 343 // Noncritical update, just exit screen as if there is no update. 344 // no break 345 case UpdateEngineClient::UPDATE_STATUS_IDLE: 346 get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE); 347 break; 348 case UpdateEngineClient::UPDATE_STATUS_ERROR: 349 case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT: 350 get_screen_observer()->OnExit(is_checking_for_update_ ? 351 ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE : 352 ScreenObserver::UPDATE_ERROR_UPDATING); 353 break; 354 default: 355 NOTREACHED(); 356 } 357 } 358 break; 359 default: 360 NOTREACHED(); 361 } 362} 363 364void UpdateScreen::OnWaitForRebootTimeElapsed() { 365 LOG(ERROR) << "Unable to reboot - asking user for a manual reboot."; 366 MakeSureScreenIsShown(); 367 if (actor_) 368 actor_->ShowManualRebootInfo(); 369} 370 371void UpdateScreen::MakeSureScreenIsShown() { 372 if (!is_shown_) 373 get_screen_observer()->ShowCurrentScreen(); 374} 375 376void UpdateScreen::SetRebootCheckDelay(int seconds) { 377 if (seconds <= 0) 378 reboot_timer_.Stop(); 379 DCHECK(!reboot_timer_.IsRunning()); 380 reboot_check_delay_ = seconds; 381} 382 383void UpdateScreen::SetIgnoreIdleStatus(bool ignore_idle_status) { 384 ignore_idle_status_ = ignore_idle_status; 385} 386 387void UpdateScreen::UpdateDownloadingStats( 388 const UpdateEngineClient::Status& status) { 389 if (!actor_) 390 return; 391 base::Time download_current_time = base::Time::Now(); 392 if (download_current_time >= download_last_time_ + kMinTimeStep) { 393 // Estimate downloading rate. 394 double progress_delta = 395 std::max(status.download_progress - download_last_progress_, 0.0); 396 double time_delta = 397 (download_current_time - download_last_time_).InSecondsF(); 398 double download_rate = status.new_size * progress_delta / time_delta; 399 400 download_last_time_ = download_current_time; 401 download_last_progress_ = status.download_progress; 402 403 // Estimate time left. 404 double progress_left = std::max(1.0 - status.download_progress, 0.0); 405 if (!is_download_average_speed_computed_) { 406 download_average_speed_ = download_rate; 407 is_download_average_speed_computed_ = true; 408 } 409 download_average_speed_ = 410 kDownloadSpeedSmoothFactor * download_rate + 411 (1.0 - kDownloadSpeedSmoothFactor) * download_average_speed_; 412 if (download_average_speed_ < kDownloadAverageSpeedDropBound) { 413 time_delta = 414 (download_current_time - download_start_time_).InSecondsF(); 415 download_average_speed_ = 416 status.new_size * 417 (status.download_progress - download_start_progress_) / 418 time_delta; 419 } 420 double work_left = progress_left * status.new_size; 421 double time_left = work_left / download_average_speed_; 422 // |time_left| may be large enough or even +infinity. So we must 423 // |bound possible estimations. 424 time_left = std::min(time_left, kMaxTimeLeft); 425 426 actor_->ShowEstimatedTimeLeft(true); 427 actor_->SetEstimatedTimeLeft( 428 base::TimeDelta::FromSeconds(static_cast<int64>(time_left))); 429 } 430 431 int download_progress = static_cast<int>( 432 status.download_progress * kDownloadProgressIncrement); 433 actor_->SetProgress(kBeforeDownloadProgress + download_progress); 434} 435 436bool UpdateScreen::HasCriticalUpdate() { 437 if (is_ignore_update_deadlines_) 438 return true; 439 440 std::string deadline; 441 // Checking for update flag file causes us to do blocking IO on UI thread. 442 // Temporarily allow it until we fix http://crosbug.com/11106 443 base::ThreadRestrictions::ScopedAllowIO allow_io; 444 base::FilePath update_deadline_file_path(kUpdateDeadlineFile); 445 if (!file_util::ReadFileToString(update_deadline_file_path, &deadline) || 446 deadline.empty()) { 447 return false; 448 } 449 450 // TODO(dpolukhin): Analyze file content. Now we can just assume that 451 // if the file exists and not empty, there is critical update. 452 return true; 453} 454 455void UpdateScreen::OnActorDestroyed(UpdateScreenActor* actor) { 456 if (actor_ == actor) 457 actor_ = NULL; 458} 459 460ErrorScreen* UpdateScreen::GetErrorScreen() { 461 return get_screen_observer()->GetErrorScreen(); 462} 463 464void UpdateScreen::StartUpdateCheck() { 465 NetworkPortalDetector* detector = NetworkPortalDetector::GetInstance(); 466 if (detector) 467 detector->RemoveObserver(this); 468 state_ = STATE_UPDATE; 469 DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this); 470 VLOG(1) << "Initiate update check"; 471 DBusThreadManager::Get()->GetUpdateEngineClient()->RequestUpdateCheck( 472 base::Bind(StartUpdateCallback, this)); 473} 474 475void UpdateScreen::ShowErrorMessage() { 476 LOG(WARNING) << "UpdateScreen::ShowErrorMessage()"; 477 state_ = STATE_ERROR; 478 GetErrorScreen()->SetUIState(ErrorScreen::UI_STATE_UPDATE); 479 get_screen_observer()->ShowErrorScreen(); 480} 481 482void UpdateScreen::HideErrorMessage() { 483 LOG(WARNING) << "UpdateScreen::HideErrorMessage()"; 484 get_screen_observer()->HideErrorScreen(this); 485} 486 487void UpdateScreen::UpdateErrorMessage( 488 const Network* network, 489 const NetworkPortalDetector::CaptivePortalStatus status) { 490 switch (status) { 491 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN: 492 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE: 493 NOTREACHED(); 494 break; 495 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE: 496 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_OFFLINE, 497 std::string()); 498 break; 499 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL: 500 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PORTAL, 501 network->name()); 502 if (is_first_portal_notification_) { 503 is_first_portal_notification_ = false; 504 GetErrorScreen()->FixCaptivePortal(); 505 } 506 break; 507 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED: 508 GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PROXY, 509 std::string()); 510 break; 511 default: 512 NOTREACHED(); 513 break; 514 } 515} 516 517} // namespace chromeos 518