blob: 6ec1696307d6f4530c8fc9c1e45b96933deb1274 [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/files/file_util.h"
#include "base/logging.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/chromeos/login/error_screens_histogram_helper.h"
#include "chrome/browser/chromeos/login/screen_manager.h"
#include "chrome/browser/chromeos/login/screens/network_error.h"
#include "chrome/browser/ui/webui/chromeos/login/update_screen_handler.h"
#include "chromeos/network/network_state.h"
namespace chromeos {
namespace {
constexpr const char kUserActionAcceptUpdateOverCellular[] =
"update-accept-cellular";
constexpr const char kUserActionRejectUpdateOverCellular[] =
"update-reject-cellular";
constexpr const char kUserActionCancelUpdateShortcut[] = "cancel-update";
const char kUpdateDeadlineFile[] = "/tmp/update-check-response-deadline";
// Delay before showing error message if captive portal is detected.
// We wait for this delay to let captive portal to perform redirect and show
// its login page before error message appears.
constexpr const base::TimeDelta kDelayErrorMessage =
base::TimeDelta::FromSeconds(10);
constexpr const base::TimeDelta kShowDelay =
base::TimeDelta::FromMicroseconds(400);
} // anonymous namespace
// static
UpdateScreen* UpdateScreen::Get(ScreenManager* manager) {
return static_cast<UpdateScreen*>(manager->GetScreen(UpdateView::kScreenId));
}
UpdateScreen::UpdateScreen(UpdateView* view,
ErrorScreen* error_screen,
const ScreenExitCallback& exit_callback)
: BaseScreen(UpdateView::kScreenId),
view_(view),
error_screen_(error_screen),
exit_callback_(exit_callback),
histogram_helper_(
std::make_unique<ErrorScreensHistogramHelper>("Update")),
version_updater_(std::make_unique<VersionUpdater>(this)) {
if (view_)
view_->Bind(this);
}
UpdateScreen::~UpdateScreen() {
if (view_)
view_->Unbind();
}
void UpdateScreen::OnViewDestroyed(UpdateView* view) {
if (view_ == view)
view_ = nullptr;
}
void UpdateScreen::Show() {
#if !defined(OFFICIAL_BUILD)
if (view_) {
view_->SetCancelUpdateShortcutEnabled(true);
}
#endif
RefreshView(version_updater_->update_info());
show_timer_.Start(FROM_HERE, kShowDelay,
base::BindOnce(&UpdateScreen::MakeSureScreenIsShown,
weak_factory_.GetWeakPtr()));
version_updater_->StartNetworkCheck();
}
void UpdateScreen::Hide() {
show_timer_.Stop();
if (view_)
view_->Hide();
is_shown_ = false;
}
void UpdateScreen::OnUserAction(const std::string& action_id) {
bool is_official_build = false;
#if defined(OFFICIAL_BUILD)
is_official_build = true;
#endif
if (!is_official_build && action_id == kUserActionCancelUpdateShortcut) {
// Skip update UI, usually used only in debug builds/tests.
VLOG(1) << "Forced update cancel";
ExitUpdate(Result::UPDATE_NOT_REQUIRED);
} else if (action_id == kUserActionAcceptUpdateOverCellular) {
version_updater_->SetUpdateOverCellularOneTimePermission();
} else if (action_id == kUserActionRejectUpdateOverCellular) {
version_updater_->RejectUpdateOverCellular();
ExitUpdate(Result::UPDATE_ERROR);
} else {
BaseScreen::OnUserAction(action_id);
}
}
base::OneShotTimer* UpdateScreen::GetShowTimerForTesting() {
return &show_timer_;
}
base::OneShotTimer* UpdateScreen::GetErrorMessageTimerForTesting() {
return &error_message_timer_;
}
VersionUpdater* UpdateScreen::GetVersionUpdaterForTesting() {
return version_updater_.get();
}
void UpdateScreen::ExitUpdate(Result result) {
version_updater_->StartExitUpdate(result);
}
void UpdateScreen::OnWaitForRebootTimeElapsed() {
LOG(ERROR) << "Unable to reboot - asking user for a manual reboot.";
MakeSureScreenIsShown();
if (view_)
view_->SetUpdateCompleted(true);
}
void UpdateScreen::PrepareForUpdateCheck() {
error_message_timer_.Stop();
error_screen_->HideCaptivePortal();
connect_request_subscription_.reset();
if (version_updater_->update_info().state ==
VersionUpdater::State::STATE_ERROR)
HideErrorMessage();
}
void UpdateScreen::ShowErrorMessage() {
LOG(WARNING) << "UpdateScreen::ShowErrorMessage()";
error_message_timer_.Stop();
is_shown_ = false;
show_timer_.Stop();
connect_request_subscription_ =
error_screen_->RegisterConnectRequestCallback(base::BindRepeating(
&UpdateScreen::OnConnectRequested, weak_factory_.GetWeakPtr()));
error_screen_->SetUIState(NetworkError::UI_STATE_UPDATE);
error_screen_->SetParentScreen(UpdateView::kScreenId);
error_screen_->SetHideCallback(base::BindRepeating(
&UpdateScreen::OnErrorScreenHidden, weak_factory_.GetWeakPtr()));
error_screen_->Show();
histogram_helper_->OnErrorShow(error_screen_->GetErrorState());
}
void UpdateScreen::UpdateErrorMessage(
const NetworkPortalDetector::CaptivePortalStatus status,
const NetworkError::ErrorState& error_state,
const std::string& network_name) {
error_screen_->SetErrorState(error_state, network_name);
if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL) {
if (is_first_portal_notification_) {
is_first_portal_notification_ = false;
error_screen_->FixCaptivePortal();
}
}
}
void UpdateScreen::DelayErrorMessage() {
if (error_message_timer_.IsRunning())
return;
error_message_timer_.Start(FROM_HERE, kDelayErrorMessage, this,
&UpdateScreen::ShowErrorMessage);
}
void UpdateScreen::UpdateInfoChanged(
const VersionUpdater::UpdateInfo& update_info) {
const update_engine::StatusResult& status = update_info.status;
hide_progress_on_exit_ = false;
bool need_refresh_view = true;
switch (status.current_operation()) {
case update_engine::Operation::CHECKING_FOR_UPDATE:
// Do nothing in these cases, we don't want to notify the user of the
// check unless there is an update.
case update_engine::Operation::ATTEMPTING_ROLLBACK:
case update_engine::Operation::DISABLED:
case update_engine::Operation::IDLE:
need_refresh_view = false;
break;
case update_engine::Operation::UPDATE_AVAILABLE:
case update_engine::Operation::DOWNLOADING:
MakeSureScreenIsShown();
if (!is_critical_checked_) {
// Because update engine doesn't send UPDATE_STATUS_UPDATE_AVAILABLE we
// need to check if update is critical on first downloading
// notification.
is_critical_checked_ = true;
if (!HasCriticalUpdate()) {
VLOG(1) << "Non-critical update available: " << status.new_version();
hide_progress_on_exit_ = true;
ExitUpdate(Result::UPDATE_NOT_REQUIRED);
} else {
VLOG(1) << "Critical update available: " << status.new_version();
}
}
break;
case update_engine::Operation::VERIFYING:
case update_engine::Operation::FINALIZING:
case update_engine::Operation::NEED_PERMISSION_TO_UPDATE:
MakeSureScreenIsShown();
break;
case update_engine::Operation::UPDATED_NEED_REBOOT:
MakeSureScreenIsShown();
if (HasCriticalUpdate()) {
version_updater_->RebootAfterUpdate();
} else {
hide_progress_on_exit_ = true;
ExitUpdate(Result::UPDATE_NOT_REQUIRED);
}
break;
case update_engine::Operation::ERROR:
case update_engine::Operation::REPORTING_ERROR_EVENT:
// Ignore update errors for non-critical updates to prevent blocking the
// user from getting to login screen during OOBE if the pending update is
// not critical.
if (update_info.is_checking_for_update || !HasCriticalUpdate()) {
ExitUpdate(Result::UPDATE_NOT_REQUIRED);
} else {
ExitUpdate(Result::UPDATE_ERROR);
}
need_refresh_view = false;
break;
default:
NOTREACHED();
}
if (need_refresh_view)
RefreshView(update_info);
}
void UpdateScreen::FinishExitUpdate(Result result) {
show_timer_.Stop();
exit_callback_.Run(result);
}
void UpdateScreen::RefreshView(const VersionUpdater::UpdateInfo& update_info) {
if (view_) {
view_->SetProgress(update_info.progress);
view_->SetProgressMessage(update_info.progress_message);
view_->SetEstimatedTimeLeft(update_info.estimated_time_left_in_secs);
view_->SetShowEstimatedTimeLeft(update_info.show_estimated_time_left);
view_->SetShowCurtain(update_info.progress_unavailable ||
hide_progress_on_exit_);
view_->SetRequiresPermissionForCellular(
update_info.requires_permission_for_cellular);
}
}
bool UpdateScreen::HasCriticalUpdate() {
if (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::MakeSureScreenIsShown() {
show_timer_.Stop();
if (is_shown_ || !view_)
return;
is_shown_ = true;
histogram_helper_->OnScreenShow();
view_->Show();
}
void UpdateScreen::HideErrorMessage() {
LOG(WARNING) << "UpdateScreen::HideErrorMessage()";
error_screen_->Hide();
histogram_helper_->OnErrorHide();
}
void UpdateScreen::OnConnectRequested() {
if (version_updater_->update_info().state ==
VersionUpdater::State::STATE_ERROR) {
LOG(WARNING) << "Hiding error message since AP was reselected";
version_updater_->StartUpdateCheck();
}
}
void UpdateScreen::OnErrorScreenHidden() {
error_screen_->SetParentScreen(OobeScreen::SCREEN_UNKNOWN);
Show();
}
} // namespace chromeos