chromium/chrome/browser/apps/app_service/publishers/arc_apps_unittest.cc

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

#include "chrome/browser/apps/app_service/publishers/arc_apps.h"

#include <functional>
#include <memory>

#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_prefs.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/mojom/app.mojom.h"
#include "ash/components/arc/mojom/intent_helper.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/components/arc/test/connection_holder_util.h"
#include "ash/components/arc/test/fake_app_instance.h"
#include "ash/components/arc/test/fake_file_system_instance.h"
#include "ash/constants/ash_features.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/app_service_test.h"
#include "chrome/browser/apps/app_service/launch_result_type.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_service.h"
#include "chrome/browser/apps/app_service/publishers/arc_apps_factory.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ash/app_list/arc/arc_app_test.h"
#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ash/app_list/arc/intent.h"
#include "chrome/browser/ash/apps/apk_web_app_service.h"
#include "chrome/browser/ash/arc/fileapi/arc_file_system_bridge.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/ash/system_web_apps/types/system_web_app_delegate_map.h"
#include "chrome/browser/policy/system_features_disable_list_policy_handler.h"
#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/arc/intent_helper/arc_intent_helper_bridge.h"
#include "components/arc/intent_helper/intent_constants.h"
#include "components/arc/intent_helper/intent_filter.h"
#include "components/arc/test/fake_intent_helper_instance.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/intent_filter_util.h"
#include "components/services/app_service/public/cpp/intent_util.h"
#include "components/services/app_service/public/cpp/permission.h"
#include "components/services/app_service/public/cpp/preferred_apps_list_handle.h"
#include "content/public/test/browser_task_environment.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/common/file_system/file_system_mount_option.h"
#include "storage/common/file_system/file_system_types.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/utility/utility.h"

namespace {

const char kTestPackageName[] = "com.example.this";
const apps::PackageId kTestPackageId(apps::PackageType::kArc,
                                     "com.example.this");

std::vector<arc::IntentFilter> CreateFilterList(
    const std::string& package_name,
    const std::vector<std::string>& authorities) {
  std::vector<arc::IntentFilter::AuthorityEntry> filter_authorities;
  for (const std::string& authority : authorities) {
    filter_authorities.emplace_back(authority, 0);
  }
  std::vector<arc::IntentFilter::PatternMatcher> patterns;
  patterns.emplace_back("/", arc::mojom::PatternType::PATTERN_PREFIX);

  auto filter = arc::IntentFilter(package_name, {arc::kIntentActionView},
                                  std::move(filter_authorities),
                                  std::move(patterns), {"https"}, {});
  std::vector<arc::IntentFilter> filters;
  filters.push_back(std::move(filter));
  return filters;
}

apps::IntentFilters CreateIntentFilters(
    const std::vector<std::string>& authorities) {
  apps::IntentFilters filters;
  apps::IntentFilterPtr filter = std::make_unique<apps::IntentFilter>();

  apps::ConditionValues values1;
  values1.push_back(std::make_unique<apps::ConditionValue>(
      apps_util::kIntentActionView, apps::PatternMatchType::kLiteral));
  filter->conditions.push_back(std::make_unique<apps::Condition>(
      apps::ConditionType::kAction, std::move(values1)));

  apps::ConditionValues values2;
  values2.push_back(std::make_unique<apps::ConditionValue>(
      "https", apps::PatternMatchType::kLiteral));
  filter->conditions.push_back(std::make_unique<apps::Condition>(
      apps::ConditionType::kScheme, std::move(values2)));

  apps::ConditionValues values;
  for (const std::string& authority : authorities) {
    values.push_back(std::make_unique<apps::ConditionValue>(
        authority, apps::PatternMatchType::kLiteral));
  }
  filter->conditions.push_back(std::make_unique<apps::Condition>(
      apps::ConditionType::kAuthority, std::move(values)));

  apps::ConditionValues values3;
  values3.push_back(std::make_unique<apps::ConditionValue>(
      "/", apps::PatternMatchType::kPrefix));
  filter->conditions.push_back(std::make_unique<apps::Condition>(
      apps::ConditionType::kPath, std::move(values3)));

  filters.push_back(std::move(filter));

  return filters;
}

// Returns a FileSystemURL, encoded as a GURL, that points to a file in the
// Downloads directory.
GURL FileInDownloads(Profile* profile, base::FilePath file) {
  url::Origin origin = file_manager::util::GetFilesAppOrigin();
  std::string mount_point_name =
      file_manager::util::GetDownloadsMountPointName(profile);
  storage::ExternalMountPoints* mount_points =
      storage::ExternalMountPoints::GetSystemInstance();
  mount_points->RegisterFileSystem(
      mount_point_name, storage::kFileSystemTypeLocal,
      storage::FileSystemMountOption(),
      file_manager::util::GetDownloadsFolderForProfile(profile));
  return mount_points
      ->CreateExternalFileSystemURL(blink::StorageKey::CreateFirstParty(origin),
                                    mount_point_name, file)
      .ToGURL();
}

std::vector<arc::mojom::AppInfoPtr> GetArcSettingsAppInfo() {
  std::vector<arc::mojom::AppInfoPtr> apps;
  arc::mojom::AppInfoPtr app(arc::mojom::AppInfo::New());
  app->name = "settings";
  app->package_name = "com.android.settings";
  app->activity = "com.android.settings.Settings";
  app->sticky = false;
  apps.push_back(std::move(app));
  return apps;
}

}  // namespace

