blob: fe1b3e607cbfaa5bb4800548fa32aacfbccbe3d7 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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/app_service_proxy_lacros.h"
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/containers/contains.h"
#include "base/debug/dump_without_crashing.h"
#include "base/location.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/app_icon_source.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/browser_app_instance_tracker.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/apps/app_service/publishers/extension_apps.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/app_service/web_apps.h"
#include "chrome/browser/web_applications/app_service/web_apps_publisher_host.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chromeos/lacros/lacros_service.h"
#include "components/services/app_service/app_service_impl.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/types_util.h"
#include "components/services/app_service/public/mojom/types.mojom.h"
#include "content/public/browser/url_data_source.h"
#include "ui/display/types/display_constants.h"
#include "url/url_constants.h"
namespace apps {
AppServiceProxyLacros::InnerIconLoader::InnerIconLoader(
AppServiceProxyLacros* host)
: host_(host), overriding_icon_loader_for_testing_(nullptr) {}
apps::mojom::IconKeyPtr AppServiceProxyLacros::InnerIconLoader::GetIconKey(
const std::string& app_id) {
if (overriding_icon_loader_for_testing_) {
return overriding_icon_loader_for_testing_->GetIconKey(app_id);
}
apps::mojom::IconKeyPtr icon_key;
if (host_->crosapi_receiver_.is_bound()) {
host_->app_registry_cache_.ForOneApp(
app_id, [&icon_key](const apps::AppUpdate& update) {
icon_key = update.IconKey();
});
}
return icon_key;
}
std::unique_ptr<IconLoader::Releaser>
AppServiceProxyLacros::InnerIconLoader::LoadIconFromIconKey(
apps::mojom::AppType app_type,
const std::string& app_id,
apps::mojom::IconKeyPtr icon_key,
apps::mojom::IconType icon_type,
int32_t size_hint_in_dip,
bool allow_placeholder_icon,
apps::mojom::Publisher::LoadIconCallback callback) {
if (overriding_icon_loader_for_testing_) {
return overriding_icon_loader_for_testing_->LoadIconFromIconKey(
app_type, app_id, std::move(icon_key), icon_type, size_hint_in_dip,
allow_placeholder_icon, std::move(callback));
}
auto* service = chromeos::LacrosService::Get();
if (!service || !service->IsAvailable<crosapi::mojom::AppServiceProxy>()) {
std::move(callback).Run(apps::mojom::IconValue::New());
} else if (host_->crosapi_app_service_proxy_version_ <
int{crosapi::mojom::AppServiceProxy::MethodMinVersions::
kLoadIconMinVersion}) {
LOG(WARNING) << "Ash AppServiceProxy version "
<< host_->crosapi_app_service_proxy_version_
<< " does not support LoadIcon().";
std::move(callback).Run(apps::mojom::IconValue::New());
} else {
service->GetRemote<crosapi::mojom::AppServiceProxy>()->LoadIcon(
app_id, std::move(icon_key), icon_type, size_hint_in_dip,
std::move(callback));
}
return nullptr;
}
AppServiceProxyLacros::AppServiceProxyLacros(Profile* profile)
: inner_icon_loader_(this),
icon_coalescer_(&inner_icon_loader_),
outer_icon_loader_(&icon_coalescer_,
apps::IconCache::GarbageCollectionPolicy::kEager),
profile_(profile),
browser_app_instance_tracker_(
apps::BrowserAppInstanceTracker::Create(profile_,
app_registry_cache_)) {
Initialize();
}
AppServiceProxyLacros::~AppServiceProxyLacros() = default;
void AppServiceProxyLacros::Initialize() {
if (!IsValidProfile()) {
return;
}
browser_app_launcher_ = std::make_unique<apps::BrowserAppLauncher>(profile_);
Observe(&app_registry_cache_);
web_apps_publisher_host_ =
std::make_unique<web_app::WebAppsPublisherHost>(profile_);
web_apps_publisher_host_->Init();
// Asynchronously add app icon source, so we don't do too much work in the
// constructor.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&AppServiceProxyLacros::AddAppIconSource,
weak_ptr_factory_.GetWeakPtr(), profile_));
auto* service = chromeos::LacrosService::Get();
if (!service || !service->IsAvailable<crosapi::mojom::AppServiceProxy>()) {
return;
}
crosapi_app_service_proxy_version_ =
service->GetInterfaceVersion(crosapi::mojom::AppServiceProxy::Uuid_);
if (crosapi_app_service_proxy_version_ <
int{crosapi::mojom::AppServiceProxy::MethodMinVersions::
kRegisterAppServiceSubscriberMinVersion}) {
LOG(WARNING) << "Ash AppServiceProxy version "
<< crosapi_app_service_proxy_version_
<< " does not support RegisterAppServiceSubscriber().";
return;
}
service->GetRemote<crosapi::mojom::AppServiceProxy>()
->RegisterAppServiceSubscriber(
crosapi_receiver_.BindNewPipeAndPassRemote());
}
void AppServiceProxyLacros::ReInitializeForTesting(Profile* profile) {
// Some test code creates a profile and profile-linked services, like the App
// Service, before the profile is fully initialized. Such tests can call this
// after full profile initialization to ensure the App Service implementation
// has all of profile state it needs.
crosapi_receiver_.reset();
profile_ = profile;
is_using_testing_profile_ = true;
Initialize();
}
bool AppServiceProxyLacros::IsValidProfile() {
if (!profile_) {
return false;
}
// We only initialize the App Service for regular or guest profiles. Non-guest
// off-the-record profiles do not get an instance.
if (profile_->IsOffTheRecord() && !profile_->IsGuestSession()) {
return false;
}
return true;
}
apps::AppRegistryCache& AppServiceProxyLacros::AppRegistryCache() {
return app_registry_cache_;
}
apps::AppCapabilityAccessCache&
AppServiceProxyLacros::AppCapabilityAccessCache() {
return app_capability_access_cache_;
}
BrowserAppLauncher* AppServiceProxyLacros::BrowserAppLauncher() {
return browser_app_launcher_.get();
}
apps::PreferredAppsList& AppServiceProxyLacros::PreferredApps() {
return preferred_apps_;
}
apps::BrowserAppInstanceTracker*
AppServiceProxyLacros::BrowserAppInstanceTracker() {
return browser_app_instance_tracker_.get();
}
apps::mojom::IconKeyPtr AppServiceProxyLacros::GetIconKey(
const std::string& app_id) {
return outer_icon_loader_.GetIconKey(app_id);
}
std::unique_ptr<apps::IconLoader::Releaser>
AppServiceProxyLacros::LoadIconFromIconKey(
apps::mojom::AppType app_type,
const std::string& app_id,
apps::mojom::IconKeyPtr icon_key,
apps::mojom::IconType icon_type,
int32_t size_hint_in_dip,
bool allow_placeholder_icon,
apps::mojom::Publisher::LoadIconCallback callback) {
return outer_icon_loader_.LoadIconFromIconKey(
app_type, app_id, std::move(icon_key), icon_type, size_hint_in_dip,
allow_placeholder_icon, std::move(callback));
}
void AppServiceProxyLacros::Launch(const std::string& app_id,
int32_t event_flags,
apps::mojom::LaunchSource launch_source,
apps::mojom::WindowInfoPtr window_info) {
auto* service = chromeos::LacrosService::Get();
if (!service || !service->IsAvailable<crosapi::mojom::AppServiceProxy>()) {
return;
}
if (crosapi_app_service_proxy_version_ <
int{crosapi::mojom::AppServiceProxy::MethodMinVersions::
kLaunchMinVersion}) {
LOG(WARNING) << "Ash AppServiceProxy version "
<< crosapi_app_service_proxy_version_
<< " does not support Launch().";
return;
}
auto launch_params = crosapi::mojom::LaunchParams::New();
launch_params->app_id = app_id;
launch_params->launch_source = launch_source;
service->GetRemote<crosapi::mojom::AppServiceProxy>()->Launch(
std::move(launch_params));
}
void AppServiceProxyLacros::LaunchAppWithFiles(
const std::string& app_id,
int32_t event_flags,
apps::mojom::LaunchSource launch_source,
apps::mojom::FilePathsPtr file_paths) {
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::LaunchAppWithFileUrls(
const std::string& app_id,
int32_t event_flags,
apps::mojom::LaunchSource launch_source,
const std::vector<GURL>& file_urls,
const std::vector<std::string>& mime_types) {
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::LaunchAppWithIntent(
const std::string& app_id,
int32_t event_flags,
apps::mojom::IntentPtr intent,
apps::mojom::LaunchSource launch_source,
apps::mojom::WindowInfoPtr window_info) {
CHECK(intent);
auto* service = chromeos::LacrosService::Get();
if (!service || !service->IsAvailable<crosapi::mojom::AppServiceProxy>()) {
return;
}
if (crosapi_app_service_proxy_version_ <
int{crosapi::mojom::AppServiceProxy::MethodMinVersions::
kLaunchMinVersion}) {
LOG(WARNING) << "Ash AppServiceProxy version "
<< crosapi_app_service_proxy_version_
<< " does not support Launch().";
return;
}
auto launch_params = crosapi::mojom::LaunchParams::New();
launch_params->app_id = app_id;
launch_params->launch_source = launch_source;
launch_params->intent = std::move(intent);
service->GetRemote<crosapi::mojom::AppServiceProxy>()->Launch(
std::move(launch_params));
}
void AppServiceProxyLacros::LaunchAppWithUrl(
const std::string& app_id,
int32_t event_flags,
GURL url,
apps::mojom::LaunchSource launch_source,
apps::mojom::WindowInfoPtr window_info) {
LaunchAppWithIntent(app_id, event_flags, apps_util::CreateIntentFromUrl(url),
launch_source, std::move(window_info));
}
void AppServiceProxyLacros::SetPermission(
const std::string& app_id,
apps::mojom::PermissionPtr permission) {
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::Uninstall(
const std::string& app_id,
apps::mojom::UninstallSource uninstall_source,
gfx::NativeWindow parent_window) {
// On non-ChromeOS, publishers run the remove dialog.
apps::mojom::AppType app_type = app_registry_cache_.GetAppType(app_id);
if (app_type == apps::mojom::AppType::kWeb) {
web_app::WebApps::UninstallImpl(
web_app::WebAppProvider::GetForWebApps(profile_), app_id,
uninstall_source, parent_window);
}
}
void AppServiceProxyLacros::UninstallSilently(
const std::string& app_id,
apps::mojom::UninstallSource uninstall_source) {
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::StopApp(const std::string& app_id) {
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::GetMenuModel(
const std::string& app_id,
apps::mojom::MenuType menu_type,
int64_t display_id,
apps::mojom::Publisher::GetMenuModelCallback callback) {
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::ExecuteContextMenuCommand(
const std::string& app_id,
int command_id,
const std::string& shortcut_id,
int64_t display_id) {
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::OpenNativeSettings(const std::string& app_id) {
NOTIMPLEMENTED();
}
apps::IconLoader* AppServiceProxyLacros::OverrideInnerIconLoaderForTesting(
apps::IconLoader* icon_loader) {
apps::IconLoader* old =
inner_icon_loader_.overriding_icon_loader_for_testing_;
inner_icon_loader_.overriding_icon_loader_for_testing_ = icon_loader;
return old;
}
std::vector<std::string> AppServiceProxyLacros::GetAppIdsForUrl(
const GURL& url,
bool exclude_browsers,
bool exclude_browser_tab_apps) {
auto intent_launch_info =
GetAppsForIntent(apps_util::CreateIntentFromUrl(url), exclude_browsers,
exclude_browser_tab_apps);
std::vector<std::string> app_ids;
for (auto& entry : intent_launch_info) {
app_ids.push_back(std::move(entry.app_id));
}
return app_ids;
}
std::vector<IntentLaunchInfo> AppServiceProxyLacros::GetAppsForIntent(
const apps::mojom::IntentPtr& intent,
bool exclude_browsers,
bool exclude_browser_tab_apps) {
std::vector<IntentLaunchInfo> intent_launch_info;
if (apps_util::OnlyShareToDrive(intent) ||
!apps_util::IsIntentValid(intent)) {
return intent_launch_info;
}
if (crosapi_receiver_.is_bound()) {
app_registry_cache_.ForEachApp(
[&intent_launch_info, &intent, &exclude_browsers,
&exclude_browser_tab_apps](const apps::AppUpdate& update) {
if (!apps_util::IsInstalled(update.Readiness()) ||
update.ShowInLauncher() != apps::mojom::OptionalBool::kTrue) {
return;
}
if (exclude_browser_tab_apps &&
update.WindowMode() == mojom::WindowMode::kBrowser) {
return;
}
std::set<std::string> existing_activities;
for (const auto& filter : update.IntentFilters()) {
if (exclude_browsers && apps_util::IsBrowserFilter(filter)) {
continue;
}
if (apps_util::IntentMatchesFilter(intent, filter)) {
IntentLaunchInfo entry;
entry.app_id = update.AppId();
std::string activity_label;
if (filter->activity_label &&
!filter->activity_label.value().empty()) {
activity_label = filter->activity_label.value();
} else {
activity_label = update.Name();
}
if (base::Contains(existing_activities, activity_label)) {
continue;
}
existing_activities.insert(activity_label);
entry.activity_label = activity_label;
entry.activity_name = filter->activity_name.value_or("");
intent_launch_info.push_back(entry);
}
}
});
}
return intent_launch_info;
}
std::vector<IntentLaunchInfo> AppServiceProxyLacros::GetAppsForFiles(
const std::vector<GURL>& filesystem_urls,
const std::vector<std::string>& mime_types) {
return GetAppsForIntent(
apps_util::CreateShareIntentFromFiles(filesystem_urls, mime_types));
}
void AppServiceProxyLacros::AddPreferredApp(const std::string& app_id,
const GURL& url) {
AddPreferredApp(app_id, apps_util::CreateIntentFromUrl(url));
}
void AppServiceProxyLacros::AddPreferredApp(
const std::string& app_id,
const apps::mojom::IntentPtr& intent) {
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::SetWindowMode(const std::string& app_id,
apps::mojom::WindowMode window_mode) {
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::AddAppIconSource(Profile* profile) {
// Make the chrome://app-icon/ resource available.
content::URLDataSource::Add(profile,
std::make_unique<apps::AppIconSource>(profile));
}
void AppServiceProxyLacros::OnApps(std::vector<apps::mojom::AppPtr> deltas,
apps::mojom::AppType app_type,
bool should_notify_initialized) {
app_registry_cache_.OnApps(std::move(deltas), app_type,
should_notify_initialized);
}
void AppServiceProxyLacros::OnAppUpdate(const apps::AppUpdate& update) {
if (!update.ReadinessChanged() ||
!apps_util::IsInstalled(update.Readiness())) {
return;
}
preferred_apps_.DeleteAppId(update.AppId());
}
void AppServiceProxyLacros::OnAppRegistryCacheWillBeDestroyed(
apps::AppRegistryCache* cache) {
Observe(nullptr);
}
apps::mojom::IntentFilterPtr AppServiceProxyLacros::FindBestMatchingFilter(
const apps::mojom::IntentPtr& intent) {
apps::mojom::IntentFilterPtr best_matching_intent_filter;
if (!crosapi_receiver_.is_bound()) {
return best_matching_intent_filter;
}
int best_match_level = apps_util::IntentFilterMatchLevel::kNone;
app_registry_cache_.ForEachApp(
[&intent, &best_match_level,
&best_matching_intent_filter](const apps::AppUpdate& update) {
for (const auto& filter : update.IntentFilters()) {
if (!apps_util::IntentMatchesFilter(intent, filter)) {
continue;
}
auto match_level = apps_util::GetFilterMatchLevel(filter);
if (match_level <= best_match_level) {
continue;
}
best_matching_intent_filter = filter->Clone();
best_match_level = match_level;
}
});
return best_matching_intent_filter;
}
void AppServiceProxyLacros::FlushMojoCallsForTesting() {
crosapi_receiver_.FlushForTesting();
}
web_app::WebAppsPublisherHost*
AppServiceProxyLacros::WebAppsPublisherHostForTesting() {
return web_apps_publisher_host_.get();
}
void AppServiceProxyLacros::Shutdown() {
if (web_apps_publisher_host_) {
web_apps_publisher_host_->Shutdown();
}
}
} // namespace apps