chromium/ash/webui/shimless_rma/backend/shimless_rma_service.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/shimless_rma_service.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/network_config_service.h"
#include "ash/system/diagnostics/diagnostics_log_controller.h"
#include "ash/webui/shimless_rma/backend/external_app_dialog.h"
#include "ash/webui/shimless_rma/backend/shimless_rma_delegate.h"
#include "ash/webui/shimless_rma/backend/version_updater.h"
#include "ash/webui/shimless_rma/mojom/shimless_rma.mojom.h"
#include "ash/webui/shimless_rma/mojom/shimless_rma_mojom_traits.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chromeos/ash/components/dbus/rmad/rmad.pb.h"
#include "chromeos/ash/components/dbus/rmad/rmad_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"
#include "chromeos/ash/components/network/technology_state_controller.h"
#include "chromeos/ash/services/cros_healthd/public/cpp/service_connection.h"
#include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd.mojom.h"
#include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h"
#include "chromeos/ash/services/network_config/in_process_instance.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
#include "chromeos/version/version_loader.h"
#include "components/web_package/signed_web_bundles/signed_web_bundle_id.h"
#include "third_party/skia/include/core/SkBitmap.h"

namespace ash {
namespace shimless_rma {

namespace {

namespace network_mojom = ::chromeos::network_config::mojom;

mojom::State RmadStateToMojo(rmad::RmadState::StateCase rmadState) {
  return mojo::EnumTraits<ash::shimless_rma::mojom::State,
                          rmad::RmadState::StateCase>::ToMojom(rmadState);
}

// Returns whether the device is connected to an unmetered network.
// Metered networks are excluded for RMA to avoid any cost to the owner who
// does not have control of the device during RMA.
bool HaveAllowedNetworkConnection() {
  NetworkStateHandler* network_state_handler =
      NetworkHandler::Get()->network_state_handler();
  const NetworkState* network = network_state_handler->DefaultNetwork();
  // TODO(gavindodd): Confirm that metered networks should be excluded. This
  // should only be true for cellular networks which are already blocked.
  const bool metered = network_state_handler->default_network_is_metered();
  // Return true if connected to an unmetered network.
  return network && network->IsConnectedState() && !metered;
}

network_mojom::NetworkFilterPtr GetConfiguredWiFiFilter() {
  return network_mojom::NetworkFilter::New(
      network_mojom::FilterType::kConfigured, network_mojom::NetworkType::kWiFi,
      network_mojom::kNoLimit);
}

}  // namespace

ShimlessRmaService::ShimlessRmaService(
    std::unique_ptr<ShimlessRmaDelegate> shimless_rma_delegate)
    : shimless_rma_delegate_(std::move(shimless_rma_delegate)),
      task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {
  RmadClient::Get()->AddObserver(this);

  // Enable accessibility features.
  shimless_rma_delegate_->RefreshAccessibilityManagerProfile();

  network_config::BindToInProcessInstance(
      remote_cros_network_config_.BindNewPipeAndPassReceiver());

  if (features::IsShimlessRMAOsUpdateEnabled()) {
    version_updater_.SetOsUpdateStatusCallback(
        base::BindRepeating(&ShimlessRmaService::OnOsUpdateStatusCallback,
                            weak_ptr_factory_.GetWeakPtr()));
    // Check if an OS update is available to minimize delays if needed later.
    if (HaveAllowedNetworkConnection()) {
      version_updater_.CheckOsUpdateAvailable();
    }
  }
}

ShimlessRmaService::~ShimlessRmaService() {
  RmadClient::Get()->RemoveObserver(this);
}

void ShimlessRmaService::GetCurrentState(GetCurrentStateCallback callback) {
  RmadClient::Get()->GetCurrentState(base::BindOnce(
      &ShimlessRmaService::OnGetStateResponse<GetCurrentStateCallback>,
      weak_ptr_factory_.GetWeakPtr(), std::move(callback), kGetCurrentState));
}

mojom::StateResultPtr ShimlessRmaService::CreateStateResult(
    mojom::State state,
    bool can_exit,
    bool can_go_back,
    rmad::RmadErrorCode error) {
  return mojom::StateResult::New(state, can_exit, can_go_back, error);
}

mojom::StateResultPtr ShimlessRmaService::CreateStateResultForInvalidRequest() {
  return CreateStateResult(RmadStateToMojo(state_proto_.state_case()),
                           can_abort_, can_go_back_,
                           rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID);
}

void ShimlessRmaService::TransitionPreviousState(
    TransitionPreviousStateCallback callback) {
  // If current rmad state is rmad::RmadState::kWelcome and the mojom state
  // is kConfigureNetwork or kUpdateOs, we don't call rmad service. Otherwise,
  // it will respond with error.
  if (state_proto_.state_case() == rmad::RmadState::kWelcome &&
      (mojo_state_ == mojom::State::kConfigureNetwork ||
       mojo_state_ == mojom::State::kUpdateOs)) {
    mojo_state_ = mojom::State::kWelcomeScreen;
    std::move(callback).Run(
        CreateStateResult(mojom::State::kWelcomeScreen,
                          /*can_exit=*/true, /*can_go_back=*/false,
                          rmad::RmadErrorCode::RMAD_ERROR_OK));
    return;
  }

  RmadClient::Get()->TransitionPreviousState(base::BindOnce(
      &ShimlessRmaService::OnGetStateResponse<TransitionPreviousStateCallback>,
      weak_ptr_factory_.GetWeakPtr(), std::move(callback),
      kTransitPreviousState));
}

void ShimlessRmaService::AbortRma(AbortRmaCallback callback) {
  RmadClient::Get()->AbortRma(base::BindOnce(
      &ShimlessRmaService::OnAbortRmaResponse, weak_ptr_factory_.GetWeakPtr(),
      std::move(callback), /*reboot=*/true));
}

void ShimlessRmaService::CriticalErrorExitToLogin(
    CriticalErrorExitToLoginCallback callback) {
  if (!critical_error_occurred_) {
    std::move(callback).Run(rmad::RMAD_ERROR_REQUEST_INVALID);
    return;
  }
  RmadClient::Get()->AbortRma(base::BindOnce(
      &ShimlessRmaService::OnAbortRmaResponse, weak_ptr_factory_.GetWeakPtr(),
      std::move(callback), /*reboot=*/false));
}

void ShimlessRmaService::CriticalErrorReboot(
    CriticalErrorRebootCallback callback) {
  if (!critical_error_occurred_) {
    std::move(callback).Run(rmad::RMAD_ERROR_REQUEST_INVALID);
    return;
  }
  RmadClient::Get()->AbortRma(base::BindOnce(
      &ShimlessRmaService::OnAbortRmaResponse, weak_ptr_factory_.GetWeakPtr(),
      std::move(callback), /*reboot=*/true));
}

void ShimlessRmaService::ShutDownAfterHardwareError() {
  if (state_proto_.state_case() != rmad::RmadState::kProvisionDevice &&
      state_proto_.state_case() != rmad::RmadState::kFinalize) {
    LOG(ERROR) << "ShutDownAfterHardwareError called from incorrect state "
               << state_proto_.state_case() << " / " << mojo_state_;
    return;
  }

  chromeos::PowerManagerClient::Get()->RequestShutdown(
      power_manager::REQUEST_SHUTDOWN_FOR_USER,
      "Shutting down after encountering a hardware error.");
}

void ShimlessRmaService::BeginFinalization(BeginFinalizationCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWelcome ||
      mojo_state_ != mojom::State::kWelcomeScreen) {
    LOG(ERROR) << "FinalizeRepair called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_welcome()->set_choice(
      rmad::WelcomeState::RMAD_CHOICE_FINALIZE_REPAIR);

