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

#include "chrome/updater/update_service_impl_impl.h"

#include <algorithm>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/barrier_callback.h"
#include "base/barrier_closure.h"
#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/containers/queue.h"
#include "base/containers/to_vector.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_string_value_serializer.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/values.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/enterprise_companion/global_constants.h"
#include "chrome/updater/app/app_uninstall.h"
#include "chrome/updater/app/app_utils.h"
#include "chrome/updater/auto_run_on_os_upgrade_task.h"
#include "chrome/updater/branded_constants.h"
#include "chrome/updater/change_owners_task.h"
#include "chrome/updater/check_for_updates_task.h"
#include "chrome/updater/cleanup_task.h"
#include "chrome/updater/configurator.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/event_history.h"
#include "chrome/updater/get_updater_scope.h"
#include "chrome/updater/handle_inconsistent_apps_task.h"
#include "chrome/updater/installer.h"
#include "chrome/updater/persisted_data.h"
#include "chrome/updater/policy/service.h"
#include "chrome/updater/prefs.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/remove_uninstalled_apps_task.h"
#include "chrome/updater/update_block_check.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/usage_stats_permissions.h"
#include "chrome/updater/util/util.h"
#include "components/policy/core/common/policy_types.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h"
#include "components/update_client/utils.h"
#include "third_party/abseil-cpp/absl/strings/str_format.h"

#if BUILDFLAG(IS_MAC)
#include <sys/mount.h>
#endif  // BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_WIN)
#include <winhttp.h>

#include "base/win/registry.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/ui/l10n_util.h"
#include "chrome/updater/win/ui/resources/resources.grh"
#include "chrome/updater/win/ui/resources/updater_installer_strings.h"
#include "chrome/updater/win/win_constants.h"
#endif  // BUILDFLAG(IS_WIN)

namespace updater {

// The functions below are various adaptors between |update_client| and
// |UpdateService| types.

namespace internal {
UpdateService::Result ToResult(update_client::Error error) {
  switch (error) {
    case update_client::Error::NONE:
      return UpdateService::Result::kSuccess;
    case update_client::Error::UPDATE_IN_PROGRESS:
      return UpdateService::Result::kUpdateInProgress;
    case update_client::Error::UPDATE_CANCELED:
      return UpdateService::Result::kUpdateCanceled;
    case update_client::Error::RETRY_LATER:
      return UpdateService::Result::kRetryLater;
    case update_client::Error::SERVICE_ERROR:
      return UpdateService::Result::kServiceFailed;
    case update_client::Error::UPDATE_CHECK_ERROR:
      return UpdateService::Result::kUpdateCheckFailed;
    case update_client::Error::CRX_NOT_FOUND:
      return UpdateService::Result::kAppNotFound;
    case update_client::Error::INVALID_ARGUMENT:
    case update_client::Error::BAD_CRX_DATA_CALLBACK:
      return UpdateService::Result::kInvalidArgument;
  }
}

void GetComponents(
    scoped_refptr<PolicyService> policy_service,
    crx_file::VerifierFormat verifier_format,
    std::optional<std::vector<uint8_t>> crx_public_key_hash,
    scoped_refptr<PersistedData> persisted_data,
    const base::flat_map<std::string, std::string>& app_client_install_data,
    const base::flat_map<std::string, std::string>& app_install_data_index,
    const std::string& install_source,
    UpdateService::Priority priority,
    bool update_blocked,
    UpdateService::PolicySameVersionUpdate policy_same_version_update,
    const std::vector<std::string>& ids,
    base::OnceCallback<
        void(const std::vector<std::optional<update_client::CrxComponent>>&)>
        callback) {
  VLOG(1) << __func__
          << ". Same version update: " << policy_same_version_update;
  const bool is_foreground = priority == UpdateService::Priority::kForeground;
  auto barrier_callback =
      base::BarrierCallback<std::optional<update_client::CrxComponent>>(
          ids.size(),
          base::BindOnce(
              [](const std::vector<std::string>& ids,
                 const std::vector<std::optional<update_client::CrxComponent>>&
                     unordered) {
                // Re-order the vector to match the order of `ids`.
                return base::ToVector(ids, [&](const auto& id) {
                  auto it = std::ranges::find_if(
                      unordered,
                      [&id](std::optional<update_client::CrxComponent> v) {
                        return v && v->app_id == id;
                      });
                  return it != unordered.end() ? *it : std::nullopt;
                });
              },
              ids)
              .Then(std::move(callback)));
  for (const auto& id : ids) {
    base::MakeRefCounted<Installer>(
        id,
        [&app_client_install_data, &id] {
          auto it = app_client_install_data.find(id);
          return it != app_client_install_data.end() ? it->second : "";
        }(),
        [&app_install_data_index, &id] {
          auto it = app_install_data_index.find(id);
          return it != app_install_data_index.end() ? it->second : "";
        }(),
        install_source,
        policy_service->GetTargetChannel(id).policy_or(std::string()),
        policy_service->GetTargetVersionPrefix(id).policy_or(std::string()),
        policy_service->IsRollbackToTargetVersionAllowed(id).policy_or(false),
        policy_service->GetMajorVersionRolloutPolicy(id)
            .effective_policy_value(),
        policy_service->GetMinorVersionRolloutPolicy(id)
            .effective_policy_value(),
        [&policy_service, &id, &is_foreground, update_blocked] {
          if (update_blocked) {
            return true;
          }
          PolicyStatus<int> app_updates =
              policy_service->GetPolicyForAppUpdates(id);
          return app_updates &&
                 (app_updates.policy() == kPolicyDisabled ||
                  (!is_foreground &&
                   app_updates.policy() == kPolicyManualUpdatesOnly) ||
                  (is_foreground &&
                   app_updates.policy() == kPolicyAutomaticUpdatesOnly));
        }(),
        policy_same_version_update, persisted_data, verifier_format,
        crx_public_key_hash)
        ->MakeCrxComponent(
            base::BindOnce([](update_client::CrxComponent component) {
              return component;
            }).Then(barrier_callback));
  }
}

#if BUILDFLAG(IS_WIN)
namespace {

std::wstring GetTextForUpdateClientInstallError(int error_code,
                                                const std::wstring& language) {
#define INSTALL_SWITCH_ENTRY(error_code)                                     \
  case std::to_underlying(error_code):                                       \
    return GetLocalizedStringF(IDS_GENERIC_INSTALL_ERROR_BASE, L#error_code, \
                               language)

  switch (error_code) {
    INSTALL_SWITCH_ENTRY(update_client::InstallError::NONE);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::FINGERPRINT_WRITE_FAILED);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::BAD_MANIFEST);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::GENERIC_ERROR);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::MOVE_FILES_ERROR);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::SET_PERMISSIONS_FAILED);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::INVALID_VERSION);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::VERSION_NOT_UPGRADED);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::NO_DIR_COMPONENT_USER);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::CLEAN_INSTALL_DIR_FAILED);
    INSTALL_SWITCH_ENTRY(
        update_client::InstallError::INSTALL_VERIFICATION_FAILED);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::MISSING_INSTALL_PARAMS);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::LAUNCH_PROCESS_FAILED);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::INSTALL_PATH_ERROR);
    INSTALL_SWITCH_ENTRY(update_client::InstallError::CUSTOM_ERROR_BASE);
    default:
      return GetLocalizedStringF(IDS_GENERIC_INSTALL_ERROR_BASE,
                                 GetTextForSystemError(error_code), language);
  }
#undef INSTALL_SWITCH_ENTRY
}

std::wstring GetTextForDownloadError(int error, const std::wstring& language) {
#define DOWNLOAD_SWITCH_ENTRY(error_code)                                     \
  case std::to_underlying(error_code):                                        \
    return GetLocalizedStringF(IDS_GENERIC_DOWNLOAD_ERROR_BASE, L#error_code, \
                               language)

  switch (error) {
    DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::NO_URL);
    DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::NO_HASH);
    DOWNLOAD_SWITCH_ENTRY(
        update_client::CrxDownloaderError::BITS_TOO_MANY_JOBS);
    DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::GENERIC_ERROR);

    case std::to_underlying(update_client::CrxDownloaderError::BAD_HASH):
      return GetLocalizedString(IDS_DOWNLOAD_HASH_MISMATCH_BASE);
    case std::to_underlying(update_client::CrxDownloaderError::DISK_FULL):
      return GetLocalizedString(IDS_UPDATER_DISK_FULL_BASE);

    default:
      return GetLocalizedStringF(IDS_GENERIC_DOWNLOAD_ERROR_BASE,
                                 GetTextForSystemError(error), language);
  }
