// 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/browser/web_applications/os_integration/os_integration_manager.h"

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

#include "base/atomic_ref_count.h"
#include "base/barrier_callback.h"
#include "base/barrier_closure.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/concurrent_closures.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/current_thread.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/types/pass_key.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/os_integration/file_handling_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/os_integration_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/os_integration_test_override.h"
#include "chrome/browser/web_applications/os_integration/protocol_handling_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/shortcut_menu_handling_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/shortcut_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/uninstallation_via_os_settings_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
#include "chrome/browser/web_applications/os_integration/web_app_uninstallation_via_os_settings_registration.h"
#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_icon_manager.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_profile_deletion_manager.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_app_ui_manager.h"
#include "chrome/common/pref_names.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
#include "third_party/re2/src/re2/re2.h"
#include "ui/gfx/image/image_skia_rep_default.h"

#if BUILDFLAG(IS_MAC)
#include "base/system/sys_info.h"
#include "chrome/common/mac/app_mode_common.h"
#endif

#if BUILDFLAG(IS_MAC)
#include "chrome/browser/web_applications/os_integration/mac/app_shim_registry.h"
#endif

namespace web_app {

namespace {
base::AtomicRefCount& GetSuppressCount() {
  static base::AtomicRefCount g_ref_count;
  return g_ref_count;
}

#if BUILDFLAG(IS_MAC)
// This version number is stored in local prefs to check whether app shortcuts
// need to be recreated. This might happen when we change various aspects of app
// shortcuts like command-line flags or associated icons, binaries, etc.
const int kCurrentAppShortcutsVersion = APP_SHIM_VERSION_NUMBER;

// The architecture that was last used to create app shortcuts for this user
// directory.
std::string CurrentAppShortcutsArch() {
  return base::SysInfo::OperatingSystemArchitecture();
}

std::string CurrentAppShortcutsOsVersion() {
  return base::SysInfo::OperatingSystemVersion();
}
#else
std::string CurrentAppShortcutsArch() {
  return "";
}

std::string CurrentAppShortcutsOsVersion() {
  return "";
}
#if BUILDFLAG(IS_WIN)
const int kCurrentAppShortcutsVersion = 1;
#else
// Non-mac/win platforms do not update shortcuts.
const int kCurrentAppShortcutsVersion = 0;
#endif  // BUILDFLAG(IS_WIN)
#endif  // BUILDFLAG(IS_MAC)

// Delay in seconds before running UpdateShortcutsForAllApps.
const int kUpdateShortcutsForAllAppsDelay = 10;

OsIntegrationManager::UpdateShortcutsForAllAppsCallback&
GetUpdateShortcutsForAllAppsCallback() {
  static base::NoDestructor<
      OsIntegrationManager::UpdateShortcutsForAllAppsCallback>
      callback;
  return *callback;
}

}  // namespace

OsIntegrationManager::ScopedSuppressForTesting::ScopedSuppressForTesting() {
// Creating OS hooks on ChromeOS doesn't write files to disk, so it's
// unnecessary to suppress and it provides better crash coverage.
#if !BUILDFLAG(IS_CHROMEOS)
  GetSuppressCount().Increment();
#endif
}

OsIntegrationManager::ScopedSuppressForTesting::~ScopedSuppressForTesting() {
#if !BUILDFLAG(IS_CHROMEOS)
  CHECK(!GetSuppressCount().IsZero());
  GetSuppressCount().Decrement();
#endif
}

// static
bool OsIntegrationManager::AreOsHooksSuppressedForTesting() {
  return !GetSuppressCount().IsZero();
}

// static
void OsIntegrationManager::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  // LINT.IfChange(WebAppPrefs)
  // Indicates whether app shortcuts have been created.
  registry->RegisterIntegerPref(prefs::kAppShortcutsVersion,
                                kCurrentAppShortcutsVersion);
  registry->RegisterStringPref(prefs::kAppShortcutsArch,
                               CurrentAppShortcutsArch());
  registry->RegisterStringPref(prefs::kAppShortcutsOsVersion,
                               CurrentAppShortcutsOsVersion());
  // LINT.ThenChange(//chrome/browser/web_applications/web_app_utils.cc:WebAppPrefs)
}

// static
void OsIntegrationManager::SetUpdateShortcutsForAllAppsCallback(
    UpdateShortcutsForAllAppsCallback callback) {
  GetUpdateShortcutsForAllAppsCallback() = std::move(callback);
}