class ArcAppsPublisherTest : public testing::Test {
 public:
  ArcAppsPublisherTest()
      : local_state_(std::make_unique<ScopedTestingLocalState>(
            TestingBrowserProcess::GetGlobal())) {}
  void SetUp() override {
    testing::Test::SetUp();

    profile_ = MakeProfile();

    // Do not destroy the ArcServiceManager during TearDown, so that Arc
    // KeyedServices can be correctly destroyed during profile shutdown.
    arc_test_.set_persist_service_manager(true);
    // We will manually start ArcApps after setting up IntentHelper, this allows
    // ArcApps to observe the correct IntentHelper during initialization.
    arc_test_.set_start_app_service_publisher(false);
    // We want to use the real ArcIntentHelper KeyedService so that it's the
    // same object that ArcApps uses.
    arc_test_.set_initialize_real_intent_helper_bridge(true);
    arc_test_.SetUp(profile());

    auto* arc_bridge_service =
        arc_test_.arc_service_manager()->arc_bridge_service();

    intent_helper_ =
        arc::ArcIntentHelperBridge::GetForBrowserContext(profile());
    arc_file_system_bridge_ = std::make_unique<arc::ArcFileSystemBridge>(
        profile(), arc_bridge_service);

    auto web_app_policy_manager =
        std::make_unique<web_app::WebAppPolicyManager>(profile());
    web_app_policy_manager->SetSystemWebAppDelegateMap(&system_apps_);
    auto* provider = web_app::FakeWebAppProvider::Get(profile());
    provider->SetWebAppPolicyManager(std::move(web_app_policy_manager));
    web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());

    app_service_test_.SetUp(profile_.get());
    apps::ArcAppsFactory::GetForProfile(profile());
    // Ensure that the PreferredAppsList is fully initialized before running the
    // test.
    task_environment_.RunUntilIdle();
  }

  void TearDown() override {
    arc_test_.StopArcInstance();
    apps::ArcAppsFactory::GetInstance()->ShutDownForTesting(profile());
    arc_test_.TearDown();
  }

  virtual std::unique_ptr<TestingProfile> MakeProfile() {
    return std::make_unique<TestingProfile>();
  }

  void VerifyIntentFilters(const std::string& app_id,
                           const std::vector<std::string>& authorities) {
    apps::IntentFilters source = CreateIntentFilters(authorities);

    apps::IntentFilters target;
    apps::AppServiceProxyFactory::GetForProfile(profile())
        ->AppRegistryCache()
        .ForOneApp(app_id, [&target](const apps::AppUpdate& update) {
          target = update.IntentFilters();
        });

    EXPECT_EQ(source.size(), target.size());
    for (size_t i = 0; i < source.size(); i++) {
      EXPECT_EQ(*source[i], *target[i]);
    }
  }

  void SetUpFileSystemInstance() {
    auto* arc_bridge_service =
        arc_test()->arc_service_manager()->arc_bridge_service();
    file_system_instance_ = std::make_unique<arc::FakeFileSystemInstance>();
    arc_bridge_service->file_system()->SetInstance(file_system_instance());
    arc::WaitForInstanceReady(arc_bridge_service->file_system());
  }

  TestingProfile* profile() { return profile_.get(); }

  apps::AppServiceProxy* app_service_proxy() {
    return apps::AppServiceProxyFactory::GetForProfile(profile());
  }

  arc::ArcIntentHelperBridge* intent_helper() { return intent_helper_; }

  arc::FakeIntentHelperInstance* intent_helper_instance() {
    return arc_test_.intent_helper_instance();
  }

  arc::FakeFileSystemInstance* file_system_instance() {
    return file_system_instance_.get();
  }

  ArcAppTest* arc_test() { return &arc_test_; }

  apps::PreferredAppsListHandle& preferred_apps() {
    return apps::AppServiceProxyFactory::GetForProfile(profile())
        ->PreferredAppsList();
  }

  std::vector<arc::mojom::SupportedLinksPackagePtr> CreateSupportedLinks(
      const std::string& package_name) {
    std::vector<arc::mojom::SupportedLinksPackagePtr> result;
    auto link = arc::mojom::SupportedLinksPackage::New();
    link->package_name = package_name;
    result.push_back(std::move(link));

    return result;
  }

 protected:
  std::unique_ptr<ScopedTestingLocalState> local_state_;

 private:
  content::BrowserTaskEnvironment task_environment_;
  ash::SystemWebAppDelegateMap system_apps_;
  ArcAppTest arc_test_;
  std::unique_ptr<TestingProfile> profile_;
  apps::AppServiceTest app_service_test_;
  raw_ptr<arc::ArcIntentHelperBridge, DanglingUntriaged> intent_helper_;
  std::unique_ptr<arc::FakeFileSystemInstance> file_system_instance_;
  std::unique_ptr<arc::ArcFileSystemBridge> arc_file_system_bridge_;
};

// Verifies that a call to set the supported links preference from the ARC
// system doesn't change the setting in app service.
TEST_F(ArcAppsPublisherTest, SetSupportedLinksFromArcSystem) {
  constexpr char kTestAuthority[] = "www.example.com";
  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  // Update intent filters and supported links for the app, as if it was just
  // installed.
  intent_helper()->OnIntentFiltersUpdatedForPackage(
      package_name, CreateFilterList(package_name, {kTestAuthority}));
  VerifyIntentFilters(app_id, {kTestAuthority});
  intent_helper()->OnSupportedLinksChanged(
      CreateSupportedLinks(package_name), {},
      arc::mojom::SupportedLinkChangeSource::kArcSystem);

  ASSERT_EQ(std::nullopt, preferred_apps().FindPreferredAppForUrl(
                              GURL("https://www.example.com/foo")));
}