  // Only when the `ShimlessRMAOsUpdate` flag is enabled should the network
  // connection and OS update status be checked.
  if (features::IsShimlessRMAOsUpdateEnabled()) {
    if (!HaveAllowedNetworkConnection()) {
      // Enable WiFi on the device.
      NetworkStateHandler* network_state_handler =
          NetworkHandler::Get()->network_state_handler();
      TechnologyStateController* technology_state_controller =
          NetworkHandler::Get()->technology_state_controller();
      if (!network_state_handler->IsTechnologyEnabled(
              NetworkTypePattern::WiFi())) {
        technology_state_controller->SetTechnologiesEnabled(
            NetworkTypePattern::WiFi(), /*enabled=*/true,
            network_handler::ErrorCallback());
      }

      user_has_seen_network_page_ = true;
      mojo_state_ = mojom::State::kConfigureNetwork;
      std::move(callback).Run(
          CreateStateResult(mojom::State::kConfigureNetwork,
                            /*can_exit=*/true, /*can_go_back=*/true,
                            rmad::RmadErrorCode::RMAD_ERROR_OK));
    } else {
      // This callback is invoked once VersionUpdated determines if an OS Update
      // is available.
      check_os_callback_ =
          base::BindOnce(&ShimlessRmaService::OsUpdateOrNextRmadStateCallback,
                         weak_ptr_factory_.GetWeakPtr(), std::move(callback));
      version_updater_.CheckOsUpdateAvailable();
    }
  } else {
    TransitionNextStateGeneric(std::move(callback));
  }
}

void ShimlessRmaService::TrackConfiguredNetworks() {
  if (mojo_state_ != mojom::State::kConfigureNetwork) {
    LOG(ERROR) << "TrackConfiguredNetworks called from incorrect state "
               << state_proto_.state_case() << " / " << mojo_state_;
    return;
  }

  // Only populate `existing_saved_network_guids_` once to avoid treating a new
  // network like an existing network. TrackConfiguredNetworks() can potentially
  // be called twice if the user navigates back to the networking page.
  if (existing_saved_network_guids_.has_value()) {
    LOG(WARNING) << "Already captured configured networks.";
    return;
  }

  remote_cros_network_config_->GetNetworkStateList(
      GetConfiguredWiFiFilter(),
      base::BindOnce(&ShimlessRmaService::OnTrackConfiguredNetworks,
                     weak_ptr_factory_.GetWeakPtr()));
}

void ShimlessRmaService::OnTrackConfiguredNetworks(
    std::vector<network_mojom::NetworkStatePropertiesPtr> networks) {
  DCHECK(!existing_saved_network_guids_.has_value());

  existing_saved_network_guids_ = base::flat_set<std::string>();
  for (auto& network : networks) {
    existing_saved_network_guids_->insert(std::move(network->guid));
  }
}

void ShimlessRmaService::ForgetNewNetworkConnections(
    base::OnceClosure end_rma_callback) {
  // Skip forgetting networks if a saved list of network guids was never
  // created.
  if (!existing_saved_network_guids_.has_value()) {
    std::move(end_rma_callback).Run();
    return;
  }

  end_rma_callback_ = std::move(end_rma_callback);
  remote_cros_network_config_->GetNetworkStateList(
      GetConfiguredWiFiFilter(),
      base::BindOnce(&ShimlessRmaService::OnForgetNewNetworkConnections,
                     weak_ptr_factory_.GetWeakPtr()));
}

void ShimlessRmaService::OnForgetNewNetworkConnections(
    std::vector<network_mojom::NetworkStatePropertiesPtr> networks) {
  DCHECK(existing_saved_network_guids_.has_value());
  DCHECK(pending_network_guids_to_forget_.empty());

  for (auto& network : networks) {
    const std::string& guid = network->guid;
    const bool found_network_guid =
        base::Contains(existing_saved_network_guids_.value(), guid);

    if (!found_network_guid) {
      pending_network_guids_to_forget_.insert(guid);
    }
  }

  // No networks to forget, invoke end RMA callback.
  if (pending_network_guids_to_forget_.empty()) {
    DCHECK(!end_rma_callback_.is_null());

    std::move(end_rma_callback_).Run();
    return;
  }

  for (const auto& guid : pending_network_guids_to_forget_) {
    remote_cros_network_config_->ForgetNetwork(
        guid, base::BindOnce(&ShimlessRmaService::OnForgetNetwork,
                             weak_ptr_factory_.GetWeakPtr(), guid));
  }
}

void ShimlessRmaService::OnForgetNetwork(const std::string& guid,
                                         bool success) {
  pending_network_guids_to_forget_.erase(guid);
  if (!success) {
    LOG(ERROR) << "Failed to forget saved network configuration GUID: " << guid;
  }

  // End RMA once each network has been forgotten.
  if (pending_network_guids_to_forget_.empty()) {
    std::move(end_rma_callback_).Run();
  }
}

void ShimlessRmaService::NetworkSelectionComplete(
    NetworkSelectionCompleteCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWelcome ||
      mojo_state_ != mojom::State::kConfigureNetwork) {
    LOG(ERROR) << "NetworkSelectionComplete called from incorrect state "
               << state_proto_.state_case() << " / " << mojo_state_;
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  if (HaveAllowedNetworkConnection() &&
      features::IsShimlessRMAOsUpdateEnabled()) {
    check_os_callback_ =
        base::BindOnce(&ShimlessRmaService::OsUpdateOrNextRmadStateCallback,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback));
    version_updater_.CheckOsUpdateAvailable();
  } else {
    TransitionNextStateGeneric(std::move(callback));
  }
}

void ShimlessRmaService::GetCurrentOsVersion(
    GetCurrentOsVersionCallback callback) {
  DCHECK(features::IsShimlessRMAOsUpdateEnabled());
  // TODO(gavindodd): Decide whether to use full or short Chrome version.
  std::optional<std::string> version = chromeos::version_loader::GetVersion(
      chromeos::version_loader::VERSION_FULL);
  std::move(callback).Run(version);
}

void ShimlessRmaService::CheckForOsUpdates(CheckForOsUpdatesCallback callback) {
  DCHECK(features::IsShimlessRMAOsUpdateEnabled());
  if (state_proto_.state_case() != rmad::RmadState::kWelcome ||
      mojo_state_ != mojom::State::kUpdateOs) {
    LOG(ERROR) << "CheckForOsUpdates called from incorrect state "
               << state_proto_.state_case() << " / " << mojo_state_;
    std::move(callback).Run(false, "");
    return;
  }
  // This should never be called if a check is pending.
  DCHECK(!check_os_callback_);
  check_os_callback_ = base::BindOnce(
      [](CheckForOsUpdatesCallback callback, const std::string& version) {
        std::move(callback).Run(!version.empty(), version);
      },
      std::move(callback));
  version_updater_.CheckOsUpdateAvailable();
}

void ShimlessRmaService::UpdateOs(UpdateOsCallback callback) {
  DCHECK(features::IsShimlessRMAOsUpdateEnabled());
  if (state_proto_.state_case() != rmad::RmadState::kWelcome ||
      mojo_state_ != mojom::State::kUpdateOs) {
    LOG(ERROR) << "UpdateOs called from incorrect state "
               << state_proto_.state_case() << " / " << mojo_state_;
    std::move(callback).Run(false);
    return;
  }
  std::move(callback).Run(version_updater_.UpdateOs());

  SendMetricOnUpdateOs();
}