// static
base::OnceClosure&
OsIntegrationManager::OnSetCurrentAppShortcutsVersionCallbackForTesting() {
  static base::NoDestructor<base::OnceClosure> callback;
  return *callback;
}

OsIntegrationManager::OsIntegrationManager(
    Profile* profile,
    std::unique_ptr<WebAppFileHandlerManager> file_handler_manager,
    std::unique_ptr<WebAppProtocolHandlerManager> protocol_handler_manager)
    : profile_(profile),
      file_handler_manager_(std::move(file_handler_manager)),
      protocol_handler_manager_(std::move(protocol_handler_manager)) {}

OsIntegrationManager::~OsIntegrationManager() = default;

void OsIntegrationManager::SetProvider(base::PassKey<WebAppProvider>,
                                       WebAppProvider& provider) {
  CHECK(!first_synchronize_called_);

  provider_ = &provider;

  base::PassKey<OsIntegrationManager> pass_key;
  file_handler_manager_->SetProvider(pass_key, provider);
  if (protocol_handler_manager_)
    protocol_handler_manager_->SetProvider(pass_key, provider);

  sub_managers_.clear();
  sub_managers_.push_back(
      std::make_unique<ShortcutSubManager>(*profile_, provider));
  sub_managers_.push_back(
      std::make_unique<FileHandlingSubManager>(profile_->GetPath(), provider));
  sub_managers_.push_back(std::make_unique<ProtocolHandlingSubManager>(
      profile_->GetPath(), provider));
  sub_managers_.push_back(std::make_unique<ShortcutMenuHandlingSubManager>(
      profile_->GetPath(), provider));
  sub_managers_.push_back(
      std::make_unique<RunOnOsLoginSubManager>(*profile_, provider));
  sub_managers_.push_back(
      std::make_unique<UninstallationViaOsSettingsSubManager>(
          profile_->GetPath(), provider));

  set_provider_called_ = true;
}

void OsIntegrationManager::Start() {
  CHECK(provider_);
  CHECK(file_handler_manager_);

  file_handler_manager_->Start();
  if (protocol_handler_manager_) {
    protocol_handler_manager_->Start();
  }
  UpdateShortcutsForAllAppsIfNeeded();
}

void OsIntegrationManager::Synchronize(
    const webapps::AppId& app_id,
    base::OnceClosure callback,
    std::optional<SynchronizeOsOptions> options) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  first_synchronize_called_ = true;

  // This is usually called to clean up OS integration states on the OS,
  // regardless of whether there are apps existing in the app registry or not.
  if (options.has_value() && options.value().force_unregister_os_integration) {
    CHECK_OS_INTEGRATION_ALLOWED();
    ForceUnregisterOsIntegrationOnSubManager(
        app_id, /*index=*/0,
        std::move(callback).Then(
            base::BindOnce(force_unregister_callback_for_testing_, app_id)));
    return;
  }

  // If the app does not exist in the DB and an unregistration is required, it
  // should have been done in the past Synchronize call.
  CHECK(provider_->registrar_unsafe().GetAppById(app_id))
      << "Can't perform OS integration without the app existing in the "
         "registrar. If the use-case requires an app to not be installed, "
         "consider setting the force_unregister_os_integration flag inside "
         "SynchronizeOsOptions";

  CHECK(set_provider_called_);

  // Do not allow apps that are suggested for migration to have OS integration.
  if (provider_->registrar_unsafe().AppMatches(
          app_id, WebAppFilter::IsAppSuggestedForMigration())) {
    std::move(callback).Run();
    return;
  }

  if (sub_managers_.empty()) {
    std::move(callback).Run();
    return;
  }

#if !BUILDFLAG(IS_CHROMEOS)
  if (KeepAliveRegistry::GetInstance()->IsShuttingDown()) {
    LOG(ERROR)
        << "Can't perform OS integration while the browser is shutting down.";
    std::move(callback).Run();
    return;
  }

  std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive =
      ScopedProfileKeepAlive::TryAcquire(profile_,
                                         ProfileKeepAliveOrigin::kWebAppUpdate);
  if (!profile_keep_alive) {
    // Profile is scheduled for destruction, abort.
    std::move(callback).Run();
    return;
  }
  std::unique_ptr<ScopedKeepAlive> browser_keep_alive =
      std::make_unique<ScopedKeepAlive>(KeepAliveOrigin::WEB_APP_INSTALL,
                                        KeepAliveRestartOption::DISABLED);

  auto end_keep_alive_then_run_callback =
      base::OnceClosure(
          base::DoNothingWithBoundArgs(std::move(profile_keep_alive),
                                       std::move(browser_keep_alive)))
          .Then(std::move(callback));