// Verifies that a call to set the supported links preference from App Service
// syncs the setting to ARC.
TEST_F(ArcAppsPublisherTest, SetSupportedLinksFromAppService) {
  constexpr char kTestAuthority[] = "www.example.com";
  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);
  intent_helper()->OnIntentFiltersUpdatedForPackage(
      package_name, CreateFilterList(package_name, {kTestAuthority}));
  VerifyIntentFilters(app_id, {kTestAuthority});

  apps::AppServiceProxyFactory::GetForProfile(profile())
      ->SetSupportedLinksPreference(app_id);

  ASSERT_TRUE(
      intent_helper_instance()->verified_links().find(package_name)->second);
}

// Verifies that the ARC system can still update preferred intent filters for
// apps which are already preferred.
TEST_F(ArcAppsPublisherTest, SetSupportedLinksAllowsUpdates) {
  constexpr char kTestAuthority[] = "www.example.com";
  constexpr char kTestAuthority2[] = "www.newexample.com";
  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  // Update intent filters and supported links for the app, as if it was just
  // installed.
  intent_helper()->OnIntentFiltersUpdatedForPackage(
      package_name, CreateFilterList(package_name, {kTestAuthority}));
  VerifyIntentFilters(app_id, {kTestAuthority});

  // Set a user preference for the app.
  apps::AppServiceProxyFactory::GetForProfile(profile())
      ->SetSupportedLinksPreference(app_id);

  // Update filters with a new authority added.
  intent_helper()->OnIntentFiltersUpdatedForPackage(
      package_name,
      CreateFilterList(package_name, {kTestAuthority, kTestAuthority2}));
  VerifyIntentFilters(app_id, {kTestAuthority, kTestAuthority2});
  intent_helper()->OnSupportedLinksChanged(
      CreateSupportedLinks(package_name), {},
      arc::mojom::SupportedLinkChangeSource::kArcSystem);

  // Verify that the user preference has been extended to the new filter.
  ASSERT_EQ(app_id, preferred_apps().FindPreferredAppForUrl(
                        GURL("https://www.newexample.com/foo")));
}

// Verifies that the user can set an app as preferred through ARC settings.
TEST_F(ArcAppsPublisherTest, SetSupportedLinksAllowsUserChanges) {
  constexpr char kTestAuthority[] = "www.example.com";
  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  // Update intent filters and supported links for the app, as if it was just
  // installed.
  intent_helper()->OnIntentFiltersUpdatedForPackage(
      package_name, CreateFilterList(package_name, {kTestAuthority}));
  intent_helper()->OnSupportedLinksChanged(
      CreateSupportedLinks(package_name), {},
      arc::mojom::SupportedLinkChangeSource::kUserPreference);

  ASSERT_EQ(app_id, preferred_apps().FindPreferredAppForUrl(
                        GURL("https://www.example.com/foo")));
}

// Verifies that the Play Store app can be set as preferred by the system.
TEST_F(ArcAppsPublisherTest, SetSupportedLinksAllowsPlayStoreDefault) {
  constexpr char kTestAuthority[] = "play.google.com";

  std::vector<arc::mojom::AppInfoPtr> apps;
  apps.push_back(arc::mojom::AppInfo::New("Play Store", arc::kPlayStorePackage,
                                          arc::kPlayStoreActivity));
  arc_test()->app_instance()->SendRefreshAppList(apps);

  // Update intent filters and supported links for the app, as if it was just
  // installed.
  intent_helper()->OnIntentFiltersUpdatedForPackage(
      arc::kPlayStorePackage,
      CreateFilterList(arc::kPlayStorePackage, {kTestAuthority}));
  intent_helper()->OnSupportedLinksChanged(
      CreateSupportedLinks(arc::kPlayStorePackage), {},
      arc::mojom::SupportedLinkChangeSource::kArcSystem);

  ASSERT_EQ(arc::kPlayStoreAppId, preferred_apps().FindPreferredAppForUrl(
                                      GURL("https://play.google.com/foo")));
}

// Verifies that disabling OS settings by SystemFeaturesDisableList policy will
// disable ARC settings as well. Clearing the policy should re-enable ARC
// settings.
TEST_F(ArcAppsPublisherTest, DisableOSSettingArcSettings) {
  arc_test()->app_instance()->SendRefreshAppList(GetArcSettingsAppInfo());

  // Change SystemFeaturesDisableList policy to disable OS Setting.
  {
    ScopedListPrefUpdate update(
        local_state_->Get(), policy::policy_prefs::kSystemFeaturesDisableList);
    update->Append(static_cast<int>(policy::SystemFeature::kOsSettings));
  }

  // Verify that ARC settings readiness is set to disabled by policy.
  bool found = app_service_proxy()->AppRegistryCache().ForOneApp(
      arc::kSettingsAppId, [](const apps::AppUpdate& update) {
        EXPECT_EQ(update.Readiness(), apps::Readiness::kDisabledByPolicy);
      });
  ASSERT_TRUE(found);

  // Clear SystemFeaturesDisableList policy.
  {
    ScopedListPrefUpdate update(
        local_state_->Get(), policy::policy_prefs::kSystemFeaturesDisableList);
    update->clear();
  }

  // Verify that ARC settings readiness is set to ready.
  found = false;
  found = app_service_proxy()->AppRegistryCache().ForOneApp(
      arc::kSettingsAppId, [](const apps::AppUpdate& update) {
        EXPECT_EQ(update.Readiness(), apps::Readiness::kReady);
      });
  ASSERT_TRUE(found);
}