#undef DOWNLOAD_SWITCH_ENTRY
}

std::wstring GetTextForUnpackError(int error, const std::wstring& language) {
#define UNPACK_SWITCH_ENTRY(error_code)                                     \
  case std::to_underlying(error_code):                                      \
    return GetLocalizedStringF(IDS_GENERIC_UNPACK_ERROR_BASE, L#error_code, \
                               language)
#define UNPACK_CACHING_SWITCH_ENTRY(error_code)                             \
  case std::to_underlying(error_code):                                      \
    return GetLocalizedStringF(IDS_UNPACK_CACHING_ERROR_BASE, L#error_code, \
                               language)

  switch (error) {
    UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kInvalidParams);
    UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kInvalidFile);
    UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kUnzipPathError);
    UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kUnzipFailed);
    UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kBadManifest);
    UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kBadExtension);
    UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kIoError);
    UNPACK_SWITCH_ENTRY(
        update_client::UnpackerError::kDeltaVerificationFailure);
    UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaBadCommands);
    UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaUnsupportedCommand);
    UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaOperationFailure);
    UNPACK_SWITCH_ENTRY(
        update_client::UnpackerError::kDeltaPatchProcessFailure);
    UNPACK_SWITCH_ENTRY(
        update_client::UnpackerError::kDeltaMissingExistingFile);
    UNPACK_SWITCH_ENTRY(
        update_client::UnpackerError::kPuffinMissingPreviousCrx);
    UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kCrxCacheNotProvided);

    UNPACK_CACHING_SWITCH_ENTRY(
        update_client::UnpackerError::kFailedToAddToCache);
    UNPACK_CACHING_SWITCH_ENTRY(
        update_client::UnpackerError::kFailedToCreateCacheDir);

    default:
      return GetLocalizedStringF(IDS_GENERIC_UNPACK_ERROR_BASE,
                                 GetTextForSystemError(error), language);
  }
#undef UNPACK_SWITCH_ENTRY
#undef UNPACK_CACHING_SWITCH_ENTRY
}

std::wstring GetTextForServiceError(int error, const std::wstring& language) {
#define SERVICE_SWITCH_ENTRY(error_code)                                     \
  case static_cast<int>(error_code):                                         \
    return GetLocalizedStringF(IDS_GENERIC_SERVICE_ERROR_BASE, L#error_code, \
                               language)

  switch (error) {
    SERVICE_SWITCH_ENTRY(update_client::ServiceError::SERVICE_WAIT_FAILED);
    SERVICE_SWITCH_ENTRY(update_client::ServiceError::UPDATE_DISABLED);
    SERVICE_SWITCH_ENTRY(update_client::ServiceError::CHECK_FOR_UPDATE_ONLY);

    case std::to_underlying(update_client::ServiceError::CANCELLED):
      return GetLocalizedString(IDS_SERVICE_ERROR_CANCELLED_BASE, language);

    default:
      return GetLocalizedStringF(IDS_GENERIC_SERVICE_ERROR_BASE,
                                 GetTextForSystemError(error), language);
  }
#undef SERVICE_SWITCH_ENTRY
}

std::wstring GetTextForUpdateCheckError(int error,
                                        const std::wstring& language) {
#define UPDATE_CHECK_SWITCH_ENTRY(error_code)                       \
  case static_cast<int>(error_code):                                \
    return GetLocalizedStringF(IDS_GENERIC_UPDATE_CHECK_ERROR_BASE, \
                               L#error_code, language)

  switch (error) {
    UPDATE_CHECK_SWITCH_ENTRY(
        update_client::ProtocolError::RESPONSE_NOT_TRUSTED);
    UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::MISSING_URLS);
    UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::PARSE_FAILED);
    UPDATE_CHECK_SWITCH_ENTRY(
        update_client::ProtocolError::UPDATE_RESPONSE_NOT_FOUND);
    UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::URL_FETCHER_FAILED);
    UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::INVALID_APPID);

    case std::to_underlying(update_client::ProtocolError::UNKNOWN_APPLICATION):
      return GetLocalizedString(IDS_UNKNOWN_APPLICATION_BASE, language);

    case std::to_underlying(
        update_client::ProtocolError::RESTRICTED_APPLICATION):
      return GetLocalizedString(IDS_RESTRICTED_RESPONSE_FROM_SERVER_BASE,
                                language);

    case std::to_underlying(update_client::ProtocolError::OS_NOT_SUPPORTED):
      return GetLocalizedString(IDS_OS_NOT_SUPPORTED_BASE, language);

    case std::to_underlying(update_client::ProtocolError::HW_NOT_SUPPORTED):
      return GetLocalizedString(IDS_HW_NOT_SUPPORTED_BASE, language);

    case std::to_underlying(update_client::ProtocolError::NO_HASH):
      return GetLocalizedString(IDS_NO_HASH_BASE, language);

    case std::to_underlying(update_client::ProtocolError::UNSUPPORTED_PROTOCOL):
      return GetLocalizedString(IDS_UNSUPPORTED_PROTOCOL_BASE, language);

    case std::to_underlying(update_client::ProtocolError::INTERNAL):
      return GetLocalizedString(IDS_INTERNAL_BASE, language);

    // Http Status Code `401` Unauthorized.
    case 401:
      return GetLocalizedString(IDS_ERROR_HTTPSTATUS_UNAUTHORIZED_BASE,
                                language);

    // Http Status Code `403` Forbidden.
    case 403:
      return GetLocalizedString(IDS_ERROR_HTTPSTATUS_FORBIDDEN_BASE, language);

    // Http Status Code `407` Proxy Authentication Required.
    case 407:
      return GetLocalizedString(IDS_ERROR_HTTPSTATUS_PROXY_AUTH_REQUIRED_BASE,
                                language);

    case HRESULT_FROM_WIN32(ERROR_WINHTTP_NAME_NOT_RESOLVED):
      return GetLocalizedStringF(IDS_NO_NETWORK_PRESENT_ERROR_BASE,
                                 GetExecutableRelativePath().value(), language);
    default:
      return GetLocalizedStringF(
          IDS_GENERIC_UPDATE_CHECK_ERROR_BASE,
          error >= 400 && error < 600
              ? base::UTF8ToWide(absl::StrFormat("HTTP %d", error))
              : GetTextForSystemError(error),
          language);
  }
#undef UPDATE_CHECK_SWITCH_ENTRY
}

std::wstring GetTextForInstallerError(int error_code,
                                      const std::wstring& language) {
#define POLICY_ERROR_SWITCH_ENTRY(error_code)                                 \
  case error_code:                                                            \
    return GetLocalizedStringF(IDS_APP_INSTALL_DISABLED_BY_GROUP_POLICY_BASE, \
                               L#error_code, language)

  switch (error_code) {
    POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_INSTALL_DISABLED_BY_POLICY);
    POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY);
    POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL);

    case GOOPDATEINSTALL_E_FILENAME_INVALID:
      return GetLocalizedString(IDS_INVALID_INSTALLER_FILENAME_BASE, language);

    case GOOPDATEINSTALL_E_INSTALLER_FAILED_START:
      return GetLocalizedString(IDS_INSTALLER_FAILED_TO_START_BASE, language);

    case GOOPDATEINSTALL_E_INSTALLER_TIMED_OUT:
      return GetLocalizedString(IDS_INSTALLER_TIMED_OUT_BASE, language);

    case GOOPDATEINSTALL_E_INSTALL_ALREADY_RUNNING:
      return GetLocalizedStringF(
          IDS_GENERIC_INSTALLER_ERROR_BASE,
          GetTextForSystemError(ERROR_INSTALL_ALREADY_RUNNING), language);

    case ERROR_SUCCESS_REBOOT_INITIATED:
    case ERROR_SUCCESS_REBOOT_REQUIRED:
    case ERROR_SUCCESS_RESTART_REQUIRED:
      return GetLocalizedStringF(IDS_INSTALL_REBOOT_BASE,
                                 GetTextForSystemError(error_code), language);

    default:
      return GetLocalizedStringF(IDS_GENERIC_INSTALLER_ERROR_BASE,
                                 GetTextForSystemError(error_code), language);
  }