void ShimlessRmaService::UpdateOsSkipped(UpdateOsSkippedCallback callback) {
  DCHECK(features::IsShimlessRMAOsUpdateEnabled());
  if (state_proto_.state_case() != rmad::RmadState::kWelcome ||
      mojo_state_ != mojom::State::kUpdateOs) {
    LOG(ERROR) << "UpdateOsSkipped called from incorrect state "
               << state_proto_.state_case() << " / " << mojo_state_;
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  if (!version_updater_.IsUpdateEngineIdle()) {
    LOG(ERROR) << "UpdateOsSkipped called while UpdateEngine active";
    // Override the rmad state (kWelcome) with the mojo sub-state for OS
    // updates.
    std::move(callback).Run(
        CreateStateResult(mojom::State::kUpdateOs,
                          /*can_exit=*/true, /*can_go_back=*/true,
                          rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID));
    return;
  }
  TransitionNextStateGeneric(std::move(callback));
}

VersionUpdater* ShimlessRmaService::GetVersionUpdaterForTesting() {
  return &version_updater_;
}

void ShimlessRmaService::SetSameOwner(SetSameOwnerCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kDeviceDestination) {
    LOG(ERROR) << "SetSameOwner called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_device_destination()->set_destination(
      rmad::DeviceDestinationState::RMAD_DESTINATION_SAME);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::SetDifferentOwner(SetDifferentOwnerCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kDeviceDestination) {
    LOG(ERROR) << "SetDifferentOwner called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_device_destination()->set_destination(
      rmad::DeviceDestinationState::RMAD_DESTINATION_DIFFERENT);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::SetWipeDevice(bool should_wipe_device,
                                       SetWipeDeviceCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWipeSelection) {
    LOG(ERROR) << "SetWipeDevice called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_wipe_selection()->set_wipe_device(should_wipe_device);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::ChooseManuallyDisableWriteProtect(
    ChooseManuallyDisableWriteProtectCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWpDisableMethod) {
    LOG(ERROR)
        << "ChooseManuallyDisableWriteProtect called from incorrect state "
        << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_wp_disable_method()->set_disable_method(
      rmad::WriteProtectDisableMethodState::RMAD_WP_DISABLE_PHYSICAL);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::ChooseRsuDisableWriteProtect(
    ChooseRsuDisableWriteProtectCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWpDisableMethod) {
    LOG(ERROR) << "ChooseRsuDisableWriteProtect called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_wp_disable_method()->set_disable_method(
      rmad::WriteProtectDisableMethodState::RMAD_WP_DISABLE_RSU);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::GetRsuDisableWriteProtectChallenge(
    GetRsuDisableWriteProtectChallengeCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWpDisableRsu) {
    LOG(ERROR)
        << "GetRsuDisableWriteProtectChallenge called from incorrect state "
        << state_proto_.state_case();
    std::move(callback).Run("");
    return;
  }
  std::move(callback).Run(state_proto_.wp_disable_rsu().challenge_code());
}

void ShimlessRmaService::GetRsuDisableWriteProtectHwid(
    GetRsuDisableWriteProtectHwidCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWpDisableRsu) {
    LOG(ERROR) << "GetRsuDisableWriteProtectHwid called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run("");
    return;
  }
  std::move(callback).Run(state_proto_.wp_disable_rsu().hwid());
}

void ShimlessRmaService::GetRsuDisableWriteProtectChallengeQrCode(
    GetRsuDisableWriteProtectChallengeQrCodeCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWpDisableRsu) {
    LOG(ERROR) << "GetRsuDisableWriteProtectChallengeQrCode called from "
                  "incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(std::vector<uint8_t>{});
    return;
  }

  shimless_rma_delegate_->GenerateQrCode(
      state_proto_.wp_disable_rsu().challenge_url(),
      base::BindOnce(&ShimlessRmaService::OnQrCodeGenerated,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ShimlessRmaService::OnQrCodeGenerated(
    GetRsuDisableWriteProtectChallengeQrCodeCallback callback,
    const std::string& qr_code_image) {
  std::move(callback).Run(
      std::vector<uint8_t>(qr_code_image.begin(), qr_code_image.end()));
}

void ShimlessRmaService::SetRsuDisableWriteProtectCode(
    const std::string& code,
    SetRsuDisableWriteProtectCodeCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWpDisableRsu) {
    LOG(ERROR) << "SetRsuDisableWriteProtectCode called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_wp_disable_rsu()->set_unlock_code(code);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::WriteProtectManuallyDisabled(
    WriteProtectManuallyDisabledCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWpDisablePhysical) {
    LOG(ERROR) << "WriteProtectManuallyDisabled called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::GetWriteProtectDisableCompleteAction(
    GetWriteProtectDisableCompleteActionCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWpDisableComplete) {
    LOG(ERROR) << "ConfirmManualWpDisableComplete called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(
        rmad::WriteProtectDisableCompleteState::RMAD_WP_DISABLE_UNKNOWN);
    return;
  }
  std::move(callback).Run(state_proto_.wp_disable_complete().action());
}

void ShimlessRmaService::ConfirmManualWpDisableComplete(
    ConfirmManualWpDisableCompleteCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWpDisableComplete) {
    LOG(ERROR) << "ConfirmManualWpDisableComplete called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::GetComponentList(GetComponentListCallback callback) {
  std::vector<::rmad::ComponentsRepairState_ComponentRepairStatus> components;
  if (state_proto_.state_case() != rmad::RmadState::kComponentsRepair) {
    LOG(ERROR) << "GetComponentList called from incorrect state "
               << state_proto_.state_case();
  } else {
    components.reserve(state_proto_.components_repair().components_size());
    components.assign(state_proto_.components_repair().components().begin(),
                      state_proto_.components_repair().components().end());
  }
  std::move(callback).Run(std::move(components));
}

