chromium/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc

// 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/ash/system_web_apps/system_web_app_manager.h"

#include <memory>
#include <optional>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "base/feature_list.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_background_task.h"
#include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_installation.h"
#include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_manager.h"
#include "chrome/browser/ash/system_web_apps/types/system_web_app_delegate_map.h"
#include "chrome/browser/web_applications/external_install_options.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/test/fake_externally_managed_app_manager.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/test_web_app_url_loader.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chromeos/components/kiosk/kiosk_test_utils.h"
#include "components/prefs/pref_service.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/idle/idle.h"
#include "ui/base/idle/scoped_set_idle_state.h"
#include "url/gurl.h"

namespace ash {

namespace {
using testing::ElementsAre;

const char kSettingsAppInternalName[] = "OSSettings";
const char kCameraAppInternalName[] = "Camera";

GURL AppUrl1() {
  return GURL(content::GetWebUIURL("system-app1"));
}
GURL AppUrl2() {
  return GURL(content::GetWebUIURL("system-app2"));
}
GURL AppUrl3() {
  return GURL(content::GetWebUIURL("system-app3"));
}

std::unique_ptr<web_app::WebAppInstallInfo> GetWebAppInstallInfo(
    const GURL& url) {
  std::unique_ptr<web_app::WebAppInstallInfo> info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(url);
  info->scope = url.GetWithoutFilename();
  info->title = u"Web App";
  return info;
}

web_app::WebAppInstallInfoFactory GetApp1WebAppInfoFactory() {
  // "static" so that web_app::ExternalInstallOptions comparisons in tests work.
  static auto factory = base::BindRepeating(&GetWebAppInstallInfo, AppUrl1());
  return factory;
}

web_app::WebAppInstallInfoFactory GetApp2WebAppInfoFactory() {
  // "static" so that web_app::ExternalInstallOptions comparisons in tests work.
  static auto factory = base::BindRepeating(&GetWebAppInstallInfo, AppUrl2());
  return factory;
}

class SystemWebAppWaiter {
 public:
  explicit SystemWebAppWaiter(SystemWebAppManager* system_web_app_manager) {
    system_web_app_manager->ResetForTesting();
    system_web_app_manager->on_apps_synchronized().Post(
        FROM_HERE, base::BindLambdaForTesting([&]() {
          // Wait one execution loop for on_apps_synchronized() to be called on
          // all listeners.
          base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
              FROM_HERE, run_loop_.QuitClosure());
        }));
  }

  void Wait() { run_loop_.Run(); }

 private:
  base::RunLoop run_loop_;
};

class TestUiManagerObserver : public web_app::WebAppUiManagerObserver {
 public:
  explicit TestUiManagerObserver(web_app::WebAppUiManager* ui_manager) {
    ui_manager_observation_.Observe(ui_manager);
  }

  using ReadyToCommitNavigationCallback = base::RepeatingCallback<void(
      const webapps::AppId& app_id,
      content::NavigationHandle* navigation_handle)>;

  void SetReadyToCommitNavigationCallback(
      ReadyToCommitNavigationCallback callback) {
    ready_to_commit_navigation_callback_ = std::move(callback);
  }

  void OnReadyToCommitNavigation(
      const webapps::AppId& app_id,
      content::NavigationHandle* navigation_handle) override {
    if (ready_to_commit_navigation_callback_)
      ready_to_commit_navigation_callback_.Run(app_id, navigation_handle);
  }

  using UiManagerDestroyedCallback = base::RepeatingCallback<void()>;

  void SetUiManagerDestroyedCallback(UiManagerDestroyedCallback callback) {
    ui_manager_destroyed_callback_ = std::move(callback);
  }

  void OnWebAppUiManagerDestroyed() override {
    if (ui_manager_destroyed_callback_)
      ui_manager_destroyed_callback_.Run();
    ui_manager_observation_.Reset();
  }

 private:
  ReadyToCommitNavigationCallback ready_to_commit_navigation_callback_;
  UiManagerDestroyedCallback ui_manager_destroyed_callback_;

  base::ScopedObservation<web_app::WebAppUiManager,
                          web_app::WebAppUiManagerObserver>
      ui_manager_observation_{this};
};

}  // namespace

class SystemWebAppManagerTest : public ChromeRenderViewHostTestHarness {
 public:
  template <typename... TaskEnvironmentTraits>
  explicit SystemWebAppManagerTest(TaskEnvironmentTraits&&... traits)
      : ChromeRenderViewHostTestHarness(
            std::forward<TaskEnvironmentTraits>(traits)...) {}
  SystemWebAppManagerTest(const SystemWebAppManagerTest&) = delete;
  SystemWebAppManagerTest& operator=(const SystemWebAppManagerTest&) = delete;

  ~SystemWebAppManagerTest() override = default;

  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();
    web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());
  }

 protected:
  web_app::FakeWebAppProvider& provider() {
    return static_cast<web_app::FakeWebAppProvider&>(
        *SystemWebAppManager::GetWebAppProvider(profile()));
  }

  web_app::FakeExternallyManagedAppManager& externally_managed_app_manager() {
    return static_cast<web_app::FakeExternallyManagedAppManager&>(
        provider().externally_managed_app_manager());
  }

  TestSystemWebAppManager& system_web_app_manager() {
    return static_cast<TestSystemWebAppManager&>(
        *SystemWebAppManager::Get(profile()));
  }

  bool IsInstalled(const GURL& install_url) {
    return provider().registrar_unsafe().IsInstalled(
        web_app::GenerateAppId(/*manifest_id=*/std::nullopt, install_url));
  }

  void StartAndWaitForAppsToSynchronize() {
    SystemWebAppWaiter waiter(&system_web_app_manager());
    system_web_app_manager().Start();
    waiter.Wait();
  }

  void StartAndWaitForIconCheck() {
    StartAndWaitForAppsToSynchronize();

    base::RunLoop run_loop;
    system_web_app_manager().on_icon_check_completed().Post(
        FROM_HERE, run_loop.QuitClosure());
    run_loop.Run();
  }

  void AwaitSystemWebAppCommandsCompletePostStartup() {
    base::RunLoop().RunUntilIdle();
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }
};

// Test that changing the set of System Apps uninstalls apps.
TEST_F(SystemWebAppManagerTest, UninstallAppInstalledInPreviousSession) {
  // Simulate System Apps and a regular app that were installed in the
  // previous session.
  web_app::test::InstallDummyWebApp(
      profile(), "App1", AppUrl1(),
      webapps::WebappInstallSource::SYSTEM_DEFAULT);
  web_app::test::InstallDummyWebApp(
      profile(), "App2", AppUrl2(),
      webapps::WebappInstallSource::SYSTEM_DEFAULT);
  web_app::test::InstallDummyWebApp(
      profile(), "App3", AppUrl3(),
      webapps::WebappInstallSource::INTERNAL_DEFAULT);

  SystemWebAppDelegateMap system_apps;
  system_apps.emplace(SystemWebAppType::SETTINGS,
                      std::make_unique<UnittestingSystemAppDelegate>(
                          SystemWebAppType::SETTINGS, kSettingsAppInternalName,
                          AppUrl1(), GetApp1WebAppInfoFactory()));

  system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
  StartAndWaitForAppsToSynchronize();

  // We should only try to install the app in the System App list.
  web_app::ExternalInstallOptions options(
      AppUrl1(), web_app::mojom::UserDisplayMode::kStandalone,
      web_app::ExternalInstallSource::kSystemInstalled);
  options.add_to_applications_menu = true;
  options.add_to_desktop = false;
  options.add_to_quick_launch_bar = false;
  options.add_to_search = true;
  options.add_to_management = false;
  options.is_disabled = false;
  options.handles_file_open_intents = false;
  options.force_reinstall = true;
  options.only_use_app_info_factory = true;
  options.system_app_type = SystemWebAppType::SETTINGS;
  options.app_info_factory = GetApp1WebAppInfoFactory();

  EXPECT_THAT(externally_managed_app_manager().install_requests(),
              ElementsAre(options));
  EXPECT_THAT(externally_managed_app_manager().uninstall_requests(),
              ElementsAre(AppUrl2()));
}