// Verifies that disabling OS settings by SystemFeaturesDisableList policy and
// re-enabling does not remove the local settings block.
TEST_F(ArcAppsPublisherTest, DisableAndBlockOSSettingArcSettings) {
  arc_test()->app_instance()->SendRefreshAppList(GetArcSettingsAppInfo());

  // Change SystemFeaturesDisableList policy to disable OS Setting.
  {
    ScopedListPrefUpdate update(
        local_state_->Get(), policy::policy_prefs::kSystemFeaturesDisableList);
    update->Append(static_cast<int>(policy::SystemFeature::kOsSettings));
  }

  // Verify that ARC settings readiness is set to disabled by policy.
  bool found = app_service_proxy()->AppRegistryCache().ForOneApp(
      arc::kSettingsAppId, [](const apps::AppUpdate& update) {
        EXPECT_EQ(update.Readiness(), apps::Readiness::kDisabledByPolicy);
      });
  ASSERT_TRUE(found);

  // Blocks the ARC settings app. It stays in kDisabledByPolicy.
  app_service_proxy()->BlockApps({arc::kSettingsAppId});
  found = app_service_proxy()->AppRegistryCache().ForOneApp(
      arc::kSettingsAppId, [](const apps::AppUpdate& update) {
        EXPECT_EQ(update.Readiness(), apps::Readiness::kDisabledByPolicy);
      });
  ASSERT_TRUE(found);

  // Clear SystemFeaturesDisableList policy.
  {
    ScopedListPrefUpdate update(
        local_state_->Get(), policy::policy_prefs::kSystemFeaturesDisableList);
    update->clear();
  }

  // ARC settings should be in kDisabledByLocalSettings.
  found = app_service_proxy()->AppRegistryCache().ForOneApp(
      arc::kSettingsAppId, [](const apps::AppUpdate& update) {
        EXPECT_EQ(update.Readiness(),
                  apps::Readiness::kDisabledByLocalSettings);
      });
  ASSERT_TRUE(found);

  // Unblocks the app.
  app_service_proxy()->UnblockApps({arc::kSettingsAppId});

  // Verify that ARC settings readiness is set to ready.
  found = app_service_proxy()->AppRegistryCache().ForOneApp(
      arc::kSettingsAppId, [](const apps::AppUpdate& update) {
        EXPECT_EQ(update.Readiness(), apps::Readiness::kReady);
      });
  ASSERT_TRUE(found);
}

class ArcAppsPublisherManagedProfileTest : public ArcAppsPublisherTest {
 public:
  std::unique_ptr<TestingProfile> MakeProfile() override {
    TestingProfile::Builder builder;
    builder.OverridePolicyConnectorIsManagedForTesting(true);
    return builder.Build();
  }
};

// Verifies that a call to set the default supported links preference from the
// ARC system changes the app service setting, for a managed profile.
TEST_F(ArcAppsPublisherManagedProfileTest, SetSupportedLinksByDefault) {
  constexpr char kTestAuthority[] = "www.example.com";
  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  // Update intent filters and supported links for the app, as if it was just
  // installed.
  intent_helper()->OnIntentFiltersUpdatedForPackage(
      package_name, CreateFilterList(package_name, {kTestAuthority}));
  VerifyIntentFilters(app_id, {kTestAuthority});
  intent_helper()->OnSupportedLinksChanged(
      CreateSupportedLinks(package_name), {},
      arc::mojom::SupportedLinkChangeSource::kArcSystem);

  ASSERT_EQ(app_id, preferred_apps().FindPreferredAppForUrl(
                        GURL("https://www.example.com/foo")));
}

TEST_F(ArcAppsPublisherManagedProfileTest,
       SetSupportedLinksIgnoresWorkspaceInstall) {
  constexpr char kTestAuthority[] = "drive.google.com";
  std::string package_name = "com.google.android.apps.docs";
  std::string activity_name = base::StrCat({package_name, ".MainActivity"});
  std::string app_id = ArcAppListPrefs::GetAppId(package_name, activity_name);

  arc::mojom::AppInfoPtr app =
      arc::mojom::AppInfo::New("Google Drive", package_name, activity_name);
  std::vector<arc::mojom::AppInfoPtr> app_list;
  app_list.push_back(std::move(app));

  arc_test()->app_instance()->SendRefreshAppList(std::move(app_list));

  // Update intent filters and supported links for the app, as if it was just
  // installed.
  intent_helper()->OnIntentFiltersUpdatedForPackage(
      package_name, CreateFilterList(package_name, {kTestAuthority}));
  VerifyIntentFilters(app_id, {kTestAuthority});
  intent_helper()->OnSupportedLinksChanged(
      CreateSupportedLinks(package_name), {},
      arc::mojom::SupportedLinkChangeSource::kArcSystem);

  ASSERT_EQ(std::nullopt, preferred_apps().FindPreferredAppForUrl(
                              GURL("https://drive.google.com/foo")));
}

TEST_F(ArcAppsPublisherManagedProfileTest,
       SetSupportedLinksAllowsWorkspaceUserChange) {
  constexpr char kTestAuthority[] = "docs.google.com";
  std::string package_name = "com.google.android.apps.docs.editor.docs";
  std::string activity_name = base::StrCat({package_name, ".MainActivity"});
  std::string app_id = ArcAppListPrefs::GetAppId(package_name, activity_name);

  arc::mojom::AppInfoPtr app =
      arc::mojom::AppInfo::New("Google Docs", package_name, activity_name);
  std::vector<arc::mojom::AppInfoPtr> app_list;
  app_list.push_back(std::move(app));

  arc_test()->app_instance()->SendRefreshAppList(std::move(app_list));
  intent_helper()->OnIntentFiltersUpdatedForPackage(
      package_name, CreateFilterList(package_name, {kTestAuthority}));

  intent_helper()->OnSupportedLinksChanged(
      CreateSupportedLinks(package_name), {},
      arc::mojom::SupportedLinkChangeSource::kUserPreference);

  ASSERT_EQ(app_id, preferred_apps().FindPreferredAppForUrl(
                        GURL("https://docs.google.com/document/")));
}