#undef POLICY_ERROR_SWITCH_ENTRY
}

}  // namespace

std::string GetInstallerText(UpdateService::ErrorCategory error_category,
                             int error_code,
                             int extra_code,
                             const std::string& language) {
  if (!error_code) {
    return {};
  }

  const std::wstring language_w = base::UTF8ToWide(language);
  return base::WideToUTF8(base::StrCat(
      {[&] {
         switch (error_category) {
           case UpdateService::ErrorCategory::kInstall:
             return GetTextForUpdateClientInstallError(error_code, language_w);
           case UpdateService::ErrorCategory::kDownload:
             return GetTextForDownloadError(error_code, language_w);
           case UpdateService::ErrorCategory::kUnpack:
             return GetTextForUnpackError(error_code, language_w);
           case UpdateService::ErrorCategory::kService:
             return GetTextForServiceError(error_code, language_w);
           case UpdateService::ErrorCategory::kUpdateCheck:
             return GetTextForUpdateCheckError(error_code, language_w);
           case UpdateService::ErrorCategory::kInstaller:
             return GetTextForInstallerError(error_code, language_w);
           default:
             LOG(ERROR) << "Unknown error category: " << error_category;
             return std::wstring();
         }
       }(),
       [&] {
         if (!extra_code) {
           return std::wstring();
         }
         return base::StrCat(
             {L"\n", GetLocalizedStringF(
                         IDS_EXTRA_CODE_BASE,
                         base::UTF8ToWide(absl::StrFormat("%#x", extra_code)),
                         language_w)});
       }()}));
}
#endif  // BUILDFLAG(IS_WIN)

base::Version GetRegisteredInstallerVersion(const std::string& app_id) {
#if BUILDFLAG(IS_WIN)
  std::wstring pv;
  return base::win::RegKey(UpdaterScopeToHKeyRoot(GetUpdaterScope()),
                           GetAppClientsKey(app_id).c_str(), Wow6432(KEY_READ))
                     .ReadValue(kRegValuePV, &pv) == ERROR_SUCCESS
             ? base::Version(base::WideToUTF8(pv))
             : base::Version();
#else   // BUILDFLAG(IS_WIN)
  return {};
#endif  // BUILDFLAG(IS_WIN)
}

}  // namespace internal

namespace {

constexpr base::flat_map<std::string, std::string> kEmptyFlatMap;

update_client::Callback MakeUpdateClientCallback(
    base::OnceCallback<void(UpdateService::Result)> callback) {
  return base::BindOnce(
      [](base::OnceCallback<void(UpdateService::Result)> callback,
         update_client::Error error) {
        std::move(callback).Run(internal::ToResult(error));
      },
      std::move(callback));
}

UpdateService::UpdateState::State ToUpdateState(
    update_client::ComponentState component_state) {
  switch (component_state) {
    case update_client::ComponentState::kNew:
      return UpdateService::UpdateState::State::kNotStarted;

    case update_client::ComponentState::kChecking:
      return UpdateService::UpdateState::State::kCheckingForUpdates;

    case update_client::ComponentState::kDownloading:
      return UpdateService::UpdateState::State::kDownloading;

    case update_client::ComponentState::kCanUpdate:
      return UpdateService::UpdateState::State::kUpdateAvailable;

    case update_client::ComponentState::kDecompressing:
      return UpdateService::UpdateState::State::kDecompressing;

    case update_client::ComponentState::kPatching:
      return UpdateService::UpdateState::State::kPatching;

    case update_client::ComponentState::kUpdating:
      return UpdateService::UpdateState::State::kInstalling;

    case update_client::ComponentState::kUpdated:
      return UpdateService::UpdateState::State::kUpdated;

    case update_client::ComponentState::kUpToDate:
      return UpdateService::UpdateState::State::kNoUpdate;

    case update_client::ComponentState::kUpdateError:
      return UpdateService::UpdateState::State::kUpdateError;

    case update_client::ComponentState::kRun:
      NOTREACHED();
  }
}

UpdateService::ErrorCategory ToErrorCategory(
    update_client::ErrorCategory error_category) {
  switch (error_category) {
    case update_client::ErrorCategory::kNone:
      return UpdateService::ErrorCategory::kNone;
    case update_client::ErrorCategory::kDownload:
      return UpdateService::ErrorCategory::kDownload;
    case update_client::ErrorCategory::kUnpack:
      return UpdateService::ErrorCategory::kUnpack;
    case update_client::ErrorCategory::kInstall:
      return UpdateService::ErrorCategory::kInstall;
    case update_client::ErrorCategory::kService:
      return UpdateService::ErrorCategory::kService;
    case update_client::ErrorCategory::kUpdateCheck:
      return UpdateService::ErrorCategory::kUpdateCheck;
    case update_client::ErrorCategory::kInstaller:
      return UpdateService::ErrorCategory::kInstaller;
  }
}

update_client::UpdateClient::CrxStateChangeCallback
MakeUpdateClientCrxStateChangeCallback(
    scoped_refptr<update_client::Configurator> config,
    scoped_refptr<PersistedData> persisted_data,
    const bool new_install,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateService::UpdateState&)> callback) {
  return base::BindRepeating(
      [](scoped_refptr<update_client::Configurator> config,
         scoped_refptr<PersistedData> persisted_data, const bool new_install,
         const std::string& language,
         base::RepeatingCallback<void(const UpdateService::UpdateState&)>
             callback,
         const update_client::CrxUpdateItem& crx_update_item) {
        UpdateService::UpdateState update_state;
        update_state.app_id = crx_update_item.id;
        update_state.state = ToUpdateState(crx_update_item.state);
        update_state.next_version = crx_update_item.next_version.GetString();
        update_state.downloaded_bytes = crx_update_item.downloaded_bytes;
        update_state.total_bytes = crx_update_item.total_bytes;
        update_state.install_progress = crx_update_item.install_progress;
        update_state.error_category =
            ToErrorCategory(crx_update_item.error_category);
        update_state.error_code = crx_update_item.error_code;
        update_state.extra_code1 = crx_update_item.extra_code1;
        if (crx_update_item.installer_result) {
          update_state.installer_cmd_line =
              crx_update_item.installer_result->installer_cmd_line;
          update_state.installer_text =
              crx_update_item.installer_result->installer_text;
#if BUILDFLAG(IS_WIN)
          if (update_state.installer_text.empty())
            update_state.installer_text = internal::GetInstallerText(
                UpdateService::ErrorCategory::kInstaller,
                update_state.error_code, update_state.extra_code1, language);
#endif  // BUILDFLAG(IS_WIN)
        }

        if (update_state.state == UpdateService::UpdateState::State::kUpdated ||
            update_state.state ==
                UpdateService::UpdateState::State::kUpdateError ||
            update_state.state ==
                UpdateService::UpdateState::State::kNoUpdate) {
#if BUILDFLAG(IS_WIN)
          if (update_state.installer_text.empty())
            update_state.installer_text = internal::GetInstallerText(
                update_state.error_category, update_state.error_code,
                update_state.extra_code1, language);
#endif  // BUILDFLAG(IS_WIN)

          // If a new install encounters an error, the AppId registered in
          // `UpdateServiceImplImpl::Install` needs to be removed here.
          // Otherwise the updater may remain installed even if there are no
          // other apps to manage, and try to update the app even though the app
          // was not installed.
          if (new_install &&
              (update_state.state ==
                   UpdateService::UpdateState::State::kUpdateError ||
               update_state.state ==
                   UpdateService::UpdateState::State::kNoUpdate)) {
            persisted_data->RemoveApp(update_state.app_id);
          }

          // Commit the prefs values written by |update_client| when the
          // update has completed, such as `pv` and `fingerprint`.
          config->GetPrefService()->CommitPendingWrite();
        }

        CHECK(!update_state.app_id.empty());
        CHECK_NE(update_state.state,
                 UpdateService::UpdateState::State::kUnknown);
        callback.Run(update_state);
      },
      config, persisted_data, new_install, language, callback);
}