TEST_F(SystemWebAppManagerTest, AlwaysUpdate) {
  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kAlwaysUpdate);

  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
  }
  system_web_app_manager().set_current_version(base::Version("1.0.0.0"));
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(1u, externally_managed_app_manager().install_requests().size());

  // Create another app. The version hasn't changed but the app should still
  // install.
  {
    SystemWebAppDelegateMap system_apps;

    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    system_apps.emplace(SystemWebAppType::CAMERA,
                        std::make_unique<UnittestingSystemAppDelegate>(
                            SystemWebAppType::CAMERA, kCameraAppInternalName,
                            AppUrl2(), GetApp2WebAppInfoFactory()));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
  }
  // This one returns because on_apps_synchronized runs immediately.
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(3u, externally_managed_app_manager().install_requests().size());
}

TEST_F(SystemWebAppManagerTest, UpdateOnVersionChange) {
  const std::vector<web_app::ExternalInstallOptions>& install_requests =
      externally_managed_app_manager().install_requests();

  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kOnVersionChange);

  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
    system_web_app_manager().set_current_version(base::Version("1.0.0.0"));
  }
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(1u, install_requests.size());
  EXPECT_TRUE(install_requests[0].force_reinstall);
  EXPECT_TRUE(IsInstalled(AppUrl1()));

  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    // Create another app. The version hasn't changed, but we should immediately
    // install anyway, as if a user flipped a chrome://flag. The first app won't
    // force reinstall.
    system_apps.emplace(SystemWebAppType::CAMERA,
                        std::make_unique<UnittestingSystemAppDelegate>(
                            SystemWebAppType::CAMERA, kCameraAppInternalName,
                            AppUrl2(), GetApp2WebAppInfoFactory()));

    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
  }
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(3u, install_requests.size());
  EXPECT_FALSE(install_requests[1].force_reinstall);
  EXPECT_FALSE(install_requests[2].force_reinstall);
  EXPECT_TRUE(IsInstalled(AppUrl1()));
  EXPECT_TRUE(IsInstalled(AppUrl2()));

  // Bump the version number, and an update will trigger, and force
  // reinstallation of both apps.
  system_web_app_manager().set_current_version(base::Version("2.0.0.0"));
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(5u, install_requests.size());
  EXPECT_TRUE(install_requests[3].force_reinstall);
  EXPECT_TRUE(install_requests[4].force_reinstall);
  EXPECT_TRUE(IsInstalled(AppUrl1()));
  EXPECT_TRUE(IsInstalled(AppUrl2()));

  // Changing the install URL of a system app propagates even without a
  // version change.
  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl3(),
            base::BindRepeating(&GetWebAppInstallInfo, AppUrl3())));

    system_apps.emplace(SystemWebAppType::CAMERA,
                        std::make_unique<UnittestingSystemAppDelegate>(
                            SystemWebAppType::CAMERA, kCameraAppInternalName,
                            AppUrl2(), GetApp2WebAppInfoFactory()));

    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
  }
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(7u, install_requests.size());
  EXPECT_FALSE(install_requests[5].force_reinstall);
  EXPECT_FALSE(install_requests[6].force_reinstall);
  EXPECT_FALSE(IsInstalled(AppUrl1()));
  EXPECT_TRUE(IsInstalled(AppUrl2()));
  EXPECT_TRUE(IsInstalled(AppUrl3()));
}

TEST_F(SystemWebAppManagerTest, UpdateOnVersionChangeEvenIfIconsBroken) {
  const std::vector<web_app::ExternalInstallOptions>& install_requests =
      externally_managed_app_manager().install_requests();

  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kOnVersionChange);

  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
  }
  system_web_app_manager().set_current_version(base::Version("1.0.0.0"));
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(1u, externally_managed_app_manager().install_requests().size());
  EXPECT_TRUE(install_requests.front().force_reinstall);
  EXPECT_TRUE(IsInstalled(AppUrl1()));

  // Simulate something going wrong in the interim.
  system_web_app_manager().set_icons_are_broken(true);
  // Must be greater than `kInstallFailureAttempts` constant.
  profile()->GetPrefs()->SetInteger(
      prefs::kSystemWebAppInstallFailureCount,
      SystemWebAppManager::kInstallFailureAttempts + 1);

  system_web_app_manager().set_current_version(base::Version("1.0.0.1"));
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(2u, externally_managed_app_manager().install_requests().size());
  EXPECT_TRUE(install_requests.back().force_reinstall);
  EXPECT_TRUE(IsInstalled(AppUrl1()));
}

TEST_F(SystemWebAppManagerTest, RetryBrokenIcons) {
  const std::vector<web_app::ExternalInstallOptions>& install_requests =
      externally_managed_app_manager().install_requests();

  // We don't want to force reinstall by default, we want to check that we
  // correctly set to force reinstall when icons are broken.
  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kOnVersionChange);

  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
  }

  {
    // Initial install.
    StartAndWaitForAppsToSynchronize();

    EXPECT_EQ(1u, install_requests.size());
    EXPECT_TRUE(install_requests[0].force_reinstall);
    EXPECT_TRUE(IsInstalled(AppUrl1()));
  }

  {
    // Icons not broken.
    system_web_app_manager().set_icons_are_broken(false);
    StartAndWaitForAppsToSynchronize();

    EXPECT_EQ(2u, install_requests.size());
    EXPECT_FALSE(install_requests[1].force_reinstall);
  }

  {
    // Broken icons should force reinstall.
    system_web_app_manager().set_icons_are_broken(true);
    StartAndWaitForAppsToSynchronize();

    EXPECT_EQ(3u, install_requests.size());
    EXPECT_TRUE(install_requests[2].force_reinstall);
  }
}

