#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/publishers/app_publisher.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/testing_profile.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/features.h"
#include "components/services/app_service/public/cpp/icon_types.h"
#include "components/services/app_service/public/cpp/intent.h"
#include "components/services/app_service/public/cpp/intent_filter.h"
#include "components/services/app_service/public/cpp/intent_filter_util.h"
#include "components/services/app_service/public/cpp/intent_test_util.h"
#include "components/services/app_service/public/cpp/intent_util.h"
#include "components/services/app_service/public/cpp/preferred_app.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia_rep.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/apps/app_service/subscriber_crosapi.h"
#include "components/services/app_service/public/cpp/types_util.h"
#endif
namespace apps {
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
class FakePublisherForProxyTest : public AppPublisher { … };
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
class FakeAppRegistryCacheObserver : public apps::AppRegistryCache::Observer {
public:
explicit FakeAppRegistryCacheObserver(apps::AppRegistryCache* cache) {
app_registry_cache_observer_.Observe(cache);
}
~FakeAppRegistryCacheObserver() override = default;
void OnAppUpdate(const apps::AppUpdate& update) override {
if (base::Contains(app_ids_, update.AppId())) {
app_ids_.erase(update.AppId());
}
if (app_ids_.empty() && !result_.IsReady()) {
result_.SetValue();
}
}
void OnAppRegistryCacheWillBeDestroyed(
apps::AppRegistryCache* cache) override {
app_registry_cache_observer_.Reset();
}
void WaitForOnAppUpdate(const std::set<std::string>& app_ids) {
app_ids_ = app_ids;
EXPECT_TRUE(result_.Wait());
}
private:
base::test::TestFuture<void> result_;
base::ScopedObservation<apps::AppRegistryCache,
apps::AppRegistryCache::Observer>
app_registry_cache_observer_{this};
std::set<std::string> app_ids_;
};
class FakeSubscriberForProxyTest : public SubscriberCrosapi {
public:
explicit FakeSubscriberForProxyTest(Profile* profile)
: SubscriberCrosapi(profile) {
apps::AppServiceProxyFactory::GetForProfile(profile)
->RegisterCrosApiSubScriber(this);
}
PreferredAppsList& preferred_apps_list() { return preferred_apps_list_; }
void OnPreferredAppsChanged(PreferredAppChangesPtr changes) override {
preferred_apps_list_.ApplyBulkUpdate(std::move(changes));
}
void InitializePreferredApps(apps::PreferredApps preferred_apps) override {
preferred_apps_list_.Init(std::move(preferred_apps));
}
private:
apps::PreferredAppsList preferred_apps_list_;
};
#endif
class AppServiceProxyTest : public testing::Test { … };
class AppServiceProxyIconTest : public AppServiceProxyTest { … };
TEST_F(AppServiceProxyIconTest, IconCache) { … }
TEST_F(AppServiceProxyIconTest, IconCoalescer) { … }
TEST_F(AppServiceProxyTest, ProxyAccessPerProfile) { … }
TEST_F(AppServiceProxyTest, ReinitializeClearsCache) { … }
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
class AppServiceProxyPreferredAppsTest : public AppServiceProxyTest { … };
TEST_F(AppServiceProxyPreferredAppsTest, UpdatedOnUninstall) { … }
TEST_F(AppServiceProxyPreferredAppsTest, SetPreferredApp) { … }
TEST_F(AppServiceProxyPreferredAppsTest, PreferredAppsWriteBeforeInit) { … }
TEST_F(AppServiceProxyPreferredAppsTest, PreferredAppsPersistency) { … }
TEST_F(AppServiceProxyPreferredAppsTest,
PreferredAppsSetSupportedLinksPublisher) { … }
TEST_F(AppServiceProxyPreferredAppsTest, PreferredAppsOverlapSupportedLink) { … }
TEST_F(AppServiceProxyPreferredAppsTest, PreferredAppsDuplicatedSupportedLink) { … }
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(AppServiceProxyPreferredAppsTest, PreferredAppsSetSupportedLinks) {
GetPreferredAppsList().Init();
const char kAppId1[] = "abcdefg";
const char kAppId2[] = "hijklmn";
const char kAppId3[] = "opqrstu";
auto intent_filter_a =
apps_util::MakeIntentFilterForUrlScope(GURL("https://www.a.com/"));
auto intent_filter_b =
apps_util::MakeIntentFilterForUrlScope(GURL("https://www.b.com/"));
auto intent_filter_c =
apps_util::MakeIntentFilterForUrlScope(GURL("https://www.c.com/"));
FakeSubscriberForProxyTest sub(proxy()->profile());
FakePublisherForProxyTest pub(
proxy(), AppType::kArc,
std::vector<std::string>{kAppId1, kAppId2, kAppId3});
IntentFilters app_1_filters;
app_1_filters.push_back(intent_filter_a->Clone());
app_1_filters.push_back(intent_filter_b->Clone());
proxy()->SetSupportedLinksPreference(kAppId1, std::move(app_1_filters));
IntentFilters app_2_filters;
app_2_filters.push_back(intent_filter_c->Clone());
proxy()->SetSupportedLinksPreference(kAppId2, std::move(app_2_filters));
EXPECT_TRUE(pub.AppHasSupportedLinksPreference(kAppId1));
EXPECT_TRUE(pub.AppHasSupportedLinksPreference(kAppId2));
EXPECT_FALSE(pub.AppHasSupportedLinksPreference(kAppId3));
EXPECT_EQ(kAppId1, sub.preferred_apps_list().FindPreferredAppForUrl(
GURL("https://www.a.com/")));
EXPECT_EQ(kAppId1, sub.preferred_apps_list().FindPreferredAppForUrl(
GURL("https://www.b.com/")));
EXPECT_EQ(kAppId2, sub.preferred_apps_list().FindPreferredAppForUrl(
GURL("https://www.c.com/")));
IntentFilters app_3_filters;
app_3_filters.push_back(intent_filter_b->Clone());
app_3_filters.push_back(intent_filter_c->Clone());
proxy()->SetSupportedLinksPreference(kAppId3, std::move(app_3_filters));
EXPECT_FALSE(pub.AppHasSupportedLinksPreference(kAppId1));
EXPECT_FALSE(pub.AppHasSupportedLinksPreference(kAppId2));
EXPECT_TRUE(pub.AppHasSupportedLinksPreference(kAppId3));
EXPECT_EQ(std::nullopt, sub.preferred_apps_list().FindPreferredAppForUrl(
GURL("https://www.a.com/")));
EXPECT_EQ(kAppId3, sub.preferred_apps_list().FindPreferredAppForUrl(
GURL("https://www.b.com/")));
EXPECT_EQ(kAppId3, sub.preferred_apps_list().FindPreferredAppForUrl(
GURL("https://www.c.com/")));
app_3_filters = std::vector<IntentFilterPtr>();
app_3_filters.push_back(intent_filter_b->Clone());
app_3_filters.push_back(intent_filter_c->Clone());
proxy()->SetSupportedLinksPreference(kAppId3, std::move(app_3_filters));
EXPECT_TRUE(pub.AppHasSupportedLinksPreference(kAppId3));
EXPECT_EQ(kAppId3, sub.preferred_apps_list().FindPreferredAppForUrl(
GURL("https://www.c.com/")));
proxy()->RemoveSupportedLinksPreference(kAppId3);
EXPECT_FALSE(pub.AppHasSupportedLinksPreference(kAppId3));
EXPECT_EQ(std::nullopt, sub.preferred_apps_list().FindPreferredAppForUrl(
GURL("https://www.c.com/")));
}
TEST_F(AppServiceProxyTest, LaunchCallback) {
bool called_1 = false;
bool called_2 = false;
auto instance_id_1 = base::UnguessableToken::Create();
auto instance_id_2 = base::UnguessableToken::Create();
{
LaunchResult result_1;
result_1.instance_ids.push_back(instance_id_1);
proxy()->OnLaunched(
base::BindOnce(
[](bool* called, apps::LaunchResult&& launch_result) {
*called = true;
},
&called_1),
std::move(result_1));
}
EXPECT_EQ(proxy()->callback_list_.size(), 1U);
EXPECT_FALSE(called_1);
{
LaunchResult result_2;
result_2.instance_ids.push_back(instance_id_2);
proxy()->OnLaunched(
base::BindOnce(
[](bool* called, apps::LaunchResult&& launch_result) {
*called = true;
},
&called_2),
std::move(result_2));
}
EXPECT_EQ(proxy()->callback_list_.size(), 2U);
EXPECT_FALSE(called_2);
{
auto delta =
std::make_unique<apps::Instance>("abc", instance_id_1, nullptr);
proxy()->InstanceRegistry().OnInstance(std::move(delta));
}
EXPECT_EQ(proxy()->callback_list_.size(), 1U);
EXPECT_TRUE(called_1);
EXPECT_FALSE(called_2);
called_1 = false;
{
LaunchResult result_3;
proxy()->OnLaunched(
base::BindOnce(
[](bool* called, apps::LaunchResult&& launch_result) {
*called = true;
},
&called_1),
std::move(result_3));
}
EXPECT_EQ(proxy()->callback_list_.size(), 1U);
EXPECT_TRUE(called_1);
EXPECT_FALSE(called_2);
auto instance_id_3 = base::UnguessableToken::Create();
auto instance_id_4 = base::UnguessableToken::Create();
bool called_multi = false;
{
LaunchResult result_multi;
result_multi.instance_ids.push_back(instance_id_3);
result_multi.instance_ids.push_back(instance_id_4);
proxy()->OnLaunched(
base::BindOnce(
[](bool* called, apps::LaunchResult&& launch_result) {
*called = true;
},
&called_multi),
std::move(result_multi));
}
EXPECT_EQ(proxy()->callback_list_.size(), 2U);
EXPECT_FALSE(called_multi);
proxy()->InstanceRegistry().OnInstance(
std::make_unique<apps::Instance>("foo", instance_id_3, nullptr));
proxy()->InstanceRegistry().OnInstance(
std::make_unique<apps::Instance>("bar", instance_id_4, nullptr));
EXPECT_EQ(proxy()->callback_list_.size(), 1U);
EXPECT_TRUE(called_multi);
}
TEST_F(AppServiceProxyTest, GetAppsForIntentBestHandler) {
const char kAppId1[] = "abcdefg";
const GURL kTestUrl = GURL("https://www.example.com/");
std::vector<AppPtr> apps;
AppPtr app = std::make_unique<App>(AppType::kWeb, kAppId1);
app->readiness = Readiness::kReady;
app->handles_intents = true;
auto intent_filter = std::make_unique<apps::IntentFilter>();
intent_filter->AddSingleValueCondition(apps::ConditionType::kScheme,
kTestUrl.scheme(),
apps::PatternMatchType::kLiteral);
intent_filter->activity_name = "name 1";
intent_filter->activity_label = "same label";
app->intent_filters.push_back(std::move(intent_filter));
auto intent_filter2 = std::make_unique<apps::IntentFilter>();
intent_filter2->AddSingleValueCondition(apps::ConditionType::kAction,
apps_util::kIntentActionView,
apps::PatternMatchType::kLiteral);
intent_filter2->AddSingleValueCondition(apps::ConditionType::kFile,
"text/plain",
apps::PatternMatchType::kMimeType);
intent_filter2->activity_name = "name 2";
intent_filter2->activity_label = "same label";
app->intent_filters.push_back(std::move(intent_filter2));
apps.push_back(std::move(app));
proxy()->OnApps(std::move(apps), AppType::kWeb, false);
std::vector<apps::IntentFilePtr> files;
auto file = std::make_unique<apps::IntentFile>(GURL("abc.txt"));
file->mime_type = "text/plain";
file->is_directory = false;
files.push_back(std::move(file));
apps::IntentPtr intent = std::make_unique<apps::Intent>(
apps_util::kIntentActionView, std::move(files));
std::vector<apps::IntentLaunchInfo> intent_launch_info =
proxy()->GetAppsForIntent(intent, true);
EXPECT_EQ(1U, intent_launch_info.size());
EXPECT_EQ("name 2", intent_launch_info[0].activity_name);
}
#endif
}