bool IsPathOnReadOnlyMount(const base::FilePath& path) {
  if (path.empty()) {
    return false;
  }
#if BUILDFLAG(IS_MAC)
  struct statfs fsinfo = {};
  if (statfs(path.value().c_str(), &fsinfo) == -1) {
    LOG(ERROR) << "Failed to stat: " << path;
    return false;
  }
  return fsinfo.f_flags & MNT_RDONLY;
#else
  return false;
#endif  // BUILDFLAG(IS_MAC)
}

void FetchPoliciesDone(
    base::OnceCallback<void(base::OnceCallback<void(UpdateService::Result)>)>
        fetch_policies_done,
    base::OnceCallback<void(UpdateService::Result)> callback,
    int result) {
  if (result != kErrorOk) {
    LOG(ERROR) << "FetchPolicies failed: " << result;
    // Ignore policy fetch failures and fall through.
  }

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

}  // namespace

UpdateServiceImplImpl::UpdateServiceImplImpl(scoped_refptr<Configurator> config)
    : config_(config),
      main_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
      update_client_(update_client::UpdateClientFactory(config)) {}

void UpdateServiceImplImpl::GetVersion(
    base::OnceCallback<void(const base::Version&)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  main_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), base::Version(kUpdaterVersion)));
}

void UpdateServiceImplImpl::MaybeInstallEnterpriseCompanionAppOTA(
    base::OnceClosure callback,
    bool is_cloud_managed) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!is_cloud_managed) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(callback));
    return;
  }

  VLOG(1) << "Starting an OTA installation of the enterprise companion app.";
  RegistrationRequest registration;
  registration.app_id = enterprise_companion::kCompanionAppId;
  registration.version = kNullVersion;
  RegisterApp(
      registration,
      base::OnceCallback<void(int)>(base::DoNothing())
          .Then(base::BindPostTask(
              main_task_runner_,
              base::BindOnce(
                  base::IgnoreResult(&update_client::UpdateClient::Install),
                  update_client_, enterprise_companion::kCompanionAppId,
                  base::BindOnce(&internal::GetComponents,
                                 config_->GetPolicyService(),
                                 config_->GetCrxVerifierFormat(),
                                 config_->GetCrxPublicKeyHash(),
                                 config_->GetUpdaterPersistedData(),
                                 kEmptyFlatMap, kEmptyFlatMap,
                                 kInstallSourcePolicy, Priority::kForeground,
                                 /*update_blocked=*/false,
                                 PolicySameVersionUpdate::kNotAllowed),
                  MakeUpdateClientCrxStateChangeCallback(
                      config_, config_->GetUpdaterPersistedData(),
                      /*new_install=*/false,
                      /*language=*/{},
                      /*callback=*/base::DoNothing()),
                  MakeUpdateClientCallback(base::BindOnce([](Result result) {
                                             VLOG(1)
                                                 << "OTA installation of the "
                                                    "enterprise companion app "
                                                    "completed with result: "
                                                 << result;
                                           }).Then(std::move(callback)))))));
}

void UpdateServiceImplImpl::FetchPolicies(
    policy::PolicyFetchReason reason,
    base::OnceCallback<void(int)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (GetUpdaterScope() == UpdaterScope::kUser) {
    VLOG(2) << "Policy fetch skipped for user updater.";
    std::move(callback).Run(0);
    return;
  }

  if (!config_->GetUpdaterPersistedData()
           ->GetProductVersion(enterprise_companion::kCompanionAppId)
           .IsValid()) {
    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE, {base::MayBlock(), base::WithBaseSyncPrimitives()},
        base::BindOnce(&IsCloudManaged),
        base::BindOnce(
            &UpdateServiceImplImpl::MaybeInstallEnterpriseCompanionAppOTA,
            base::WrapRefCounted(this),
            base::BindOnce(&PolicyService::FetchPolicies,
                           config_->GetPolicyService(), reason,
                           std::move(callback))));
  } else {
    config_->GetPolicyService()->FetchPolicies(reason, std::move(callback));
  }
}

void UpdateServiceImplImpl::RegisterApp(
    const RegistrationRequest& request,
    base::OnceCallback<void(int)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (IsPathOnReadOnlyMount(request.existence_checker_path)) {
    VLOG(1) << "Existence check path " << request.existence_checker_path
            << " is on read-only file system. Registration of "
            << request.app_id << " is skipped.";
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), kRegistrationError));
    return;
  }

  if (!IsValidAppId(request.app_id)) {
    VLOG(1) << "Refusing to register an invalid app ID: " << request.app_id;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), kRegistrationError));
    return;
  }

  if (!IsUpdaterOrCompanionApp(request.app_id)) {
    config_->GetUpdaterPersistedData()->SetHadApps();
  }
  bool send_event =
      !config_->GetUpdaterPersistedData()
           ->GetProductVersion(request.app_id)
           .IsValid() &&
      base::Version(request.version).IsValid() &&
      base::Version(request.version) > base::Version(kNullVersion) &&
      !config_->GetUpdaterPersistedData()->GetEulaRequired() &&
      !base::EqualsCaseInsensitiveASCII(request.app_id,
                                        enterprise_companion::kCompanionAppId);
  config_->GetUpdaterPersistedData()->RegisterApp(request);
  if (send_event) {
    update_client::CrxComponent install_data;
    install_data.ap = request.ap;
    install_data.app_id = request.app_id;
    install_data.brand = request.brand_code;
    install_data.lang = request.lang.value_or("");
    install_data.requires_network_encryption = false;
    install_data.version = base::Version(request.version);
    update_client_->SendPing(
        install_data,
        {.event_type = update_client::protocol_request::kEventInstall,
         .result = update_client::protocol_request::kEventResultSuccess},
        base::BindOnce([](update_client::Error error) {
          // Ignore event ping errors; registration has been successful.
        }).Then(base::BindOnce(std::move(callback), kRegistrationSuccess)));
    return;
  }
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), kRegistrationSuccess));
}

void UpdateServiceImplImpl::GetAppStates(
    base::OnceCallback<void(const std::vector<AppState>&)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
      ->Run(base::BindOnce(&UpdateServiceImplImpl::GetAppStatesImpl, this,
                           std::move(callback)));
}

void UpdateServiceImplImpl::GetAppStatesImpl(
    base::OnceCallback<void(const std::vector<AppState>&)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  scoped_refptr<PersistedData> persisted_data =
      config_->GetUpdaterPersistedData();
  std::vector<AppState> apps = base::ToVector(
      persisted_data->GetAppIds(), [&](const std::string& app_id) {
        AppState app_state;
        app_state.app_id = app_id;
        app_state.version =
            persisted_data->GetProductVersion(app_id).GetString();
        app_state.version_path = persisted_data->GetProductVersionPath(app_id);
        app_state.version_key = persisted_data->GetProductVersionKey(app_id);
        app_state.ap = persisted_data->GetAP(app_id);
        app_state.ap_path = persisted_data->GetAPPath(app_id);
        app_state.ap_key = persisted_data->GetAPKey(app_id);
        app_state.brand_code = persisted_data->GetBrandCode(app_id);
        app_state.brand_path = persisted_data->GetBrandPath(app_id);
        app_state.ecp = persisted_data->GetExistenceCheckerPath(app_id);
        app_state.cohort = persisted_data->GetCohort(app_id);
        return app_state;
      });
  main_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), std::move(apps)));
}