#else
  // TODO(crbug.com/394384898): Do this for ChromeOS too once it
  // doesn't break browser tests using InstallSystemAppsForTesting.
  auto end_keep_alive_then_run_callback = std::move(callback);
#endif

  std::unique_ptr<proto::os_state::WebAppOsIntegration> desired_states =
      std::make_unique<proto::os_state::WebAppOsIntegration>();
  proto::os_state::WebAppOsIntegration* desired_states_ptr =
      desired_states.get();

  // Note: Sometimes the execute step is a no-op based on feature flags or if os
  // integration is disabled for testing. This logic is in the
  // StartSubManagerExecutionIfRequired method.
  base::RepeatingClosure configure_barrier;
  configure_barrier = base::BarrierClosure(
      sub_managers_.size(),
      base::BindOnce(&OsIntegrationManager::StartSubManagerExecutionIfRequired,
                     weak_ptr_factory_.GetWeakPtr(), app_id, options,
                     std::move(desired_states),
                     std::move(end_keep_alive_then_run_callback)));

  for (const auto& sub_manager : sub_managers_) {
    // This dereference is safe because the barrier closure guarantees that it
    // will not be called until `configure_barrier` is called from each sub-
    // manager.
    sub_manager->Configure(app_id, *desired_states_ptr, configure_barrier);
  }
}

void OsIntegrationManager::GetAppExistingShortCutLocation(
    ShortcutLocationCallback callback,
    std::unique_ptr<ShortcutInfo> shortcut_info) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  // Ownership of `shortcut_info` moves to the Reply, which is guaranteed to
  // outlive the const reference.
  const ShortcutInfo& shortcut_info_ref = *shortcut_info;
  internals::GetShortcutIOTaskRunner()->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&internals::GetAppExistingShortCutLocationImpl,
                     std::cref(shortcut_info_ref)),
      base::BindOnce(
          [](ShortcutLocationCallback callback, ShortcutLocations locations) {
            DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
            std::move(callback).Run(locations);
          },
          std::move(callback).Then(base::OnceClosure(
              base::DoNothingWithBoundArgs(std::move(shortcut_info))))));
}

void OsIntegrationManager::GetShortcutInfoForAppFromRegistrar(
    const webapps::AppId& app_id,
    GetShortcutInfoCallback callback) {
  const WebApp* app = provider_->registrar_unsafe().GetAppById(app_id);

  // app could be nullptr if registry profile is being deleted or the app is not
  // in the registry.
  if (!app) {
    std::move(callback).Run(nullptr);
    return;
  }

  // Build a common intersection between desired and downloaded icons.
  auto icon_sizes_in_px = base::STLSetIntersection<std::vector<SquareSizePx>>(
      app->downloaded_icon_sizes(IconPurpose::ANY),
      GetDesiredIconSizesForShortcut());

  if (!icon_sizes_in_px.empty()) {
    provider_->icon_manager().ReadTrustedIconsWithFallbackToManifestIcons(
        app_id, icon_sizes_in_px, IconPurpose::ANY,
        web_app::WebAppIconManager::BitmapsFromIconMetadataExtractor(
            base::BindOnce(&OsIntegrationManager::OnIconsRead,
                           weak_ptr_factory_.GetWeakPtr(), app_id,
                           std::move(callback))));
    return;
  }

  // If there is no single icon at the desired sizes, we will resize what we can
  // get.
  SquareSizePx desired_icon_size = GetDesiredIconSizesForShortcut().back();

  provider_->icon_manager().ReadIconAndResize(
      app_id, IconPurpose::ANY, desired_icon_size,
      base::BindOnce(&OsIntegrationManager::OnIconsRead,
                     weak_ptr_factory_.GetWeakPtr(), app_id,
                     std::move(callback)));
}

const apps::FileHandlers* OsIntegrationManager::GetEnabledFileHandlers(
    const webapps::AppId& app_id) const {
  CHECK(file_handler_manager_);
  return file_handler_manager_->GetEnabledFileHandlers(app_id);
}

std::optional<GURL> OsIntegrationManager::TranslateProtocolUrl(
    const webapps::AppId& app_id,
    const GURL& protocol_url) {
  if (!protocol_handler_manager_)
    return std::optional<GURL>();

  return protocol_handler_manager_->TranslateProtocolUrl(app_id, protocol_url);
}

