// 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/app_service_proxy_lacros.h"
#include <utility>
#include "base/containers/contains.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/app_icon_source.h"
#include "chrome/browser/apps/app_service/app_install/app_install_service.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/apps/app_service/intent_util.h"
#include "chrome/browser/apps/app_service/launch_result_type.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.h"
#include "chrome/browser/apps/app_service/publishers/extension_apps.h"
#include "chrome/browser/apps/browser_instance/browser_app_instance_forwarder.h"
#include "chrome/browser/apps/browser_instance/browser_app_instance_tracker.h"
#include "chrome/browser/extensions/extension_keeplist_chromeos.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/browser/web_applications/app_service/lacros_web_apps_controller.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chromeos/lacros/lacros_service.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/icon_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/preferred_app.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "content/public/browser/url_data_source.h"
#include "ui/display/types/display_constants.h"
#include "url/url_constants.h"
namespace apps {
AppServiceProxyLacros::AppServiceProxyLacros(Profile* profile)
: app_inner_icon_loader_(this),
app_icon_coalescer_(&app_inner_icon_loader_),
app_outer_icon_loader_(&app_icon_coalescer_,
apps::IconCache::GarbageCollectionPolicy::kEager),
profile_(profile) {
if (web_app::IsWebAppsCrosapiEnabled()) {
auto* service = chromeos::LacrosService::Get();
if (service &&
service->IsAvailable<crosapi::mojom::BrowserAppInstanceRegistry>()) {
browser_app_instance_tracker_ =
std::make_unique<apps::BrowserAppInstanceTrackerLacros>(
profile_, app_registry_cache_);
auto& registry =
service->GetRemote<crosapi::mojom::BrowserAppInstanceRegistry>();
DCHECK(registry);
browser_app_instance_forwarder_ =
std::make_unique<apps::BrowserAppInstanceForwarder>(
*browser_app_instance_tracker_, registry);
}
}
}
AppServiceProxyLacros::~AppServiceProxyLacros() = default;
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();
remote_crosapi_app_service_proxy_ = nullptr;
profile_ = profile;
is_using_testing_profile_ = true;
app_registry_cache_.ReinitializeForTesting(); // IN-TEST
Initialize();
}
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::PreferredAppsListHandle& AppServiceProxyLacros::PreferredAppsList() {
return preferred_apps_list_;
}
apps::BrowserAppInstanceTracker*
AppServiceProxyLacros::BrowserAppInstanceTracker() {
return browser_app_instance_tracker_.get();
}
apps::WebsiteMetricsServiceLacros*
AppServiceProxyLacros::WebsiteMetricsService() {
return metrics_service_.get();
}
AppInstallService& AppServiceProxyLacros::AppInstallService() {
return *app_install_service_;
}
void AppServiceProxyLacros::OnApps(std::vector<AppPtr> deltas,
AppType app_type,
bool should_notify_initialized) {
app_registry_cache_.OnApps(std::move(deltas), app_type,
should_notify_initialized);
}
std::unique_ptr<IconLoader::Releaser> AppServiceProxyLacros::LoadIcon(
const std::string& app_id,
const IconType& icon_type,
int32_t size_hint_in_dip,
bool allow_placeholder_icon,
apps::LoadIconCallback callback) {
return app_icon_loader()->LoadIcon(app_id, 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::LaunchSource launch_source,
apps::WindowInfoPtr window_info) {
if (!remote_crosapi_app_service_proxy_) {
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;
}
ProxyLaunch(CreateCrosapiLaunchParamsWithEventFlags(
this, app_id, event_flags, launch_source, display::kInvalidDisplayId));
}
void AppServiceProxyLacros::LaunchAppWithFiles(
const std::string& app_id,
int32_t event_flags,
LaunchSource launch_source,
std::vector<base::FilePath> file_paths) {
if (!remote_crosapi_app_service_proxy_) {
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 params = CreateCrosapiLaunchParamsWithEventFlags(
this, app_id, event_flags, launch_source, display::kInvalidDisplayId);
params->intent =
apps_util::CreateCrosapiIntentForViewFiles(std::move(file_paths));
ProxyLaunch(std::move(params));
}
void AppServiceProxyLacros::LaunchAppWithIntent(const std::string& app_id,
int32_t event_flags,
IntentPtr intent,
LaunchSource launch_source,
WindowInfoPtr window_info,
LaunchCallback callback) {
CHECK(intent);
if (!remote_crosapi_app_service_proxy_) {
std::move(callback).Run(LaunchResult(State::kFailed));
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().";
std::move(callback).Run(LaunchResult(State::kFailed));
return;
}
auto params = CreateCrosapiLaunchParamsWithEventFlags(
this, app_id, event_flags, launch_source,
window_info ? window_info->display_id : display::kInvalidDisplayId);
params->intent =
apps_util::ConvertAppServiceToCrosapiIntent(intent, profile_);
ProxyLaunch(std::move(params), base::BindOnce(std::move(callback)));
}
void AppServiceProxyLacros::LaunchAppWithUrl(const std::string& app_id,
int32_t event_flags,
GURL url,
LaunchSource launch_source,
WindowInfoPtr window_info,
LaunchCallback callback) {
LaunchAppWithIntent(
app_id, event_flags,
std::make_unique<apps::Intent>(apps_util::kIntentActionView, url),
launch_source, std::move(window_info), std::move(callback));
}
void AppServiceProxyLacros::LaunchAppWithParams(AppLaunchParams&& params,
LaunchCallback callback) {
if (!remote_crosapi_app_service_proxy_) {
std::move(callback).Run(LaunchResult(State::kFailed));
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().";
std::move(callback).Run(LaunchResult(State::kFailed));
return;
}
ProxyLaunch(ConvertLaunchParamsToCrosapi(params, profile_),
std::move(callback));
}
void AppServiceProxyLacros::SetPermission(const std::string& app_id,
PermissionPtr permission) {
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::Uninstall(const std::string& app_id,
UninstallSource uninstall_source,
gfx::NativeWindow parent_window) {
// On non-ChromeOS, publishers run the remove dialog.
auto app_type = app_registry_cache_.GetAppType(app_id);
if (app_type == AppType::kWeb) {
web_app::UninstallImpl(web_app::WebAppProvider::GetForWebApps(profile_),
app_id, uninstall_source, parent_window);
} else {
NOTIMPLEMENTED();
}
}
void AppServiceProxyLacros::UninstallSilently(
const std::string& app_id,
UninstallSource uninstall_source) {
if (!remote_crosapi_app_service_proxy_) {
return;
}
if (crosapi_app_service_proxy_version_ <
int{crosapi::mojom::AppServiceProxy::MethodMinVersions::
kUninstallSilentlyMinVersion}) {
LOG(WARNING) << "Ash AppServiceProxy version "
<< crosapi_app_service_proxy_version_
<< " does not support UninstallSilently().";
return;
}
remote_crosapi_app_service_proxy_->UninstallSilently(app_id,
uninstall_source);
}
void AppServiceProxyLacros::StopApp(const std::string& app_id) {
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::UpdateAppSize(const std::string& app_id) {
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 =
app_inner_icon_loader_.overriding_icon_loader_for_testing_;
app_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(
std::make_unique<apps::Intent>(apps_util::kIntentActionView, 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 IntentPtr& intent,
bool exclude_browsers,
bool exclude_browser_tab_apps) {
std::vector<IntentLaunchInfo> intent_launch_info;
if (!intent || intent->OnlyShareToDrive() || !intent->IsIntentValid()) {
return intent_launch_info;
}
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().value_or(false)) {
return;
}
if (exclude_browser_tab_apps &&
update.WindowMode() == WindowMode::kBrowser) {
return;
}
std::set<std::string> existing_activities;
for (const auto& filter : update.IntentFilters()) {
DCHECK(filter);
if (exclude_browsers && filter->IsBrowserFilter()) {
continue;
}
if (intent->MatchFilter(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::MakeShareIntent(filesystem_urls, mime_types));
}
void AppServiceProxyLacros::SetSupportedLinksPreference(
const std::string& app_id) {
DCHECK(!app_id.empty());
if (!remote_crosapi_app_service_proxy_) {
return;
}
if (crosapi_app_service_proxy_version_ <
int{crosapi::mojom::AppServiceProxy::MethodMinVersions::
kSetSupportedLinksPreferenceMinVersion}) {
LOG(WARNING) << "Ash AppServiceProxy version "
<< crosapi_app_service_proxy_version_
<< " does not support SetSupportedLinksPreference().";
return;
}
remote_crosapi_app_service_proxy_->SetSupportedLinksPreference(app_id);
}
void AppServiceProxyLacros::RemoveSupportedLinksPreference(
const std::string& app_id) {
// If this is implemented, also add the crosapi method to
// LoopbackCrosapiAppServiceProxy.
NOTIMPLEMENTED();
}
void AppServiceProxyLacros::SetWindowMode(const std::string& app_id,
WindowMode window_mode) {
NOTIMPLEMENTED();
}
web_app::LacrosWebAppsController*
AppServiceProxyLacros::LacrosWebAppsControllerForTesting() {
return lacros_web_apps_controller_.get();
}
void AppServiceProxyLacros::SetCrosapiAppServiceProxyForTesting(
crosapi::mojom::AppServiceProxy* proxy) {
remote_crosapi_app_service_proxy_ = proxy;
// Set the proxy version to the newest version for testing.
crosapi_app_service_proxy_version_ =
crosapi::mojom::AppServiceProxy::Version_;
}
void AppServiceProxyLacros::SetWebsiteMetricsServiceForTesting(
std::unique_ptr<apps::WebsiteMetricsServiceLacros>
website_metrics_service) {
metrics_service_ = std::move(website_metrics_service);
}
void AppServiceProxyLacros::SetBrowserAppInstanceTrackerForTesting(
std::unique_ptr<apps::BrowserAppInstanceTracker>
browser_app_instance_tracker) {
browser_app_instance_tracker_ = std::move(browser_app_instance_tracker);
}
crosapi::mojom::AppServiceSubscriber*
AppServiceProxyLacros::AsAppServiceSubscriberForTesting() {
return this;
}
base::WeakPtr<AppServiceProxyLacros> AppServiceProxyLacros::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
AppServiceProxyLacros::AppInnerIconLoader::AppInnerIconLoader(
AppServiceProxyLacros* host)
: host_(host) {}
std::optional<IconKey> AppServiceProxyLacros::AppInnerIconLoader::GetIconKey(
const std::string& id) {
if (overriding_icon_loader_for_testing_) {
return overriding_icon_loader_for_testing_->GetIconKey(id);
}
if (!host_->crosapi_receiver_.is_bound()) {
return std::nullopt;
}
std::optional<IconKey> icon_key;
host_->app_registry_cache_.ForOneApp(
id,
[&icon_key](const AppUpdate& update) { icon_key = update.IconKey(); });
return icon_key;
}
std::unique_ptr<IconLoader::Releaser>
AppServiceProxyLacros::AppInnerIconLoader::LoadIconFromIconKey(
const std::string& id,
const IconKey& icon_key,
IconType icon_type,
int32_t size_hint_in_dip,
bool allow_placeholder_icon,
apps::LoadIconCallback callback) {
if (overriding_icon_loader_for_testing_) {
return overriding_icon_loader_for_testing_->LoadIconFromIconKey(
id, icon_key, icon_type, size_hint_in_dip, allow_placeholder_icon,
std::move(callback));
}
if (!host_->remote_crosapi_app_service_proxy_) {
std::move(callback).Run(std::make_unique<IconValue>());
} 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(std::make_unique<IconValue>());
} else {
host_->remote_crosapi_app_service_proxy_->LoadIcon(
id, icon_key.Clone(), icon_type, size_hint_in_dip, std::move(callback));
}
return nullptr;
}
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;
}
void AppServiceProxyLacros::Initialize() {
if (remote_crosapi_app_service_proxy_) {
return;
}
if (!IsValidProfile()) {
return;
}
browser_app_launcher_ = std::make_unique<apps::BrowserAppLauncher>(profile_);
if (profile_->IsMainProfile()) {
lacros_web_apps_controller_ =
std::make_unique<web_app::LacrosWebAppsController>(profile_);
lacros_web_apps_controller_->Init();
}
// Make the chrome://app-icon/ resource available.
content::URLDataSource::Add(profile_,
std::make_unique<apps::AppIconSource>(profile_));
if (!profile_->AsTestingProfile()) {
metrics_service_ = std::make_unique<WebsiteMetricsServiceLacros>(profile_);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&AppServiceProxyLacros::InitWebsiteMetrics,
weak_ptr_factory_.GetWeakPtr()));
}
auto* service = chromeos::LacrosService::Get();
if (!service || !service->IsAvailable<crosapi::mojom::AppServiceProxy>()) {
return;
}
crosapi_app_service_proxy_version_ =
service->GetInterfaceVersion<crosapi::mojom::AppServiceProxy>();
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());
remote_crosapi_app_service_proxy_ =
service->GetRemote<crosapi::mojom::AppServiceProxy>().get();
app_install_service_ = AppInstallService::Create(*profile_);
}
void AppServiceProxyLacros::Shutdown() {
metrics_service_.reset();
if (lacros_web_apps_controller_) {
lacros_web_apps_controller_->Shutdown();
}
}
void AppServiceProxyLacros::OnPreferredAppsChanged(
PreferredAppChangesPtr changes) {
preferred_apps_list_.ApplyBulkUpdate(std::move(changes));
}
void AppServiceProxyLacros::InitializePreferredApps(
PreferredApps preferred_apps) {
preferred_apps_list_.Init(std::move(preferred_apps));
}
void AppServiceProxyLacros::ProxyLaunch(crosapi::mojom::LaunchParamsPtr params,
LaunchCallback callback) {
// Extensions that run in both the OS and standalone browser are not published
// to the app service. Thus launching must happen directly.
if (extensions::ExtensionRunsInBothOSAndStandaloneBrowser(params->app_id) ||
extensions::ExtensionAppRunsInBothOSAndStandaloneBrowser(
params->app_id)) {
OpenApplication(profile_,
ConvertCrosapiToLaunchParams(std::move(params), profile_));
std::move(callback).Run(ConvertBoolToLaunchResult(true));
return;
}
if (crosapi_app_service_proxy_version_ <
int{crosapi::mojom::AppServiceProxy::MethodMinVersions::
kLaunchWithResultMinVersion}) {
remote_crosapi_app_service_proxy_->Launch(std::move(params));
std::move(callback).Run(ConvertBoolToLaunchResult(true));
} else {
remote_crosapi_app_service_proxy_->LaunchWithResult(
std::move(params),
LaunchResultToMojomLaunchResultCallback(std::move(callback)));
}
}
void AppServiceProxyLacros::InitWebsiteMetrics() {
if (metrics_service_) {
metrics_service_->InitDeviceTypeAndStart();
}
}
} // namespace apps