TEST_F(ArcAppsPublisherManagedProfileTest,
       SetSupportedLinksAllowsWorkspaceUpdate) {
  constexpr char kDriveAuthority[] = "drive.google.com";
  constexpr char kDocsAuthority[] = "docs.google.com";
  std::string package_name = "com.google.android.apps.docs";
  std::string activity_name = base::StrCat({package_name, ".MainActivity"});
  std::string app_id = ArcAppListPrefs::GetAppId(package_name, activity_name);

  arc::mojom::AppInfoPtr app =
      arc::mojom::AppInfo::New("Google Drive", package_name, activity_name);
  std::vector<arc::mojom::AppInfoPtr> app_list;
  app_list.push_back(std::move(app));
  arc_test()->app_instance()->SendRefreshAppList(std::move(app_list));
  intent_helper()->OnIntentFiltersUpdatedForPackage(
      package_name, CreateFilterList(package_name, {kDriveAuthority}));

  apps::AppServiceProxyFactory::GetForProfile(profile())
      ->SetSupportedLinksPreference(app_id);
  ASSERT_EQ(app_id, preferred_apps().FindPreferredAppForUrl(
                        GURL("https://drive.google.com/foo")));

  // Simulate the app being updated to add a new intent filter.
  intent_helper()->OnIntentFiltersUpdatedForPackage(
      package_name,
      CreateFilterList(package_name, {kDriveAuthority, kDocsAuthority}));
  intent_helper()->OnSupportedLinksChanged(
      CreateSupportedLinks(package_name), {},
      arc::mojom::SupportedLinkChangeSource::kArcSystem);

  // Verify that the new intent filter is also marked as preferred.
  ASSERT_EQ(app_id, preferred_apps().FindPreferredAppForUrl(
                        GURL("https://docs.google.com/document")));
}

// Verifies that ARC permissions are published to App Service correctly.
TEST_F(ArcAppsPublisherTest, PublishPermission) {
  constexpr char kPackageName[] = "com.test.package";
  constexpr char kActivityName[] = "com.test.package.activity";

  const std::string kAppId =
      ArcAppListPrefs::GetAppId(kPackageName, kActivityName);

  std::vector<arc::mojom::AppInfoPtr> apps;
  apps.push_back(
      arc::mojom::AppInfo::New("Fake app", kPackageName, kActivityName));
  arc_test()->app_instance()->SendRefreshAppList(apps);

  std::vector<arc::mojom::ArcPackageInfoPtr> packages;

  auto package = arc::mojom::ArcPackageInfo::New(
      kPackageName, /*package_version=*/1, /*last_backup_android_id=*/1,
      /*last_backup_time=*/1, /*sync=*/true);

  base::flat_map<arc::mojom::AppPermission, arc::mojom::PermissionStatePtr>
      permissions;

  permissions.emplace(arc::mojom::AppPermission::CAMERA,
                      arc::mojom::PermissionState::New(
                          /*granted=*/true, /*managed=*/false,
                          /*details=*/std::nullopt, /*one_time=*/true));
  permissions.emplace(
      arc::mojom::AppPermission::LOCATION,
      arc::mojom::PermissionState::New(/*granted=*/true, /*managed=*/true,
                                       /*details=*/"While in use"));

  package->permission_states = std::move(permissions);
  packages.push_back(std::move(package));

  arc_test()->app_instance()->SendRefreshPackageList(std::move(packages));

  apps::Permissions result;
  bool found = app_service_proxy()->AppRegistryCache().ForOneApp(
      kAppId, [&result](const apps::AppUpdate& update) {
        result = ClonePermissions(update.Permissions());
      });
  ASSERT_TRUE(found);

  EXPECT_EQ(result.size(), 2ul);

  // Sort permissions by permission type.
  base::ranges::sort(result, std::less<>(), &apps::Permission::permission_type);

  EXPECT_EQ(result[0]->permission_type, apps::PermissionType::kCamera);
  EXPECT_EQ(absl::get<apps::TriState>(result[0]->value), apps::TriState::kAsk);
  EXPECT_FALSE(result[0]->is_managed);
  EXPECT_EQ(result[0]->details, std::nullopt);

  EXPECT_EQ(result[1]->permission_type, apps::PermissionType::kLocation);
  EXPECT_TRUE(result[1]->IsPermissionEnabled());
  EXPECT_TRUE(result[1]->is_managed);
  EXPECT_EQ(result[1]->details, "While in use");
}

TEST_F(ArcAppsPublisherTest,
       LaunchAppWithIntent_EditIntent_SendsOpenUrlRequest) {
  SetUpFileSystemInstance();
  auto intent = apps_util::MakeEditIntent(
      FileInDownloads(profile(), base::FilePath("test.txt")), "text/plain");

  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  std::optional<apps::State> result;
  app_service_proxy()->LaunchAppWithIntent(
      app_id, 0, std::move(intent), apps::LaunchSource::kFromFileManager,
      /*window_info=*/nullptr,
      base::BindLambdaForTesting(
          [&result](apps::LaunchResult&& callback_result) {
            result = callback_result.state;
          }));

  ASSERT_EQ(apps::State::kSuccess, result.value_or(apps::State::kFailed));

  ASSERT_EQ(file_system_instance()->handledUrlRequests().size(), 1u);
  auto& url_request = file_system_instance()->handledUrlRequests()[0];
  ASSERT_EQ(url_request->action_type, arc::mojom::ActionType::EDIT);
  ASSERT_EQ(url_request->urls.size(), 1u);
  ASSERT_EQ(url_request->urls[0]->mime_type, "text/plain");
  ASSERT_TRUE(
      base::EndsWith(url_request->urls[0]->content_url.spec(), "test.txt"));
}