std::vector<custom_handlers::ProtocolHandler>
OsIntegrationManager::GetAppProtocolHandlers(
    const webapps::AppId& app_id) const {
  if (!protocol_handler_manager_)
    return std::vector<custom_handlers::ProtocolHandler>();

  return protocol_handler_manager_->GetAppProtocolHandlers(app_id);
}

std::vector<custom_handlers::ProtocolHandler>
OsIntegrationManager::GetAllowedHandlersForProtocol(
    const std::string& protocol) {
  if (!protocol_handler_manager_)
    return std::vector<custom_handlers::ProtocolHandler>();

  return protocol_handler_manager_->GetAllowedHandlersForProtocol(protocol);
}

std::vector<custom_handlers::ProtocolHandler>
OsIntegrationManager::GetDisallowedHandlersForProtocol(
    const std::string& protocol) {
  if (!protocol_handler_manager_)
    return std::vector<custom_handlers::ProtocolHandler>();

  return protocol_handler_manager_->GetDisallowedHandlersForProtocol(protocol);
}

WebAppProtocolHandlerManager&
OsIntegrationManager::protocol_handler_manager_for_testing() {
  CHECK(protocol_handler_manager_);
  return *protocol_handler_manager_;
}

FakeOsIntegrationManager* OsIntegrationManager::AsTestOsIntegrationManager() {
  return nullptr;
}

void OsIntegrationManager::UnregisterOsIntegrationOnProfileMarkedForDeletion(
    base::PassKey<WebAppProfileDeletionManager>,
    const webapps::AppId& app_id) {
  CHECK_OS_INTEGRATION_ALLOWED();
  // This is used to keep the profile from being deleted while doing a
  // ForceUnregister when profile deletion is started.
  auto profile_keep_alive = std::make_unique<ScopedProfileKeepAlive>(
      profile_, ProfileKeepAliveOrigin::kOsIntegrationForceUnregistration);
  ForceUnregisterOsIntegrationOnSubManager(
      app_id, 0,
      base::BindOnce(&OsIntegrationManager::SubManagersUnregistered,
                     weak_ptr_factory_.GetWeakPtr(), app_id,
                     std::move(profile_keep_alive)));
}

void OsIntegrationManager::SetForceUnregisterCalledForTesting(
    base::RepeatingCallback<void(const webapps::AppId&)> on_force_unregister) {
  force_unregister_callback_for_testing_ = on_force_unregister;
}

void OsIntegrationManager::StartSubManagerExecutionIfRequired(
    const webapps::AppId& app_id,
    std::optional<SynchronizeOsOptions> options,
    std::unique_ptr<proto::os_state::WebAppOsIntegration> desired_states,
    base::OnceClosure on_all_execution_done) {
  // The "execute" step is skipped in the following cases:
  // 1. The app is no longer in the registrar. The whole synchronize process is
  //    stopped here.
  // 2. The `g_suppress_os_hooks_for_testing_` flag is set.

  const WebApp* web_app = provider_->registrar_unsafe().GetAppById(app_id);
  if (!web_app) {
    std::move(on_all_execution_done).Run();
    return;
  }

  proto::os_state::WebAppOsIntegration* desired_states_ptr =
      desired_states.get();
  auto write_state_to_db = base::BindOnce(
      &OsIntegrationManager::WriteStateToDB, weak_ptr_factory_.GetWeakPtr(),
      app_id, std::move(desired_states), std::move(on_all_execution_done));

  if (AreOsHooksSuppressedForTesting()) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(write_state_to_db));
    return;
  }

  ExecuteNextSubmanager(app_id, options, desired_states_ptr,
                        web_app->current_os_integration_states(), /*index=*/0,
                        std::move(write_state_to_db));
}

void OsIntegrationManager::ExecuteNextSubmanager(
    const webapps::AppId& app_id,
    std::optional<SynchronizeOsOptions> options,
    proto::os_state::WebAppOsIntegration* desired_state,
    const proto::os_state::WebAppOsIntegration current_state,
    size_t index,
    base::OnceClosure on_all_execution_done_db_write) {
  CHECK(index < sub_managers_.size());
  base::OnceClosure next_callback = base::OnceClosure();
  if (index == sub_managers_.size() - 1) {
    next_callback = std::move(on_all_execution_done_db_write);
  } else {
    next_callback = base::BindOnce(
        &OsIntegrationManager::ExecuteNextSubmanager,
        weak_ptr_factory_.GetWeakPtr(), app_id, options, desired_state,
        current_state, index + 1, std::move(on_all_execution_done_db_write));
  }
  sub_managers_[index]->Execute(app_id, options, *desired_state, current_state,
                                std::move(next_callback));
}