TEST_F(SystemWebAppManagerTest, AbortOnExceedRetryLimit) {
  const std::vector<web_app::ExternalInstallOptions>& install_requests =
      externally_managed_app_manager().install_requests();

  base::HistogramTester histograms;

  // We don't want to force reinstall by default, we want to check that we
  // correctly set to force reinstall when icons are broken.
  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kOnVersionChange);

  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
    system_web_app_manager().set_icons_are_broken(true);
  }

  {
    // Initial install
    StartAndWaitForAppsToSynchronize();

    EXPECT_EQ(1u, install_requests.size());
    EXPECT_TRUE(install_requests[0].force_reinstall);
    EXPECT_TRUE(IsInstalled(AppUrl1()));
  }

  {
    // 1st retry
    StartAndWaitForIconCheck();

    histograms.ExpectBucketCount(
        SystemWebAppManager::kIconsFixedOnReinstallHistogramName, false, 1);

    EXPECT_EQ(2u, install_requests.size());
    EXPECT_TRUE(install_requests[1].force_reinstall);
  }

  {
    // 2nd retry
    StartAndWaitForIconCheck();

    histograms.ExpectBucketCount(
        SystemWebAppManager::kIconsFixedOnReinstallHistogramName, false, 2);
    EXPECT_EQ(3u, install_requests.size());
    EXPECT_TRUE(install_requests[2].force_reinstall);
  }

  {
    // 3rd retry
    StartAndWaitForIconCheck();

    histograms.ExpectBucketCount(
        SystemWebAppManager::kIconsFixedOnReinstallHistogramName, false, 3);

    EXPECT_EQ(4u, install_requests.size());
    EXPECT_TRUE(install_requests[3].force_reinstall);
  }

  {
    // 4th retry should be aborted - no new install request
    system_web_app_manager().ResetForTesting();
    system_web_app_manager().Start();
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(4u, install_requests.size());
  }
}

TEST_F(SystemWebAppManagerTest, UpdateOnLocaleChange) {
  const std::vector<web_app::ExternalInstallOptions>& install_requests =
      externally_managed_app_manager().install_requests();

  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kOnVersionChange);

  SystemWebAppDelegateMap system_apps;
  system_apps.emplace(SystemWebAppType::SETTINGS,
                      std::make_unique<UnittestingSystemAppDelegate>(
                          SystemWebAppType::SETTINGS, kSettingsAppInternalName,
                          AppUrl1(), GetApp1WebAppInfoFactory()));
  system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));

  // First execution.
  system_web_app_manager().set_current_locale("en-US");
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(1u, install_requests.size());
  EXPECT_TRUE(IsInstalled(AppUrl1()));

  // Change locale setting, should trigger reinstall.
  system_web_app_manager().set_current_locale("ja");
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(2u, install_requests.size());
  EXPECT_TRUE(install_requests[1].force_reinstall);
  EXPECT_TRUE(IsInstalled(AppUrl1()));

  // Do not reinstall because locale is not changed.
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(3u, install_requests.size());
  EXPECT_FALSE(install_requests[2].force_reinstall);
}

TEST_F(SystemWebAppManagerTest, InstallResultHistogram) {
  base::HistogramTester histograms;
  const std::string settings_app_install_result_histogram =
      std::string(SystemWebAppManager::kInstallResultHistogramName) + ".Apps." +
      kSettingsAppInternalName;
  const std::string camera_app_install_result_histogram =
      std::string(SystemWebAppManager::kInstallResultHistogramName) + ".Apps." +
      kCameraAppInternalName;
  // Profile category for Chrome OS testing environment is "Other".
  const std::string profile_install_result_histogram =
      std::string(SystemWebAppManager::kInstallResultHistogramName) +
      ".Profiles.Other";

  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kAlwaysUpdate);

  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));

    histograms.ExpectTotalCount(
        SystemWebAppManager::kInstallResultHistogramName, 0);
    histograms.ExpectTotalCount(settings_app_install_result_histogram, 0);
    histograms.ExpectTotalCount(profile_install_result_histogram, 0);
    histograms.ExpectTotalCount(
        SystemWebAppManager::kInstallDurationHistogramName, 0);

    StartAndWaitForAppsToSynchronize();

    histograms.ExpectTotalCount(
        SystemWebAppManager::kInstallResultHistogramName, 1);
    histograms.ExpectBucketCount(
        SystemWebAppManager::kInstallResultHistogramName,
        webapps::InstallResultCode::kSuccessOfflineOnlyInstall, 1);
    histograms.ExpectTotalCount(settings_app_install_result_histogram, 1);
    histograms.ExpectBucketCount(
        settings_app_install_result_histogram,
        webapps::InstallResultCode::kSuccessOfflineOnlyInstall, 1);
    histograms.ExpectTotalCount(profile_install_result_histogram, 1);
    histograms.ExpectBucketCount(
        profile_install_result_histogram,
        webapps::InstallResultCode::kSuccessOfflineOnlyInstall, 1);
    histograms.ExpectTotalCount(
        SystemWebAppManager::kInstallDurationHistogramName, 1);
  }

  externally_managed_app_manager().SetHandleInstallRequestCallback(
      base::BindLambdaForTesting(
          [](const web_app::ExternalInstallOptions&)
              -> web_app::ExternallyManagedAppManager::InstallResult {
            return web_app::ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kWebAppDisabled);
          }));

  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    system_apps.emplace(SystemWebAppType::CAMERA,
                        std::make_unique<UnittestingSystemAppDelegate>(
                            SystemWebAppType::CAMERA, kCameraAppInternalName,
                            AppUrl2(), GetApp2WebAppInfoFactory()));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));

    StartAndWaitForAppsToSynchronize();

    histograms.ExpectTotalCount(
        SystemWebAppManager::kInstallResultHistogramName, 3);
    histograms.ExpectBucketCount(
        SystemWebAppManager::kInstallResultHistogramName,
        webapps::InstallResultCode::kWebAppDisabled, 2);
    histograms.ExpectTotalCount(settings_app_install_result_histogram, 2);
    histograms.ExpectBucketCount(settings_app_install_result_histogram,
                                 webapps::InstallResultCode::kWebAppDisabled,
                                 1);
    histograms.ExpectBucketCount(camera_app_install_result_histogram,
                                 webapps::InstallResultCode::kWebAppDisabled,
                                 1);
  }

  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));

    histograms.ExpectTotalCount(
        SystemWebAppManager::kInstallDurationHistogramName, 2);
    histograms.ExpectBucketCount(
        settings_app_install_result_histogram,
        webapps::InstallResultCode::kCancelledOnWebAppProviderShuttingDown, 0);
    histograms.ExpectBucketCount(
        profile_install_result_histogram,
        webapps::InstallResultCode::kCancelledOnWebAppProviderShuttingDown, 0);

    {
      SystemWebAppWaiter waiter(&system_web_app_manager());
      system_web_app_manager().Start();
      system_web_app_manager().Shutdown();
      waiter.Wait();
    }

    histograms.ExpectBucketCount(
        SystemWebAppManager::kInstallResultHistogramName,
        webapps::InstallResultCode::kCancelledOnWebAppProviderShuttingDown, 1);
    histograms.ExpectBucketCount(
        SystemWebAppManager::kInstallResultHistogramName,
        webapps::InstallResultCode::kWebAppDisabled, 2);

    histograms.ExpectBucketCount(
        settings_app_install_result_histogram,
        webapps::InstallResultCode::kCancelledOnWebAppProviderShuttingDown, 1);
    histograms.ExpectBucketCount(
        profile_install_result_histogram,
        webapps::InstallResultCode::kCancelledOnWebAppProviderShuttingDown, 1);
    // If install was interrupted by shutdown, do not report duration.
    histograms.ExpectTotalCount(
        SystemWebAppManager::kInstallDurationHistogramName, 2);
  }
}

