blob: 4469c120e0f96959aa4b677fc99c571bbf10d973 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/screens/update_screen.h"
#include <algorithm>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/chromeos/login/screens/error_screen.h"
#include "chrome/browser/chromeos/login/screens/screen_observer.h"
#include "chrome/browser/chromeos/login/screens/update_screen_actor.h"
#include "chrome/browser/chromeos/login/startup_utils.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/network/network_state.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace chromeos {
namespace {
// Progress bar stages. Each represents progress bar value
// at the beginning of each stage.
// TODO(nkostylev): Base stage progress values on approximate time.
// TODO(nkostylev): Animate progress during each state.
const int kBeforeUpdateCheckProgress = 7;
const int kBeforeDownloadProgress = 14;
const int kBeforeVerifyingProgress = 74;
const int kBeforeFinalizingProgress = 81;
const int kProgressComplete = 100;
// Defines what part of update progress does download part takes.
const int kDownloadProgressIncrement = 60;
// Considering 10px shadow from each side.
const int kUpdateScreenWidth = 580;
const int kUpdateScreenHeight = 305;
const char kUpdateDeadlineFile[] = "/tmp/update-check-response-deadline";
// Minimum timestep between two consecutive measurements for the
// download rate.
const base::TimeDelta kMinTimeStep = base::TimeDelta::FromSeconds(1);
// Minimum allowed progress between two consecutive ETAs.
const double kMinProgressStep = 1e-3;
// Smooth factor that is used for the average downloading speed
// estimation.
// avg_speed = smooth_factor * cur_speed + (1.0 - smooth_factor) * avg_speed.
const double kDownloadSpeedSmoothFactor = 0.1;
// Minumum allowed value for the average downloading speed.
const double kDownloadAverageSpeedDropBound = 1e-8;
// An upper bound for possible downloading time left estimations.
const double kMaxTimeLeft = 24 * 60 * 60;
// Invoked from call to RequestUpdateCheck upon completion of the DBus call.
void StartUpdateCallback(UpdateScreen* screen,
UpdateEngineClient::UpdateCheckResult result) {
VLOG(1) << "Callback from RequestUpdateCheck, result " << result;
if (UpdateScreen::HasInstance(screen)) {
if (result == UpdateEngineClient::UPDATE_RESULT_SUCCESS)
screen->SetIgnoreIdleStatus(false);
else
screen->ExitUpdate(UpdateScreen::REASON_UPDATE_INIT_FAILED);
}
}
// Returns true if blocking AU is enabled in command line.
bool IsBlockingUpdateEnabledInCommandLine() {
return !CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kDisableOOBEBlockingUpdate);
}
} // anonymous namespace
// static
UpdateScreen::InstanceSet& UpdateScreen::GetInstanceSet() {
CR_DEFINE_STATIC_LOCAL(std::set<UpdateScreen*>, instance_set, ());
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // not threadsafe.
return instance_set;
}
// static
bool UpdateScreen::HasInstance(UpdateScreen* inst) {
InstanceSet& instance_set = GetInstanceSet();
InstanceSet::iterator found = instance_set.find(inst);
return (found != instance_set.end());
}
UpdateScreen::UpdateScreen(
ScreenObserver* screen_observer,
UpdateScreenActor* actor)
: WizardScreen(screen_observer),
state_(STATE_IDLE),
reboot_check_delay_(0),
is_checking_for_update_(true),
is_downloading_update_(false),
is_ignore_update_deadlines_(false),
is_shown_(false),
ignore_idle_status_(true),
actor_(actor),
is_first_detection_notification_(true),
is_first_portal_notification_(true),
weak_factory_(this) {
DCHECK(actor_);
if (actor_)
actor_->SetDelegate(this);
GetInstanceSet().insert(this);
}
UpdateScreen::~UpdateScreen() {
DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
GetInstanceSet().erase(this);
if (actor_)
actor_->SetDelegate(NULL);
}
void UpdateScreen::UpdateStatusChanged(
const UpdateEngineClient::Status& status) {
if (!actor_)
return;
if (is_checking_for_update_ &&
status.status > UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE) {
is_checking_for_update_ = false;
}
if (ignore_idle_status_ && status.status >
UpdateEngineClient::UPDATE_STATUS_IDLE) {
ignore_idle_status_ = false;
}
switch (status.status) {
case UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE:
// Do nothing in these cases, we don't want to notify the user of the
// check unless there is an update.
break;
case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE:
MakeSureScreenIsShown();
actor_->SetProgress(kBeforeDownloadProgress);
actor_->ShowEstimatedTimeLeft(false);
if (!HasCriticalUpdate()) {
LOG(INFO) << "Noncritical update available: "
<< status.new_version;
ExitUpdate(REASON_UPDATE_NON_CRITICAL);
} else {
LOG(INFO) << "Critical update available: "
<< status.new_version;
actor_->SetProgressMessage(
UpdateScreenActor::PROGRESS_MESSAGE_UPDATE_AVAILABLE);
actor_->ShowProgressMessage(true);
actor_->ShowCurtain(false);
}
break;
case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING:
{
MakeSureScreenIsShown();
if (!is_downloading_update_) {
// Because update engine doesn't send UPDATE_STATUS_UPDATE_AVAILABLE
// we need to is update critical on first downloading notification.
is_downloading_update_ = true;
download_start_time_ = download_last_time_ = base::Time::Now();
download_start_progress_ = status.download_progress;
download_last_progress_ = status.download_progress;
is_download_average_speed_computed_ = false;
download_average_speed_ = 0.0;
if (!HasCriticalUpdate()) {
LOG(INFO) << "Non-critical update available: "
<< status.new_version;
ExitUpdate(REASON_UPDATE_NON_CRITICAL);
} else {
LOG(INFO) << "Critical update available: "
<< status.new_version;
actor_->SetProgressMessage(
UpdateScreenActor::PROGRESS_MESSAGE_INSTALLING_UPDATE);
actor_->ShowProgressMessage(true);
actor_->ShowCurtain(false);
}
}
UpdateDownloadingStats(status);
}
break;
case UpdateEngineClient::UPDATE_STATUS_VERIFYING:
MakeSureScreenIsShown();
actor_->SetProgress(kBeforeVerifyingProgress);
actor_->SetProgressMessage(UpdateScreenActor::PROGRESS_MESSAGE_VERIFYING);
actor_->ShowProgressMessage(true);
break;
case UpdateEngineClient::UPDATE_STATUS_FINALIZING:
MakeSureScreenIsShown();
actor_->SetProgress(kBeforeFinalizingProgress);
actor_->SetProgressMessage(
UpdateScreenActor::PROGRESS_MESSAGE_FINALIZING);
actor_->ShowProgressMessage(true);
break;
case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT:
MakeSureScreenIsShown();
// Make sure that first OOBE stage won't be shown after reboot.
StartupUtils::MarkOobeCompleted();
actor_->SetProgress(kProgressComplete);
actor_->ShowEstimatedTimeLeft(false);
if (HasCriticalUpdate()) {
actor_->ShowCurtain(false);
VLOG(1) << "Initiate reboot after update";
DBusThreadManager::Get()->GetUpdateEngineClient()->RebootAfterUpdate();
reboot_timer_.Start(FROM_HERE,
base::TimeDelta::FromSeconds(reboot_check_delay_),
this,
&UpdateScreen::OnWaitForRebootTimeElapsed);
} else {
ExitUpdate(REASON_UPDATE_NON_CRITICAL);
}
break;
case UpdateEngineClient::UPDATE_STATUS_IDLE:
if (ignore_idle_status_) {
// It is first IDLE status that is sent before we initiated the check.
break;
}
// else no break
case UpdateEngineClient::UPDATE_STATUS_ERROR:
case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT:
ExitUpdate(REASON_UPDATE_ENDED);
break;
default:
NOTREACHED();
break;
}
}
void UpdateScreen::OnPortalDetectionCompleted(
const NetworkState* network,
const NetworkPortalDetector::CaptivePortalState& state) {
LOG(WARNING) << "UpdateScreen::PortalDetectionCompleted(): "
<< "network=" << (network ? network->path() : "") << ", "
<< "state.status=" << state.status << ", "
<< "state.response_code=" << state.response_code;
// Wait for the sane detection results.
if (network &&
state.status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN) {
return;
}
// Restart portal detection for the first notification about offline state.
if ((!network ||
state.status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE) &&
is_first_detection_notification_) {
is_first_detection_notification_ = false;
NetworkPortalDetector* detector = NetworkPortalDetector::GetInstance();
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(
base::IgnoreResult(&NetworkPortalDetector::StartDetectionIfIdle),
base::Unretained(detector)));
return;
}
is_first_detection_notification_ = false;
NetworkPortalDetector::CaptivePortalStatus status = state.status;
if (state_ == STATE_ERROR) {
// In the case of online state hide error message and proceed to
// the update stage. Otherwise, update error message content.
if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE)
StartUpdateCheck();
else
UpdateErrorMessage(network, status);
} else if (state_ == STATE_FIRST_PORTAL_CHECK) {
// In the case of online state immediately proceed to the update
// stage. Otherwise, prepare and show error message.
if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE) {
StartUpdateCheck();
} else {
UpdateErrorMessage(network, status);
ShowErrorMessage();
}
}
}
void UpdateScreen::StartNetworkCheck() {
NetworkPortalDetector* detector = NetworkPortalDetector::GetInstance();
// If portal detector is enabled and portal detection before AU is
// allowed, initiate network state check. Otherwise, directly
// proceed to update.
if (!NetworkPortalDetector::IsEnabledInCommandLine() || !detector ||
!IsBlockingUpdateEnabledInCommandLine()) {
StartUpdateCheck();
return;
}
state_ = STATE_FIRST_PORTAL_CHECK;
is_first_detection_notification_ = true;
is_first_portal_notification_ = true;
detector->AddAndFireObserver(this);
}
void UpdateScreen::CancelUpdate() {
VLOG(1) << "Forced update cancel";
ExitUpdate(REASON_UPDATE_CANCELED);
}
void UpdateScreen::Show() {
is_shown_ = true;
if (actor_) {
actor_->Show();
actor_->SetProgress(kBeforeUpdateCheckProgress);
}
}
void UpdateScreen::Hide() {
if (actor_)
actor_->Hide();
is_shown_ = false;
}
std::string UpdateScreen::GetName() const {
return WizardController::kUpdateScreenName;
}
void UpdateScreen::PrepareToShow() {
if (actor_)
actor_->PrepareToShow();
}
void UpdateScreen::ExitUpdate(UpdateScreen::ExitReason reason) {
DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
if (NetworkPortalDetector::GetInstance())
NetworkPortalDetector::GetInstance()->RemoveObserver(this);
switch (reason) {
case REASON_UPDATE_CANCELED:
get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE);
break;
case REASON_UPDATE_INIT_FAILED:
get_screen_observer()->OnExit(
ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE);
break;
case REASON_UPDATE_NON_CRITICAL:
case REASON_UPDATE_ENDED:
{
UpdateEngineClient* update_engine_client =
DBusThreadManager::Get()->GetUpdateEngineClient();
switch (update_engine_client->GetLastStatus().status) {
case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE:
case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT:
case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING:
case UpdateEngineClient::UPDATE_STATUS_FINALIZING:
case UpdateEngineClient::UPDATE_STATUS_VERIFYING:
DCHECK(!HasCriticalUpdate());
// Noncritical update, just exit screen as if there is no update.
// no break
case UpdateEngineClient::UPDATE_STATUS_IDLE:
get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE);
break;
case UpdateEngineClient::UPDATE_STATUS_ERROR:
case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT:
get_screen_observer()->OnExit(is_checking_for_update_ ?
ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE :
ScreenObserver::UPDATE_ERROR_UPDATING);
break;
default:
NOTREACHED();
}
}
break;
default:
NOTREACHED();
}
}
void UpdateScreen::OnWaitForRebootTimeElapsed() {
LOG(ERROR) << "Unable to reboot - asking user for a manual reboot.";
MakeSureScreenIsShown();
if (actor_)
actor_->ShowManualRebootInfo();
}
void UpdateScreen::MakeSureScreenIsShown() {
if (!is_shown_)
get_screen_observer()->ShowCurrentScreen();
}
void UpdateScreen::SetRebootCheckDelay(int seconds) {
if (seconds <= 0)
reboot_timer_.Stop();
DCHECK(!reboot_timer_.IsRunning());
reboot_check_delay_ = seconds;
}
void UpdateScreen::SetIgnoreIdleStatus(bool ignore_idle_status) {
ignore_idle_status_ = ignore_idle_status;
}
void UpdateScreen::UpdateDownloadingStats(
const UpdateEngineClient::Status& status) {
if (!actor_)
return;
base::Time download_current_time = base::Time::Now();
if (download_current_time >= download_last_time_ + kMinTimeStep) {
// Estimate downloading rate.
double progress_delta =
std::max(status.download_progress - download_last_progress_, 0.0);
double time_delta =
(download_current_time - download_last_time_).InSecondsF();
double download_rate = status.new_size * progress_delta / time_delta;
download_last_time_ = download_current_time;
download_last_progress_ = status.download_progress;
// Estimate time left.
double progress_left = std::max(1.0 - status.download_progress, 0.0);
if (!is_download_average_speed_computed_) {
download_average_speed_ = download_rate;
is_download_average_speed_computed_ = true;
}
download_average_speed_ =
kDownloadSpeedSmoothFactor * download_rate +
(1.0 - kDownloadSpeedSmoothFactor) * download_average_speed_;
if (download_average_speed_ < kDownloadAverageSpeedDropBound) {
time_delta =
(download_current_time - download_start_time_).InSecondsF();
download_average_speed_ =
status.new_size *
(status.download_progress - download_start_progress_) /
time_delta;
}
double work_left = progress_left * status.new_size;
double time_left = work_left / download_average_speed_;
// |time_left| may be large enough or even +infinity. So we must
// |bound possible estimations.
time_left = std::min(time_left, kMaxTimeLeft);
actor_->ShowEstimatedTimeLeft(true);
actor_->SetEstimatedTimeLeft(
base::TimeDelta::FromSeconds(static_cast<int64>(time_left)));
}
int download_progress = static_cast<int>(
status.download_progress * kDownloadProgressIncrement);
actor_->SetProgress(kBeforeDownloadProgress + download_progress);
}
bool UpdateScreen::HasCriticalUpdate() {
if (is_ignore_update_deadlines_)
return true;
std::string deadline;
// Checking for update flag file causes us to do blocking IO on UI thread.
// Temporarily allow it until we fix http://crosbug.com/11106
base::ThreadRestrictions::ScopedAllowIO allow_io;
base::FilePath update_deadline_file_path(kUpdateDeadlineFile);
if (!base::ReadFileToString(update_deadline_file_path, &deadline) ||
deadline.empty()) {
return false;
}
// TODO(dpolukhin): Analyze file content. Now we can just assume that
// if the file exists and not empty, there is critical update.
return true;
}
void UpdateScreen::OnActorDestroyed(UpdateScreenActor* actor) {
if (actor_ == actor)
actor_ = NULL;
}
void UpdateScreen::OnConnectToNetworkRequested(
const std::string& service_path) {
if (state_ == STATE_ERROR) {
LOG(WARNING) << "Hiding error message since AP was reselected";
StartUpdateCheck();
}
}
ErrorScreen* UpdateScreen::GetErrorScreen() {
return get_screen_observer()->GetErrorScreen();
}
void UpdateScreen::StartUpdateCheck() {
NetworkPortalDetector* detector = NetworkPortalDetector::GetInstance();
if (detector)
detector->RemoveObserver(this);
if (state_ == STATE_ERROR)
HideErrorMessage();
state_ = STATE_UPDATE;
DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this);
VLOG(1) << "Initiate update check";
DBusThreadManager::Get()->GetUpdateEngineClient()->RequestUpdateCheck(
base::Bind(StartUpdateCallback, this));
}
void UpdateScreen::ShowErrorMessage() {
LOG(WARNING) << "UpdateScreen::ShowErrorMessage()";
state_ = STATE_ERROR;
GetErrorScreen()->SetUIState(ErrorScreen::UI_STATE_UPDATE);
get_screen_observer()->ShowErrorScreen();
}
void UpdateScreen::HideErrorMessage() {
LOG(WARNING) << "UpdateScreen::HideErrorMessage()";
get_screen_observer()->HideErrorScreen(this);
}
void UpdateScreen::UpdateErrorMessage(
const NetworkState* network,
const NetworkPortalDetector::CaptivePortalStatus status) {
switch (status) {
case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE:
NOTREACHED();
break;
case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN:
case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE:
GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_OFFLINE,
std::string());
break;
case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL:
DCHECK(network);
GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PORTAL,
network->name());
if (is_first_portal_notification_) {
is_first_portal_notification_ = false;
GetErrorScreen()->FixCaptivePortal();
}
break;
case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED:
GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PROXY,
std::string());
break;
default:
NOTREACHED();
break;
}
}
} // namespace chromeos