void ShimlessRmaService::SetComponentList(
    const std::vector<::rmad::ComponentsRepairState_ComponentRepairStatus>&
        component_list,
    SetComponentListCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kComponentsRepair) {
    LOG(ERROR) << "SetComponentList called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_components_repair()->set_mainboard_rework(false);
  state_proto_.mutable_components_repair()->clear_components();
  state_proto_.mutable_components_repair()->mutable_components()->Reserve(
      component_list.size());
  for (auto& component : component_list) {
    rmad::ComponentsRepairState_ComponentRepairStatus* proto_component =
        state_proto_.mutable_components_repair()->add_components();
    proto_component->set_component(component.component());
    proto_component->set_repair_status(component.repair_status());
    proto_component->set_identifier(component.identifier());
  }
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::ReworkMainboard(ReworkMainboardCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kComponentsRepair) {
    LOG(ERROR) << "ReworkMainboard called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_components_repair()->set_mainboard_rework(true);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::RoFirmwareUpdateComplete(
    RoFirmwareUpdateCompleteCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kUpdateRoFirmware) {
    LOG(ERROR) << "RoFirmwareUpdateComplete called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_update_ro_firmware()->set_choice(
      rmad::UpdateRoFirmwareState::RMAD_UPDATE_CHOICE_CONTINUE);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::ShutdownForRestock(
    ShutdownForRestockCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kRestock) {
    LOG(ERROR) << "ShutdownForRestock called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_restock()->set_choice(
      rmad::RestockState::RMAD_RESTOCK_SHUTDOWN_AND_RESTOCK);
  TransitionNextStateGeneric(std::move(callback));
}
void ShimlessRmaService::ContinueFinalizationAfterRestock(
    ContinueFinalizationAfterRestockCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kRestock) {
    LOG(ERROR)
        << "ContinueFinalizationAfterRestock called from incorrect state "
        << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_restock()->set_choice(
      rmad::RestockState::RMAD_RESTOCK_CONTINUE_RMA);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::GetRegionList(GetRegionListCallback callback) {
  std::vector<std::string> regions;
  if (state_proto_.state_case() != rmad::RmadState::kUpdateDeviceInfo) {
    LOG(ERROR) << "GetRegionList called from incorrect state "
               << state_proto_.state_case();
  } else {
    regions.reserve(state_proto_.update_device_info().region_list_size());
    regions.assign(state_proto_.update_device_info().region_list().begin(),
                   state_proto_.update_device_info().region_list().end());
  }
  std::move(callback).Run(std::move(regions));
}

void ShimlessRmaService::GetSkuList(GetSkuListCallback callback) {
  std::vector<uint64_t> skus;
  if (state_proto_.state_case() != rmad::RmadState::kUpdateDeviceInfo) {
    LOG(ERROR) << "GetSkuList called from incorrect state "
               << state_proto_.state_case();
  } else {
    skus.reserve(state_proto_.update_device_info().sku_list_size());
    skus.assign(state_proto_.update_device_info().sku_list().begin(),
                state_proto_.update_device_info().sku_list().end());
  }
  std::move(callback).Run(std::move(skus));
}

void ShimlessRmaService::GetCustomLabelList(
    GetCustomLabelListCallback callback) {
  std::vector<std::string> custom_labels;
  if (state_proto_.state_case() != rmad::RmadState::kUpdateDeviceInfo) {
    LOG(ERROR) << "GetCustomLabelList called from incorrect state "
               << state_proto_.state_case();
  } else {
    custom_labels.reserve(
        state_proto_.update_device_info().custom_label_list_size());
    custom_labels.assign(
        state_proto_.update_device_info().custom_label_list().begin(),
        state_proto_.update_device_info().custom_label_list().end());
  }
  std::move(callback).Run(std::move(custom_labels));
}

void ShimlessRmaService::GetSkuDescriptionList(
    GetSkuDescriptionListCallback callback) {
  std::vector<std::string> sku_descriptions;
  if (state_proto_.state_case() != rmad::RmadState::kUpdateDeviceInfo) {
    LOG(ERROR) << "GetSkuDescriptionList called from incorrect state "
               << state_proto_.state_case();
  } else {
    sku_descriptions.reserve(
        state_proto_.update_device_info().sku_description_list_size());
    sku_descriptions.assign(
        state_proto_.update_device_info().sku_description_list().begin(),
        state_proto_.update_device_info().sku_description_list().end());
  }
  std::move(callback).Run(std::move(sku_descriptions));
}

void ShimlessRmaService::GetOriginalSerialNumber(
    GetOriginalSerialNumberCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kUpdateDeviceInfo) {
    LOG(ERROR) << "GetOriginalSerialNumber called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run("");
    return;
  }
  std::move(callback).Run(
      state_proto_.update_device_info().original_serial_number());
}

void ShimlessRmaService::GetOriginalRegion(GetOriginalRegionCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kUpdateDeviceInfo) {
    LOG(ERROR) << "GetOriginalRegion called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(0);
    return;
  }
  std::move(callback).Run(
      state_proto_.update_device_info().original_region_index());
}

void ShimlessRmaService::GetOriginalSku(GetOriginalSkuCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kUpdateDeviceInfo) {
    LOG(ERROR) << "GetOriginalSku called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(0);
    return;
  }
  std::move(callback).Run(
      state_proto_.update_device_info().original_sku_index());
}

void ShimlessRmaService::GetOriginalCustomLabel(
    GetOriginalCustomLabelCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kUpdateDeviceInfo) {
    // TODO(gavindodd): Consider replacing all invalid call handling with
    // mojo::ReportBadMessage("error message");
    LOG(ERROR) << "GetOriginalCustomLabel called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(0);
    return;
  }
  std::move(callback).Run(
      state_proto_.update_device_info().original_custom_label_index());
}

void ShimlessRmaService::GetOriginalDramPartNumber(
    GetOriginalDramPartNumberCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kUpdateDeviceInfo) {
    // TODO(gavindodd): Consider replacing all invalid call handling with
    // mojo::ReportBadMessage("error message");
    LOG(ERROR) << "GetOriginalDramPartNumber called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run("");
    return;
  }
  std::move(callback).Run(
      state_proto_.update_device_info().original_dram_part_number());
}

void ShimlessRmaService::GetOriginalFeatureLevel(
    GetOriginalFeatureLevelCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kUpdateDeviceInfo) {
    LOG(ERROR) << "GetOriginalFeatureLevel called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(
        rmad::UpdateDeviceInfoState::RMAD_FEATURE_LEVEL_UNSUPPORTED);
    return;
  }
  std::move(callback).Run(
      state_proto_.update_device_info().original_feature_level());
}

void ShimlessRmaService::SetDeviceInformation(
    const std::string& serial_number,
    int32_t region_index,
    int32_t sku_index,
    int32_t custom_label_index,
    const std::string& dram_part_number,
    bool is_chassis_branded,
    int32_t hw_compliance_version,
    SetDeviceInformationCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kUpdateDeviceInfo) {
    LOG(ERROR) << "SetDeviceInformation called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_update_device_info()->set_serial_number(serial_number);
  state_proto_.mutable_update_device_info()->set_region_index(region_index);
  state_proto_.mutable_update_device_info()->set_sku_index(sku_index);
  state_proto_.mutable_update_device_info()->set_custom_label_index(
      custom_label_index);
  state_proto_.mutable_update_device_info()->set_dram_part_number(
      dram_part_number);
  state_proto_.mutable_update_device_info()->set_is_chassis_branded(
      is_chassis_branded);
  state_proto_.mutable_update_device_info()->set_hw_compliance_version(
      hw_compliance_version);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::GetCalibrationComponentList(
    GetCalibrationComponentListCallback callback) {
  std::vector<::rmad::CalibrationComponentStatus> components;
  if (state_proto_.state_case() != rmad::RmadState::kCheckCalibration) {
    LOG(ERROR) << "GetCalibrationComponentList called from incorrect state "
               << state_proto_.state_case();
  } else {
    components.reserve(state_proto_.check_calibration().components_size());
    components.assign(state_proto_.check_calibration().components().begin(),
                      state_proto_.check_calibration().components().end());
  }
  std::move(callback).Run(std::move(components));
}

void ShimlessRmaService::GetCalibrationSetupInstructions(
    GetCalibrationSetupInstructionsCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kSetupCalibration) {
    LOG(ERROR) << "GetCalibrationSetupInstructions called from incorrect state "
               << state_proto_.state_case();
    // TODO(gavindodd): Is RMAD_CALIBRATION_INSTRUCTION_UNKNOWN the correct
    // error value? Confirm with rmad team that this is not overloaded.
    std::move(callback).Run(rmad::CalibrationSetupInstruction::
                                RMAD_CALIBRATION_INSTRUCTION_UNKNOWN);
    return;
  }
  std::move(callback).Run(state_proto_.setup_calibration().instruction());
}