void OsIntegrationManager::WriteStateToDB(
    const webapps::AppId& app_id,
    std::unique_ptr<proto::os_state::WebAppOsIntegration> desired_states,
    base::OnceClosure callback) {
  // Exit early if the app is already uninstalled. We still need to write the
  // desired_states to the web_app DB during the uninstallation process since
  // that helps make decisions on whether the uninstallation went successfully
  // or not inside the RemoveWebAppJob.
  const WebApp* existing_app = provider_->registrar_unsafe().GetAppById(app_id);
  if (!existing_app) {
    std::move(callback).Run();
    return;
  }

  {
    ScopedRegistryUpdate update = provider_->sync_bridge_unsafe().BeginUpdate();
    WebApp* web_app = update->UpdateApp(app_id);
    CHECK(web_app);
    web_app->SetCurrentOsIntegrationStates(*desired_states.get());
  }

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

void OsIntegrationManager::SubManagersUnregistered(
    const webapps::AppId& app_id,
    std::unique_ptr<ScopedProfileKeepAlive> keep_alive) {
  force_unregister_callback_for_testing_.Run(app_id);
  keep_alive.reset();
}

void OsIntegrationManager::ForceUnregisterOsIntegrationOnSubManager(
    const webapps::AppId& app_id,
    size_t index,
    base::OnceClosure final_callback) {
  CHECK(index < sub_managers_.size());
  base::OnceClosure next_callback = base::OnceClosure();
  if (index == sub_managers_.size() - 1) {
    next_callback = std::move(final_callback);
  } else {
    next_callback = base::BindOnce(
        &OsIntegrationManager::ForceUnregisterOsIntegrationOnSubManager,
        weak_ptr_factory_.GetWeakPtr(), app_id, index + 1,
        std::move(final_callback));
  }
  sub_managers_[index]->ForceUnregister(app_id, std::move(next_callback));
}

void OsIntegrationManager::UpdateShortcutsForAllAppsIfNeeded() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // Updating shortcuts writes to user home folders, which can not be done in
  // tests without exploding disk space usage on the bots.
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) {
    return;
  }

  int last_version =
      profile_->GetPrefs()->GetInteger(prefs::kAppShortcutsVersion);
  std::string last_arch =
      profile_->GetPrefs()->GetString(prefs::kAppShortcutsArch);
  std::string last_os_version =
      profile_->GetPrefs()->GetString(prefs::kAppShortcutsOsVersion);

  if (last_version == kCurrentAppShortcutsVersion &&
      last_arch == CurrentAppShortcutsArch() &&
      last_os_version == CurrentAppShortcutsOsVersion()) {
    // This either means this is a profile where installed shortcuts already
    // match the expected version and arch, or this could be a fresh profile.
    // For the latter, make sure to actually store version and arch in prefs,
    // as otherwise this code would always just read the defaults for these
    // prefs, and not actually ever detect a version change.
    SetCurrentAppShortcutsVersion();
    return;
  }

  content::GetUIThreadTaskRunner({})->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&OsIntegrationManager::UpdateShortcutsForAllAppsNow,
                     weak_ptr_factory_.GetWeakPtr()),
      base::Seconds(kUpdateShortcutsForAllAppsDelay));
}

void OsIntegrationManager::UpdateShortcutsForAllAppsNow() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  base::ConcurrentClosures concurrent;

  SynchronizeOsOptions options;
  options.force_update_shortcuts = true;

  for (const auto& app_id : provider_->registrar_unsafe().GetAppIds()) {
    Synchronize(app_id, concurrent.CreateClosure(), options);
  }

  UpdateShortcutsForAllAppsCallback update_callback =
      GetUpdateShortcutsForAllAppsCallback();
  if (update_callback) {
    update_callback.Run(profile_, concurrent.CreateClosure());
  } else {
    concurrent.CreateClosure().Run();
  }

  std::move(concurrent)
      .Done(base::BindOnce(&OsIntegrationManager::SetCurrentAppShortcutsVersion,
                           weak_ptr_factory_.GetWeakPtr()));
}