TEST_F(SystemWebAppManagerTest,
       InstallResultHistogram_ExcludeAlreadyInstalled) {
  base::HistogramTester histograms;
  const std::string settings_app_install_result_histogram =
      std::string(SystemWebAppManager::kInstallResultHistogramName) + ".Apps." +
      kSettingsAppInternalName;
  const std::string camera_app_install_result_histogram =
      std::string(SystemWebAppManager::kInstallResultHistogramName) + ".Apps." +
      kCameraAppInternalName;
  // Profile category for Chrome OS testing environment is "Other".
  const std::string profile_install_result_histogram =
      std::string(SystemWebAppManager::kInstallResultHistogramName) +
      ".Profiles.Other";

  SystemWebAppDelegateMap system_apps;
  system_apps.emplace(SystemWebAppType::SETTINGS,
                      std::make_unique<UnittestingSystemAppDelegate>(
                          SystemWebAppType::SETTINGS, kSettingsAppInternalName,
                          AppUrl1(), GetApp1WebAppInfoFactory()));
  system_apps.emplace(SystemWebAppType::CAMERA,
                      std::make_unique<UnittestingSystemAppDelegate>(
                          SystemWebAppType::CAMERA, kCameraAppInternalName,
                          AppUrl2(), GetApp2WebAppInfoFactory()));
  system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));

  externally_managed_app_manager().SetHandleInstallRequestCallback(
      base::BindLambdaForTesting(
          [](const web_app::ExternalInstallOptions& opts)
              -> web_app::ExternallyManagedAppManager::InstallResult {
            if (opts.install_url == AppUrl1())
              return web_app::ExternallyManagedAppManager::InstallResult(
                  webapps::InstallResultCode::kSuccessAlreadyInstalled);
            return web_app::ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessNewInstall);
          }));

  StartAndWaitForAppsToSynchronize();

  // Record results that aren't kSuccessAlreadyInstalled.
  histograms.ExpectTotalCount(SystemWebAppManager::kInstallResultHistogramName,
                              1);
  histograms.ExpectTotalCount(settings_app_install_result_histogram, 0);
  histograms.ExpectTotalCount(camera_app_install_result_histogram, 1);
  histograms.ExpectTotalCount(profile_install_result_histogram, 1);
}

TEST_F(SystemWebAppManagerTest,
       InstallDurationHistogram_ExcludeNonForceInstall) {
  base::HistogramTester histograms;

  SystemWebAppDelegateMap system_apps;
  system_apps.emplace(SystemWebAppType::SETTINGS,
                      std::make_unique<UnittestingSystemAppDelegate>(
                          SystemWebAppType::SETTINGS, kSettingsAppInternalName,
                          AppUrl1(), GetApp1WebAppInfoFactory()));
  system_apps.emplace(SystemWebAppType::CAMERA,
                      std::make_unique<UnittestingSystemAppDelegate>(
                          SystemWebAppType::CAMERA, kCameraAppInternalName,
                          AppUrl2(), GetApp2WebAppInfoFactory()));
  system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kOnVersionChange);

  {
    externally_managed_app_manager().SetHandleInstallRequestCallback(
        base::BindLambdaForTesting(
            [](const web_app::ExternalInstallOptions& opts)
                -> web_app::ExternallyManagedAppManager::InstallResult {
              if (opts.install_url == AppUrl1())
                return web_app::ExternallyManagedAppManager::InstallResult(
                    webapps::InstallResultCode::kWriteDataFailed);
              return web_app::ExternallyManagedAppManager::InstallResult(
                  webapps::InstallResultCode::kSuccessNewInstall);
            }));

    StartAndWaitForAppsToSynchronize();

    // The install duration histogram should be recorded, because the first
    // install happens on a clean profile.
    histograms.ExpectTotalCount(
        SystemWebAppManager::kInstallDurationHistogramName, 1);
  }

  {
    externally_managed_app_manager().SetHandleInstallRequestCallback(
        base::BindLambdaForTesting(
            [](const web_app::ExternalInstallOptions& opts)
                -> web_app::ExternallyManagedAppManager::InstallResult {
              if (opts.install_url == AppUrl1())
                return web_app::ExternallyManagedAppManager::InstallResult(
                    webapps::InstallResultCode::kSuccessNewInstall);
              return web_app::ExternallyManagedAppManager::InstallResult(
                  webapps::InstallResultCode::kSuccessAlreadyInstalled);
            }));
    StartAndWaitForAppsToSynchronize();

    // Don't record install duration histogram, because this time we don't ask
    // to force install all apps.
    histograms.ExpectTotalCount(
        SystemWebAppManager::kInstallDurationHistogramName, 1);
  }
}

TEST_F(SystemWebAppManagerTest, AbandonFailedInstalls) {
  const std::vector<web_app::ExternalInstallOptions>& install_requests =
      externally_managed_app_manager().install_requests();

  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kOnVersionChange);

  SystemWebAppDelegateMap system_apps;
  system_apps.emplace(SystemWebAppType::SETTINGS,
                      std::make_unique<UnittestingSystemAppDelegate>(
                          SystemWebAppType::SETTINGS, kSettingsAppInternalName,
                          AppUrl1(), GetApp1WebAppInfoFactory()));
  system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));

  system_web_app_manager().set_current_version(base::Version("1.0.0.0"));
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(1u, install_requests.size());
  EXPECT_TRUE(install_requests[0].force_reinstall);
  EXPECT_TRUE(IsInstalled(AppUrl1()));

  // Bump the version number, and an update will trigger, and force
  // reinstallation of both apps.
  //
  system_web_app_manager().set_current_version(base::Version("2.0.0.0"));

  // We use RunUntilIdle because the install requests are dropped, so
  // on_app_synchronized() won't be called.
  externally_managed_app_manager().SetDropRequestsForTesting(true);
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();

  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();

  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();

  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();

  // 1 successful, 1 abandoned, and 3 more abanonded retries is 5.
  EXPECT_EQ(5u, install_requests.size());
  EXPECT_TRUE(install_requests[1].force_reinstall);
  EXPECT_TRUE(install_requests[2].force_reinstall);
  EXPECT_TRUE(install_requests[3].force_reinstall);
  EXPECT_TRUE(install_requests[4].force_reinstall);

  EXPECT_TRUE(IsInstalled(AppUrl1()));

  // If we don't abandon at the same version, it doesn't even attempt another
  // request
  externally_managed_app_manager().SetDropRequestsForTesting(false);
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().set_current_version(base::Version("2.0.0.0"));
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();
  EXPECT_EQ(5u, install_requests.size());

  // Bump the version, and it works.
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().set_current_version(base::Version("3.0.0.0"));
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();

  EXPECT_EQ(6u, install_requests.size());
}

