| // 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 |