TEST_F(ArcAppsPublisherTest,
       LaunchAppWithIntent_EditIntent_NoArcFileSystem_ReturnsFalse) {
  // Do not start up ArcFileSystem, to simulate the intent being sent before ARC
  // starts.
  auto intent = apps_util::MakeEditIntent(
      FileInDownloads(profile(), base::FilePath("test.txt")), "text/plain");

  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  std::optional<apps::State> result;
  app_service_proxy()->LaunchAppWithIntent(
      app_id, 0, std::move(intent), apps::LaunchSource::kFromFileManager,
      /*window_info=*/nullptr,
      base::BindLambdaForTesting(
          [&result](apps::LaunchResult&& callback_result) {
            result = callback_result.state;
          }));

  ASSERT_EQ(apps::State::kFailed, result.value_or(apps::State::kSuccess));
}

TEST_F(
    ArcAppsPublisherTest,
    LaunchAppWithIntent_ViewFileIntent_SendsOpenUrlRequestWithIndividualFileMimeTypes) {
  SetUpFileSystemInstance();

  auto file1 = std::make_unique<apps::IntentFile>(
      FileInDownloads(profile(), base::FilePath("test1.png")));
  file1->mime_type = "image/png";

  auto file2 = std::make_unique<apps::IntentFile>(
      FileInDownloads(profile(), base::FilePath("test2.jpeg")));
  file2->mime_type = "image/jpeg";

  std::vector<apps::IntentFilePtr> files;
  files.push_back(std::move(file1));
  files.push_back(std::move(file2));

  auto intent = std::make_unique<apps::Intent>(apps_util::kIntentActionView,
                                               std::move(files));

  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  std::optional<apps::State> result;
  app_service_proxy()->LaunchAppWithIntent(
      app_id, 0, std::move(intent), apps::LaunchSource::kFromFileManager,
      /*window_info=*/nullptr,
      base::BindLambdaForTesting(
          [&result](apps::LaunchResult&& callback_result) {
            result = callback_result.state;
          }));

  ASSERT_EQ(apps::State::kSuccess, result.value_or(apps::State::kFailed));

  ASSERT_EQ(file_system_instance()->handledUrlRequests().size(), 1u);
  auto& url_request = file_system_instance()->handledUrlRequests()[0];
  ASSERT_EQ(url_request->action_type, arc::mojom::ActionType::VIEW);
  ASSERT_EQ(url_request->urls.size(), 2u);
  ASSERT_EQ(url_request->urls[0]->mime_type, "image/png");
  ASSERT_EQ(url_request->urls[1]->mime_type, "image/jpeg");
  ASSERT_TRUE(
      base::EndsWith(url_request->urls[0]->content_url.spec(), "test1.png"));
  ASSERT_TRUE(
      base::EndsWith(url_request->urls[1]->content_url.spec(), "test2.jpeg"));
}

TEST_F(ArcAppsPublisherTest,
       LaunchAppWithIntent_ShareFileIntent_SendsOpenUrlRequest) {
  SetUpFileSystemInstance();

  std::string mime_type = "image/jpeg";
  std::string file_name = "test.jpeg";

  GURL url = FileInDownloads(profile(), base::FilePath(file_name));
  auto intent = apps_util::MakeShareIntent({url}, {mime_type});

  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  std::optional<apps::State> result;
  app_service_proxy()->LaunchAppWithIntent(
      app_id, 0, std::move(intent), apps::LaunchSource::kFromFileManager,
      /*window_info=*/nullptr,
      base::BindLambdaForTesting(
          [&result](apps::LaunchResult&& callback_result) {
            result = callback_result.state;
          }));

  ASSERT_EQ(apps::State::kSuccess, result.value_or(apps::State::kFailed));

  ASSERT_EQ(file_system_instance()->handledUrlRequests().size(), 1u);
  auto& url_request = file_system_instance()->handledUrlRequests()[0];
  ASSERT_EQ(url_request->action_type, arc::mojom::ActionType::SEND);
  ASSERT_EQ(url_request->urls.size(), 1u);
  ASSERT_EQ(url_request->urls[0]->mime_type, mime_type);
  ASSERT_TRUE(
      base::EndsWith(url_request->urls[0]->content_url.spec(), file_name));
}

TEST_F(ArcAppsPublisherTest, LaunchAppWithIntent_ShareFilesIntent_SendsExtras) {
  SetUpFileSystemInstance();

  constexpr char kTestIntentText[] = "launch text";
  constexpr char kTestIntentTitle[] = "launch title";
  constexpr char kTestExtraKey[] = "extra_key";
  constexpr char kTestExtraValue[] = "extra_value";

  GURL url = FileInDownloads(profile(), base::FilePath("test.jpeg"));
  auto intent = apps_util::MakeShareIntent({url}, {"image/jpeg"},
                                           kTestIntentText, kTestIntentTitle);
  intent->extras = {std::make_pair(kTestExtraKey, kTestExtraValue)};

  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  app_service_proxy()->LaunchAppWithIntent(
      app_id, 0, std::move(intent), apps::LaunchSource::kFromFileManager,
      /*window_info=*/nullptr, base::DoNothing());

  ASSERT_EQ(file_system_instance()->handledUrlRequests().size(), 1u);
  auto& url_request = file_system_instance()->handledUrlRequests()[0];
  ASSERT_EQ(url_request->action_type, arc::mojom::ActionType::SEND);
  ASSERT_EQ(url_request->urls.size(), 1u);
  ASSERT_EQ(url_request->extras.value()[kTestExtraKey], kTestExtraValue);
  ASSERT_EQ(url_request->extras.value()["android.intent.extra.TEXT"],
            kTestIntentText);
  ASSERT_EQ(url_request->extras.value()["android.intent.extra.SUBJECT"],
            kTestIntentTitle);
}