void UpdateServiceImplImpl::RunPeriodicTasks(base::OnceClosure callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  config_->GetUpdaterPersistedData()->SetLastStarted(
      base::Time::NowFromSystemTime());
  VLOG(1) << "last_started updated.";

  // The installer should make an updater registration, but in case it halts
  // before it does, synthesize a registration if necessary here.
  const base::Version registered_updater_version =
      config_->GetUpdaterPersistedData()->GetProductVersion(kUpdaterAppId);
  if (!registered_updater_version.IsValid() ||
      base::Version(kUpdaterVersion) > registered_updater_version) {
    RegistrationRequest updater_request;
    updater_request.app_id = kUpdaterAppId;
    updater_request.version = kUpdaterVersion;
    RegisterApp(updater_request, base::DoNothing());
  }

  std::vector<base::OnceCallback<void(base::OnceClosure)>> new_tasks;
  new_tasks.push_back(
      base::BindOnce(&HandleInconsistentAppsTask::Run,
                     base::MakeRefCounted<HandleInconsistentAppsTask>(
                         config_, GetUpdaterScope())));
  new_tasks.push_back(
      base::BindOnce(&RemoveUninstalledAppsTask::Run,
                     base::MakeRefCounted<RemoveUninstalledAppsTask>(
                         config_, GetUpdaterScope())));
  new_tasks.push_back(MakeChangeOwnersTask(config_->GetUpdaterPersistedData(),
                                           GetUpdaterScope()));

  new_tasks.push_back(base::BindOnce(
      [](scoped_refptr<UpdateServiceImplImpl> update_service_impl,
         base::OnceClosure callback) {
        update_service_impl->FetchPolicies(
            policy::PolicyFetchReason::kScheduled,
            base::BindOnce(
                [](base::OnceClosure callback, int /* ignore_result */) {
                  std::move(callback).Run();
                },
                std::move(callback)));
      },
      base::WrapRefCounted(this)));
  new_tasks.push_back(
      base::BindOnce(&CheckForUpdatesTask::Run,
                     base::MakeRefCounted<CheckForUpdatesTask>(
                         config_, GetUpdaterScope(),
                         /*task_name=*/"UpdateAll",
                         base::BindOnce(&UpdateServiceImplImpl::UpdateAll, this,
                                        base::DoNothing()))));
  new_tasks.push_back(base::BindOnce(
      [](scoped_refptr<UpdateServiceImplImpl> self,
         base::OnceClosure callback) {
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE,
            base::BindOnce(
                &UpdateServiceImplImpl::ForceInstall, self, base::DoNothing(),
                base::BindOnce(
                    [](base::OnceClosure closure,
                       UpdateService::Result result) {
                      VLOG(0) << "ForceInstall task complete: " << result;
                      std::move(closure).Run();
                    },
                    std::move(callback))));
      },
      base::WrapRefCounted(this)));
  new_tasks.push_back(base::BindOnce(
      &AutoRunOnOsUpgradeTask::Run,
      base::MakeRefCounted<AutoRunOnOsUpgradeTask>(
          GetUpdaterScope(), config_->GetUpdaterPersistedData())));
  new_tasks.push_back(base::BindOnce(
      &CleanupTask::Run,
      base::MakeRefCounted<CleanupTask>(GetUpdaterScope(), config_)));

  const auto barrier_closure =
      base::BarrierClosure(new_tasks.size(), std::move(callback));
  for (auto& task : new_tasks) {
    tasks_.push(base::BindOnce(std::move(task),
                               barrier_closure.Then(base::BindRepeating(
                                   &UpdateServiceImplImpl::TaskDone, this))));
  }

  if (tasks_.size() == new_tasks.size()) {
    TaskStart();
  }
}

void UpdateServiceImplImpl::TaskStart() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!tasks_.empty()) {
    main_task_runner_->PostDelayedTask(FROM_HERE, std::move(tasks_.front()),
                                       config_->InitialDelay());
  }
}

void UpdateServiceImplImpl::TaskDone() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  tasks_.pop();
  TaskStart();
}

void UpdateServiceImplImpl::ForceInstall(
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  PolicyStatus<std::vector<std::string>> force_install_apps_status =
      config_->GetPolicyService()->GetForceInstallApps();
  if (!force_install_apps_status) {
    base::BindPostTask(main_task_runner_, std::move(callback))
        .Run(UpdateService::Result::kSuccess);
    return;
  }
  std::vector<std::string> force_install_apps =
      force_install_apps_status.policy();
  CHECK(!force_install_apps.empty());

  std::vector<std::string> installed_app_ids =
      config_->GetUpdaterPersistedData()->GetAppIds();
  std::ranges::sort(force_install_apps);
  std::ranges::sort(installed_app_ids);

  std::vector<std::string> app_ids_to_install;
  std::ranges::set_difference(force_install_apps, installed_app_ids,
                              std::back_inserter(app_ids_to_install));
  if (app_ids_to_install.empty()) {
    base::BindPostTask(main_task_runner_, std::move(callback))
        .Run(UpdateService::Result::kSuccess);
    return;
  }

  VLOG(1) << __func__ << ": app_ids_to_install: "
          << base::JoinString(app_ids_to_install, " ");

  ShouldBlockUpdateForMeteredNetwork(
      Priority::kBackground,
      base::BindOnce(
          &UpdateServiceImplImpl::OnShouldBlockForceInstallForMeteredNetwork,
          this, app_ids_to_install, kEmptyFlatMap, kEmptyFlatMap,
          UpdateService::PolicySameVersionUpdate::kNotAllowed, state_update,
          std::move(callback)));
}

void UpdateServiceImplImpl::CheckForUpdate(
    const std::string& app_id,
    Priority priority,
    PolicySameVersionUpdate policy_same_version_update,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback) {
  VLOG(1) << __func__ << ": " << app_id;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!IsValidAppId(app_id)) {
    VLOG(1) << "Refusing to check update for an invalid app ID: " << app_id;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), Result::kInvalidArgument));
    return;
  }

  base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
      ->Run(base::BindOnce(
          &UpdateServiceImplImpl::FetchPolicies, this,
          policy::PolicyFetchReason::kUserRequest,
          base::BindOnce(
              &FetchPoliciesDone,
              base::BindOnce(&UpdateServiceImplImpl::CheckForUpdateImpl, this,
                             app_id, priority, policy_same_version_update,
                             language, state_update),
              std::move(callback))));
}

void UpdateServiceImplImpl::CheckForUpdateImpl(
    const std::string& app_id,
    Priority priority,
    PolicySameVersionUpdate policy_same_version_update,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback) {
  VLOG(1) << __func__ << ": " << app_id;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!config_->GetUpdaterPersistedData()
           ->GetProductVersion(app_id)
           .IsValid()) {
    VLOG(1) << __func__ << ": App not registered: " << app_id;
    std::move(callback).Run(Result::kInvalidArgument);
    return;
  }

  int policy = kPolicyEnabled;
  if (IsUpdateDisabledByPolicy(app_id, priority, false, policy)) {
    HandleUpdateDisabledByPolicy(app_id, policy, false, language, state_update,
                                 std::move(callback));
    return;
  }
  ShouldBlockUpdateForMeteredNetwork(
      priority,
      base::BindOnce(
          &UpdateServiceImplImpl::OnShouldBlockCheckForUpdateForMeteredNetwork,
          this, app_id, priority, policy_same_version_update, language,
          state_update, std::move(callback)));
}

void UpdateServiceImplImpl::Update(
    const std::string& app_id,
    const std::string& install_data_index,
    Priority priority,
    PolicySameVersionUpdate policy_same_version_update,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!IsValidAppId(app_id)) {
    VLOG(1) << "Refusing to update an invalid app ID: " << app_id;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), Result::kInvalidArgument));
    return;
  }

  std::unique_ptr<UpdateEndEvent> event =
      std::make_unique<UpdateEndEvent>(UpdateStartEvent()
                                           .SetAppId(app_id)
                                           .SetPriority(priority)
                                           .WriteAsyncAndReturnEndEvent());
  state_update =
      base::BindRepeating(
          [](UpdateEndEvent* event, const UpdateState& update_state) {
            if (update_state.error_category !=
                UpdateService::ErrorCategory::kNone) {
              event->AddError(
                  {.category = std::to_underlying(update_state.error_category),
                   .code = update_state.error_code,
                   .extracode1 = update_state.extra_code1});
            }
            if (!update_state.next_version.empty()) {
              event->SetNextVersion(update_state.next_version);
            }
            event->AddUpdateState(update_state.state);
            return update_state;
          },
          event.get())
          .Then(state_update);
  callback = base::BindOnce(
                 [](std::unique_ptr<UpdateEndEvent> event, Result result) {
                   event->SetResult(result).WriteAsync();
                   return result;
                 },
                 std::move(event))
                 .Then(std::move(callback));

  base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
      ->Run(base::BindOnce(
          &UpdateServiceImplImpl::FetchPolicies, this,
          policy::PolicyFetchReason::kScheduled,
          base::BindOnce(&FetchPoliciesDone,
                         base::BindOnce(&UpdateServiceImplImpl::UpdateImpl,
                                        this, app_id, install_data_index,
                                        priority, policy_same_version_update,
                                        language, state_update),
                         std::move(callback))));
}