void OsIntegrationManager::SetCurrentAppShortcutsVersion() {
  profile_->GetPrefs()->SetInteger(prefs::kAppShortcutsVersion,
                                   kCurrentAppShortcutsVersion);
  profile_->GetPrefs()->SetString(prefs::kAppShortcutsArch,
                                  CurrentAppShortcutsArch());
  profile_->GetPrefs()->SetString(prefs::kAppShortcutsOsVersion,
                                  CurrentAppShortcutsOsVersion());

  if (base::OnceClosure& callback =
          OnSetCurrentAppShortcutsVersionCallbackForTesting()) {
    std::move(callback).Run();
  }
}

void OsIntegrationManager::OnIconsRead(const webapps::AppId& app_id,
                                       GetShortcutInfoCallback callback,
                                       OrderedSizeToBitmap icon_bitmaps) {
  const WebApp* app = provider_->registrar_unsafe().GetAppById(app_id);
  if (!app) {
    std::move(callback).Run(nullptr);
    return;
  }

  gfx::ImageFamily image_family;
  for (auto& size_and_bitmap : icon_bitmaps) {
    image_family.Add(gfx::ImageSkia(
        gfx::ImageSkiaRep(size_and_bitmap.second, /*scale=*/0.0f)));
  }

  // If the image failed to load, use the standard application icon.
  if (image_family.empty()) {
    SquareSizePx icon_size_in_px = GetDesiredIconSizesForShortcut().back();
    gfx::ImageSkia image_skia = CreateDefaultApplicationIcon(icon_size_in_px);
    image_family.Add(gfx::Image(image_skia));
  }

  std::unique_ptr<ShortcutInfo> shortcut_info = BuildShortcutInfoForWebApp(app);
  shortcut_info->favicon = std::move(image_family);

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

// TODO(crbug.com/40250591): Merge into BuildShortcutInfoWithoutFavicon() for
// web_app_shortcut.cc.
std::unique_ptr<ShortcutInfo> OsIntegrationManager::BuildShortcutInfoForWebApp(
    const WebApp* app) {
  auto shortcut_info = std::make_unique<ShortcutInfo>();

  shortcut_info->app_id = app->app_id();
  shortcut_info->url = app->start_url();
  shortcut_info->is_diy_app = app->is_diy_app();
  shortcut_info->title = base::UTF8ToUTF16(
      provider_->registrar_unsafe().GetAppShortName(app->app_id()));
  shortcut_info->description = base::UTF8ToUTF16(
      provider_->registrar_unsafe().GetAppDescription(app->app_id()));
  shortcut_info->profile_path = profile_->GetPath();
  shortcut_info->profile_name =
      profile_->GetPrefs()->GetString(prefs::kProfileName);
  shortcut_info->is_multi_profile = true;

  if (const apps::FileHandlers* file_handlers =
          file_handler_manager_->GetEnabledFileHandlers(app->app_id())) {
    shortcut_info->file_handler_extensions =
        GetFileExtensionsFromFileHandlers(*file_handlers);
    shortcut_info->file_handler_mime_types =
        GetMimeTypesFromFileHandlers(*file_handlers);
  }

  std::vector<apps::ProtocolHandlerInfo> protocol_handlers =
      protocol_handler_manager_->GetAppProtocolHandlerInfos(app->app_id());
  for (const auto& protocol_handler : protocol_handlers) {
    if (!protocol_handler.protocol.empty()) {
      shortcut_info->protocol_handlers.emplace(protocol_handler.protocol);
    }
  }

#if BUILDFLAG(IS_LINUX)
  const std::vector<WebAppShortcutsMenuItemInfo>& shortcuts_menu_item_infos =
      app->shortcuts_menu_item_infos();
  DCHECK_LE(shortcuts_menu_item_infos.size(), kMaxApplicationDockMenuItems);
  for (const auto& shortcuts_menu_item_info : shortcuts_menu_item_infos) {
    if (!shortcuts_menu_item_info.name.empty() &&
        !shortcuts_menu_item_info.url.is_empty()) {
      // Generates ID from the name by replacing all characters that are not
      // numbers, letters, or '-' with '-'.
      std::string id = base::UTF16ToUTF8(shortcuts_menu_item_info.name);
      RE2::GlobalReplace(&id, "[^a-zA-Z0-9\\-]", "-");
      shortcut_info->actions.emplace(
          id, base::UTF16ToUTF8(shortcuts_menu_item_info.name),
          shortcuts_menu_item_info.url);
    }
  }
#endif  // BUILDFLAG(IS_LINUX)

#if BUILDFLAG(IS_MAC)
  shortcut_info->handlers_per_profile =
      AppShimRegistry::Get()->GetHandlersForApp(app->app_id());
#endif

  return shortcut_info;
}

}  // namespace web_app