// Same test, but for locale change.
TEST_F(SystemWebAppManagerTest, AbandonFailedInstallsLocaleChange) {
  const std::vector<web_app::ExternalInstallOptions>& install_requests =
      externally_managed_app_manager().install_requests();

  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kOnVersionChange);

  SystemWebAppDelegateMap system_apps;
  system_apps.emplace(SystemWebAppType::SETTINGS,
                      std::make_unique<UnittestingSystemAppDelegate>(
                          SystemWebAppType::SETTINGS, kSettingsAppInternalName,
                          AppUrl1(), GetApp1WebAppInfoFactory()));

  system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));

  system_web_app_manager().set_current_version(base::Version("1.0.0.0"));
  system_web_app_manager().set_current_locale("en/us");
  StartAndWaitForAppsToSynchronize();

  EXPECT_EQ(1u, install_requests.size());
  EXPECT_TRUE(install_requests[0].force_reinstall);
  EXPECT_TRUE(IsInstalled(AppUrl1()));

  // Bump the version number, and an update will trigger, and force
  // reinstallation of both apps.
  system_web_app_manager().set_current_locale("en/au");
  externally_managed_app_manager().SetDropRequestsForTesting(true);
  system_web_app_manager().ResetForTesting();

  // We use RunUntilIdle because the install requests are dropped, so
  // on_app_synchronized() won't be called.
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();

  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();

  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();

  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();

  // 1 successful, 1 abandoned, and 3 more abanonded retries is 5.
  EXPECT_EQ(5u, install_requests.size());
  EXPECT_TRUE(install_requests[1].force_reinstall);
  EXPECT_TRUE(install_requests[2].force_reinstall);
  EXPECT_TRUE(install_requests[3].force_reinstall);
  EXPECT_TRUE(install_requests[4].force_reinstall);

  EXPECT_TRUE(IsInstalled(AppUrl1()));

  // If we don't abandon at the same version, it doesn't even attempt another
  // request
  externally_managed_app_manager().SetDropRequestsForTesting(false);
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();
  EXPECT_EQ(5u, install_requests.size());

  // Bump the version, and it works.
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().set_current_locale("fr/fr");
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();
}

TEST_F(SystemWebAppManagerTest, SucceedsAfterOneRetry) {
  const std::vector<web_app::ExternalInstallOptions>& install_requests =
      externally_managed_app_manager().install_requests();

  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kOnVersionChange);

  // Set up and install a baseline
  SystemWebAppDelegateMap system_apps;
  system_apps.emplace(SystemWebAppType::SETTINGS,
                      std::make_unique<UnittestingSystemAppDelegate>(
                          SystemWebAppType::SETTINGS, kSettingsAppInternalName,
                          AppUrl1(), GetApp1WebAppInfoFactory()));

  system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));

  system_web_app_manager().set_current_version(base::Version("1.0.0.0"));
  StartAndWaitForAppsToSynchronize();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();

  EXPECT_EQ(1u, install_requests.size());
  EXPECT_TRUE(install_requests[0].force_reinstall);
  EXPECT_TRUE(IsInstalled(AppUrl1()));
  // Bump the version number, and an update will trigger, and force
  // reinstallation. But, this fails!
  system_web_app_manager().set_current_version(base::Version("2.0.0.0"));
  externally_managed_app_manager().SetDropRequestsForTesting(true);
  system_web_app_manager().ResetForTesting();
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();

  EXPECT_EQ(2u, install_requests.size());
  EXPECT_TRUE(install_requests[1].force_reinstall);
  EXPECT_TRUE(IsInstalled(AppUrl1()));

  system_web_app_manager().ResetForTesting();
  system_web_app_manager().Start();
  AwaitSystemWebAppCommandsCompletePostStartup();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();

  // Retry a few times, but not until abandonment.
  EXPECT_EQ(3u, install_requests.size());
  EXPECT_TRUE(install_requests[0].force_reinstall);
  EXPECT_TRUE(install_requests[1].force_reinstall);
  EXPECT_TRUE(install_requests[2].force_reinstall);
  EXPECT_TRUE(IsInstalled(AppUrl1()));

  // Now we succeed at the same version
  externally_managed_app_manager().SetDropRequestsForTesting(false);
  StartAndWaitForAppsToSynchronize();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();

  StartAndWaitForAppsToSynchronize();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();
  EXPECT_EQ(5u, install_requests.size());
  EXPECT_TRUE(install_requests[3].force_reinstall);
  EXPECT_FALSE(install_requests[4].force_reinstall);

  // Bump the version number, and an update will trigger, and force
  // reinstallation of both apps. This succeeds, everything works.
  system_web_app_manager().set_current_version(base::Version("3.0.0.0"));

  StartAndWaitForAppsToSynchronize();
  externally_managed_app_manager().ClearSynchronizeRequestsForTesting();
  EXPECT_EQ(6u, install_requests.size());
  EXPECT_TRUE(install_requests[5].force_reinstall);

  StartAndWaitForAppsToSynchronize();
  EXPECT_EQ(7u, install_requests.size());
  EXPECT_FALSE(install_requests[6].force_reinstall);
}

TEST_F(SystemWebAppManagerTest, ForceReinstallFeature) {
  const std::vector<web_app::ExternalInstallOptions>& install_requests =
      externally_managed_app_manager().install_requests();

  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kOnVersionChange);

  // Register a test system app.
  SystemWebAppDelegateMap system_apps;
  system_apps.emplace(SystemWebAppType::SETTINGS,
                      std::make_unique<UnittestingSystemAppDelegate>(
                          SystemWebAppType::SETTINGS, kSettingsAppInternalName,
                          AppUrl1(), GetApp1WebAppInfoFactory()));

  system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));

  // Install the App normally.
  {
    StartAndWaitForAppsToSynchronize();

    EXPECT_EQ(1u, install_requests.size());
    EXPECT_TRUE(IsInstalled(AppUrl1()));
  }

  // Enable AlwaysReinstallSystemWebApps feature, verify force_reinstall is set.
  {
    base::test::ScopedFeatureList feature_reinstall;
    feature_reinstall.InitAndEnableFeature(
        features::kAlwaysReinstallSystemWebApps);

    StartAndWaitForAppsToSynchronize();

    EXPECT_EQ(2u, install_requests.size());
    EXPECT_TRUE(install_requests[1].force_reinstall);
    EXPECT_TRUE(IsInstalled(AppUrl1()));
  }
}

TEST_F(SystemWebAppManagerTest, IsSWABeforeSync) {
  system_web_app_manager().SetUpdatePolicy(
      SystemWebAppManager::UpdatePolicy::kOnVersionChange);

  // Set up and install a baseline
  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
  }
  system_web_app_manager().set_current_version(base::Version("1.0.0.0"));
  StartAndWaitForAppsToSynchronize();
  EXPECT_TRUE(system_web_app_manager().IsSystemWebApp(
      web_app::GenerateAppId(/*manifest_id=*/std::nullopt, AppUrl1())));

  auto unsynced_system_web_app_manager =
      std::make_unique<TestSystemWebAppManager>(profile());

  {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<UnittestingSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory()));
    unsynced_system_web_app_manager->SetSystemAppsForTesting(
        std::move(system_apps));
  }

  EXPECT_TRUE(unsynced_system_web_app_manager->IsSystemWebApp(
      web_app::GenerateAppId(/*manifest_id=*/std::nullopt, AppUrl1())));
}