void UpdateServiceImplImpl::UpdateImpl(
    const std::string& app_id,
    const std::string& install_data_index,
    Priority priority,
    PolicySameVersionUpdate policy_same_version_update,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!config_->GetUpdaterPersistedData()
           ->GetProductVersion(app_id)
           .IsValid()) {
    std::move(callback).Run(Result::kInvalidArgument);
    return;
  }

  int policy = kPolicyEnabled;
  if (IsUpdateDisabledByPolicy(app_id, priority, false, policy)) {
    HandleUpdateDisabledByPolicy(app_id, policy, false, language, state_update,
                                 std::move(callback));
    return;
  }
  ShouldBlockUpdateForMeteredNetwork(
      priority,
      base::BindOnce(
          &UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork, this,
          std::vector<std::string>{app_id}, kEmptyFlatMap,
          base::flat_map<std::string, std::string>(
              {std::make_pair(app_id, install_data_index)}),
          priority, policy_same_version_update, language, state_update,
          std::move(callback)));
}

void UpdateServiceImplImpl::UpdateAll(
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto app_ids = config_->GetUpdaterPersistedData()->GetAppIds();

  CHECK(std::ranges::contains(
      app_ids, base::ToLowerASCII(kUpdaterAppId),
      static_cast<std::string (*)(std::string_view)>(&base::ToLowerASCII)));

  const Priority priority = Priority::kBackground;

  auto events_by_app_id =
      std::make_unique<base::flat_map<std::string, UpdateEndEvent>>();
  for (const std::string& app_id : app_ids) {
    (*events_by_app_id)[app_id] = UpdateStartEvent()
                                      .SetAppId(app_id)
                                      .SetPriority(priority)
                                      .WriteAsyncAndReturnEndEvent();
  }
  state_update =
      base::BindRepeating(
          [](base::flat_map<std::string, UpdateEndEvent>* events_by_app_id,
             const UpdateState& update_state) {
            if (events_by_app_id->contains(update_state.app_id)) {
              UpdateEndEvent& event = events_by_app_id->at(update_state.app_id);
              if (update_state.error_category !=
                  UpdateService::ErrorCategory::kNone) {
                event.AddError({.category = std::to_underlying(
                                    update_state.error_category),
                                .code = update_state.error_code,
                                .extracode1 = update_state.extra_code1});
              }
              if (!update_state.next_version.empty()) {
                event.SetNextVersion(update_state.next_version);
              }
              event.AddUpdateState(update_state.state);
            }
            return update_state;
          },
          events_by_app_id.get())
          .Then(state_update);
  callback = base::BindOnce(
                 [](std::unique_ptr<base::flat_map<std::string, UpdateEndEvent>>
                        events_by_app_id,
                    Result result) {
                   for (auto& [_, event] : *events_by_app_id) {
                     event.SetResult(result).WriteAsync();
                   }
                   return result;
                 },
                 std::move(events_by_app_id))
                 .Then(std::move(callback));

  ShouldBlockUpdateForMeteredNetwork(
      priority,
      base::BindOnce(
          &UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork, this,
          app_ids, kEmptyFlatMap, kEmptyFlatMap, priority,
          UpdateService::PolicySameVersionUpdate::kNotAllowed,
          /*language=*/"", state_update,
          base::BindOnce(
              [](base::OnceCallback<void(Result)> callback,
                 scoped_refptr<PersistedData> persisted_data, Result result) {
                if (result == Result::kSuccess) {
                  persisted_data->SetLastChecked(
                      base::Time::NowFromSystemTime());
                  VLOG(1) << "last_checked updated.";
                }
                std::move(callback).Run(result);
              },
              std::move(callback), config_->GetUpdaterPersistedData())));
}

void UpdateServiceImplImpl::Install(
    const RegistrationRequest& registration,
    const std::string& client_install_data,
    const std::string& install_data_index,
    Priority priority,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!IsValidAppId(registration.app_id)) {
    VLOG(1) << "Refusing to install an invalid app ID: " << registration.app_id;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), Result::kInvalidArgument));
    return;
  }

  std::unique_ptr<InstallEndEvent> event =
      std::make_unique<InstallEndEvent>(InstallStartEvent()
                                            .SetAppId(registration.app_id)
                                            .WriteAsyncAndReturnEndEvent());
  state_update =
      base::BindRepeating(
          [](InstallEndEvent* event, const UpdateState& update_state) {
            if (update_state.error_category !=
                UpdateService::ErrorCategory::kNone) {
              event->AddError(
                  {.category = std::to_underlying(update_state.error_category),
                   .code = update_state.error_code,
                   .extracode1 = update_state.extra_code1});
            }
            return update_state;
          },
          event.get())
          .Then(state_update);
  callback =
      base::BindOnce(
          [](std::unique_ptr<InstallEndEvent> event,
             scoped_refptr<PersistedData> persisted_data,
             const std::string& app_id, Result result) {
            event
                ->SetVersion(
                    persisted_data->GetProductVersion(app_id).GetString())
                .WriteAsync();
            return result;
          },
          std::move(event), config_->GetUpdaterPersistedData(),
          registration.app_id)
          .Then(std::move(callback));

  base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
      ->Run(base::BindOnce(
          &UpdateServiceImplImpl::FetchPolicies, this,
          policy::PolicyFetchReason::kUserRequest,
          base::BindOnce(&FetchPoliciesDone,
                         base::BindOnce(&UpdateServiceImplImpl::InstallImpl,
                                        this, registration, client_install_data,
                                        install_data_index, priority, language,
                                        state_update),
                         std::move(callback))));
}

void UpdateServiceImplImpl::InstallImpl(
    const RegistrationRequest& registration,
    const std::string& client_install_data,
    const std::string& install_data_index,
    Priority priority,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  int policy = kPolicyEnabled;
  if (IsUpdateDisabledByPolicy(registration.app_id, priority, true, policy)) {
    HandleUpdateDisabledByPolicy(registration.app_id, policy, true, language,
                                 state_update, std::move(callback));
    return;
  }
  if (!IsUpdaterOrCompanionApp(registration.app_id)) {
    config_->GetUpdaterPersistedData()->SetHadApps();
  }

  const bool new_install = !config_->GetUpdaterPersistedData()
                                ->GetProductVersion(registration.app_id)
                                .IsValid();
  if (new_install) {
    // Pre-register the app if there is no registration for it. This app
    // registration is removed later if the app install encounters an error.
    RegistrationRequest request = registration;
    request.lang = language;
    config_->GetUpdaterPersistedData()->RegisterApp(request);
  } else {
    // Update lang/ap/iid.
    RegistrationRequest request;
    request.app_id = registration.app_id;
    request.lang = language;
    request.ap = registration.ap;
    request.install_id = registration.install_id;
    config_->GetUpdaterPersistedData()->RegisterApp(request);
  }

  std::multimap<std::string, base::RepeatingClosure>::iterator pos =
      cancellation_callbacks_.emplace(registration.app_id, base::DoNothing());
  pos->second = update_client_->Install(
      registration.app_id,
      base::BindOnce(
          &internal::GetComponents, config_->GetPolicyService(),
          config_->GetCrxVerifierFormat(), config_->GetCrxPublicKeyHash(),
          config_->GetUpdaterPersistedData(),
          base::flat_map<std::string, std::string>(
              {std::make_pair(registration.app_id, client_install_data)}),
          base::flat_map<std::string, std::string>(
              {std::make_pair(registration.app_id, install_data_index)}),
          kInstallSourceTaggedMetainstaller, priority,
          /*update_blocked=*/false, PolicySameVersionUpdate::kAllowed),
      MakeUpdateClientCrxStateChangeCallback(
          config_, config_->GetUpdaterPersistedData(), new_install, language,
          state_update),
      MakeUpdateClientCallback(std::move(callback))
          .Then(base::BindOnce(
              [](scoped_refptr<UpdateServiceImplImpl> self,
                 const std::multimap<std::string,
                                     base::RepeatingClosure>::iterator& pos) {
                self->cancellation_callbacks_.erase(pos);
              },
              base::WrapRefCounted(this), pos)));
}

void UpdateServiceImplImpl::CancelInstalls(const std::string& app_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  VLOG(1) << __func__;

  if (!IsValidAppId(app_id)) {
    VLOG(1) << "Refusing to cancel installs for an invalid app ID: " << app_id;
    return;
  }
  auto [first, last] = cancellation_callbacks_.equal_range(app_id);
  std::ranges::for_each(first, last, [](const auto& i) { i.second.Run(); });
}

