blob: ba2c47df720a70ae76333e1f343d0233b665f2ad [file] [log] [blame]
// 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_base.h"
#include <stddef.h>
#include <map>
#include <type_traits>
#include <utility>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_source.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/apps/app_service/browser_app_launcher.h"
#include "chrome/browser/apps/app_service/metrics/app_service_metrics.h"
#include "chrome/browser/apps/app_service/publishers/app_publisher.h"
#include "chrome/browser/profiles/profile.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "components/services/app_service/public/cpp/features.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_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 "url/gurl.h"
namespace apps {
class PreferredAppsListHandle;
namespace {
// Utility struct used in GetAppsForIntent.
struct IndexAndGeneric {
size_t index;
bool is_generic;
};
std::string GetActivityLabel(const IntentFilterPtr& filter,
const AppUpdate& update) {
if (filter->activity_label.has_value() && !filter->activity_label->empty()) {
return filter->activity_label.value();
} else {
return update.Name();
}
}
} // anonymous namespace
AppServiceProxyBase::InnerIconLoader::InnerIconLoader(AppServiceProxyBase* host)
: host_(host), overriding_icon_loader_for_testing_(nullptr) {}
absl::optional<IconKey> AppServiceProxyBase::InnerIconLoader::GetIconKey(
const std::string& app_id) {
if (overriding_icon_loader_for_testing_) {
return overriding_icon_loader_for_testing_->GetIconKey(app_id);
}
absl::optional<IconKey> icon_key;
host_->app_registry_cache_.ForOneApp(
app_id,
[&icon_key](const AppUpdate& update) { icon_key = update.IconKey(); });
return icon_key;
}
std::unique_ptr<IconLoader::Releaser>
AppServiceProxyBase::InnerIconLoader::LoadIconFromIconKey(
AppType app_type,
const std::string& app_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(
app_type, app_id, icon_key, icon_type, size_hint_in_dip,
allow_placeholder_icon, std::move(callback));
}
if (host_->ShouldReadIcons(app_type)) {
host_->ReadIcons(app_type, app_id, size_hint_in_dip, icon_key.Clone(),
icon_type, std::move(callback));
return nullptr;
}
auto* publisher = host_->GetPublisher(app_type);
if (!publisher) {
LOG(WARNING) << "No publisher for requested icon";
std::move(callback).Run(std::make_unique<IconValue>());
return nullptr;
}
RecordIconLoadMethodMetrics(IconLoadingMethod::kViaNonMojomCall);
publisher->LoadIcon(app_id, icon_key, icon_type, size_hint_in_dip,
allow_placeholder_icon, std::move(callback));
return nullptr;
}
AppServiceProxyBase::AppServiceProxyBase(Profile* profile)
: inner_icon_loader_(this),
icon_coalescer_(&inner_icon_loader_),
outer_icon_loader_(&icon_coalescer_,
IconCache::GarbageCollectionPolicy::kEager),
profile_(profile) {
preferred_apps_impl_ = std::make_unique<apps::PreferredAppsImpl>(
this, profile ? profile->GetPath() : base::FilePath());
}
AppServiceProxyBase::~AppServiceProxyBase() = default;
void AppServiceProxyBase::ReinitializeForTesting(
Profile* profile,
base::OnceClosure read_completed_for_testing,
base::OnceClosure write_completed_for_testing) {
// 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.
profile_ = profile;
is_using_testing_profile_ = true;
app_registry_cache_.ReinitializeForTesting(); // IN-TEST
preferred_apps_impl_ = std::make_unique<apps::PreferredAppsImpl>(
this, profile ? profile->GetPath() : base::FilePath(),
std::move(read_completed_for_testing),
std::move(write_completed_for_testing));
publishers_.clear();
Initialize();
}
bool AppServiceProxyBase::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 AppServiceProxyBase::Initialize() {
if (!IsValidProfile()) {
return;
}
browser_app_launcher_ = std::make_unique<apps::BrowserAppLauncher>(profile_);
// Make the chrome://app-icon/ resource available.
content::URLDataSource::Add(profile_,
std::make_unique<apps::AppIconSource>(profile_));
}
AppPublisher* AppServiceProxyBase::GetPublisher(AppType app_type) {
auto it = publishers_.find(app_type);
return it == publishers_.end() ? nullptr : it->second;
}
apps::AppRegistryCache& AppServiceProxyBase::AppRegistryCache() {
return app_registry_cache_;
}
apps::AppCapabilityAccessCache&
AppServiceProxyBase::AppCapabilityAccessCache() {
return app_capability_access_cache_;
}
BrowserAppLauncher* AppServiceProxyBase::BrowserAppLauncher() {
return browser_app_launcher_.get();
}
apps::PreferredAppsListHandle& AppServiceProxyBase::PreferredAppsList() {
return preferred_apps_impl_->preferred_apps_list();
}
void AppServiceProxyBase::RegisterPublisher(AppType app_type,
AppPublisher* publisher) {
publishers_[app_type] = publisher;
}
void AppServiceProxyBase::UnregisterPublisher(AppType app_type) {
publishers_.erase(app_type);
}
void AppServiceProxyBase::OnSupportedLinksPreferenceChanged(
const std::string& app_id,
bool open_in_app) {
AppType app_type = AppRegistryCache().GetAppType(app_id);
if (!base::Contains(publishers_, app_type)) {
return;
}
publishers_[app_type]->OnSupportedLinksPreferenceChanged(app_id, open_in_app);
}
absl::optional<IconKey> AppServiceProxyBase::GetIconKey(
const std::string& app_id) {
return outer_icon_loader_.GetIconKey(app_id);
}
std::unique_ptr<apps::IconLoader::Releaser>
AppServiceProxyBase::LoadIconFromIconKey(AppType app_type,
const std::string& app_id,
const IconKey& icon_key,
IconType icon_type,
int32_t size_hint_in_dip,
bool allow_placeholder_icon,
LoadIconCallback callback) {
return outer_icon_loader_.LoadIconFromIconKey(
app_type, app_id, icon_key, icon_type, size_hint_in_dip,
allow_placeholder_icon, std::move(callback));
}
void AppServiceProxyBase::Launch(const std::string& app_id,
int32_t event_flags,
apps::LaunchSource launch_source,
apps::WindowInfoPtr window_info) {
app_registry_cache_.ForOneApp(
app_id, [this, event_flags, launch_source,
&window_info](const apps::AppUpdate& update) {
auto* publisher = GetPublisher(update.AppType());
if (!publisher) {
return;
}
if (MaybeShowLaunchPreventionDialog(update)) {
return;
}
RecordAppLaunch(update.AppId(), launch_source);
RecordAppPlatformMetrics(profile_, update, launch_source,
apps::LaunchContainer::kLaunchContainerNone);
publisher->Launch(update.AppId(), event_flags, launch_source,
std::move(window_info));
PerformPostLaunchTasks(launch_source);
});
}
void AppServiceProxyBase::LaunchAppWithFiles(
const std::string& app_id,
int32_t event_flags,
LaunchSource launch_source,
std::vector<base::FilePath> file_paths) {
app_registry_cache_.ForOneApp(
app_id, [this, event_flags, launch_source,
&file_paths](const apps::AppUpdate& update) {
auto* publisher = GetPublisher(update.AppType());
if (!publisher) {
return;
}
if (MaybeShowLaunchPreventionDialog(update)) {
return;
}
RecordAppPlatformMetrics(profile_, update, launch_source,
LaunchContainer::kLaunchContainerNone);
// TODO(crbug/1117655): File manager records metrics for apps it
// launched. So we only record launches from other places. We should
// eventually move those metrics here, after AppService supports all
// app types launched by file manager.
if (launch_source != LaunchSource::kFromFileManager) {
RecordAppLaunch(update.AppId(), launch_source);
}
publisher->LaunchAppWithFiles(update.AppId(), event_flags,
launch_source, std::move(file_paths));
PerformPostLaunchTasks(launch_source);
});
}
void AppServiceProxyBase::LaunchAppWithIntent(const std::string& app_id,
int32_t event_flags,
IntentPtr intent,
LaunchSource launch_source,
WindowInfoPtr window_info,
LaunchCallback callback) {
CHECK(intent);
app_registry_cache_.ForOneApp(
app_id,
[this, event_flags, &intent, launch_source, &window_info,
callback = std::move(callback)](const AppUpdate& update) mutable {
auto* publisher = GetPublisher(update.AppType());
if (!publisher) {
std::move(callback).Run(LaunchResult(State::FAILED));
return;
}
if (MaybeShowLaunchPreventionDialog(update)) {
std::move(callback).Run(LaunchResult(State::FAILED));
return;
}
// TODO(crbug/1117655): File manager records metrics for apps it
// launched. So we only record launches from other places. We should
// eventually move those metrics here, after AppService supports all
// app types launched by file manager.
if (launch_source != LaunchSource::kFromFileManager) {
RecordAppLaunch(update.AppId(), launch_source);
}
RecordAppPlatformMetrics(profile_, update, launch_source,
LaunchContainer::kLaunchContainerNone);
publisher->LaunchAppWithIntent(
update.AppId(), event_flags, std::move(intent), launch_source,
std::move(window_info), std::move(callback));
PerformPostLaunchTasks(launch_source);
});
}
void AppServiceProxyBase::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 AppServiceProxyBase::LaunchAppWithParams(AppLaunchParams&& params,
LaunchCallback callback) {
auto app_type = app_registry_cache_.GetAppType(params.app_id);
auto* publisher = GetPublisher(app_type);
if (!publisher) {
std::move(callback).Run(LaunchResult());
return;
}
app_registry_cache_.ForOneApp(
params.app_id,
[this, &params, &callback, &publisher](const apps::AppUpdate& update) {
if (MaybeShowLaunchPreventionDialog(update)) {
std::move(callback).Run(LaunchResult());
return;
}
auto launch_source = params.launch_source;
// TODO(crbug/1117655): File manager records metrics for apps it
// launched. So we only record launches from other places. We should
// eventually move those metrics here, after AppService supports all
// app types launched by file manager.
if (launch_source != apps::LaunchSource::kFromFileManager) {
RecordAppLaunch(update.AppId(), launch_source);
}
RecordAppPlatformMetrics(profile_, update, launch_source,
params.container);
publisher->LaunchAppWithParams(
std::move(params),
base::BindOnce(&AppServiceProxyBase::OnLaunched,
weak_factory_.GetWeakPtr(), std::move(callback)));
PerformPostLaunchTasks(launch_source);
});
}
void AppServiceProxyBase::SetPermission(const std::string& app_id,
PermissionPtr permission) {
app_registry_cache_.ForOneApp(
app_id, [this, &permission](const apps::AppUpdate& update) {
auto* publisher = GetPublisher(update.AppType());
if (!publisher) {
return;
}
publisher->SetPermission(update.AppId(), std::move(permission));
});
}
void AppServiceProxyBase::UninstallSilently(const std::string& app_id,
UninstallSource uninstall_source) {
auto app_type = app_registry_cache_.GetAppType(app_id);
auto* publisher = GetPublisher(app_type);
if (!publisher) {
return;
}
publisher->Uninstall(app_id, uninstall_source,
/*clear_site_data=*/false, /*report_abuse=*/false);
PerformPostUninstallTasks(app_type, app_id, uninstall_source);
}
void AppServiceProxyBase::StopApp(const std::string& app_id) {
auto* publisher = GetPublisher(app_registry_cache_.GetAppType(app_id));
if (publisher) {
publisher->StopApp(app_id);
}
}
void AppServiceProxyBase::GetMenuModel(
const std::string& app_id,
MenuType menu_type,
int64_t display_id,
base::OnceCallback<void(MenuItems)> callback) {
auto* publisher = GetPublisher(app_registry_cache_.GetAppType(app_id));
if (publisher) {
publisher->GetMenuModel(app_id, menu_type, display_id, std::move(callback));
} else {
std::move(callback).Run(MenuItems());
}
}
void AppServiceProxyBase::ExecuteContextMenuCommand(
const std::string& app_id,
int command_id,
const std::string& shortcut_id,
int64_t display_id) {
auto* publisher = GetPublisher(app_registry_cache_.GetAppType(app_id));
if (publisher) {
publisher->ExecuteContextMenuCommand(app_id, command_id, shortcut_id,
display_id);
}
return;
}
void AppServiceProxyBase::OpenNativeSettings(const std::string& app_id) {
auto* publisher = GetPublisher(app_registry_cache_.GetAppType(app_id));
if (publisher) {
publisher->OpenNativeSettings(app_id);
}
}
apps::IconLoader* AppServiceProxyBase::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> AppServiceProxyBase::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> AppServiceProxyBase::GetAppsForIntent(
const apps::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([this, &intent_launch_info, &intent,
&exclude_browsers, &exclude_browser_tab_apps](
const apps::AppUpdate& update) {
if (update.Readiness() != apps::Readiness::kReady &&
update.Readiness() != apps::Readiness::kDisabledByPolicy) {
// We consider apps disabled by policy to be ready as they cause URL
// loads to be blocked.
return;
}
if (!update.HandlesIntents().value_or(false)) {
return;
}
if (exclude_browser_tab_apps &&
update.WindowMode() == WindowMode::kBrowser) {
return;
}
// |activity_label| -> {index, is_generic}
std::map<std::string, IndexAndGeneric> best_handler_map;
bool is_file_handling_intent = !intent->files.empty();
const apps::IntentFilters& filters = update.IntentFilters();
for (size_t i = 0; i < filters.size(); i++) {
const IntentFilterPtr& filter = filters[i];
DCHECK(filter);
if (exclude_browsers && filter->IsBrowserFilter()) {
continue;
}
if (intent->MatchFilter(filter)) {
// Return the first non-generic match if it exists, otherwise the
// first generic match.
bool generic = false;
if (is_file_handling_intent) {
generic = apps_util::IsGenericFileHandler(intent, filter);
}
std::string activity_label = GetActivityLabel(filter, update);
// Replace the best handler if it is generic and we have a non-generic
// one.
auto it = best_handler_map.find(activity_label);
if (it == best_handler_map.end() ||
(it->second.is_generic && !generic)) {
best_handler_map[activity_label] = IndexAndGeneric{i, generic};
}
}
}
for (const auto& handler_entry : best_handler_map) {
const IntentFilterPtr& filter = filters[handler_entry.second.index];
IntentLaunchInfo entry = CreateIntentLaunchInfo(intent, filter, update);
intent_launch_info.push_back(entry);
}
});
return intent_launch_info;
}
std::vector<IntentLaunchInfo> AppServiceProxyBase::GetAppsForFiles(
std::vector<apps::IntentFilePtr> files) {
return GetAppsForIntent(std::make_unique<apps::Intent>(
apps_util::kIntentActionView, std::move(files)),
false, false);
}
void AppServiceProxyBase::AddPreferredApp(const std::string& app_id,
const GURL& url) {
AddPreferredApp(app_id,
std::make_unique<Intent>(apps_util::kIntentActionView, url));
}
void AppServiceProxyBase::AddPreferredApp(const std::string& app_id,
const IntentPtr& intent) {
DCHECK(!app_id.empty());
DCHECK(preferred_apps_impl_);
auto intent_filter = FindBestMatchingFilter(intent);
if (!intent_filter) {
return;
}
// Treat kUseBrowserForLink like an app with a single supported link, so
// that any apps with overlapping supported links will have their preference
// removed correctly.
if (app_id == apps_util::kUseBrowserForLink) {
std::vector<IntentFilterPtr> filters;
filters.push_back(std::move(intent_filter));
preferred_apps_impl_->SetSupportedLinksPreference(app_id,
std::move(filters));
return;
}
// AddPreferredApp currently only supports adding preferences for link
// intents.
DCHECK(apps_util::IsSupportedLinkForApp(app_id, intent_filter));
SetSupportedLinksPreference(app_id);
}
void AppServiceProxyBase::SetSupportedLinksPreference(
const std::string& app_id) {
IntentFilters filters;
AppRegistryCache().ForOneApp(
app_id, [&app_id, &filters](const AppUpdate& app) {
for (auto& filter : app.IntentFilters()) {
if (apps_util::IsSupportedLinkForApp(app_id, filter)) {
filters.push_back(std::move(filter));
}
}
});
SetSupportedLinksPreference(app_id, std::move(filters));
}
void AppServiceProxyBase::SetSupportedLinksPreference(
const std::string& app_id,
IntentFilters all_link_filters) {
DCHECK(!app_id.empty());
preferred_apps_impl_->SetSupportedLinksPreference(
app_id, std::move(all_link_filters));
}
void AppServiceProxyBase::RemoveSupportedLinksPreference(
const std::string& app_id) {
DCHECK(!app_id.empty());
preferred_apps_impl_->RemoveSupportedLinksPreference(app_id);
}
void AppServiceProxyBase::SetWindowMode(const std::string& app_id,
WindowMode window_mode) {
auto* publisher = GetPublisher(app_registry_cache_.GetAppType(app_id));
if (publisher) {
publisher->SetWindowMode(app_id, window_mode);
}
}
void AppServiceProxyBase::OnApps(std::vector<AppPtr> deltas,
AppType app_type,
bool should_notify_initialized) {
for (const auto& delta : deltas) {
if (delta->readiness != Readiness::kUnknown &&
!apps_util::IsInstalled(delta->readiness)) {
preferred_apps_impl_->RemovePreferredApp(delta->app_id);
}
}
app_registry_cache_.OnApps(std::move(deltas), app_type,
should_notify_initialized);
}
void AppServiceProxyBase::OnCapabilityAccesses(
std::vector<CapabilityAccessPtr> deltas) {
app_capability_access_cache_.OnCapabilityAccesses(std::move(deltas));
}
IntentFilterPtr AppServiceProxyBase::FindBestMatchingFilter(
const IntentPtr& intent) {
IntentFilterPtr best_matching_intent_filter;
if (!intent) {
return best_matching_intent_filter;
}
int best_match_level = static_cast<int>(IntentFilterMatchLevel::kNone);
app_registry_cache_.ForEachApp(
[&intent, &best_match_level,
&best_matching_intent_filter](const apps::AppUpdate& update) {
for (auto& filter : update.IntentFilters()) {
if (!intent->MatchFilter(filter)) {
continue;
}
auto match_level = filter->GetFilterMatchLevel();
if (match_level <= best_match_level) {
continue;
}
best_matching_intent_filter = std::move(filter);
best_match_level = match_level;
}
});
return best_matching_intent_filter;
}
void AppServiceProxyBase::PerformPostLaunchTasks(
apps::LaunchSource launch_source) {}
void AppServiceProxyBase::RecordAppPlatformMetrics(
Profile* profile,
const apps::AppUpdate& update,
apps::LaunchSource launch_source,
apps::LaunchContainer container) {}
void AppServiceProxyBase::PerformPostUninstallTasks(
apps::AppType app_type,
const std::string& app_id,
UninstallSource uninstall_source) {}
void AppServiceProxyBase::OnLaunched(LaunchCallback callback,
LaunchResult&& launch_result) {
std::move(callback).Run(std::move(launch_result));
}
bool AppServiceProxyBase::ShouldReadIcons(AppType app_type) {
return false;
}
IntentLaunchInfo AppServiceProxyBase::CreateIntentLaunchInfo(
const apps::IntentPtr& intent,
const apps::IntentFilterPtr& filter,
const apps::AppUpdate& update) {
IntentLaunchInfo entry;
entry.app_id = update.AppId();
entry.activity_label = GetActivityLabel(filter, update);
entry.activity_name = filter->activity_name.value_or("");
entry.is_generic_file_handler =
apps_util::IsGenericFileHandler(intent, filter);
entry.is_file_extension_match = filter->IsFileExtensionsFilter();
return entry;
}
IntentLaunchInfo::IntentLaunchInfo() = default;
IntentLaunchInfo::~IntentLaunchInfo() = default;
IntentLaunchInfo::IntentLaunchInfo(const IntentLaunchInfo& other) = default;
} // namespace apps