class TimerSystemAppDelegate : public UnittestingSystemAppDelegate {
 public:
  TimerSystemAppDelegate(SystemWebAppType type,
                         const std::string& name,
                         const GURL& url,
                         web_app::WebAppInstallInfoFactory info_factory,
                         std::optional<base::TimeDelta> period,
                         bool open_immediately)
      : UnittestingSystemAppDelegate(type, name, url, std::move(info_factory)),
        period_(period),
        open_immediately_(open_immediately) {}
  std::optional<SystemWebAppBackgroundTaskInfo> GetTimerInfo() const override;

 private:
  std::optional<base::TimeDelta> period_;
  bool open_immediately_;
};

std::optional<SystemWebAppBackgroundTaskInfo>
TimerSystemAppDelegate::GetTimerInfo() const {
  return SystemWebAppBackgroundTaskInfo(period_, GetInstallUrl(),
                                        open_immediately_);
}

class SystemWebAppManagerTimerTest : public SystemWebAppManagerTest {
 public:
  SystemWebAppManagerTimerTest()
      : SystemWebAppManagerTest(
            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
  void SetupTimer(std::optional<base::TimeDelta> period,
                  bool open_immediately) {
    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(
        SystemWebAppType::SETTINGS,
        std::make_unique<TimerSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory(), period, open_immediately));

    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
  }

  void TearDown() override {
    // Normally, WebContents used to perform background tasks are released
    // during KeyedService shutdown. In tests, we need to release them before
    // fixture tear down.
    //
    // The parent fixture (RenderViewHostTestHarness::TearDown) expects
    // us to release WebContents before tearing down (which happens before
    // KeyedService shutdown because the parent fixture owns TestingProfile).
    //
    // If we don't StopBackgroundTasks (and release WebContents) here, the
    // fixture will complain about leaking RenderWidgetHost.
    system_web_app_manager().StopBackgroundTasksForTesting();
    SystemWebAppManagerTest::TearDown();
  }
};

TEST_F(SystemWebAppManagerTimerTest, BackgroundTaskDisabled) {
  // 1) Disabled app should not push to background tasks.
  {
    std::unique_ptr<TimerSystemAppDelegate> sys_app_delegate =
        std::make_unique<TimerSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory(), base::Seconds(60), false);

    sys_app_delegate->SetIsAppEnabled(false);

    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(SystemWebAppType::SETTINGS,
                        std::move(sys_app_delegate));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
    StartAndWaitForAppsToSynchronize();

    EXPECT_EQ(0u,
              system_web_app_manager().GetBackgroundTasksForTesting().size());
  }

  // 2) Enabled app should push to background tasks.
  {
    std::unique_ptr<TimerSystemAppDelegate> sys_app_delegate =
        std::make_unique<TimerSystemAppDelegate>(
            SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
            GetApp1WebAppInfoFactory(), base::Seconds(60), false);

    SystemWebAppDelegateMap system_apps;
    system_apps.emplace(SystemWebAppType::SETTINGS,
                        std::move(sys_app_delegate));
    system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));
    StartAndWaitForAppsToSynchronize();

    EXPECT_EQ(1u,
              system_web_app_manager().GetBackgroundTasksForTesting().size());
  }
}

TEST_F(SystemWebAppManagerTimerTest, TestTimer) {
  ui::ScopedSetIdleState idle(ui::IDLE_STATE_IDLE);
  SetupTimer(base::Seconds(60), false);
  StartAndWaitForAppsToSynchronize();

  auto& timers = system_web_app_manager().GetBackgroundTasksForTesting();
  EXPECT_EQ(1u, timers.size());
  EXPECT_EQ(false, timers[0]->open_immediately_for_testing());

  auto url_loader = std::make_unique<web_app::TestWebAppUrlLoader>();
  web_app::TestWebAppUrlLoader* loader = url_loader.get();
  timers[0]->SetUrlLoaderForTesting(std::move(url_loader));
  loader->SetNextLoadUrlResult(AppUrl1(),
                               webapps::WebAppUrlLoaderResult::kUrlLoaded);

  EXPECT_EQ(base::Seconds(60), timers[0]->period_for_testing());
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0u, timers[0]->timer_activated_count_for_testing());
  EXPECT_EQ(SystemWebAppBackgroundTask::INITIAL_WAIT,
            timers[0]->get_state_for_testing());
  EXPECT_EQ(0u, timers[0]->opened_count_for_testing());
  // Fast forward until the timer fires.
  task_environment()->FastForwardBy(base::Seconds(
      SystemWebAppBackgroundTask::kInitialWaitForBackgroundTasksSeconds));
  EXPECT_EQ(1u, timers[0]->timer_activated_count_for_testing());
  EXPECT_EQ(1u, timers[0]->opened_count_for_testing());

  loader->SetNextLoadUrlResult(AppUrl1(),
                               webapps::WebAppUrlLoaderResult::kUrlLoaded);

  EXPECT_EQ(SystemWebAppBackgroundTask::WAIT_PERIOD,
            timers[0]->get_state_for_testing());

  task_environment()->FastForwardBy(base::Seconds(60));

  EXPECT_EQ(2u, timers[0]->timer_activated_count_for_testing());
  EXPECT_EQ(2u, timers[0]->opened_count_for_testing());

  loader->SetNextLoadUrlResult(
      AppUrl1(), webapps::WebAppUrlLoaderResult::kFailedUnknownReason);

  task_environment()->FastForwardBy(base::Seconds(61));

  EXPECT_EQ(3u, timers[0]->timer_activated_count_for_testing());
  // The timer fired, but we couldn't open the page.
  EXPECT_EQ(2u, timers[0]->opened_count_for_testing());
}

TEST_F(SystemWebAppManagerTimerTest,
       TestTimerStartsImmediatelyThenRunsPeriodic) {
  ui::ScopedSetIdleState idle(ui::IDLE_STATE_IDLE);
  SetupTimer(base::Seconds(300), true);
  web_app::TestWebAppUrlLoader* loader = nullptr;
  SystemWebAppWaiter waiter(&system_web_app_manager());

  // We need to wait until the web contents and url loader are created to
  // intercept the url loader with a web_app::TestWebAppUrlLoader. Do that by
  // having a hook into on_apps_synchronized.
  system_web_app_manager().on_apps_synchronized().Post(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        auto& timers = system_web_app_manager().GetBackgroundTasksForTesting();

        auto url_loader = std::make_unique<web_app::TestWebAppUrlLoader>();
        loader = url_loader.get();
        timers[0]->SetUrlLoaderForTesting(std::move(url_loader));
        loader->SetNextLoadUrlResult(
            AppUrl1(), webapps::WebAppUrlLoaderResult::kUrlLoaded);
      }));
  system_web_app_manager().Start();
  waiter.Wait();

  auto& timers = system_web_app_manager().GetBackgroundTasksForTesting();
  EXPECT_EQ(SystemWebAppBackgroundTask::INITIAL_WAIT,
            timers[0]->get_state_for_testing());
  task_environment()->FastForwardBy(base::Seconds(121));
  EXPECT_EQ(1u, timers.size());
  EXPECT_EQ(true, timers[0]->open_immediately_for_testing());
  EXPECT_EQ(base::Seconds(300), timers[0]->period_for_testing());
  EXPECT_EQ(1u, timers[0]->timer_activated_count_for_testing());
  EXPECT_EQ(1u, timers[0]->opened_count_for_testing());
  EXPECT_EQ(SystemWebAppBackgroundTask::WAIT_PERIOD,
            timers[0]->get_state_for_testing());

  loader->SetNextLoadUrlResult(AppUrl1(),
                               webapps::WebAppUrlLoaderResult::kUrlLoaded);

  task_environment()->FastForwardBy(base::Seconds(300));

  EXPECT_EQ(2u, timers[0]->timer_activated_count_for_testing());
  EXPECT_EQ(2u, timers[0]->opened_count_for_testing());
}

