| // Copyright 2020 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/extension_apps_chromeos.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/public/cpp/app_list/app_list_metrics.h" |
| #include "ash/public/cpp/app_menu_constants.h" |
| #include "ash/public/cpp/multi_user_window_manager.h" |
| #include "ash/public/cpp/shelf_types.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/scoped_observer.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "chrome/browser/apps/app_service/app_icon_factory.h" |
| #include "chrome/browser/apps/app_service/app_service_metrics.h" |
| #include "chrome/browser/apps/app_service/menu_util.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/arc/arc_util.h" |
| #include "chrome/browser/chromeos/arc/arc_web_contents_data.h" |
| #include "chrome/browser/chromeos/child_accounts/time_limits/app_time_limit_interface.h" |
| #include "chrome/browser/chromeos/crostini/crostini_util.h" |
| #include "chrome/browser/chromeos/extensions/gfx_utils.h" |
| #include "chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_uninstall_dialog.h" |
| #include "chrome/browser/extensions/extension_util.h" |
| #include "chrome/browser/extensions/launch_util.h" |
| #include "chrome/browser/notifications/notification_display_service_factory.h" |
| #include "chrome/browser/prefs/incognito_mode_prefs.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_utils.h" |
| #include "chrome/browser/ui/app_list/extension_app_utils.h" |
| #include "chrome/browser/ui/ash/multi_user/multi_user_util.h" |
| #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h" |
| #include "chrome/browser/ui/ash/session_controller_client_impl.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/web_applications/components/web_app_helpers.h" |
| #include "chrome/browser/web_applications/system_web_app_manager.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/browser/web_applications/web_app_registrar.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "chrome/common/extensions/extension_metrics.h" |
| #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/arc/arc_service_manager.h" |
| #include "components/policy/core/common/policy_pref_names.h" |
| #include "components/services/app_service/public/cpp/instance.h" |
| #include "components/services/app_service/public/cpp/intent_filter_util.h" |
| #include "components/services/app_service/public/mojom/types.mojom.h" |
| #include "content/public/browser/clear_site_data_utils.h" |
| #include "extensions/browser/app_window/app_window.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/management_policy.h" |
| #include "extensions/browser/ui_util.h" |
| #include "extensions/common/extension_urls.h" |
| #include "extensions/common/manifest_handlers/options_page_info.h" |
| #include "ui/message_center/public/cpp/notification.h" |
| |
| namespace { |
| |
| // Get the LaunchId for a given |app_window|. Set launch_id default value to an |
| // empty string. If show_in_shelf parameter is true and the window key is not |
| // empty, its value is appended to the launch_id. Otherwise, if the window key |
| // is empty, the session_id is used. |
| std::string GetLaunchId(extensions::AppWindow* app_window) { |
| std::string launch_id; |
| if (app_window->show_in_shelf()) { |
| if (!app_window->window_key().empty()) { |
| launch_id = app_window->window_key(); |
| } else { |
| launch_id = base::StringPrintf("%d", app_window->session_id().id()); |
| } |
| } |
| return launch_id; |
| } |
| |
| } // namespace |
| |
| namespace apps { |
| |
| ExtensionAppsChromeOs::ExtensionAppsChromeOs( |
| const mojo::Remote<apps::mojom::AppService>& app_service, |
| Profile* profile, |
| apps::mojom::AppType app_type, |
| apps::InstanceRegistry* instance_registry) |
| : ExtensionAppsBase(app_service, profile, app_type), |
| instance_registry_(instance_registry) { |
| DCHECK(instance_registry_); |
| Initialize(); |
| } |
| |
| ExtensionAppsChromeOs::~ExtensionAppsChromeOs() { |
| app_window_registry_.RemoveAll(); |
| |
| // In unit tests, AppServiceProxy might be ReInitializeForTesting, so |
| // ExtensionApps might be destroyed without calling Shutdown, so arc_prefs_ |
| // needs to be removed from observer in the destructor function. |
| if (arc_prefs_) { |
| arc_prefs_->RemoveObserver(this); |
| arc_prefs_ = nullptr; |
| } |
| } |
| |
| // static |
| void ExtensionAppsChromeOs::RecordUninstallCanceledAction( |
| Profile* profile, |
| const std::string& app_id) { |
| const extensions::Extension* extension = |
| extensions::ExtensionRegistry::Get(profile)->GetInstalledExtension( |
| app_id); |
| if (!extension) { |
| return; |
| } |
| |
| if (extension->from_bookmark()) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Webapp.UninstallDialogAction", |
| extensions::ExtensionUninstallDialog::CLOSE_ACTION_CANCELED, |
| extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Extensions.UninstallDialogAction", |
| extensions::ExtensionUninstallDialog::CLOSE_ACTION_CANCELED, |
| extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST); |
| } |
| } |
| |
| void ExtensionAppsChromeOs::Shutdown() { |
| if (arc_prefs_) { |
| arc_prefs_->RemoveObserver(this); |
| arc_prefs_ = nullptr; |
| } |
| } |
| |
| void ExtensionAppsChromeOs::ObserveArc() { |
| // Observe the ARC apps to set the badge on the equivalent Chrome app's icon. |
| if (arc_prefs_) { |
| arc_prefs_->RemoveObserver(this); |
| } |
| |
| arc_prefs_ = ArcAppListPrefs::Get(profile()); |
| if (arc_prefs_) { |
| arc_prefs_->AddObserver(this); |
| } |
| } |
| |
| void ExtensionAppsChromeOs::Initialize() { |
| app_window_registry_.Add(extensions::AppWindowRegistry::Get(profile())); |
| notification_display_service_.Add( |
| NotificationDisplayServiceFactory::GetForProfile(profile())); |
| |
| // Remaining initialization is only relevant to the kExtension app type. |
| if (app_type() != apps::mojom::AppType::kExtension) { |
| return; |
| } |
| |
| profile_pref_change_registrar_.Init(profile()->GetPrefs()); |
| profile_pref_change_registrar_.Add( |
| prefs::kHideWebStoreIcon, |
| base::BindRepeating(&ExtensionAppsBase::OnHideWebStoreIconPrefChanged, |
| GetWeakPtr())); |
| |
| auto* local_state = g_browser_process->local_state(); |
| if (local_state) { |
| local_state_pref_change_registrar_.Init(local_state); |
| local_state_pref_change_registrar_.Add( |
| policy::policy_prefs::kSystemFeaturesDisableList, |
| base::BindRepeating(&ExtensionAppsBase::OnSystemFeaturesPrefChanged, |
| GetWeakPtr())); |
| OnSystemFeaturesPrefChanged(); |
| } |
| } |
| |
| void ExtensionAppsChromeOs::LaunchAppWithIntent( |
| const std::string& app_id, |
| int32_t event_flags, |
| apps::mojom::IntentPtr intent, |
| apps::mojom::LaunchSource launch_source, |
| int64_t display_id) { |
| auto* tab = LaunchAppWithIntentImpl(app_id, event_flags, std::move(intent), |
| launch_source, display_id); |
| |
| if (launch_source == apps::mojom::LaunchSource::kFromArc && tab) { |
| // Add a flag to remember this tab originated in the ARC context. |
| tab->SetUserData(&arc::ArcWebContentsData::kArcTransitionFlag, |
| std::make_unique<arc::ArcWebContentsData>()); |
| } |
| } |
| |
| void ExtensionAppsChromeOs::Uninstall(const std::string& app_id, |
| bool clear_site_data, |
| bool report_abuse) { |
| // TODO(crbug.com/1009248): We need to add the error code, which could be used |
| // by ExtensionFunction, ManagementUninstallFunctionBase on the callback |
| // OnExtensionUninstallDialogClosed |
| scoped_refptr<const extensions::Extension> extension = |
| extensions::ExtensionRegistry::Get(profile())->GetInstalledExtension( |
| app_id); |
| if (!extension.get()) { |
| return; |
| } |
| |
| base::string16 error; |
| extensions::ExtensionSystem::Get(profile()) |
| ->extension_service() |
| ->UninstallExtension(app_id, extensions::UNINSTALL_REASON_USER_INITIATED, |
| &error); |
| |
| if (extension->from_bookmark()) { |
| if (!clear_site_data) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Webapp.UninstallDialogAction", |
| extensions::ExtensionUninstallDialog::CLOSE_ACTION_UNINSTALL, |
| extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST); |
| return; |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| "Webapp.UninstallDialogAction", |
| extensions::ExtensionUninstallDialog:: |
| CLOSE_ACTION_UNINSTALL_AND_CHECKBOX_CHECKED, |
| extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST); |
| |
| constexpr bool kClearCookies = true; |
| constexpr bool kClearStorage = true; |
| constexpr bool kClearCache = true; |
| constexpr bool kAvoidClosingConnections = false; |
| content::ClearSiteData( |
| base::BindRepeating( |
| [](content::BrowserContext* browser_context) { |
| return browser_context; |
| }, |
| base::Unretained(profile())), |
| url::Origin::Create( |
| extensions::AppLaunchInfo::GetFullLaunchURL(extension.get())), |
| kClearCookies, kClearStorage, kClearCache, kAvoidClosingConnections, |
| base::DoNothing()); |
| } else { |
| if (!report_abuse) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Extensions.UninstallDialogAction", |
| extensions::ExtensionUninstallDialog::CLOSE_ACTION_UNINSTALL, |
| extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST); |
| return; |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| "Extensions.UninstallDialogAction", |
| extensions::ExtensionUninstallDialog:: |
| CLOSE_ACTION_UNINSTALL_AND_CHECKBOX_CHECKED, |
| extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST); |
| |
| // If the extension specifies a custom uninstall page via |
| // chrome.runtime.setUninstallURL, then at uninstallation its uninstall |
| // page opens. To ensure that the CWS Report Abuse page is the active |
| // tab at uninstallation, navigates to the url to report abuse. |
| constexpr char kReferrerId[] = "chrome-remove-extension-dialog"; |
| NavigateParams params( |
| profile(), |
| extension_urls::GetWebstoreReportAbuseUrl(app_id, kReferrerId), |
| ui::PAGE_TRANSITION_LINK); |
| params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| Navigate(¶ms); |
| } |
| } |
| |
| void ExtensionAppsChromeOs::PauseApp(const std::string& app_id) { |
| if (paused_apps_.MaybeAddApp(app_id)) { |
| SetIconEffect(app_id); |
| } |
| |
| constexpr bool kPaused = true; |
| Publish(paused_apps_.GetAppWithPauseStatus(app_type(), app_id, kPaused), |
| subscribers()); |
| |
| if (instance_registry_->GetWindows(app_id).empty()) { |
| return; |
| } |
| |
| // For Web apps that are opened in app windows, close all tabs to close the |
| // opened window, otherwise, show pause information in browsers. |
| bool is_web_app = false; |
| for (auto* browser : *BrowserList::GetInstance()) { |
| if (!browser->is_type_app()) { |
| continue; |
| } |
| if (web_app::GetAppIdFromApplicationName(browser->app_name()) == app_id) { |
| TabStripModel* tab_strip = browser->tab_strip_model(); |
| tab_strip->CloseAllTabs(); |
| is_web_app = true; |
| } |
| } |
| |
| // For web apps that are opened in tabs, PauseApp() should be |
| // called with Chrome's app_id to show pause information in browsers. |
| if (is_web_app) { |
| return; |
| } |
| |
| chromeos::app_time::AppTimeLimitInterface* app_limit = |
| chromeos::app_time::AppTimeLimitInterface::Get(profile()); |
| DCHECK(app_limit); |
| app_limit->PauseWebActivity(app_id); |
| } |
| |
| void ExtensionAppsChromeOs::UnpauseApps(const std::string& app_id) { |
| if (paused_apps_.MaybeRemoveApp(app_id)) { |
| SetIconEffect(app_id); |
| } |
| |
| constexpr bool kPaused = false; |
| Publish(paused_apps_.GetAppWithPauseStatus(app_type(), app_id, kPaused), |
| subscribers()); |
| |
| for (auto* browser : *BrowserList::GetInstance()) { |
| if (!browser->is_type_app()) { |
| continue; |
| } |
| if (web_app::GetAppIdFromApplicationName(browser->app_name()) == app_id) { |
| return; |
| } |
| } |
| |
| chromeos::app_time::AppTimeLimitInterface* app_time = |
| chromeos::app_time::AppTimeLimitInterface::Get(profile()); |
| DCHECK(app_time); |
| app_time->ResumeWebActivity(app_id); |
| } |
| |
| void ExtensionAppsChromeOs::GetMenuModel(const std::string& app_id, |
| apps::mojom::MenuType menu_type, |
| int64_t display_id, |
| GetMenuModelCallback callback) { |
| apps::mojom::MenuItemsPtr menu_items = apps::mojom::MenuItems::New(); |
| const auto* extension = MaybeGetExtension(app_id); |
| if (!extension) { |
| std::move(callback).Run(std::move(menu_items)); |
| return; |
| } |
| |
| if (app_id == extension_misc::kChromeAppId) { |
| GetMenuModelForChromeBrowserApp(menu_type, std::move(callback)); |
| return; |
| } |
| |
| bool is_platform_app = extension->is_platform_app(); |
| bool is_system_web_app = web_app::WebAppProvider::Get(profile()) |
| ->system_web_app_manager() |
| .IsSystemWebApp(app_id); |
| |
| if (!is_platform_app && !is_system_web_app) { |
| CreateOpenNewSubmenu( |
| menu_type, |
| extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile()), |
| extension) == |
| extensions::LaunchType::LAUNCH_TYPE_WINDOW |
| ? IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW |
| : IDS_APP_LIST_CONTEXT_MENU_NEW_TAB, |
| &menu_items); |
| } |
| |
| if (!is_platform_app && menu_type == apps::mojom::MenuType::kAppList && |
| extensions::util::IsAppLaunchableWithoutEnabling(app_id, profile()) && |
| extensions::OptionsPageInfo::HasOptionsPage(extension)) { |
| AddCommandItem(ash::OPTIONS, IDS_NEW_TAB_APP_OPTIONS, &menu_items); |
| } |
| |
| if (menu_type == apps::mojom::MenuType::kShelf && |
| !instance_registry_->GetWindows(app_id).empty()) { |
| AddCommandItem(ash::MENU_CLOSE, IDS_SHELF_CONTEXT_MENU_CLOSE, &menu_items); |
| } |
| |
| const extensions::ManagementPolicy* policy = |
| extensions::ExtensionSystem::Get(profile())->management_policy(); |
| DCHECK(policy); |
| if (policy->UserMayModifySettings(extension, nullptr) && |
| !policy->MustRemainInstalled(extension, nullptr)) { |
| AddCommandItem(ash::UNINSTALL, IDS_APP_LIST_UNINSTALL_ITEM, &menu_items); |
| } |
| |
| if (!is_system_web_app && extension->ShouldDisplayInAppLauncher()) { |
| AddCommandItem(ash::SHOW_APP_INFO, IDS_APP_CONTEXT_MENU_SHOW_INFO, |
| &menu_items); |
| } |
| |
| std::move(callback).Run(std::move(menu_items)); |
| } |
| |
| void ExtensionAppsChromeOs::OnAppWindowAdded( |
| extensions::AppWindow* app_window) { |
| if (!ShouldRecordAppWindowActivity(app_window)) { |
| return; |
| } |
| |
| DCHECK(!instance_registry_->Exists(app_window->GetNativeWindow())); |
| app_window_to_aura_window_[app_window] = app_window->GetNativeWindow(); |
| |
| // Attach window to multi-user manager now to let it manage visibility state |
| // of the window correctly. |
| if (SessionControllerClientImpl::IsMultiProfileAvailable()) { |
| auto* multi_user_window_manager = |
| MultiUserWindowManagerHelper::GetWindowManager(); |
| if (multi_user_window_manager) { |
| multi_user_window_manager->SetWindowOwner( |
| app_window->GetNativeWindow(), |
| multi_user_util::GetAccountIdFromProfile(profile())); |
| } |
| } |
| RegisterInstance(app_window, InstanceState::kStarted); |
| } |
| |
| void ExtensionAppsChromeOs::OnAppWindowShown(extensions::AppWindow* app_window, |
| bool was_hidden) { |
| if (!ShouldRecordAppWindowActivity(app_window)) { |
| return; |
| } |
| |
| InstanceState state = |
| instance_registry_->GetState(app_window->GetNativeWindow()); |
| |
| // If the window is shown, it should be started, running and not hidden. |
| state = static_cast<apps::InstanceState>( |
| state | apps::InstanceState::kStarted | apps::InstanceState::kRunning); |
| state = |
| static_cast<apps::InstanceState>(state & ~apps::InstanceState::kHidden); |
| RegisterInstance(app_window, state); |
| } |
| |
| void ExtensionAppsChromeOs::OnAppWindowHidden( |
| extensions::AppWindow* app_window) { |
| if (!ShouldRecordAppWindowActivity(app_window)) { |
| return; |
| } |
| |
| // For hidden |app_window|, the other state bit, started, running, active, and |
| // visible should be cleared. |
| RegisterInstance(app_window, InstanceState::kHidden); |
| } |
| |
| void ExtensionAppsChromeOs::OnAppWindowRemoved( |
| extensions::AppWindow* app_window) { |
| if (!ShouldRecordAppWindowActivity(app_window)) { |
| return; |
| } |
| |
| RegisterInstance(app_window, InstanceState::kDestroyed); |
| app_window_to_aura_window_.erase(app_window); |
| } |
| |
| void ExtensionAppsChromeOs::OnExtensionUninstalled( |
| content::BrowserContext* browser_context, |
| const extensions::Extension* extension, |
| extensions::UninstallReason reason) { |
| if (!Accepts(extension)) { |
| return; |
| } |
| |
| app_notifications_.RemoveNotificationsForApp(extension->id()); |
| paused_apps_.MaybeRemoveApp(extension->id()); |
| |
| ExtensionAppsBase::OnExtensionUninstalled(browser_context, extension, reason); |
| } |
| |
| void ExtensionAppsChromeOs::OnPackageInstalled( |
| const arc::mojom::ArcPackageInfo& package_info) { |
| ApplyChromeBadge(package_info.package_name); |
| } |
| |
| void ExtensionAppsChromeOs::OnPackageRemoved(const std::string& package_name, |
| bool uninstalled) { |
| ApplyChromeBadge(package_name); |
| } |
| |
| void ExtensionAppsChromeOs::OnPackageListInitialRefreshed() { |
| if (!arc_prefs_) { |
| return; |
| } |
| for (const auto& app_name : arc_prefs_->GetPackagesFromPrefs()) { |
| ApplyChromeBadge(app_name); |
| } |
| } |
| |
| void ExtensionAppsChromeOs::OnArcAppListPrefsDestroyed() { |
| arc_prefs_ = nullptr; |
| } |
| |
| void ExtensionAppsChromeOs::OnNotificationDisplayed( |
| const message_center::Notification& notification, |
| const NotificationCommon::Metadata* const metadata) { |
| switch (notification.notifier_id().type) { |
| case message_center::NotifierType::APPLICATION: |
| MaybeAddNotification(notification.notifier_id().id, notification.id()); |
| return; |
| case message_center::NotifierType::WEB_PAGE: |
| MaybeAddWebPageNotifications(notification, metadata); |
| return; |
| default: |
| return; |
| } |
| } |
| |
| void ExtensionAppsChromeOs::OnNotificationClosed( |
| const std::string& notification_id) { |
| const auto app_ids = |
| app_notifications_.GetAppIdsForNotification(notification_id); |
| if (app_ids.empty()) { |
| return; |
| } |
| |
| app_notifications_.RemoveNotification(notification_id); |
| |
| for (const auto& app_id : app_ids) { |
| Publish(app_notifications_.GetAppWithHasBadgeStatus(app_type(), app_id), |
| subscribers()); |
| } |
| } |
| |
| void ExtensionAppsChromeOs::OnNotificationDisplayServiceDestroyed( |
| NotificationDisplayService* service) { |
| notification_display_service_.Remove(service); |
| } |
| |
| bool ExtensionAppsChromeOs::MaybeAddNotification( |
| const std::string& app_id, |
| const std::string& notification_id) { |
| if (MaybeGetExtension(app_id) == nullptr) { |
| return false; |
| } |
| |
| app_notifications_.AddNotification(app_id, notification_id); |
| Publish(app_notifications_.GetAppWithHasBadgeStatus(app_type(), app_id), |
| subscribers()); |
| return true; |
| } |
| |
| void ExtensionAppsChromeOs::MaybeAddWebPageNotifications( |
| const message_center::Notification& notification, |
| const NotificationCommon::Metadata* const metadata) { |
| const GURL& url = |
| metadata |
| ? PersistentNotificationMetadata::From(metadata)->service_worker_scope |
| : notification.origin_url(); |
| |
| if (app_type() == apps::mojom::AppType::kExtension) { |
| extensions::ExtensionRegistry* registry = |
| extensions::ExtensionRegistry::Get(profile()); |
| DCHECK(registry); |
| const extensions::ExtensionSet& extensions = registry->enabled_extensions(); |
| const extensions::Extension* extension = extensions.GetAppByURL(url); |
| if (extension) { |
| MaybeAddNotification(extension->id(), notification.id()); |
| } |
| return; |
| } |
| |
| if (metadata) { |
| // For persistent notifications, find the web app with the scope url. |
| base::Optional<web_app::AppId> app_id = |
| web_app::FindInstalledAppWithUrlInScope(profile(), url, |
| /*window_only=*/false); |
| if (app_id.has_value()) { |
| MaybeAddNotification(app_id.value(), notification.id()); |
| } |
| } else { |
| // For non-persistent notifications, find all web apps that are installed |
| // under the origin url. |
| auto* web_app_provider = web_app::WebAppProvider::Get(profile()); |
| if (!web_app_provider) { |
| return; |
| } |
| |
| auto app_ids = web_app_provider->registrar().FindAppsInScope(url); |
| int count = 0; |
| for (const auto& app_id : app_ids) { |
| if (MaybeAddNotification(app_id, notification.id())) { |
| ++count; |
| } |
| } |
| RecordAppsPerNotification(count); |
| } |
| } |
| |
| // static |
| bool ExtensionAppsChromeOs::IsBlocklisted(const std::string& app_id) { |
| // We blocklist (meaning we don't publish the app, in the App Service sense) |
| // some apps that are already published by other app publishers. |
| // |
| // This sense of "blocklist" is separate from the extension registry's |
| // kDisabledByBlocklist concept, which is when SafeBrowsing will send out a |
| // blocklist of malicious extensions to disable. |
| |
| // The Play Store is conceptually provided by the ARC++ publisher, but |
| // because it (the Play Store icon) is also the UI for enabling Android apps, |
| // we also want to show the app icon even before ARC++ is enabled. Prior to |
| // the App Service, as a historical implementation quirk, the Play Store both |
| // has an "ARC++ app" component and an "Extension app" component, and both |
| // share the same App ID. |
| // |
| // In the App Service world, there should be a unique app publisher for any |
| // given app. In this case, the ArcApps publisher publishes the Play Store |
| // app, and the ExtensionApps publisher does not. |
| return app_id == arc::kPlayStoreAppId; |
| } |
| |
| void ExtensionAppsChromeOs::UpdateShowInFields(const std::string& app_id) { |
| const auto* extension = MaybeGetExtension(app_id); |
| if (!extension) { |
| return; |
| } |
| |
| apps::mojom::AppPtr app = apps::mojom::App::New(); |
| app->app_type = app_type(); |
| app->app_id = app_id; |
| SetShowInFields(app, extension); |
| Publish(std::move(app), subscribers()); |
| } |
| |
| void ExtensionAppsChromeOs::OnHideWebStoreIconPrefChanged() { |
| UpdateShowInFields(extensions::kWebStoreAppId); |
| UpdateShowInFields(extension_misc::kEnterpriseWebStoreAppId); |
| } |
| |
| void ExtensionAppsChromeOs::OnSystemFeaturesPrefChanged() { |
| if (app_type() != apps::mojom::AppType::kExtension) { |
| return; |
| } |
| |
| PrefService* const local_state = g_browser_process->local_state(); |
| if (!local_state || !local_state->FindPreference( |
| policy::policy_prefs::kSystemFeaturesDisableList)) { |
| return; |
| } |
| |
| const base::ListValue* disabled_system_features_pref = |
| local_state->GetList(policy::policy_prefs::kSystemFeaturesDisableList); |
| if (!disabled_system_features_pref) { |
| return; |
| } |
| |
| bool is_disabled = base::Contains(*disabled_system_features_pref, |
| base::Value(policy::SystemFeature::CAMERA)); |
| auto* app_id = extension_misc::kCameraAppId; |
| |
| // Sometimes the policy is updated before the app is installed, so this way |
| // the disabled_apps_ is updated regardless the Publish should happen or not |
| // and the app will be published with the correct readiness upon its |
| // installation. |
| bool should_publish = (base::Contains(disabled_apps_, app_id) != is_disabled); |
| |
| if (is_disabled) { |
| disabled_apps_.insert(app_id); |
| } else { |
| disabled_apps_.erase(app_id); |
| } |
| |
| if (!should_publish) { |
| return; |
| } |
| |
| const auto* extension = MaybeGetExtension(app_id); |
| if (!extension) { |
| return; |
| } |
| |
| Publish( |
| Convert(extension, is_disabled ? apps::mojom::Readiness::kDisabledByPolicy |
| : apps::mojom::Readiness::kReady), |
| subscribers()); |
| } |
| |
| bool ExtensionAppsChromeOs::Accepts(const extensions::Extension* extension) { |
| if (!extension->is_app() || IsBlocklisted(extension->id())) { |
| return false; |
| } |
| |
| switch (app_type()) { |
| case apps::mojom::AppType::kExtension: |
| return !extension->from_bookmark(); |
| case apps::mojom::AppType::kWeb: |
| // Crostini Terminal System App is handled by Crostini Apps. |
| // TODO(crbug.com/1028898): Register Terminal as a System App rather than |
| // a crostini app. |
| if (extension->id() == crostini::kCrostiniTerminalSystemAppId) { |
| return false; |
| } |
| return extension->from_bookmark(); |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| bool ExtensionAppsChromeOs::ShouldShownInLauncher( |
| const extensions::Extension* extension) { |
| return app_list::ShouldShowInLauncher(extension, profile()); |
| } |
| |
| apps::mojom::AppPtr ExtensionAppsChromeOs::Convert( |
| const extensions::Extension* extension, |
| apps::mojom::Readiness readiness) { |
| apps::mojom::AppPtr app = |
| ConvertImpl(extension, base::Contains(disabled_apps_, extension->id()) |
| ? apps::mojom::Readiness::kDisabledByPolicy |
| : readiness); |
| bool paused = paused_apps_.IsPaused(extension->id()); |
| app->icon_key = |
| icon_key_factory().MakeIconKey(GetIconEffects(extension, paused)); |
| |
| app->has_badge = app_notifications_.HasNotification(extension->id()) |
| ? apps::mojom::OptionalBool::kTrue |
| : apps::mojom::OptionalBool::kFalse; |
| app->paused = paused ? apps::mojom::OptionalBool::kTrue |
| : apps::mojom::OptionalBool::kFalse; |
| |
| return app; |
| } |
| |
| IconEffects ExtensionAppsChromeOs::GetIconEffects( |
| const extensions::Extension* extension, |
| bool paused) { |
| IconEffects icon_effects = IconEffects::kNone; |
| icon_effects = |
| static_cast<IconEffects>(icon_effects | IconEffects::kResizeAndPad); |
| if (extensions::util::ShouldApplyChromeBadge(profile(), extension->id())) { |
| icon_effects = |
| static_cast<IconEffects>(icon_effects | IconEffects::kChromeBadge); |
| } |
| icon_effects = static_cast<IconEffects>( |
| icon_effects | ExtensionAppsBase::GetIconEffects(extension)); |
| if (paused) { |
| icon_effects = |
| static_cast<IconEffects>(icon_effects | IconEffects::kPaused); |
| } |
| if (base::Contains(disabled_apps_, extension->id())) { |
| icon_effects = |
| static_cast<IconEffects>(icon_effects | IconEffects::kBlocked); |
| } |
| return icon_effects; |
| } |
| |
| void ExtensionAppsChromeOs::ApplyChromeBadge(const std::string& package_name) { |
| const std::vector<std::string> extension_ids = |
| extensions::util::GetEquivalentInstalledExtensions(profile(), |
| package_name); |
| |
| for (auto& app_id : extension_ids) { |
| SetIconEffect(app_id); |
| } |
| } |
| |
| void ExtensionAppsChromeOs::SetIconEffect(const std::string& app_id) { |
| const auto* extension = MaybeGetExtension(app_id); |
| if (!extension) { |
| return; |
| } |
| |
| apps::mojom::AppPtr app = apps::mojom::App::New(); |
| app->app_type = app_type(); |
| app->app_id = app_id; |
| app->icon_key = icon_key_factory().MakeIconKey( |
| GetIconEffects(extension, paused_apps_.IsPaused(app_id))); |
| Publish(std::move(app), subscribers()); |
| } |
| |
| bool ExtensionAppsChromeOs::ShouldRecordAppWindowActivity( |
| extensions::AppWindow* app_window) { |
| DCHECK(app_window); |
| |
| const extensions::Extension* extension = app_window->GetExtension(); |
| if (!extension) { |
| return false; |
| } |
| |
| // ARC Play Store is not published by this publisher, but the window for Play |
| // Store should be able to be added to InstanceRegistry. |
| if (extension->id() == arc::kPlayStoreAppId && |
| app_type() == apps::mojom::AppType::kExtension) { |
| return true; |
| } |
| |
| if (!Accepts(extension)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void ExtensionAppsChromeOs::RegisterInstance(extensions::AppWindow* app_window, |
| InstanceState new_state) { |
| aura::Window* window = app_window->GetNativeWindow(); |
| |
| // If the current state has been marked as |new_state|, we don't need to |
| // update. |
| if (instance_registry_->GetState(window) == new_state) { |
| return; |
| } |
| |
| if (new_state == InstanceState::kDestroyed) { |
| DCHECK(base::Contains(app_window_to_aura_window_, app_window)); |
| window = app_window_to_aura_window_[app_window]; |
| } |
| std::vector<std::unique_ptr<apps::Instance>> deltas; |
| auto instance = |
| std::make_unique<apps::Instance>(app_window->extension_id(), window); |
| instance->SetLaunchId(GetLaunchId(app_window)); |
| instance->UpdateState(new_state, base::Time::Now()); |
| instance->SetBrowserContext(app_window->browser_context()); |
| deltas.push_back(std::move(instance)); |
| instance_registry_->OnInstances(deltas); |
| } |
| |
| void ExtensionAppsChromeOs::GetMenuModelForChromeBrowserApp( |
| apps::mojom::MenuType menu_type, |
| GetMenuModelCallback callback) { |
| apps::mojom::MenuItemsPtr menu_items = apps::mojom::MenuItems::New(); |
| |
| // "Normal" windows are not allowed when incognito is enforced. |
| if (IncognitoModePrefs::GetAvailability(profile()->GetPrefs()) != |
| IncognitoModePrefs::FORCED) { |
| AddCommandItem((menu_type == apps::mojom::MenuType::kAppList) |
| ? ash::APP_CONTEXT_MENU_NEW_WINDOW |
| : ash::MENU_NEW_WINDOW, |
| IDS_APP_LIST_NEW_WINDOW, &menu_items); |
| } |
| |
| // Incognito windows are not allowed when incognito is disabled. |
| if (!profile()->IsOffTheRecord() && |
| IncognitoModePrefs::GetAvailability(profile()->GetPrefs()) != |
| IncognitoModePrefs::DISABLED) { |
| AddCommandItem((menu_type == apps::mojom::MenuType::kAppList) |
| ? ash::APP_CONTEXT_MENU_NEW_INCOGNITO_WINDOW |
| : ash::MENU_NEW_INCOGNITO_WINDOW, |
| IDS_APP_LIST_NEW_INCOGNITO_WINDOW, &menu_items); |
| } |
| |
| AddCommandItem(ash::SHOW_APP_INFO, IDS_APP_CONTEXT_MENU_SHOW_INFO, |
| &menu_items); |
| |
| std::move(callback).Run(std::move(menu_items)); |
| } |
| |
| void ExtensionAppsChromeOs::OnWebAppDisabledStateChanged( |
| const web_app::AppId& app_id, |
| bool is_disabled) { |
| const auto* extension = MaybeGetExtension(app_id); |
| if (!extension) { |
| return; |
| } |
| |
| if (base::Contains(disabled_apps_, app_id) == is_disabled) { |
| return; |
| } |
| |
| if (is_disabled) { |
| disabled_apps_.insert(app_id); |
| } else { |
| disabled_apps_.erase(app_id); |
| } |
| |
| Publish( |
| Convert(extension, is_disabled ? apps::mojom::Readiness::kDisabledByPolicy |
| : apps::mojom::Readiness::kReady), |
| subscribers()); |
| } |
| |
| } // namespace apps |