void ShimlessRmaService::StartCalibration(
    const std::vector<::rmad::CalibrationComponentStatus>& components,
    StartCalibrationCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kCheckCalibration) {
    LOG(ERROR) << "StartCalibration called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_check_calibration()->clear_components();
  state_proto_.mutable_check_calibration()->mutable_components()->Reserve(
      components.size());
  for (auto& component : components) {
    rmad::CalibrationComponentStatus* proto_component =
        state_proto_.mutable_check_calibration()->add_components();
    proto_component->set_component(component.component());
    // rmad only cares if the status is set to skip or not.
    proto_component->set_status(component.status());
    // Progress is not relevant when sending to rmad.
    proto_component->set_progress(0.0);
  }
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::RunCalibrationStep(
    RunCalibrationStepCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kSetupCalibration) {
    LOG(ERROR) << "RunCalibrationStep called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }

  // Clear the previous calibration progress.
  last_calibration_progress_ = std::nullopt;
  last_calibration_overall_progress_ = std::nullopt;

  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::ContinueCalibration(
    ContinueCalibrationCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kRunCalibration) {
    LOG(ERROR) << "ContinueCalibration called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::CalibrationComplete(
    CalibrationCompleteCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kRunCalibration) {
    LOG(ERROR) << "CalibrationComplete called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::RetryProvisioning(RetryProvisioningCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kProvisionDevice) {
    LOG(ERROR) << "RetryProvisioning called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_provision_device()->set_choice(
      rmad::ProvisionDeviceState::RMAD_PROVISION_CHOICE_RETRY);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::ProvisioningComplete(
    ProvisioningCompleteCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kProvisionDevice) {
    LOG(ERROR) << "ProvisioningComplete called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_provision_device()->set_choice(
      rmad::ProvisionDeviceState::RMAD_PROVISION_CHOICE_CONTINUE);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::RetryFinalization(RetryFinalizationCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kFinalize) {
    LOG(ERROR) << "RetryFinalization called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_finalize()->set_choice(
      rmad::FinalizeState::RMAD_FINALIZE_CHOICE_RETRY);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::FinalizationComplete(
    FinalizationCompleteCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kFinalize) {
    LOG(ERROR) << "FinalizationComplete called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  state_proto_.mutable_finalize()->set_choice(
      rmad::FinalizeState::RMAD_FINALIZE_CHOICE_CONTINUE);
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::WriteProtectManuallyEnabled(
    WriteProtectManuallyEnabledCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kWpEnablePhysical) {
    LOG(ERROR) << "WriteProtectManuallyEnabled called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }
  TransitionNextStateGeneric(std::move(callback));
}

void ShimlessRmaService::GetLog(GetLogCallback callback) {
  RmadClient::Get()->GetLog(base::BindOnce(&ShimlessRmaService::OnGetLog,
                                           weak_ptr_factory_.GetWeakPtr(),
                                           std::move(callback)));
}

void ShimlessRmaService::SaveLog(SaveLogCallback callback) {
  if (diagnostics::DiagnosticsLogController::IsInitialized()) {
    task_runner_->PostTaskAndReplyWithResult(
        FROM_HERE,
        base::BindOnce(
            &diagnostics::DiagnosticsLogController::
                GenerateSessionStringOnBlockingPool,
            // base::Unretained safe here because ~DiagnosticsLogController is
            // called during shutdown of ash::Shell and will out-live
            // ShimlessRmaService.
            base::Unretained(diagnostics::DiagnosticsLogController::Get())),
        base::BindOnce(&ShimlessRmaService::OnDiagnosticsLogReady,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
    return;
  }

  OnDiagnosticsLogReady(std::move(callback), "");
}

void ShimlessRmaService::OnDiagnosticsLogReady(
    SaveLogCallback callback,
    const std::string& diagnostics_log_text) {
  RmadClient::Get()->SaveLog(
      diagnostics_log_text,
      base::BindOnce(&ShimlessRmaService::OnSaveLog,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ShimlessRmaService::OnGetLog(GetLogCallback callback,
                                  std::optional<rmad::GetLogReply> response) {
  if (!response) {
    LOG(ERROR) << "Failed to call rmad::GetLog";
    std::move(callback).Run("",
                            rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID);
    return;
  }

  std::move(callback).Run(response->log(), response->error());
}

void ShimlessRmaService::OnSaveLog(SaveLogCallback callback,
                                   std::optional<rmad::SaveLogReply> response) {
  if (!response) {
    LOG(ERROR) << "Failed to call rmad::SaveLog";
    std::move(callback).Run(base::FilePath(""),
                            rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID);
    return;
  }

  std::move(callback).Run(base::FilePath(response->save_path()),
                          response->error());
}

void ShimlessRmaService::GetPowerwashRequired(
    GetPowerwashRequiredCallback callback) {
  if (state_proto_.state_case() != rmad::RmadState::kRepairComplete) {
    LOG(ERROR) << "GetPowerwashRequired called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(false);
    return;
  }

  std::move(callback).Run(state_proto_.repair_complete().powerwash_required());
}

void ShimlessRmaService::LaunchDiagnostics() {
  if (state_proto_.state_case() != rmad::RmadState::kRepairComplete) {
    LOG(ERROR) << "LaunchDiagnostics called from incorrect state "
               << state_proto_.state_case();
    return;
  }
  shimless_rma_delegate_->ShowDiagnosticsDialog();

  SendMetricOnLaunchDiagnostics();
}

void ShimlessRmaService::EndRma(
    rmad::RepairCompleteState::ShutdownMethod shutdown_method,
    EndRmaCallback callback) {
  DCHECK_NE(rmad::RepairCompleteState::RMAD_REPAIR_COMPLETE_UNKNOWN,
            shutdown_method);
  if (state_proto_.state_case() != rmad::RmadState::kRepairComplete) {
    LOG(ERROR) << "EndRma called from incorrect state "
               << state_proto_.state_case();
    std::move(callback).Run(CreateStateResultForInvalidRequest());
    return;
  }

  ForgetNewNetworkConnections(base::BindOnce(
      &ShimlessRmaService::EndRmaForgetNetworkResponse,
      weak_ptr_factory_.GetWeakPtr(), shutdown_method, std::move(callback)));
}

void ShimlessRmaService::EndRmaForgetNetworkResponse(
    rmad::RepairCompleteState::ShutdownMethod shutdown_method,
    EndRmaCallback callback) {
  state_proto_.mutable_repair_complete()->set_shutdown(shutdown_method);
  TransitionNextStateGeneric(std::move(callback));
}

////////////////////////////////
// Metrics
void ShimlessRmaService::SendMetricOnLaunchDiagnostics() {
  rmad::RecordBrowserActionMetricRequest request;
  request.set_diagnostics(true);
  request.set_os_update(false);

  RmadClient::Get()->RecordBrowserActionMetric(
      request, base::BindOnce(&ShimlessRmaService::OnMetricsReply,
                              weak_ptr_factory_.GetWeakPtr()));
}

void ShimlessRmaService::SendMetricOnUpdateOs() {
  rmad::RecordBrowserActionMetricRequest request;
  request.set_diagnostics(false);
  request.set_os_update(true);

  RmadClient::Get()->RecordBrowserActionMetric(
      request, base::BindOnce(&ShimlessRmaService::OnMetricsReply,
                              weak_ptr_factory_.GetWeakPtr()));
}

void ShimlessRmaService::OnMetricsReply(
    std::optional<rmad::RecordBrowserActionMetricReply> response) {
  if (!response) {
    LOG(ERROR) << "Failed to call rmad::RecordBrowserActionMetric";
    return;
  }

  if (response->error() != rmad::RmadErrorCode::RMAD_ERROR_OK) {
    LOG(ERROR) << "Failed to upload metrics";
  }
}

////////////////////////////////
// Observers
void ShimlessRmaService::Error(rmad::RmadErrorCode error) {
  if (error_observer_.is_bound()) {
    error_observer_->OnError(error);
  }
}

void ShimlessRmaService::OsUpdateProgress(update_engine::Operation operation,
                                          double progress,
                                          update_engine::ErrorCode error_code) {
  DCHECK(features::IsShimlessRMAOsUpdateEnabled());
  if (os_update_observer_.is_bound()) {
    os_update_observer_->OnOsUpdateProgressUpdated(operation, progress,
                                                   error_code);
  }
}

void ShimlessRmaService::CalibrationProgress(
    const rmad::CalibrationComponentStatus& component_status) {
  last_calibration_progress_ = component_status;
  if (calibration_observer_.is_bound()) {
    calibration_observer_->OnCalibrationUpdated(component_status);
  }
}

void ShimlessRmaService::CalibrationOverallProgress(
    rmad::CalibrationOverallStatus status) {
  last_calibration_overall_progress_ = status;
  if (calibration_observer_.is_bound()) {
    calibration_observer_->OnCalibrationStepComplete(status);
  }
}

void ShimlessRmaService::ProvisioningProgress(
    const rmad::ProvisionStatus& status) {
  if (status.status() ==
          rmad::ProvisionStatus::RMAD_PROVISION_STATUS_FAILED_BLOCKING ||
      status.status() ==
          rmad::ProvisionStatus::RMAD_PROVISION_STATUS_FAILED_NON_BLOCKING) {
    LOG(ERROR) << "Provisioning failed with error " << status.error();
  }

  last_provisioning_progress_ = status;
  if (provisioning_observer_.is_bound()) {
    provisioning_observer_->OnProvisioningUpdated(
        status.status(), status.progress(), status.error());
  }
}

void ShimlessRmaService::HardwareWriteProtectionState(bool enabled) {
  last_hardware_protection_state_ = enabled;
  if (hwwp_state_observer_.is_bound()) {
    hwwp_state_observer_->OnHardwareWriteProtectionStateChanged(enabled);
  }
}

void ShimlessRmaService::PowerCableState(bool plugged_in) {
  last_power_cable_state_ = plugged_in;
  if (power_cable_observer_.is_bound()) {
    power_cable_observer_->OnPowerCableStateChanged(plugged_in);
  }
}

void ShimlessRmaService::ExternalDiskState(bool detected) {
  last_external_disk_state_ = detected;
  for (auto& external_disk_state_observer : external_disk_state_observers_) {
    external_disk_state_observer->OnExternalDiskStateChanged(
        *last_external_disk_state_);
  }
}

void ShimlessRmaService::HardwareVerificationResult(
    const rmad::HardwareVerificationResult& result) {
  last_hardware_verification_result_ = result;
  for (auto& observer : hardware_verification_observers_) {
    observer->OnHardwareVerificationResult(result.is_compliant(),
                                           result.error_str());
  }
}

void ShimlessRmaService::FinalizationProgress(
    const rmad::FinalizeStatus& status) {
  if (status.status() ==
          rmad::FinalizeStatus::RMAD_FINALIZE_STATUS_FAILED_BLOCKING ||
      status.status() ==
          rmad::FinalizeStatus::RMAD_FINALIZE_STATUS_FAILED_NON_BLOCKING) {
    LOG(ERROR) << "Finalization failed with error " << status.error();
  }

  last_finalization_progress_ = status;
  if (finalization_observer_.is_bound()) {
    finalization_observer_->OnFinalizationUpdated(
        status.status(), status.progress(), status.error());
  }
}

void ShimlessRmaService::RoFirmwareUpdateProgress(
    rmad::UpdateRoFirmwareStatus status) {
  last_update_ro_firmware_progress_ = status;
  if (update_ro_firmware_observer_.is_bound()) {
    update_ro_firmware_observer_->OnUpdateRoFirmwareStatusChanged(status);
  }
}

void ShimlessRmaService::ObserveError(
    ::mojo::PendingRemote<mojom::ErrorObserver> observer) {
  error_observer_.Bind(std::move(observer));
}

void ShimlessRmaService::ObserveOsUpdateProgress(
    ::mojo::PendingRemote<mojom::OsUpdateObserver> observer) {
  DCHECK(features::IsShimlessRMAOsUpdateEnabled());
  os_update_observer_.Bind(std::move(observer));
}

void ShimlessRmaService::ObserveCalibrationProgress(
    ::mojo::PendingRemote<mojom::CalibrationObserver> observer) {
  if (calibration_observer_.is_bound()) {
    calibration_observer_.reset();
  }

  calibration_observer_.Bind(std::move(observer));
  if (last_calibration_progress_) {
    calibration_observer_->OnCalibrationUpdated(*last_calibration_progress_);
  }
  if (last_calibration_overall_progress_) {
    calibration_observer_->OnCalibrationStepComplete(
        *last_calibration_overall_progress_);
  }
}

void ShimlessRmaService::ObserveProvisioningProgress(
    ::mojo::PendingRemote<mojom::ProvisioningObserver> observer) {
  provisioning_observer_.Bind(std::move(observer));
  if (last_provisioning_progress_) {
    provisioning_observer_->OnProvisioningUpdated(
        last_provisioning_progress_->status(),
        last_provisioning_progress_->progress(),
        last_provisioning_progress_->error());
  }
}

void ShimlessRmaService::ObserveHardwareWriteProtectionState(
    ::mojo::PendingRemote<mojom::HardwareWriteProtectionStateObserver>
        observer) {
  hwwp_state_observer_.Bind(std::move(observer));
  if (last_hardware_protection_state_) {
    hwwp_state_observer_->OnHardwareWriteProtectionStateChanged(
        *last_hardware_protection_state_);
  }
}

void ShimlessRmaService::ObservePowerCableState(
    ::mojo::PendingRemote<mojom::PowerCableStateObserver> observer) {
  power_cable_observer_.Bind(std::move(observer));
  if (last_power_cable_state_) {
    power_cable_observer_->OnPowerCableStateChanged(*last_power_cable_state_);
  }
}

void ShimlessRmaService::ObserveExternalDiskState(
    ::mojo::PendingRemote<mojom::ExternalDiskStateObserver> observer) {
  external_disk_state_observers_.Add(std::move(observer));
  if (last_external_disk_state_) {
    for (auto& external_disk_state_observer : external_disk_state_observers_) {
      external_disk_state_observer->OnExternalDiskStateChanged(
          *last_external_disk_state_);
    }
  }
}

void ShimlessRmaService::ObserveHardwareVerificationStatus(
    ::mojo::PendingRemote<mojom::HardwareVerificationStatusObserver> observer) {
  hardware_verification_observers_.Add(std::move(observer));
  if (last_hardware_verification_result_) {
    for (auto& hardware_verification_observer :
         hardware_verification_observers_) {
      hardware_verification_observer->OnHardwareVerificationResult(
          last_hardware_verification_result_->is_compliant(),
          last_hardware_verification_result_->error_str());
    }
  }
}

void ShimlessRmaService::ObserveFinalizationStatus(
    ::mojo::PendingRemote<mojom::FinalizationObserver> observer) {
  finalization_observer_.Bind(std::move(observer));
  if (last_finalization_progress_) {
    finalization_observer_->OnFinalizationUpdated(
        last_finalization_progress_->status(),
        last_finalization_progress_->progress(),
        last_finalization_progress_->error());
  }
}

void ShimlessRmaService::ObserveRoFirmwareUpdateProgress(
    ::mojo::PendingRemote<mojom::UpdateRoFirmwareObserver> observer) {
  update_ro_firmware_observer_.Bind(std::move(observer));
  if (last_update_ro_firmware_progress_) {
    update_ro_firmware_observer_->OnUpdateRoFirmwareStatusChanged(
        *last_update_ro_firmware_progress_);
  }
}

////////////////////////////////
// Mojom binding.
void ShimlessRmaService::BindInterface(
    mojo::PendingReceiver<mojom::ShimlessRmaService> pending_receiver) {
  receiver_.Bind(std::move(pending_receiver));
}

////////////////////////////////
// RmadClient response handlers.
template <class Callback>
void ShimlessRmaService::TransitionNextStateGeneric(Callback callback) {
  RmadClient::Get()->TransitionNextState(
      state_proto_,
      base::BindOnce(&ShimlessRmaService::OnGetStateResponse<Callback>,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                     kTransitNextState));
}

template <class Callback>
void ShimlessRmaService::OnGetStateResponse(
    Callback callback,
    StateResponseCalledFrom called_from,
    std::optional<rmad::GetStateReply> response) {
  if (!response) {
    LOG(ERROR) << "Failed to call rmadClient";
    critical_error_occurred_ = true;
    std::move(callback).Run(
        CreateStateResult(mojom::State::kUnknown,
                          /*can_exit=*/false,
                          /*can_go_back=*/false,
                          rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID));
    return;
  }
  // TODO(gavindodd): When platform and chrome release cycles are decoupled
  // there needs to be a way to detect an unexpected state and switch to update
  // Chrome screen.
  state_proto_ = response->state();
  can_abort_ = response->can_abort();
  can_go_back_ = response->can_go_back();
  mojo_state_ = RmadStateToMojo(state_proto_.state_case());
  if (response->error() != rmad::RMAD_ERROR_OK) {
    LOG(ERROR) << "rmadClient returned error " << response->error();
    if (response->error() == rmad::RMAD_ERROR_RMA_NOT_REQUIRED) {
      critical_error_occurred_ = true;
    }
    std::move(callback).Run(
        CreateStateResult(RmadStateToMojo(state_proto_.state_case()),
                          can_abort_, can_go_back_, response->error()));
    return;
  }

  // This is a special case we need to check to make sure if user has seen
  // the NetworkPage and clicks back button from the next page. The user should
  // be back to the NetworkPage. The reason why it needs special check is
  // because of state mismatch between shimless mojom and rmad. In this case,
  // the mojom kConfigureNetwork state doesn't match to any rmad state.
  if (called_from == kTransitPreviousState && user_has_seen_network_page_ &&
      state_proto_.state_case() == rmad::RmadState::kWelcome &&
      mojo_state_ == mojom::State::kWelcomeScreen) {
    user_has_seen_network_page_ = false;
    state_proto_.mutable_welcome()->set_choice(
        rmad::WelcomeState::RMAD_CHOICE_FINALIZE_REPAIR);

    mojo_state_ = mojom::State::kConfigureNetwork;
    std::move(callback).Run(
        CreateStateResult(mojom::State::kConfigureNetwork,
                          /*can_exit=*/true, /*can_go_back=*/true,
                          rmad::RmadErrorCode::RMAD_ERROR_OK));
    return;
  }

  std::move(callback).Run(
      CreateStateResult(RmadStateToMojo(state_proto_.state_case()), can_abort_,
                        can_go_back_, rmad::RmadErrorCode::RMAD_ERROR_OK));
}

void ShimlessRmaService::OnAbortRmaResponse(
    AbortRmaCallback callback,
    bool reboot,
    std::optional<rmad::AbortRmaReply> response) {
  const rmad::RmadErrorCode error_code =
      response ? response->error()
               : rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID;

  // Only reboot or exit to login if abort was successful (state will be
  // RMAD_ERROR_RMA_NOT_REQUIRED) or a critical error has occurred.
  const bool should_exit_rma = critical_error_occurred_ ||
                               error_code == rmad::RMAD_ERROR_RMA_NOT_REQUIRED;
  if (!should_exit_rma) {
    std::move(callback).Run(error_code);
    return;
  }

  ForgetNewNetworkConnections(base::BindOnce(
      &ShimlessRmaService::AbortRmaForgetNetworkResponse,
      weak_ptr_factory_.GetWeakPtr(), std::move(callback), reboot, response));
}

void ShimlessRmaService::AbortRmaForgetNetworkResponse(
    AbortRmaCallback callback,
    bool reboot,
    std::optional<rmad::AbortRmaReply> response) {
  // Send status before shutting down or restarting Chrome session.
  std::move(callback).Run(rmad::RMAD_ERROR_OK);

  // Either reboot the device or just restart the Chrome session.
  if (reboot) {
    VLOG(1) << "Rebooting...";
    chromeos::PowerManagerClient::Get()->RequestRestart(
        power_manager::REQUEST_RESTART_FOR_USER,
        critical_error_occurred_
            ? "Rebooting after user cancelled RMA due to critical error."
            : "Rebooting after user cancelled RMA.");
  } else {
    VLOG(1) << "Restarting Chrome to bypass RMA after cancel request.";
    shimless_rma_delegate_->ExitRmaThenRestartChrome();
  }
}

void ShimlessRmaService::OnOsUpdateStatusCallback(
    update_engine::Operation operation,
    double progress,
    bool rollback,
    bool powerwash,
    const std::string& version,
    int64_t update_size,
    update_engine::ErrorCode error_code) {
  DCHECK(features::IsShimlessRMAOsUpdateEnabled());
  if (check_os_callback_) {
    switch (operation) {
      // If IDLE is received when there is a callback it means no update is
      // available.
      case update_engine::Operation::DISABLED:
      case update_engine::Operation::ERROR:
      case update_engine::Operation::IDLE:
      case update_engine::Operation::REPORTING_ERROR_EVENT:
        std::move(check_os_callback_).Run("");
        break;
      case update_engine::Operation::UPDATE_AVAILABLE:
        std::move(check_os_callback_).Run(version);
        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();
    }
  }
  OsUpdateProgress(operation, progress, error_code);
}

void ShimlessRmaService::OsUpdateOrNextRmadStateCallback(
    TransitionStateCallback callback,
    const std::string& version) {
  if (version.empty()) {
    TransitionNextStateGeneric(std::move(callback));
  } else {
    mojo_state_ = mojom::State::kUpdateOs;
    std::move(callback).Run(
        CreateStateResult(mojom::State::kUpdateOs,
                          /*can_exit=*/true, /*can_go_back=*/true,
                          rmad::RmadErrorCode::RMAD_ERROR_OK));
  }
}

void ShimlessRmaService::SetCriticalErrorOccurredForTest(
    bool critical_error_occurred) {
  critical_error_occurred_ = critical_error_occurred;
}

////////////////////////////////
// Methods related to 3p diagnostics.
void ShimlessRmaService::Get3pDiagnosticsProvider(
    Get3pDiagnosticsProviderCallback callback) {
  ash::cros_healthd::ServiceConnection::GetInstance()
      ->GetProbeService()
      ->ProbeTelemetryInfo(
          {ash::cros_healthd::mojom::ProbeCategoryEnum::kSystem},
          base::BindOnce(&ShimlessRmaService::OnGetSystemInfoFor3pDiag,
                         weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ShimlessRmaService::OnGetSystemInfoFor3pDiag(
    Get3pDiagnosticsProviderCallback callback,
    ash::cros_healthd::mojom::TelemetryInfoPtr telemetry_info) {
  if (!telemetry_info->system_result ||
      !telemetry_info->system_result->is_system_info() ||
      !telemetry_info->system_result->get_system_info()->os_info->oem_name) {
    LOG(ERROR) << "Failed to get oem name from cros_healthd";
    std::move(callback).Run(std::nullopt);
    return;
  }

  const std::string& oem_name = telemetry_info->system_result->get_system_info()
                                    ->os_info->oem_name.value();
  if (shimless_rma_delegate_->IsChromeOSSystemExtensionProvider(oem_name)) {
    std::move(callback).Run(oem_name);
    return;
  }

  std::move(callback).Run(std::nullopt);
}

void ShimlessRmaService::GetInstallable3pDiagnosticsAppPath(
    GetInstallable3pDiagnosticsAppPathCallback callback) {
  RmadClient::Get()->ExtractExternalDiagnosticsApp(
      base::BindOnce(&ShimlessRmaService::OnExtractExternalDiagnosticsApp,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ShimlessRmaService::OnExtractExternalDiagnosticsApp(
    GetInstallable3pDiagnosticsAppPathCallback callback,
    std::optional<rmad::ExtractExternalDiagnosticsAppReply> response) {
  if (!response || response->error() != rmad::RmadErrorCode::RMAD_ERROR_OK) {
    LOG_IF(ERROR, !response)
        << "Failed to call rmad::ExtractExternalDiagnosticsApp";
    LOG_IF(ERROR,
           response &&
               response->error() !=
                   rmad::RmadErrorCode::RMAD_ERROR_DIAGNOSTICS_APP_NOT_FOUND)
        << "Unexpected result from rmad::ExtractExternalDiagnosticsApp: "
        << response->error();
    extracted_3p_diag_swbn_path_ = base::FilePath{};
    extracted_3p_diag_crx_path_ = base::FilePath{};
    std::move(callback).Run(std::nullopt);
    return;
  }

  extracted_3p_diag_swbn_path_ =
      base::FilePath{response->diagnostics_app_swbn_path()};
  extracted_3p_diag_crx_path_ =
      base::FilePath{response->diagnostics_app_crx_path()};
  std::move(callback).Run(
      base::FilePath{response->diagnostics_app_swbn_path()});
}

void ShimlessRmaService::InstallLastFound3pDiagnosticsApp(
    InstallLastFound3pDiagnosticsAppCallback callback) {
  if (extracted_3p_diag_swbn_path_.empty() ||
      extracted_3p_diag_swbn_path_.empty()) {
    LOG(ERROR) << "Should call GetInstallable3pDiagnosticsAppPath first";
    std::move(callback).Run(nullptr);
    return;
  }

  shimless_rma_delegate_->PrepareDiagnosticsAppBrowserContext(
      extracted_3p_diag_crx_path_, extracted_3p_diag_swbn_path_,
      base::BindOnce(&ShimlessRmaService::On3pDiagnosticsAppLoadForInstallation,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ShimlessRmaService::On3pDiagnosticsAppLoadForInstallation(
    InstallLastFound3pDiagnosticsAppCallback callback,
    base::expected<
        ShimlessRmaDelegate::PrepareDiagnosticsAppBrowserContextResult,
        std::string> result) {
  if (!result.has_value()) {
    LOG(ERROR) << "Failed to load 3p diag app: " << result.error();
    std::move(callback).Run(nullptr);
    return;
  }

  shimless_app_browser_context_ = result.value().context;
  shimless_3p_diag_iwa_id_ = result.value().iwa_id;
  shimless_3p_diag_app_name_ = result.value().name;

  auto app_info = ash::shimless_rma::mojom::Shimless3pDiagnosticsAppInfo::New();
  app_info->name = result.value().name;
  app_info->permission_message = result.value().permission_message;
  std::move(callback).Run(std::move(app_info));
}

void ShimlessRmaService::CompleteLast3pDiagnosticsInstallation(
    bool is_approved,
    CompleteLast3pDiagnosticsInstallationCallback callback) {
  if (!is_approved) {
    // Clean the cached app so it will be reloaded next time calling
    // `Show3pDiagnosticsApp`.
    shimless_app_browser_context_ = nullptr;
    shimless_3p_diag_iwa_id_ = std::nullopt;
    shimless_3p_diag_app_name_ = "";
    std::move(callback).Run();
    return;
  }

  RmadClient::Get()->InstallExtractedDiagnosticsApp(base::BindOnce(
      [](CompleteLast3pDiagnosticsInstallationCallback callback,
         std::optional<rmad::InstallExtractedDiagnosticsAppReply> response) {
        LOG_IF(ERROR, !response)
            << "Failed to call rmad::InstallExtractedDiagnosticsApp";
        LOG_IF(ERROR, response->error() != rmad::RmadErrorCode::RMAD_ERROR_OK)
            << "rmad::InstallExtractedDiagnosticsApp returned "
            << response->error();
        std::move(callback).Run();
      },
      std::move(callback)));
}

void ShimlessRmaService::Show3pDiagnosticsApp(
    Show3pDiagnosticsAppCallback callback) {
  if (!shimless_app_browser_context_) {
    RmadClient::Get()->GetInstalledDiagnosticsApp(
        base::BindOnce(&ShimlessRmaService::GetInstalledDiagnosticsApp,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
    return;
  }

  ExternalAppDialog::InitParams params;
  params.context = shimless_app_browser_context_;
  params.app_name = shimless_3p_diag_app_name_;
  params.content_url = GURL("isolated-app://" + shimless_3p_diag_iwa_id_->id());
  params.shimless_rma_delegate = shimless_rma_delegate_->GetWeakPtr();
  ExternalAppDialog::Show(params);
  std::move(callback).Run(
      ash::shimless_rma::mojom::Show3pDiagnosticsAppResult::kOk);
}

void ShimlessRmaService::GetInstalledDiagnosticsApp(
    Show3pDiagnosticsAppCallback callback,
    std::optional<rmad::GetInstalledDiagnosticsAppReply> response) {
  if (!response) {
    LOG(ERROR) << "Failed to call rmad::GetInstalledDiagnosticsApp";
    std::move(callback).Run(
        ash::shimless_rma::mojom::Show3pDiagnosticsAppResult::kFailedToLoad);
    return;
  }

  switch (response->error()) {
    case rmad::RmadErrorCode::RMAD_ERROR_DIAGNOSTICS_APP_NOT_FOUND:
      std::move(callback).Run(ash::shimless_rma::mojom::
                                  Show3pDiagnosticsAppResult::kAppNotInstalled);
      return;
    case rmad::RmadErrorCode::RMAD_ERROR_OK:
      shimless_rma_delegate_->PrepareDiagnosticsAppBrowserContext(
          base::FilePath{response->diagnostics_app_crx_path()},
          base::FilePath{response->diagnostics_app_swbn_path()},
          base::BindOnce(&ShimlessRmaService::On3pDiagnosticsAppLoadForShow,
                         weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
      return;
    default:
      LOG(ERROR) << "rmad::GetInstalledDiagnosticsApp returned "
                 << response->error();
      std::move(callback).Run(
          ash::shimless_rma::mojom::Show3pDiagnosticsAppResult::kFailedToLoad);
      return;
  }
}

void ShimlessRmaService::On3pDiagnosticsAppLoadForShow(
    Show3pDiagnosticsAppCallback callback,
    base::expected<
        ShimlessRmaDelegate::PrepareDiagnosticsAppBrowserContextResult,
        std::string> result) {
  if (!result.has_value()) {
    LOG(ERROR) << "Failed to load 3p diag app: " << result.error();
    std::move(callback).Run(
        ash::shimless_rma::mojom::Show3pDiagnosticsAppResult::kFailedToLoad);
    return;
  }

  shimless_app_browser_context_ = result.value().context;
  shimless_3p_diag_iwa_id_ = result.value().iwa_id;
  shimless_3p_diag_app_name_ = result.value().name;
  Show3pDiagnosticsApp(std::move(callback));
}

}  // namespace shimless_rma
}  // namespace ash