TEST_F(SystemWebAppManagerTimerTest, TestTimerStartsImmediately) {
  ui::ScopedSetIdleState idle(ui::IDLE_STATE_IDLE);
  SetupTimer(std::nullopt, true);
  web_app::TestWebAppUrlLoader* loader = nullptr;
  SystemWebAppWaiter waiter(&system_web_app_manager());

  // We need to wait until the web contents and url loader are created to
  // intercept the url loader with a web_app::TestWebAppUrlLoader. Do that by
  // having a hook into on_apps_synchronized.
  system_web_app_manager().on_apps_synchronized().Post(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        auto& timers = system_web_app_manager().GetBackgroundTasksForTesting();

        auto url_loader = std::make_unique<web_app::TestWebAppUrlLoader>();
        loader = url_loader.get();
        timers[0]->SetUrlLoaderForTesting(std::move(url_loader));
        loader->SetNextLoadUrlResult(
            AppUrl1(), webapps::WebAppUrlLoaderResult::kUrlLoaded);
      }));
  system_web_app_manager().Start();
  waiter.Wait();

  auto& timers = system_web_app_manager().GetBackgroundTasksForTesting();
  EXPECT_EQ(SystemWebAppBackgroundTask::INITIAL_WAIT,
            timers[0]->get_state_for_testing());
  task_environment()->FastForwardBy(base::Seconds(121));
  EXPECT_EQ(1u, timers.size());
  EXPECT_EQ(true, timers[0]->open_immediately_for_testing());
  EXPECT_EQ(std::nullopt, timers[0]->period_for_testing());
  EXPECT_EQ(1u, timers[0]->timer_activated_count_for_testing());
  EXPECT_EQ(1u, timers[0]->opened_count_for_testing());

  timers[0]->web_contents_for_testing()->Close();

  EXPECT_EQ(nullptr, timers[0]->web_contents_for_testing());
  EXPECT_EQ(SystemWebAppBackgroundTask::WAIT_PERIOD,
            timers[0]->get_state_for_testing());

  loader->SetNextLoadUrlResult(AppUrl1(),
                               webapps::WebAppUrlLoaderResult::kUrlLoaded);

  task_environment()->FastForwardBy(base::Seconds(300));

  EXPECT_EQ(1u, timers[0]->timer_activated_count_for_testing());
  EXPECT_EQ(1u, timers[0]->opened_count_for_testing());
}

TEST_F(SystemWebAppManagerTimerTest, TestTimerWaitsForIdle) {
  ui::ScopedSetIdleState scoped_active(ui::IDLE_STATE_ACTIVE);
  SetupTimer(base::Seconds(300), true);

  web_app::TestWebAppUrlLoader* loader = nullptr;
  SystemWebAppWaiter waiter(&system_web_app_manager());

  // We need to wait until the web contents and url loader are created to
  // intercept the url loader with a web_app::TestWebAppUrlLoader. Do that by
  // having a hook into on_apps_synchronized.
  system_web_app_manager().on_apps_synchronized().Post(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        auto& timers = system_web_app_manager().GetBackgroundTasksForTesting();

        auto url_loader = std::make_unique<web_app::TestWebAppUrlLoader>();
        loader = url_loader.get();
        timers[0]->SetUrlLoaderForTesting(std::move(url_loader));
        loader->SetNextLoadUrlResult(
            AppUrl1(), webapps::WebAppUrlLoaderResult::kUrlLoaded);
      }));
  system_web_app_manager().Start();
  waiter.Wait();

  auto& timers = system_web_app_manager().GetBackgroundTasksForTesting();
  EXPECT_EQ(SystemWebAppBackgroundTask::INITIAL_WAIT,
            timers[0]->get_state_for_testing());
  task_environment()->FastForwardBy(base::Seconds(
      SystemWebAppBackgroundTask::kInitialWaitForBackgroundTasksSeconds));
  EXPECT_EQ(SystemWebAppBackgroundTask::WAIT_IDLE,
            timers[0]->get_state_for_testing());
  EXPECT_EQ(1u, timers.size());
  EXPECT_EQ(true, timers[0]->open_immediately_for_testing());
  EXPECT_EQ(base::Seconds(300), timers[0]->period_for_testing());
  EXPECT_EQ(SystemWebAppBackgroundTask::WAIT_IDLE,
            timers[0]->get_state_for_testing());
  EXPECT_EQ(0u, timers[0]->timer_activated_count_for_testing());
  EXPECT_EQ(0u, timers[0]->opened_count_for_testing());
  EXPECT_EQ(base::Time::Now(), timers[0]->polling_since_time_for_testing());

  {
    ui::ScopedSetIdleState scoped_idle(ui::IDLE_STATE_IDLE);
    task_environment()->FastForwardBy(base::Seconds(30));
    EXPECT_EQ(SystemWebAppBackgroundTask::WAIT_PERIOD,
              timers[0]->get_state_for_testing());
    EXPECT_EQ(1u, timers[0]->timer_activated_count_for_testing());
    EXPECT_EQ(1u, timers[0]->opened_count_for_testing());
    EXPECT_EQ(base::Time(), timers[0]->polling_since_time_for_testing());
    loader->SetNextLoadUrlResult(AppUrl1(),
                                 webapps::WebAppUrlLoaderResult::kUrlLoaded);
    task_environment()->FastForwardBy(base::Seconds(300));

    EXPECT_EQ(2u, timers[0]->timer_activated_count_for_testing());
    EXPECT_EQ(2u, timers[0]->opened_count_for_testing());
  }
  {
    ui::ScopedSetIdleState scoped_locked(ui::IDLE_STATE_LOCKED);
    loader->SetNextLoadUrlResult(AppUrl1(),
                                 webapps::WebAppUrlLoaderResult::kUrlLoaded);
    task_environment()->FastForwardBy(base::Seconds(300));
    EXPECT_EQ(SystemWebAppBackgroundTask::WAIT_PERIOD,
              timers[0]->get_state_for_testing());
    EXPECT_EQ(3u, timers[0]->timer_activated_count_for_testing());
    EXPECT_EQ(3u, timers[0]->opened_count_for_testing());
  }
}