void UpdateServiceImplImpl::RunInstaller(
    const std::string& app_id,
    const base::FilePath& installer_path,
    const std::string& install_args,
    const std::string& install_data,
    const std::string& install_settings,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback) {
  VLOG(1) << __func__ << ": " << app_id << ": " << installer_path << ": "
          << install_args << ": " << install_data << ": " << install_settings;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!IsValidAppId(app_id)) {
    VLOG(1) << "Refusing to run installer for an invalid app ID: " << app_id;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), Result::kInvalidArgument));
    return;
  }

  base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
      ->Run(base::BindOnce(
          &UpdateServiceImplImpl::FetchPolicies, this,
          policy::PolicyFetchReason::kUserRequest,
          base::BindOnce(
              &FetchPoliciesDone,
              base::BindOnce(&UpdateServiceImplImpl::RunInstallerImpl, this,
                             app_id, installer_path, install_args, install_data,
                             install_settings, language, state_update),
              std::move(callback))));
}

void UpdateServiceImplImpl::RunInstallerImpl(
    const std::string& app_id,
    const base::FilePath& installer_path,
    const std::string& install_args,
    const std::string& install_data,
    const std::string& install_settings,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback) {
  VLOG(1) << __func__ << ": " << app_id << ": " << installer_path << ": "
          << install_args << ": " << install_data << ": " << install_settings;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  int policy = kPolicyEnabled;
  if (IsUpdateDisabledByPolicy(app_id, Priority::kForeground, true, policy)) {
    HandleUpdateDisabledByPolicy(app_id, policy, true, language, state_update,
                                 std::move(callback));
    return;
  }

  if (!IsUpdaterOrCompanionApp(app_id)) {
    config_->GetUpdaterPersistedData()->SetHadApps();
  }

  const base::Version pv =
      config_->GetUpdaterPersistedData()->GetProductVersion(app_id);
  const bool new_install = !pv.IsValid();
  AppInfo app_info(
      GetUpdaterScope(), app_id,
      config_->GetUpdaterPersistedData()->GetAP(app_id),
      !language.empty() ? language
                        : config_->GetUpdaterPersistedData()->GetLang(app_id),
      config_->GetUpdaterPersistedData()->GetBrandCode(app_id), pv,
      config_->GetUpdaterPersistedData()->GetExistenceCheckerPath(app_id));

  std::unique_ptr<base::Value> install_settings_deserialized =
      JSONStringValueDeserializer(install_settings)
          .Deserialize(
              /*error_code=*/nullptr, /*error_message=*/nullptr);
  auto get_install_setting_string =
      [](base::Value* install_settings_deserialized_raw,
         std::string_view setting_key) -> std::string {
    if (install_settings_deserialized_raw) {
      const base::DictValue* install_settings_dict =
          install_settings_deserialized_raw->GetIfDict();
      if (install_settings_dict) {
        const std::string* install_setting_value =
            install_settings_dict->FindString(setting_key);
        if (install_setting_value) {
          return *install_setting_value;
        }
      }
    }

    return {};
  };

  const base::Version installer_version(get_install_setting_string(
      install_settings_deserialized.get(), kInstallerVersion));
  const std::string install_source(get_install_setting_string(
      install_settings_deserialized.get(), kInstallSourceSwitch));
  // Create a task runner that:
  //   1) has SequencedTaskRunner::CurrentDefaultHandle set, to run
  //      `state_update` callback.
  //   2) may block, since `RunApplicationInstaller` blocks.
  //   3) has `base::WithBaseSyncPrimitives()`, since
  //   `RunApplicationInstaller`
  //      waits on process.
  auto task_runner = base::ThreadPool::CreateSequencedTaskRunner(
      {base::MayBlock(), base::WithBaseSyncPrimitives(),
       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
  task_runner->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(
          [](const AppInfo& app_info, const base::FilePath& installer_path,
             const std::string& install_args, const std::string& install_data,
             base::RepeatingCallback<void(const UpdateState&)> state_update) {
#if BUILDFLAG(IS_WIN)
            std::optional<base::ScopedTempDir> temp_dir_owner =
                CreateSecureTempDir();
            if (!temp_dir_owner) {
              return InstallerResult(
                  {.category = update_client::ErrorCategory::kInstall,
                   .code = kErrorCreatingTempDir,
                   .extra = HRESULTFromLastError()});
            }
            base::ScopedTempDir temp_dir = std::move(*temp_dir_owner);
#else
            base::ScopedTempDir temp_dir;
            if (!temp_dir.CreateUniqueTempDir()) {
              return InstallerResult(
                  {.category = update_client::ErrorCategory::kInstall,
                   .code = kErrorCreatingTempDir,
                   .extra = logging::GetLastSystemErrorCode()});
            }
#endif

            return RunApplicationInstaller(
                app_info, installer_path, install_args,
                WriteInstallerDataToTempFile(temp_dir.GetPath(), install_data),
                /*usage_stats_enabled=*/
                IsUpdaterOrCompanionApp(app_info.app_id) &&
                    AnyAppEnablesUsageStats(GetUpdaterScope()),
                kWaitForAppInstaller,
                base::BindRepeating(
                    [](base::RepeatingCallback<void(const UpdateState&)>
                           state_update,
                       const std::string& app_id, int progress) {
                      VLOG(4) << "Install progress: " << progress;
                      UpdateState state;
                      state.app_id = app_id;
                      state.state = UpdateState::State::kInstalling;
                      state.install_progress = progress;
                      state_update.Run(state);
                    },
                    state_update, app_info.app_id));
          },
          app_info, installer_path, install_args, install_data, state_update),
      base::BindOnce(
          [](scoped_refptr<Configurator> config,
             scoped_refptr<PersistedData> persisted_data,
             scoped_refptr<update_client::UpdateClient> update_client,
             base::Version installer_version,
             base::RepeatingCallback<void(const UpdateState&)> state_update,
             const std::string& app_id, const std::string& ap,
             const std::string& brand, const std::string& language,
             const std::string& install_source, bool new_install,
             base::OnceCallback<void(Result)> callback,
             const InstallerResult& result) {
            // Final state update after installation completes.
            UpdateState state;
            state.app_id = app_id;
            state.state =
                result.result.category == update_client::ErrorCategory::kNone
                    ? UpdateState::State::kUpdated
                    : UpdateState::State::kUpdateError;

            const base::Version registered_version =
                internal::GetRegisteredInstallerVersion(app_id);
            if (registered_version.IsValid()) {
              VLOG(1) << app_id << " registered_version " << registered_version
                      << " overrides the original installer_version "
                      << installer_version;
              installer_version = registered_version;
            }

            if (result.result.category == update_client::ErrorCategory::kNone &&
                installer_version.IsValid()) {
              persisted_data->SetProductVersion(app_id, installer_version);
            } else if (new_install) {
              persisted_data->RemoveApp(app_id);
            }
            config->GetPrefService()->CommitPendingWrite();

            state.error_category = ToErrorCategory(result.result.category);
            state.error_code = result.result.code;
            state.extra_code1 = result.result.extra;
            state.installer_text = result.installer_text;
#if BUILDFLAG(IS_WIN)
            if (state.installer_text.empty())
              state.installer_text = internal::GetInstallerText(
                  state.error_category, state.error_code, state.extra_code1,
                  language);
#endif  // BUILDFLAG(IS_WIN)
            state.installer_cmd_line = result.installer_cmd_line;
            state_update.Run(state);
            VLOG(1) << app_id
                    << " installation completed: " << state.error_code;

            if (!persisted_data->GetEulaRequired()) {
              // Send an install ping. In some environments the ping cannot be
              // sent, so do not wait for it to be sent before calling back
              // the client.
              update_client::CrxComponent install_data;
              install_data.ap = ap;
              install_data.app_id = app_id;
              install_data.lang = language;
              install_data.brand = brand;
              install_data.requires_network_encryption = false;
              install_data.install_source = install_source.empty()
                                                ? kInstallSourceOffline
                                                : install_source;
              install_data.version = installer_version;
              update_client->SendPing(
                  install_data,
                  {.event_type = update_client::protocol_request::kEventInstall,
                   .result =
                       result.result.category ==
                               update_client::ErrorCategory::kNone
                           ? update_client::protocol_request::
                                 kEventResultSuccess
                           : update_client::protocol_request::kEventResultError,
                   .error_category = result.result.category,
                   .error_code = result.result.code,
                   .extra_code1 = result.result.extra},
                  base::DoNothing());
            }

            std::move(callback).Run(result.result.category ==
                                            update_client::ErrorCategory::kNone
                                        ? Result::kSuccess
                                        : Result::kInstallFailed);
          },
          config_, config_->GetUpdaterPersistedData(), update_client_,
          installer_version, state_update, app_info.app_id, app_info.ap,
          app_info.brand, language, install_source, new_install,
          std::move(callback)));
}

void UpdateServiceImplImpl::GetUpdaterState(
    base::OnceCallback<void(const UpdaterState&)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  base::ThreadPool::CreateSequencedTaskRunner(
      {base::MayBlock(), base::WithBaseSyncPrimitives(),
       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})
      ->PostTaskAndReplyWithResult(
          FROM_HERE, base::BindOnce([] {
            return base::ToVector(
                GetVersionExecutablePaths(GetUpdaterScope()),
                [](const base::FilePath& version_executable_path) {
                  return update_client::StringTypeToUTF8(
                      version_executable_path.DirName().BaseName().value());
                });
          }),
          base::BindOnce(
              [](scoped_refptr<PersistedData> persisted_data,
                 base::OnceCallback<void(const UpdaterState&)> callback,
                 const std::vector<std::string>& inactive_versions) {
                std::move(callback).Run(
                    UpdaterState(kUpdaterVersion, inactive_versions,
                                 persisted_data->GetLastChecked(),
                                 persisted_data->GetLastStarted()));
              },
              config_->GetUpdaterPersistedData(), std::move(callback)));
}

void UpdateServiceImplImpl::GetPoliciesJson(
    base::OnceCallback<void(const std::string&)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  std::string policies_json;
  base::JSONWriter::Write(config_->GetPolicyService()->GetAllPolicies(),
                          &policies_json);
  main_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), std::move(policies_json)));
}