TEST_F(ArcAppsPublisherTest, SetAppLocale_SendsLocaleToArc) {
  // Setup.
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(arc::kPerAppLanguage);
  ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile());
  ASSERT_NE(nullptr, prefs);
  // fake_packages[4] is the test package with localeInfo.
  const std::string& test_package_name =
      arc_test()->fake_apps()[4]->package_name;
  const std::string& app_id =
      prefs->GetAppId(test_package_name, arc_test()->fake_apps()[4]->activity);

  // Setup app.
  std::vector<arc::mojom::AppInfoPtr> test_app_info_list;
  test_app_info_list.push_back(arc_test()->fake_apps()[4]->Clone());
  arc_test()->app_instance()->SendRefreshAppList(test_app_info_list);
  // Setup package.
  // Initially pref will be set with "en" as selectedLocale.
  std::vector<arc::mojom::ArcPackageInfoPtr> test_packages;
  test_packages.push_back(arc_test()->fake_packages()[4]->Clone());
  arc_test()->app_instance()->SendRefreshPackageList(
      ArcAppTest::ClonePackages(test_packages));

  // Run.
  app_service_proxy()->SetAppLocale(app_id, "ja");

  // Assert.
  ASSERT_EQ("ja",
            arc_test()->app_instance()->selected_locale(test_package_name));
  ASSERT_EQ("ja",
            profile()->GetPrefs()->GetString(arc::prefs::kArcLastSetAppLocale));
}

class ArcAppsPublisherPromiseAppTest : public ArcAppsPublisherTest {
 public:
  void SetUp() override {
    ArcAppsPublisherTest::SetUp();
    feature_list_.InitAndEnableFeature(ash::features::kPromiseIcons);
    app_service_proxy()->ReinitializeForTesting(profile());
    service()->SetSkipAlmanacForTesting(true);
  }

  apps::PromiseAppService* service() {
    return app_service_proxy()->PromiseAppService();
  }

