blob: 9edf2fedb441958b244480588c14d53c24232114 [file] [log] [blame]
// Copyright 2018 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.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/feature_list.h"
#include "base/location.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/apps/app_service/app_icon_source.h"
#include "chrome/browser/apps/app_service/app_service_metrics.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h"
#include "chrome/services/app_service/app_service_impl.h"
#include "chrome/services/app_service/public/cpp/intent_filter_util.h"
#include "chrome/services/app_service/public/cpp/intent_util.h"
#include "chrome/services/app_service/public/mojom/types.mojom.h"
#include "content/public/browser/url_data_source.h"
#include "url/url_constants.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/apps/app_service/uninstall_dialog.h"
#include "chrome/browser/chromeos/child_accounts/time_limits/app_time_limit_interface.h"
#include "chrome/browser/supervised_user/grit/supervised_user_unscaled_resources.h"
#include "extensions/common/constants.h"
#endif
namespace apps {
AppServiceProxy::InnerIconLoader::InnerIconLoader(AppServiceProxy* host)
: host_(host), overriding_icon_loader_for_testing_(nullptr) {}
apps::mojom::IconKeyPtr AppServiceProxy::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_->app_service_.is_connected()) {
host_->cache_.ForOneApp(app_id, [&icon_key](const apps::AppUpdate& update) {
icon_key = update.IconKey();
});
}
return icon_key;
}
std::unique_ptr<IconLoader::Releaser>
AppServiceProxy::InnerIconLoader::LoadIconFromIconKey(
apps::mojom::AppType app_type,
const std::string& app_id,
apps::mojom::IconKeyPtr icon_key,
apps::mojom::IconCompression icon_compression,
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_compression,
size_hint_in_dip, allow_placeholder_icon, std::move(callback));
}
if (host_->app_service_.is_connected() && icon_key) {
// TODO(crbug.com/826982): Mojo doesn't guarantee the order of messages,
// so multiple calls to this method might not resolve their callbacks in
// order. As per khmel@, "you may have race here, assume you publish change
// for the app and app requested new icon. But new icon is not delivered
// yet and you resolve old one instead. Now new icon arrives asynchronously
// but you no longer notify the app or do?"
host_->app_service_->LoadIcon(app_type, app_id, std::move(icon_key),
icon_compression, size_hint_in_dip,
allow_placeholder_icon, std::move(callback));
} else {
std::move(callback).Run(apps::mojom::IconValue::New());
}
return nullptr;
}
AppServiceProxy::AppServiceProxy(Profile* profile)
: inner_icon_loader_(this),
icon_coalescer_(&inner_icon_loader_),
outer_icon_loader_(&icon_coalescer_,
apps::IconCache::GarbageCollectionPolicy::kEager),
profile_(profile) {
Initialize();
}
AppServiceProxy::~AppServiceProxy() = default;
// static
void AppServiceProxy::RegisterProfilePrefs(PrefRegistrySimple* registry) {
AppServiceImpl::RegisterProfilePrefs(registry);
}
void AppServiceProxy::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.
app_service_.reset();
profile_ = profile;
is_using_testing_profile_ = true;
Initialize();
}
void AppServiceProxy::Initialize() {
if (!profile_) {
return;
}
// 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;
}
browser_app_launcher_ = std::make_unique<apps::BrowserAppLauncher>(profile_);
app_service_impl_ = std::make_unique<apps::AppServiceImpl>(
profile_->GetPrefs(), profile_->GetPath());
app_service_impl_->BindReceiver(app_service_.BindNewPipeAndPassReceiver());
if (app_service_.is_connected()) {
// The AppServiceProxy is a subscriber: something that wants to be able to
// list all known apps.
mojo::PendingRemote<apps::mojom::Subscriber> subscriber;
receivers_.Add(this, subscriber.InitWithNewPipeAndPassReceiver());
app_service_->RegisterSubscriber(std::move(subscriber), nullptr);
#if defined(OS_CHROMEOS)
// The AppServiceProxy is also a publisher, of a variety of app types. That
// responsibility isn't intrinsically part of the AppServiceProxy, but doing
// that here, for each such app type, is as good a place as any.
built_in_chrome_os_apps_ =
std::make_unique<BuiltInChromeOsApps>(app_service_, profile_);
crostini_apps_ = std::make_unique<CrostiniApps>(app_service_, profile_);
extension_apps_ = std::make_unique<ExtensionAppsChromeOs>(
app_service_, profile_, apps::mojom::AppType::kExtension,
&instance_registry_);
plugin_vm_apps_ = std::make_unique<PluginVmApps>(app_service_, profile_);
if (base::FeatureList::IsEnabled(features::kDesktopPWAsWithoutExtensions)) {
web_apps_ = std::make_unique<WebAppsChromeOs>(app_service_, profile_,
&instance_registry_);
} else {
extension_web_apps_ = std::make_unique<ExtensionAppsChromeOs>(
app_service_, profile_, apps::mojom::AppType::kWeb,
&instance_registry_);
}
#else
if (base::FeatureList::IsEnabled(features::kDesktopPWAsWithoutExtensions)) {
web_apps_ = std::make_unique<WebApps>(app_service_, profile_);
} else {
extension_web_apps_ = std::make_unique<ExtensionApps>(
app_service_, profile_, apps::mojom::AppType::kWeb);
}
#endif
// Asynchronously add app icon source, so we don't do too much work in the
// constructor.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&AppServiceProxy::AddAppIconSource,
weak_ptr_factory_.GetWeakPtr(), profile_));
}
Observe(&cache_);
}
mojo::Remote<apps::mojom::AppService>& AppServiceProxy::AppService() {
return app_service_;
}
apps::AppRegistryCache& AppServiceProxy::AppRegistryCache() {
return cache_;
}
#if defined(OS_CHROMEOS)
apps::InstanceRegistry& AppServiceProxy::InstanceRegistry() {
return instance_registry_;
}
#endif
BrowserAppLauncher& AppServiceProxy::BrowserAppLauncher() {
return *browser_app_launcher_;
}
apps::PreferredAppsList& AppServiceProxy::PreferredApps() {
return preferred_apps_;
}
apps::mojom::IconKeyPtr AppServiceProxy::GetIconKey(const std::string& app_id) {
return outer_icon_loader_.GetIconKey(app_id);
}
std::unique_ptr<apps::IconLoader::Releaser>
AppServiceProxy::LoadIconFromIconKey(
apps::mojom::AppType app_type,
const std::string& app_id,
apps::mojom::IconKeyPtr icon_key,
apps::mojom::IconCompression icon_compression,
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_compression, size_hint_in_dip,
allow_placeholder_icon, std::move(callback));
}
void AppServiceProxy::Launch(const std::string& app_id,
int32_t event_flags,
apps::mojom::LaunchSource launch_source,
int64_t display_id) {
if (app_service_.is_connected()) {
cache_.ForOneApp(app_id, [this, event_flags, launch_source,
display_id](const apps::AppUpdate& update) {
#if defined(OS_CHROMEOS)
if (MaybeShowLaunchPreventionDialog(update)) {
return;
}
#endif
RecordAppLaunch(update.AppId(), launch_source);
app_service_->Launch(update.AppType(), update.AppId(), event_flags,
launch_source, display_id);
});
}
}
void AppServiceProxy::LaunchAppWithFiles(
const std::string& app_id,
apps::mojom::LaunchContainer container,
int32_t event_flags,
apps::mojom::LaunchSource launch_source,
apps::mojom::FilePathsPtr file_paths) {
if (app_service_.is_connected()) {
cache_.ForOneApp(app_id, [this, container, event_flags, launch_source,
&file_paths](const apps::AppUpdate& update) {
#if defined(OS_CHROMEOS)
if (MaybeShowLaunchPreventionDialog(update)) {
return;
}
#endif
app_service_->LaunchAppWithFiles(update.AppType(), update.AppId(),
container, event_flags, launch_source,
std::move(file_paths));
});
}
}
void AppServiceProxy::LaunchAppWithIntent(
const std::string& app_id,
int32_t event_flags,
apps::mojom::IntentPtr intent,
apps::mojom::LaunchSource launch_source,
int64_t display_id) {
if (app_service_.is_connected()) {
cache_.ForOneApp(app_id, [this, event_flags, &intent, launch_source,
display_id](const apps::AppUpdate& update) {
#if defined(OS_CHROMEOS)
if (MaybeShowLaunchPreventionDialog(update)) {
return;
}
#endif
RecordAppLaunch(update.AppId(), launch_source);
app_service_->LaunchAppWithIntent(update.AppType(), update.AppId(),
event_flags, std::move(intent),
launch_source, display_id);
});
}
}
void AppServiceProxy::LaunchAppWithUrl(const std::string& app_id,
int32_t event_flags,
GURL url,
apps::mojom::LaunchSource launch_source,
int64_t display_id) {
LaunchAppWithIntent(app_id, event_flags, apps_util::CreateIntentFromUrl(url),
launch_source, display_id);
}
void AppServiceProxy::SetPermission(const std::string& app_id,
apps::mojom::PermissionPtr permission) {
if (app_service_.is_connected()) {
cache_.ForOneApp(
app_id, [this, &permission](const apps::AppUpdate& update) {
app_service_->SetPermission(update.AppType(), update.AppId(),
std::move(permission));
});
}
}
void AppServiceProxy::Uninstall(const std::string& app_id,
gfx::NativeWindow parent_window) {
#if defined(OS_CHROMEOS)
UninstallImpl(app_id, parent_window, base::DoNothing());
#else
// On non-ChromeOS, publishers run the remove dialog.
apps::mojom::AppType app_type = cache_.GetAppType(app_id);
if (app_type == apps::mojom::AppType::kWeb) {
if (!base::FeatureList::IsEnabled(
features::kDesktopPWAsWithoutExtensions)) {
ExtensionApps::UninstallImpl(profile_, app_id, parent_window);
} else {
WebApps::UninstallImpl(profile_, app_id, parent_window);
}
}
#endif
}
#if defined(OS_CHROMEOS)
void AppServiceProxy::PauseApps(
const std::map<std::string, PauseData>& pause_data) {
if (!app_service_.is_connected()) {
return;
}
for (auto& data : pause_data) {
apps::mojom::AppType app_type = cache_.GetAppType(data.first);
if (app_type == apps::mojom::AppType::kUnknown) {
continue;
}
cache_.ForOneApp(data.first, [this](const apps::AppUpdate& update) {
if (update.Paused() != apps::mojom::OptionalBool::kTrue) {
pending_pause_requests_.MaybeAddApp(update.AppId());
}
});
// The app pause dialog can't be loaded for unit tests.
if (!data.second.should_show_pause_dialog || is_using_testing_profile_) {
app_service_->PauseApp(app_type, data.first);
continue;
}
cache_.ForOneApp(data.first, [this, &data](const apps::AppUpdate& update) {
LoadIconForDialog(
update,
base::BindOnce(&AppServiceProxy::OnLoadIconForPauseDialog,
weak_ptr_factory_.GetWeakPtr(), update.AppType(),
update.AppId(), update.Name(), data.second));
});
}
}
void AppServiceProxy::UnpauseApps(const std::set<std::string>& app_ids) {
if (!app_service_.is_connected()) {
return;
}
for (auto& app_id : app_ids) {
apps::mojom::AppType app_type = cache_.GetAppType(app_id);
if (app_type == apps::mojom::AppType::kUnknown) {
continue;
}
pending_pause_requests_.MaybeRemoveApp(app_id);
app_service_->UnpauseApps(app_type, app_id);
}
}
#endif // OS_CHROMEOS
void AppServiceProxy::GetMenuModel(
const std::string& app_id,
apps::mojom::MenuType menu_type,
int64_t display_id,
apps::mojom::Publisher::GetMenuModelCallback callback) {
if (!app_service_.is_connected()) {
return;
}
apps::mojom::AppType app_type = cache_.GetAppType(app_id);
app_service_->GetMenuModel(app_type, app_id, menu_type, display_id,
std::move(callback));
}
void AppServiceProxy::OpenNativeSettings(const std::string& app_id) {
if (app_service_.is_connected()) {
cache_.ForOneApp(app_id, [this](const apps::AppUpdate& update) {
app_service_->OpenNativeSettings(update.AppType(), update.AppId());
});
}
}
void AppServiceProxy::FlushMojoCallsForTesting() {
app_service_impl_->FlushMojoCallsForTesting();
#if defined(OS_CHROMEOS)
built_in_chrome_os_apps_->FlushMojoCallsForTesting();
crostini_apps_->FlushMojoCallsForTesting();
extension_apps_->FlushMojoCallsForTesting();
plugin_vm_apps_->FlushMojoCallsForTesting();
if (web_apps_) {
web_apps_->FlushMojoCallsForTesting();
} else {
extension_web_apps_->FlushMojoCallsForTesting();
}
#endif
receivers_.FlushForTesting();
}
apps::IconLoader* AppServiceProxy::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;
}
#if defined(OS_CHROMEOS)
void AppServiceProxy::ReInitializeCrostiniForTesting(Profile* profile) {
if (app_service_.is_connected()) {
crostini_apps_->ReInitializeForTesting(app_service_, profile);
}
}
void AppServiceProxy::SetDialogCreatedCallbackForTesting(
base::OnceClosure callback) {
dialog_created_callback_ = std::move(callback);
}
void AppServiceProxy::UninstallForTesting(const std::string& app_id,
gfx::NativeWindow parent_window,
base::OnceClosure callback) {
UninstallImpl(app_id, parent_window, std::move(callback));
}
#endif
std::vector<std::string> AppServiceProxy::GetAppIdsForUrl(const GURL& url) {
return GetAppIdsForIntent(apps_util::CreateIntentFromUrl(url));
}
std::vector<std::string> AppServiceProxy::GetAppIdsForIntent(
apps::mojom::IntentPtr intent) {
std::vector<std::string> app_ids;
if (app_service_.is_bound()) {
cache_.ForEachApp([&app_ids, &intent](const apps::AppUpdate& update) {
if (update.Readiness() == apps::mojom::Readiness::kUninstalledByUser) {
return;
}
for (const auto& filter : update.IntentFilters()) {
if (apps_util::IntentMatchesFilter(intent, filter)) {
app_ids.push_back(update.AppId());
}
}
});
}
return app_ids;
}
void AppServiceProxy::SetArcIsRegistered() {
#if defined(OS_CHROMEOS)
if (arc_is_registered_) {
return;
}
arc_is_registered_ = true;
extension_apps_->ObserveArc();
if (web_apps_) {
web_apps_->ObserveArc();
} else {
extension_web_apps_->ObserveArc();
}
#endif
}
void AppServiceProxy::AddPreferredApp(const std::string& app_id,
const GURL& url) {
AddPreferredApp(app_id, apps_util::CreateIntentFromUrl(url));
}
void AppServiceProxy::AddPreferredApp(const std::string& app_id,
const apps::mojom::IntentPtr& intent) {
auto intent_filter = FindBestMatchingFilter(intent);
if (!intent_filter) {
return;
}
preferred_apps_.AddPreferredApp(app_id, intent_filter);
if (app_service_.is_connected()) {
cache_.ForOneApp(
app_id, [this, &intent_filter, &intent](const apps::AppUpdate& update) {
constexpr bool kFromPublisher = false;
app_service_->AddPreferredApp(update.AppType(), update.AppId(),
std::move(intent_filter),
intent->Clone(), kFromPublisher);
});
}
}
void AppServiceProxy::AddAppIconSource(Profile* profile) {
// Make the chrome://app-icon/ resource available.
content::URLDataSource::Add(profile,
std::make_unique<apps::AppIconSource>(profile));
}
void AppServiceProxy::Shutdown() {
#if defined(OS_CHROMEOS)
uninstall_dialogs_.clear();
if (app_service_.is_connected()) {
extension_apps_->Shutdown();
if (web_apps_) {
web_apps_->Shutdown();
} else {
extension_web_apps_->Shutdown();
}
}
#endif
}
void AppServiceProxy::OnApps(std::vector<apps::mojom::AppPtr> deltas) {
cache_.OnApps(std::move(deltas));
}
void AppServiceProxy::Clone(
mojo::PendingReceiver<apps::mojom::Subscriber> receiver) {
receivers_.Add(this, std::move(receiver));
}
void AppServiceProxy::OnPreferredAppSet(
const std::string& app_id,
apps::mojom::IntentFilterPtr intent_filter) {
preferred_apps_.AddPreferredApp(app_id, intent_filter);
}
void AppServiceProxy::OnPreferredAppRemoved(
const std::string& app_id,
apps::mojom::IntentFilterPtr intent_filter) {
preferred_apps_.DeletePreferredApp(app_id, intent_filter);
}
void AppServiceProxy::InitializePreferredApps(
PreferredAppsList::PreferredApps preferred_apps) {
preferred_apps_.Init(preferred_apps);
}
#if defined(OS_CHROMEOS)
void AppServiceProxy::UninstallImpl(const std::string& app_id,
gfx::NativeWindow parent_window,
base::OnceClosure callback) {
if (!app_service_.is_connected()) {
return;
}
cache_.ForOneApp(app_id, [this, parent_window,
&callback](const apps::AppUpdate& update) {
apps::mojom::IconKeyPtr icon_key = update.IconKey();
auto uninstall_dialog = std::make_unique<UninstallDialog>(
profile_, update.AppType(), update.AppId(), update.Name(),
std::move(icon_key), this, parent_window,
base::BindOnce(&AppServiceProxy::OnUninstallDialogClosed,
weak_ptr_factory_.GetWeakPtr(), update.AppType(),
update.AppId()));
uninstall_dialog->SetDialogCreatedCallbackForTesting(std::move(callback));
uninstall_dialogs_.emplace(std::move(uninstall_dialog));
});
}
void AppServiceProxy::OnUninstallDialogClosed(
apps::mojom::AppType app_type,
const std::string& app_id,
bool uninstall,
bool clear_site_data,
bool report_abuse,
UninstallDialog* uninstall_dialog) {
if (uninstall) {
cache_.ForOneApp(app_id, RecordAppBounce);
app_service_->Uninstall(app_type, app_id, clear_site_data, report_abuse);
}
DCHECK(uninstall_dialog);
auto it = uninstall_dialogs_.find(uninstall_dialog);
DCHECK(it != uninstall_dialogs_.end());
uninstall_dialogs_.erase(it);
}
bool AppServiceProxy::MaybeShowLaunchPreventionDialog(
const apps::AppUpdate& update) {
if (update.AppId() == extension_misc::kChromeAppId) {
return false;
}
// Return true, and load the icon for the app block dialog when the app
// is blocked by policy.
if (update.Readiness() == apps::mojom::Readiness::kDisabledByPolicy) {
LoadIconForDialog(
update, base::BindOnce(&AppServiceProxy::OnLoadIconForBlockDialog,
weak_ptr_factory_.GetWeakPtr(), update.Name()));
return true;
}
// Return true, and load the icon for the app pause dialog when the app
// is paused.
if (update.Paused() == apps::mojom::OptionalBool::kTrue ||
pending_pause_requests_.IsPaused(update.AppId())) {
chromeos::app_time::AppTimeLimitInterface* app_limit =
chromeos::app_time::AppTimeLimitInterface::Get(profile_);
DCHECK(app_limit);
auto time_limit =
app_limit->GetTimeLimitForApp(update.AppId(), update.AppType());
if (!time_limit.has_value()) {
NOTREACHED();
return true;
}
PauseData pause_data;
pause_data.hours = time_limit.value().InHours();
pause_data.minutes = time_limit.value().InMinutes() % 60;
LoadIconForDialog(
update, base::BindOnce(&AppServiceProxy::OnLoadIconForPauseDialog,
weak_ptr_factory_.GetWeakPtr(), update.AppType(),
update.AppId(), update.Name(), pause_data));
return true;
}
// The app is not prevented from launching and we didn't show any dialog.
return false;
}
void AppServiceProxy::LoadIconForDialog(
const apps::AppUpdate& update,
apps::mojom::Publisher::LoadIconCallback callback) {
apps::mojom::IconKeyPtr icon_key = update.IconKey();
constexpr bool kAllowPlaceholderIcon = false;
constexpr int32_t kIconSize = 48;
// For browser tests, load the app icon, because there is no family link
// logo for browser tests.
//
// For non_child profile, load the app icon, because the app is blocked by
// admin.
if (!dialog_created_callback_.is_null() || !profile_->IsChild()) {
LoadIconFromIconKey(update.AppType(), update.AppId(), std::move(icon_key),
apps::mojom::IconCompression::kUncompressed, kIconSize,
kAllowPlaceholderIcon, std::move(callback));
return;
}
// Load the family link kite logo icon for the app pause dialog or the app
// block dialog for the child profile.
LoadIconFromResource(apps::mojom::IconCompression::kUncompressed, kIconSize,
IDR_SUPERVISED_USER_ICON, kAllowPlaceholderIcon,
IconEffects::kNone, std::move(callback));
}
void AppServiceProxy::OnLoadIconForBlockDialog(
const std::string& app_name,
apps::mojom::IconValuePtr icon_value) {
if (icon_value->icon_compression !=
apps::mojom::IconCompression::kUncompressed) {
return;
}
AppServiceProxy::CreateBlockDialog(app_name, icon_value->uncompressed,
profile_);
// For browser tests, call the dialog created callback to stop the run loop.
if (!dialog_created_callback_.is_null()) {
std::move(dialog_created_callback_).Run();
}
}
void AppServiceProxy::OnLoadIconForPauseDialog(
apps::mojom::AppType app_type,
const std::string& app_id,
const std::string& app_name,
const PauseData& pause_data,
apps::mojom::IconValuePtr icon_value) {
if (icon_value->icon_compression !=
apps::mojom::IconCompression::kUncompressed) {
OnPauseDialogClosed(app_type, app_id);
return;
}
AppServiceProxy::CreatePauseDialog(
app_type, app_name, icon_value->uncompressed, pause_data,
base::BindOnce(&AppServiceProxy::OnPauseDialogClosed,
weak_ptr_factory_.GetWeakPtr(), app_type, app_id));
// For browser tests, call the dialog created callback to stop the run loop.
if (!dialog_created_callback_.is_null()) {
std::move(dialog_created_callback_).Run();
}
}
void AppServiceProxy::OnPauseDialogClosed(apps::mojom::AppType app_type,
const std::string& app_id) {
if (pending_pause_requests_.IsPaused(app_id)) {
app_service_->PauseApp(app_type, app_id);
}
}
#endif // OS_CHROMEOS
void AppServiceProxy::OnAppUpdate(const apps::AppUpdate& update) {
#if defined(OS_CHROMEOS)
if ((update.PausedChanged() &&
update.Paused() == apps::mojom::OptionalBool::kTrue) ||
(update.ReadinessChanged() &&
update.Readiness() == apps::mojom::Readiness::kUninstalledByUser)) {
pending_pause_requests_.MaybeRemoveApp(update.AppId());
}
#endif // OS_CHROMEOS
if (!update.ReadinessChanged() ||
update.Readiness() != apps::mojom::Readiness::kUninstalledByUser) {
return;
}
preferred_apps_.DeleteAppId(update.AppId());
}
void AppServiceProxy::OnAppRegistryCacheWillBeDestroyed(
apps::AppRegistryCache* cache) {
Observe(nullptr);
}
apps::mojom::IntentFilterPtr AppServiceProxy::FindBestMatchingFilter(
const apps::mojom::IntentPtr& intent) {
apps::mojom::IntentFilterPtr best_matching_intent_filter;
if (!app_service_.is_bound()) {
return best_matching_intent_filter;
}
int best_match_level = apps_util::IntentFilterMatchLevel::kNone;
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;
}
} // namespace apps