bool UpdateServiceImplImpl::IsUpdateDisabledByPolicy(const std::string& app_id,
                                                     Priority priority,
                                                     bool is_install,
                                                     int& policy) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  policy = kPolicyEnabled;

  if (is_install) {
    PolicyStatus<int> app_install_policy_status =
        config_->GetPolicyService()->GetPolicyForAppInstalls(app_id);
    if (app_install_policy_status) {
      policy = app_install_policy_status.policy();
    }
    return app_install_policy_status &&
           (policy == kPolicyDisabled || (config_->IsPerUserInstall() &&
                                          policy == kPolicyEnabledMachineOnly));
  } else {
    PolicyStatus<int> app_update_policy_status =
        config_->GetPolicyService()->GetPolicyForAppUpdates(app_id);
    if (app_update_policy_status) {
      policy = app_update_policy_status.policy();
    }
    return app_update_policy_status &&
           (policy == kPolicyDisabled ||
            ((policy == kPolicyManualUpdatesOnly) &&
             (priority != Priority::kForeground)) ||
            ((policy == kPolicyAutomaticUpdatesOnly) &&
             (priority == Priority::kForeground)));
  }
}

void UpdateServiceImplImpl::HandleUpdateDisabledByPolicy(
    const std::string& app_id,
    int policy,
    bool is_install,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  UpdateState update_state;
  update_state.app_id = app_id;
  update_state.state = UpdateService::UpdateState::State::kUpdateError;
  update_state.error_category = UpdateService::ErrorCategory::kInstaller;
  update_state.error_code =
      is_install ? GOOPDATE_E_APP_INSTALL_DISABLED_BY_POLICY
      : policy != kPolicyAutomaticUpdatesOnly
          ? GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY
          : GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL;
  update_state.extra_code1 = 0;
#if BUILDFLAG(IS_WIN)
  update_state.installer_text = internal::GetInstallerText(
      update_state.error_category, update_state.error_code,
      update_state.extra_code1, language);
#endif  // BUILDFLAG(IS_WIN)

  base::BindPostTask(main_task_runner_, state_update).Run(update_state);
  base::BindPostTask(main_task_runner_, std::move(callback))
      .Run(UpdateService::Result::kUpdateCheckFailed);
}

void UpdateServiceImplImpl::OnShouldBlockCheckForUpdateForMeteredNetwork(
    const std::string& app_id,
    Priority priority,
    PolicySameVersionUpdate policy_same_version_update,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback,
    bool update_blocked) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  main_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &update_client::UpdateClient::CheckForUpdate, update_client_, app_id,
          base::BindOnce(
              &internal::GetComponents, config_->GetPolicyService(),
              config_->GetCrxVerifierFormat(), config_->GetCrxPublicKeyHash(),
              config_->GetUpdaterPersistedData(), kEmptyFlatMap, kEmptyFlatMap,
              priority == UpdateService::Priority::kForeground
                  ? kInstallSourceOnDemand
                  : "",
              priority, update_blocked, policy_same_version_update),
          MakeUpdateClientCrxStateChangeCallback(
              config_, config_->GetUpdaterPersistedData(),
              /*new_install=*/false, language, state_update),
          priority == Priority::kForeground,
          MakeUpdateClientCallback(std::move(callback))));
}

void UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork(
    const std::vector<std::string>& app_ids,
    const base::flat_map<std::string, std::string>& app_client_install_data,
    const base::flat_map<std::string, std::string>& app_install_data_index,
    Priority priority,
    PolicySameVersionUpdate policy_same_version_update,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback,
    bool update_blocked) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  main_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &update_client::UpdateClient::Update, update_client_, app_ids,
          base::BindOnce(&internal::GetComponents, config_->GetPolicyService(),
                         config_->GetCrxVerifierFormat(),
                         config_->GetCrxPublicKeyHash(),
                         config_->GetUpdaterPersistedData(),
                         app_client_install_data, app_install_data_index,
                         priority == UpdateService::Priority::kForeground
                             ? kInstallSourceOnDemand
                             : "",
                         priority, update_blocked, policy_same_version_update),
          MakeUpdateClientCrxStateChangeCallback(
              config_, config_->GetUpdaterPersistedData(),
              /*new_install=*/false, language, state_update),
          priority == Priority::kForeground,
          MakeUpdateClientCallback(std::move(callback))));
}

void UpdateServiceImplImpl::OnShouldBlockForceInstallForMeteredNetwork(
    const std::vector<std::string>& app_ids,
    const base::flat_map<std::string, std::string>& app_client_install_data,
    const base::flat_map<std::string, std::string>& app_install_data_index,
    PolicySameVersionUpdate policy_same_version_update,
    base::RepeatingCallback<void(const UpdateState&)> state_update,
    base::OnceCallback<void(Result)> callback,
    bool update_blocked) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // The result from Install is only used for logging. Thus, arbitrarily pick
  // the first non-success result to propagate.
  auto barrier_callback = base::BarrierCallback<Result>(
      app_ids.size(),
      base::BindOnce([](const std::vector<Result>& results) {
        auto error_it = std::ranges::find_if(
            results, [](Result result) { return result != Result::kSuccess; });
        return error_it == std::end(results) ? Result::kSuccess : *error_it;
      }).Then(std::move(callback)));

  for (const std::string& id : app_ids) {
    main_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(
            base::IgnoreResult(&update_client::UpdateClient::Install),
            update_client_, id,
            base::BindOnce(
                &internal::GetComponents, config_->GetPolicyService(),
                config_->GetCrxVerifierFormat(), config_->GetCrxPublicKeyHash(),
                config_->GetUpdaterPersistedData(), app_client_install_data,
                app_install_data_index, kInstallSourcePolicy,
                Priority::kBackground, update_blocked,
                policy_same_version_update),
            MakeUpdateClientCrxStateChangeCallback(
                config_, config_->GetUpdaterPersistedData(),
                /*new_install=*/false,
                /*language=*/{}, state_update),
            MakeUpdateClientCallback(barrier_callback)));
  }
}

UpdateServiceImplImpl::~UpdateServiceImplImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  config_->GetPrefService()->SchedulePendingLossyWrites();
}

}  // namespace updater