  apps::PromiseAppRegistryCache* cache() {
    return app_service_proxy()->PromiseAppRegistryCache();
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

TEST_F(ArcAppsPublisherPromiseAppTest,
       StartingInstallationRegistersPromiseApp) {
  // Verify that the promise app is not yet registered.
  EXPECT_FALSE(cache()->HasPromiseApp(kTestPackageId));

  arc_test()->app_instance()->SendInstallationStarted(kTestPackageName);

  // Verify that the promise app is now registered.
  EXPECT_TRUE(cache()->HasPromiseApp(kTestPackageId));
}

TEST_F(ArcAppsPublisherPromiseAppTest,
       InstallationProgressChangeUpdatesPromiseApp) {
  float progress_initial = 0.1;
  float progress_next = 0.9;

  // Add a promise app for testing.
  std::unique_ptr<apps::PromiseApp> promise_app =
      std::make_unique<apps::PromiseApp>(kTestPackageId);
  promise_app->progress = progress_initial;
  cache()->OnPromiseApp(std::move(promise_app));

  // Check that the initial progress value is correct.
  const apps::PromiseApp* promise_app_result =
      cache()->GetPromiseApp(kTestPackageId);
  EXPECT_TRUE(promise_app_result);
  EXPECT_TRUE(promise_app_result->progress.has_value());
  EXPECT_EQ(promise_app_result->progress.value(), progress_initial);

  // Send an update and check the progress value.
  arc_test()->app_instance()->SendInstallationProgressChanged(kTestPackageName,
                                                              progress_next);
  promise_app_result = cache()->GetPromiseApp(kTestPackageId);
  EXPECT_TRUE(promise_app_result);
  EXPECT_TRUE(promise_app_result->progress.has_value());
  EXPECT_EQ(promise_app_result->progress.value(), progress_next);
}

TEST_F(ArcAppsPublisherPromiseAppTest, ProgressUpdateChangesPromiseStatus) {
  // Add a promise app for testing.
  std::unique_ptr<apps::PromiseApp> promise_app =
      std::make_unique<apps::PromiseApp>(kTestPackageId);
  promise_app->status = apps::PromiseStatus::kPending;
  cache()->OnPromiseApp(std::move(promise_app));

  // Check that the initial status is kPending.
  const apps::PromiseApp* promise_app_result =
      cache()->GetPromiseApp(kTestPackageId);
  EXPECT_TRUE(promise_app_result);
  EXPECT_EQ(promise_app_result->status, apps::PromiseStatus::kPending);

  // Send a progress update and check the status.
  arc_test()->app_instance()->SendInstallationProgressChanged(kTestPackageName,
                                                              0.2);
  promise_app_result = cache()->GetPromiseApp(kTestPackageId);
  EXPECT_TRUE(promise_app_result);
  EXPECT_EQ(promise_app_result->status, apps::PromiseStatus::kInstalling);
}

TEST_F(ArcAppsPublisherPromiseAppTest, CancelledInstallationRemovesPromiseApp) {
  // Add a promise app to the cache.
  std::unique_ptr<apps::PromiseApp> promise_app =
      std::make_unique<apps::PromiseApp>(kTestPackageId);
  promise_app->status = apps::PromiseStatus::kPending;
  cache()->OnPromiseApp(std::move(promise_app));

  // Check that the promise app exists.
  EXPECT_TRUE(cache()->HasPromiseApp(kTestPackageId));

  // Confirm that the promise app gets removed after a cancelled/ failed
  // installation update.
  arc_test()->app_instance()->SendInstallationFinished(kTestPackageName, false);
  EXPECT_FALSE(cache()->HasPromiseApp(kTestPackageId));
}

TEST_F(ArcAppsPublisherPromiseAppTest,
       SuccessfulInstallationOfNonLaunchablePackageRemovesPromiseApp) {
  // Add a promise app to the cache.
  std::unique_ptr<apps::PromiseApp> promise_app =
      std::make_unique<apps::PromiseApp>(kTestPackageId);
  promise_app->status = apps::PromiseStatus::kPending;
  cache()->OnPromiseApp(std::move(promise_app));

  // Check that the promise app exists.
  EXPECT_TRUE(cache()->HasPromiseApp(kTestPackageId));

  // Confirm that the promise app gets removed after successful installation of
  // a non-launchable package.
  arc_test()->app_instance()->SendInstallationFinished(
      kTestPackageName, /*success=*/true,
      /*is_launchable_app=*/false);
  EXPECT_FALSE(cache()->HasPromiseApp(kTestPackageId));
}

TEST_F(ArcAppsPublisherPromiseAppTest,
       SuccessfulInstallationRemovesPromiseApp) {
  // Add a promise app to the cache.
  std::unique_ptr<apps::PromiseApp> promise_app =
      std::make_unique<apps::PromiseApp>(kTestPackageId);
  promise_app->status = apps::PromiseStatus::kPending;
  cache()->OnPromiseApp(std::move(promise_app));

  // Check that the promise app exists.
  EXPECT_TRUE(cache()->HasPromiseApp(kTestPackageId));

  // Confirm that the promise app gets removed after a successfully completed
  // installation.
  const auto& fake_apps = arc_test()->fake_apps();
  fake_apps[0]->package_name = kTestPackageName;
  std::string app_id =
      ArcAppListPrefs::GetAppId(kTestPackageName, "testActivity");
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  // Confirm that the promise app gets removed after the installed app gets
  // registered.
  EXPECT_FALSE(cache()->HasPromiseApp(kTestPackageId));
}

TEST_F(ArcAppsPublisherPromiseAppTest, PromiseAppsAreSuppressedForPiArc) {
  // Set ARC version to P, which we should not create promise apps for.
  apps::ArcApps::SetArcVersionForTesting(arc::kArcVersionP);

  // Verify that the promise app is not registered to begin with.
  EXPECT_FALSE(cache()->HasPromiseApp(kTestPackageId));

  // Trigger an installation event notification.
  arc_test()->app_instance()->SendInstallationStarted(kTestPackageName);

  // Verify that the promise app still isn't registered.
  EXPECT_FALSE(cache()->HasPromiseApp(kTestPackageId));
}

TEST_F(ArcAppsPublisherPromiseAppTest, PromiseAppsAreCreatedForRvcArc) {
  // Set ARC version to R, which should allow promise apps to be created.
  apps::ArcApps::SetArcVersionForTesting(arc::kArcVersionR);

  // Verify that the promise app is not registered to begin with.
  EXPECT_FALSE(cache()->HasPromiseApp(kTestPackageId));

  // Trigger an installation event notification.
  arc_test()->app_instance()->SendInstallationStarted(kTestPackageName);

  // Verify that the promise app is registered.
  EXPECT_TRUE(cache()->HasPromiseApp(kTestPackageId));
}

// Verifies that only valid intent filters will be published from ARC.
TEST_F(ArcAppsPublisherTest, OnlyValidFilterIsPublished) {
  const GURL kTestUrl("https://www.example.com");
  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  std::vector<arc::IntentFilter::AuthorityEntry> filter_authorities1;
  filter_authorities1.emplace_back(kTestUrl.host(), 0);
  std::vector<arc::IntentFilter::PatternMatcher> patterns;
  patterns.emplace_back(kTestUrl.path(),
                        arc::mojom::PatternType::PATTERN_PREFIX);

  auto filter = arc::IntentFilter(package_name, {arc::kIntentActionView},
                                  std::move(filter_authorities1),
                                  std::move(patterns), {kTestUrl.scheme()}, {});
  std::vector<arc::IntentFilter> filters;
  filters.push_back(std::move(filter));

  std::vector<arc::IntentFilter::AuthorityEntry> filter_authorities2;
  filter_authorities2.emplace_back(kTestUrl.host(), 0);
  int invalid_pattern_type =
      static_cast<int>(arc::mojom::PatternType::kMaxValue) + 1;
  std::vector<arc::IntentFilter::PatternMatcher> invalid_pattern;
  invalid_pattern.emplace_back(
      kTestUrl.path(),
      static_cast<arc::mojom::PatternType>(invalid_pattern_type));

  auto invalid_filter = arc::IntentFilter(
      package_name, {arc::kIntentActionView}, std::move(filter_authorities2),
      std::move(invalid_pattern), {"https"}, {});
  filters.push_back(std::move(invalid_filter));

  // Update intent filters and supported links for the app, as if it was just
  // installed.
  intent_helper()->OnIntentFiltersUpdatedForPackage(package_name,
                                                    std::move(filters));

  apps::IntentFilters published_filters;
  apps::AppServiceProxyFactory::GetForProfile(profile())
      ->AppRegistryCache()
      .ForOneApp(app_id, [&published_filters](const apps::AppUpdate& update) {
        published_filters = update.IntentFilters();
      });
  // Only one valid filter should be published.
  EXPECT_EQ(published_filters.size(), 1u);

  apps::IntentFilterPtr expected_filter =
      apps_util::MakeIntentFilterForUrlScope(kTestUrl,
                                             /*omit_port_for_testing=*/true);
  EXPECT_EQ(*published_filters[0], *expected_filter);
}