chromium/ash/webui/shimless_rma/backend/version_updater.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/webui/shimless_rma/backend/version_updater.h"

#include "ash/constants/ash_features.h"
#include "base/containers/contains.h"
#include "base/logging.h"
#include "chromeos/ash/components/dbus/update_engine/update_engine.pb.h"
#include "chromeos/ash/components/dbus/update_engine/update_engine_client.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_type_pattern.h"

namespace ash {
namespace shimless_rma {

namespace {

// The list of operations signifying the UpdateEngine is not active. Denotes
// it's safe to perform other actions.
const update_engine::Operation kIdleUpdateOperations[] = {
    update_engine::Operation::IDLE,
    update_engine::Operation::CHECKING_FOR_UPDATE,
    update_engine::Operation::UPDATE_AVAILABLE,
    update_engine::Operation::DISABLED,
    update_engine::Operation::NEED_PERMISSION_TO_UPDATE,
    update_engine::Operation::CLEANUP_PREVIOUS_UPDATE,
    update_engine::Operation::UPDATED_BUT_DEFERRED,
    update_engine::Operation::ERROR};

void ReportUpdateFailure(const VersionUpdater::OsUpdateStatusCallback& callback,
                         update_engine::Operation operation,
                         const update_engine::ErrorCode& error_code) {
  callback.Run(operation, /*progress=*/0,
               /*rollback=*/false, /*powerwash=*/false,
               /*version=*/std::string(), /*update_size=*/0, error_code);
}

// Returns whether an update is allowed. If not, it calls the callback with
// the appropriate status. |interactive| indicates whether the user is actively
// checking for updates.
bool IsUpdateAllowed() {
  NetworkStateHandler* network_state_handler =
      NetworkHandler::Get()->network_state_handler();
  const NetworkState* network = network_state_handler->DefaultNetwork();
  // Don't allow an update if device is currently offline or connected
  // to a network for which data is metered.
  if (!network || !network->IsConnectedState()) {
    return false;
  }

  // TODO(gavinwill): Confirm that metered networks should be excluded.
  const bool is_metered = network_state_handler->default_network_is_metered();
  if (is_metered) {
    return false;
  }

  return true;
}

}  // namespace

VersionUpdater::VersionUpdater() {
  if (!features::IsShimlessRMAOsUpdateEnabled()) {
    return;
  }

  UpdateEngineClient::Get()->AddObserver(this);
}

VersionUpdater::~VersionUpdater() {
  if (!features::IsShimlessRMAOsUpdateEnabled()) {
    return;
  }

  UpdateEngineClient::Get()->RemoveObserver(this);
}

void VersionUpdater::SetOsUpdateStatusCallback(
    OsUpdateStatusCallback callback) {
  status_callback_ = std::move(callback);
}

// TODO(gavindodd): Align with chrome/browser/ui/webui/help/version_updater.h:27
// so that the update messages are the same as normal Chrome updates.
// See
// chrome/browser/ui/webui/help/version_updater_chromeos.cc:271
// chrome/browser/resources/ash/settings/os_about_page/os_about_page.js:418
// chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc:261
// chrome/app/shared_settings_strings.grdp:378
// chrome/app/os_settings_strings.grdp:66
// for mapping of OS update status to strings
void VersionUpdater::CheckOsUpdateAvailable() {
  if (disable_update_for_testing_) {
    disable_update_for_testing_ = false;
    status_callback_.Run(update_engine::Operation::IDLE,
                         /*progress=*/0, /*rollback=*/false,
                         /*powerwash=*/false, /*newVersion*/ "",
                         /*update_size=*/0, update_engine::ErrorCode::kSuccess);
    return;
  }

  // TODO(gavinwill): Does this need thread guarding.
  if (check_update_available_ == UPDATE_AVAILABLE) {
    status_callback_.Run(update_engine::Operation::UPDATE_AVAILABLE,
                         /*progress=*/0, /*rollback=*/false,
                         /*powerwash=*/false, new_version_, /*update_size=*/0,
                         update_engine::ErrorCode::kSuccess);
    return;
  }

  if (check_update_available_ == NO_UPDATE_AVAILABLE) {
    status_callback_.Run(update_engine::Operation::IDLE,
                         /*progress=*/0, /*rollback=*/false,
                         /*powerwash=*/false, new_version_, /*update_size=*/0,
                         update_engine::ErrorCode::kSuccess);
    return;
  }

  if (check_update_available_ == CHECKING) {
    return;
  }

  if (!IsUpdateAllowed()) {
    ReportUpdateFailure(status_callback_, update_engine::REPORTING_ERROR_EVENT,
                        update_engine::ErrorCode::kError);
    return;
  }

  if (!IsUpdateEngineIdle()) {
    LOG(ERROR) << "Tried to check for update when UpdateEngine not IDLE.";
    ReportUpdateFailure(status_callback_, update_engine::REPORTING_ERROR_EVENT,
                        update_engine::ErrorCode::kError);
    return;
  }

  check_update_available_ = CHECKING;
  // RequestUpdateCheckWithoutApplying() will check if an update is available
  // without installing it.
  UpdateEngineClient::Get()->RequestUpdateCheckWithoutApplying(base::BindOnce(
      &VersionUpdater::OnRequestUpdateCheck, weak_ptr_factory_.GetWeakPtr()));
}

bool VersionUpdater::UpdateOs() {
  if (!IsUpdateAllowed()) {
    return false;
  }

  // TODO(swifton): Find out if we need to add an observer to the update engine
  // client.

  // TODO(swifton): Find out how the state of the engine client should be
  // checked after using RequestUpdateCheckWithoutApplying.

  // RequestUpdateCheck will check if an update is available and install it.
  UpdateEngineClient::Get()->RequestUpdateCheck(base::BindOnce(
      &VersionUpdater::OnRequestUpdateCheck, weak_ptr_factory_.GetWeakPtr()));
  return true;
}

bool VersionUpdater::IsUpdateEngineIdle() {
  return base::Contains(
      kIdleUpdateOperations,
      UpdateEngineClient::Get()->GetLastStatus().current_operation());
}

void VersionUpdater::UpdateStatusChanged(
    const update_engine::StatusResult& status) {
  if (status.current_operation() == update_engine::UPDATED_NEED_REBOOT) {
    // During RMA there are no other critical processes running so we can
    // automatically reboot.
    UpdateEngineClient::Get()->RebootAfterUpdate();
  }
  switch (status.current_operation()) {
    // If IDLE is received when there is a callback it means no update is
    // available.
    case update_engine::Operation::IDLE:
      // If we reach idle when explicitly checking for update then none is
      // available.
      if (check_update_available_ == CHECKING) {
        check_update_available_ = NO_UPDATE_AVAILABLE;
      }
      break;
    case update_engine::Operation::DISABLED:
    case update_engine::Operation::ERROR:
    case update_engine::Operation::REPORTING_ERROR_EVENT:
      // If get an error when explicitly checking for update then allow
      // another check to be requested later.
      if (check_update_available_ == CHECKING) {
        check_update_available_ = IDLE;
      }
      break;
    case update_engine::Operation::UPDATE_AVAILABLE:
      // If we ever see update available then update the next version.
      new_version_ = status.new_version();
      check_update_available_ = UPDATE_AVAILABLE;
      break;
    case update_engine::Operation::ATTEMPTING_ROLLBACK:
    case update_engine::Operation::CHECKING_FOR_UPDATE:
    case update_engine::Operation::DOWNLOADING:
    case update_engine::Operation::FINALIZING:
    case update_engine::Operation::NEED_PERMISSION_TO_UPDATE:
    case update_engine::Operation::UPDATED_NEED_REBOOT:
    case update_engine::Operation::VERIFYING:
    case update_engine::Operation::CLEANUP_PREVIOUS_UPDATE:
    case update_engine::Operation::UPDATED_BUT_DEFERRED:
      break;
    // Added to avoid lint error
    case update_engine::Operation::Operation_INT_MIN_SENTINEL_DO_NOT_USE_:
    case update_engine::Operation::Operation_INT_MAX_SENTINEL_DO_NOT_USE_:
      NOTREACHED();
  }

  status_callback_.Run(
      status.current_operation(), status.progress(), false,
      status.will_powerwash_after_reboot(), status.new_version(),
      status.new_size(),
      static_cast<update_engine::ErrorCode>(status.last_attempt_error()));
}

void VersionUpdater::OnRequestUpdateCheck(
    UpdateEngineClient::UpdateCheckResult result) {
  if (result != UpdateEngineClient::UPDATE_RESULT_SUCCESS) {
    LOG(ERROR) << "OS update request failed.";
    ReportUpdateFailure(status_callback_, update_engine::REPORTING_ERROR_EVENT,
                        update_engine::ErrorCode::kDownloadTransferError);
  }
}

void VersionUpdater::UpdateStatusChangedForTesting(
    const update_engine::StatusResult& status) {
  UpdateStatusChanged(status);
}

void VersionUpdater::DisableUpdateOnceForTesting() {
  disable_update_for_testing_ = true;
}

}  // namespace shimless_rma
}  // namespace ash