TEST_F(SystemWebAppManagerTimerTest, TestTimerRunsAfterIdleLimitReached) {
  ui::ScopedSetIdleState idle(ui::IDLE_STATE_ACTIVE);
  SetupTimer(base::Seconds(300), true);

  web_app::TestWebAppUrlLoader* loader = nullptr;
  SystemWebAppWaiter waiter(&system_web_app_manager());

  // We need to wait until the web contents and url loader are created to
  // intercept the url loader with a web_app::TestWebAppUrlLoader. Do that by
  // having a hook into on_apps_synchronized.
  system_web_app_manager().on_apps_synchronized().Post(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        auto& timers = system_web_app_manager().GetBackgroundTasksForTesting();

        auto url_loader = std::make_unique<web_app::TestWebAppUrlLoader>();
        loader = url_loader.get();
        timers[0]->SetUrlLoaderForTesting(std::move(url_loader));
        loader->SetNextLoadUrlResult(
            AppUrl1(), webapps::WebAppUrlLoaderResult::kUrlLoaded);
      }));
  system_web_app_manager().Start();
  waiter.Wait();

  auto& timers = system_web_app_manager().GetBackgroundTasksForTesting();
  EXPECT_EQ(SystemWebAppBackgroundTask::INITIAL_WAIT,
            timers[0]->get_state_for_testing());
  task_environment()->FastForwardBy(base::Seconds(
      SystemWebAppBackgroundTask::kInitialWaitForBackgroundTasksSeconds));
  EXPECT_EQ(1u, timers.size());
  EXPECT_EQ(true, timers[0]->open_immediately_for_testing());
  EXPECT_EQ(base::Seconds(300), timers[0]->period_for_testing());
  EXPECT_EQ(SystemWebAppBackgroundTask::WAIT_IDLE,
            timers[0]->get_state_for_testing());
  EXPECT_EQ(0u, timers[0]->timer_activated_count_for_testing());
  EXPECT_EQ(0u, timers[0]->opened_count_for_testing());

  base::Time polling_since(timers[0]->polling_since_time_for_testing());
  // Poll up to not quite the maximum.
  task_environment()->FastForwardBy(base::Seconds(
      SystemWebAppBackgroundTask::kIdlePollMaxTimeToWaitSeconds - 1));
  EXPECT_EQ(SystemWebAppBackgroundTask::WAIT_IDLE,
            timers[0]->get_state_for_testing());
  EXPECT_EQ(polling_since, timers[0]->polling_since_time_for_testing());
  EXPECT_EQ(0u, timers[0]->timer_activated_count_for_testing());
  EXPECT_EQ(0u, timers[0]->opened_count_for_testing());

  // Poll to the maximum wait.
  task_environment()->FastForwardBy(base::Seconds(1));
  EXPECT_EQ(SystemWebAppBackgroundTask::WAIT_PERIOD,
            timers[0]->get_state_for_testing());
  EXPECT_EQ(1u, timers[0]->timer_activated_count_for_testing());
  EXPECT_EQ(base::Time(), timers[0]->polling_since_time_for_testing());
  EXPECT_EQ(1u, timers[0]->opened_count_for_testing());

  loader->SetNextLoadUrlResult(AppUrl1(),
                               webapps::WebAppUrlLoaderResult::kUrlLoaded);
}

TEST_F(SystemWebAppManagerTest,
       HonorsRegisteredAppsDespiteOfPersistedWebAppInfo) {
  SystemWebAppDelegateMap system_apps;
  system_apps.emplace(
      SystemWebAppType::SETTINGS,
      std::make_unique<UnittestingSystemAppDelegate>(
          SystemWebAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(),
          base::BindRepeating(&GetWebAppInstallInfo, AppUrl1())));
  system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps));

  base::RunLoop run_loop;
  system_web_app_manager().on_apps_synchronized().Post(FROM_HERE,
                                                       run_loop.QuitClosure());
  system_web_app_manager().Start();
  run_loop.Run();

  // App should be installed.
  auto opt_app_id =
      system_web_app_manager().GetAppIdForSystemApp(SystemWebAppType::SETTINGS);
  ASSERT_TRUE(opt_app_id.has_value());
  auto opt_type =
      system_web_app_manager().GetSystemAppTypeForAppId(*opt_app_id);
  ASSERT_TRUE(opt_type.has_value());
  ASSERT_EQ(SystemWebAppType::SETTINGS, *opt_type);

  // Creates a new SystemWebAppManager without the previously installed App.
  auto unsynced_system_web_app_manager =
      std::make_unique<TestSystemWebAppManager>(profile());

  // Before Apps are synchronized, WebAppRegistry should know about the App.
  const web_app::WebApp* web_app =
      provider().registrar_unsafe().GetAppById(*opt_app_id);
  ASSERT_TRUE(web_app);
  ASSERT_TRUE(web_app->client_data().system_web_app_data.has_value());
  ASSERT_EQ(SystemWebAppType::SETTINGS,
            web_app->client_data().system_web_app_data->system_app_type);

  // Checks the new SystemWebAppManager reports the App being non-SWA.
  auto opt_app_id2 = unsynced_system_web_app_manager->GetAppIdForSystemApp(
      SystemWebAppType::SETTINGS);
  EXPECT_FALSE(opt_app_id2.has_value());
  auto opt_type2 =
      unsynced_system_web_app_manager->GetSystemAppTypeForAppId(*opt_app_id);
  EXPECT_FALSE(opt_type2.has_value());
  EXPECT_FALSE(unsynced_system_web_app_manager->IsSystemWebApp(*opt_app_id));
}

TEST_F(SystemWebAppManagerTest, DestroyUiManager) {
  StartAndWaitForAppsToSynchronize();

  base::RunLoop run_loop;
  TestUiManagerObserver observer{&provider().ui_manager()};
  observer.SetUiManagerDestroyedCallback(run_loop.QuitClosure());

  // Should not crash.
  provider().ShutDownUiManagerForTesting();
  run_loop.Run();
}

class SystemWebAppManagerInKioskTest : public ChromeRenderViewHostTestHarness {
 public:
  template <typename... TaskEnvironmentTraits>
  explicit SystemWebAppManagerInKioskTest(TaskEnvironmentTraits&&... traits)
      : ChromeRenderViewHostTestHarness(
            std::forward<TaskEnvironmentTraits>(traits)...) {}
  SystemWebAppManagerInKioskTest(const SystemWebAppManagerInKioskTest&) =
      delete;
  SystemWebAppManagerInKioskTest& operator=(
      const SystemWebAppManagerInKioskTest&) = delete;

  ~SystemWebAppManagerInKioskTest() override = default;

  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();
    chromeos::SetUpFakeKioskSession();
  }

  void TearDown() override {
    ChromeRenderViewHostTestHarness::TearDown();
  }
};

// Checks that SWA manager is not created in Kiosk sessions.
TEST_F(SystemWebAppManagerInKioskTest, ShouldNotCreateManagerByDefault) {
  EXPECT_FALSE(SystemWebAppManager::Get(profile()));
}

// Checks that SWA manager is created in Kiosk sessions if the feature is
// enabled.
TEST_F(SystemWebAppManagerInKioskTest, ShouldCreateManagerIfEnabled) {
  base::test::ScopedFeatureList scoped_feature_list(
      ash::features::kKioskEnableSystemWebApps);
  EXPECT_TRUE(SystemWebAppManager::Get(profile()));
}

}  // namespace ash