// Copyright (c) 2011 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/update_screen.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/login/screen_observer.h"
#include "chrome/browser/chromeos/login/update_view.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "content/browser/browser_thread.h"
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";
} // anonymous namespace
namespace chromeos {
// static
UpdateScreen::InstanceSet& UpdateScreen::GetInstanceSet() {
static 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(WizardScreenDelegate* delegate)
: DefaultViewScreen<chromeos::UpdateView>(delegate,
kUpdateScreenWidth,
kUpdateScreenHeight),
checking_for_update_(true),
reboot_check_delay_(0),
is_downloading_update_(false),
is_all_updates_critical_(true) { // See http://crosbug.com/10068
GetInstanceSet().insert(this);
}
UpdateScreen::~UpdateScreen() {
// Remove pointer to this object from view.
if (view())
view()->set_controller(NULL);
CrosLibrary::Get()->GetUpdateLibrary()->RemoveObserver(this);
GetInstanceSet().erase(this);
}
void UpdateScreen::UpdateStatusChanged(UpdateLibrary* library) {
UpdateStatusOperation status = library->status().status;
if (checking_for_update_ && status > UPDATE_STATUS_CHECKING_FOR_UPDATE) {
checking_for_update_ = false;
}
switch (status) {
case 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 UPDATE_STATUS_UPDATE_AVAILABLE:
MakeSureScreenIsShown();
view()->SetProgress(kBeforeDownloadProgress);
if (!HasCriticalUpdate()) {
LOG(INFO) << "Noncritical update available: "
<< library->status().new_version;
ExitUpdate(REASON_UPDATE_NON_CRITICAL);
} else {
LOG(INFO) << "Critical update available: "
<< library->status().new_version;
view()->ShowPreparingUpdatesInfo(true);
view()->ShowCurtain(false);
}
break;
case 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;
if (!HasCriticalUpdate()) {
LOG(INFO) << "Non-critical update available: "
<< library->status().new_version;
ExitUpdate(REASON_UPDATE_NON_CRITICAL);
} else {
LOG(INFO) << "Critical update available: "
<< library->status().new_version;
}
}
view()->ShowPreparingUpdatesInfo(false);
view()->ShowCurtain(false);
int download_progress = static_cast<int>(
library->status().download_progress * kDownloadProgressIncrement);
view()->SetProgress(kBeforeDownloadProgress + download_progress);
}
break;
case UPDATE_STATUS_VERIFYING:
MakeSureScreenIsShown();
view()->SetProgress(kBeforeVerifyingProgress);
break;
case UPDATE_STATUS_FINALIZING:
MakeSureScreenIsShown();
view()->SetProgress(kBeforeFinalizingProgress);
break;
case UPDATE_STATUS_UPDATED_NEED_REBOOT:
MakeSureScreenIsShown();
// Make sure that first OOBE stage won't be shown after reboot.
WizardController::MarkOobeCompleted();
view()->SetProgress(kProgressComplete);
if (HasCriticalUpdate()) {
view()->ShowCurtain(false);
VLOG(1) << "Initiate reboot after update";
CrosLibrary::Get()->GetUpdateLibrary()->RebootAfterUpdate();
reboot_timer_.Start(base::TimeDelta::FromSeconds(reboot_check_delay_),
this,
&UpdateScreen::OnWaitForRebootTimeElapsed);
} else {
ExitUpdate(REASON_UPDATE_NON_CRITICAL);
}
break;
case UPDATE_STATUS_IDLE:
case UPDATE_STATUS_ERROR:
case UPDATE_STATUS_REPORTING_ERROR_EVENT:
ExitUpdate(REASON_UPDATE_ENDED);
break;
default:
NOTREACHED();
break;
}
}
namespace {
// Invoked from call to RequestUpdateCheck upon completion of the DBus call.
void StartUpdateCallback(void* user_data,
UpdateResult result,
const char* msg) {
if (result != UPDATE_RESULT_SUCCESS) {
DCHECK(user_data);
UpdateScreen* screen = static_cast<UpdateScreen*>(user_data);
if (UpdateScreen::HasInstance(screen))
screen->ExitUpdate(UpdateScreen::REASON_UPDATE_INIT_FAILED);
}
}
} // namespace
void UpdateScreen::StartUpdate() {
// Reset view if view was created.
if (view()) {
view()->Reset();
view()->set_controller(this);
is_downloading_update_ = false;
view()->SetProgress(kBeforeUpdateCheckProgress);
}
if (!CrosLibrary::Get()->EnsureLoaded()) {
LOG(ERROR) << "Error loading CrosLibrary";
ExitUpdate(REASON_UPDATE_INIT_FAILED);
} else {
CrosLibrary::Get()->GetUpdateLibrary()->AddObserver(this);
VLOG(1) << "Initiate update check";
CrosLibrary::Get()->GetUpdateLibrary()->RequestUpdateCheck(
StartUpdateCallback, this);
}
}
void UpdateScreen::CancelUpdate() {
// Screen has longer lifetime than it's view.
// View is deleted after wizard proceeds to the next screen.
if (view())
ExitUpdate(REASON_UPDATE_CANCELED);
}
void UpdateScreen::Show() {
DefaultViewScreen<UpdateView>::Show();
view()->set_controller(this);
is_downloading_update_ = false;
view()->SetProgress(kBeforeUpdateCheckProgress);
}
void UpdateScreen::ExitUpdate(UpdateScreen::ExitReason reason) {
ScreenObserver* observer = delegate()->GetObserver(this);
if (CrosLibrary::Get()->EnsureLoaded())
CrosLibrary::Get()->GetUpdateLibrary()->RemoveObserver(this);
switch(reason) {
case REASON_UPDATE_CANCELED:
observer->OnExit(ScreenObserver::UPDATE_NOUPDATE);
break;
case REASON_UPDATE_INIT_FAILED:
observer->OnExit(ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE);
break;
case REASON_UPDATE_NON_CRITICAL:
case REASON_UPDATE_ENDED:
{
UpdateLibrary* update_library = CrosLibrary::Get()->GetUpdateLibrary();
switch (update_library->status().status) {
case UPDATE_STATUS_UPDATE_AVAILABLE:
case UPDATE_STATUS_UPDATED_NEED_REBOOT:
case UPDATE_STATUS_DOWNLOADING:
case UPDATE_STATUS_FINALIZING:
case UPDATE_STATUS_VERIFYING:
DCHECK(!HasCriticalUpdate());
// Noncritical update, just exit screen as if there is no update.
// no break
case UPDATE_STATUS_IDLE:
observer->OnExit(ScreenObserver::UPDATE_NOUPDATE);
break;
case UPDATE_STATUS_ERROR:
case UPDATE_STATUS_REPORTING_ERROR_EVENT:
observer->OnExit(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();
view()->ShowManualRebootInfo();
}
void UpdateScreen::MakeSureScreenIsShown() {
if (!view()) {
delegate()->ShowCurrentScreen();
}
}
void UpdateScreen::SetRebootCheckDelay(int seconds) {
if (seconds <= 0)
reboot_timer_.Stop();
DCHECK(!reboot_timer_.IsRunning());
reboot_check_delay_ = seconds;
}
bool UpdateScreen::HasCriticalUpdate() {
if (is_all_updates_critical_)
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;
FilePath update_deadline_file_path(kUpdateDeadlineFile);
if (!file_util::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::SetAllUpdatesCritical(bool is_critical) {
is_all_updates_critical_ = is_critical;
}
} // namespace chromeos