// Copyright 2018 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/publishers/arc_apps.h"
#include <algorithm>
#include <cstdint>
#include <optional>
#include <utility>
#include "ash/components/arc/arc_prefs.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/metrics/arc_metrics_constants.h"
#include "ash/components/arc/metrics/arc_metrics_service.h"
#include "ash/components/arc/mojom/app_permissions.mojom.h"
#include "ash/components/arc/mojom/compatibility_mode.mojom.h"
#include "ash/components/arc/mojom/file_system.mojom.h"
#include "ash/components/arc/mojom/intent_helper.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/app_menu_constants.h"
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_set.h"
#include "base/containers/flat_map.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "chrome/browser/apps/app_service/app_icon/dip_px_util.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/file_utils.h"
#include "chrome/browser/apps/app_service/intent_util.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/apps/app_service/menu_util.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache.h"
#include "chrome/browser/apps/app_service/publishers/arc_apps_factory.h"
#include "chrome/browser/apps/app_service/webapk/webapk_manager.h"
#include "chrome/browser/ash/app_list/arc/arc_app_icon.h"
#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ash/apps/apk_web_app_service.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/system_features_disable_list_policy_handler.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/component_extension_resources.h"
#include "chrome/grit/generated_resources.h"
#include "components/app_restore/app_launch_info.h"
#include "components/app_restore/full_restore_save_handler.h"
#include "components/app_restore/full_restore_utils.h"
#include "components/arc/common/intent_helper/arc_intent_helper_package.h"
#include "components/arc/intent_helper/intent_constants.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/capability_access.h"
#include "components/services/app_service/public/cpp/icon_types.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_util.h"
#include "components/services/app_service/public/cpp/package_id.h"
#include "components/services/app_service/public/cpp/permission.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "extensions/grit/extensions_browser_resources.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia_operations.h"
// TODO(crbug.com/40569217): consider that, per khmel@, "App icon can be
// overwritten (setTaskDescription) or by assigning the icon for the app
// window. In this case some consumers (Shelf for example) switch to
// overwritten icon... IIRC this applies to shelf items and ArcAppWindow icon".
namespace {
std::optional<int> g_test_arc_version_;
apps::PermissionType GetPermissionType(
arc::mojom::AppPermission arc_permission_type) {
switch (arc_permission_type) {
case arc::mojom::AppPermission::CAMERA:
return apps::PermissionType::kCamera;
case arc::mojom::AppPermission::LOCATION:
return apps::PermissionType::kLocation;
case arc::mojom::AppPermission::MICROPHONE:
return apps::PermissionType::kMicrophone;
case arc::mojom::AppPermission::NOTIFICATIONS:
return apps::PermissionType::kNotifications;
case arc::mojom::AppPermission::CONTACTS:
return apps::PermissionType::kContacts;
case arc::mojom::AppPermission::STORAGE:
return apps::PermissionType::kStorage;
}
}
bool GetArcPermissionType(apps::PermissionType app_service_permission_type,
arc::mojom::AppPermission& arc_permission) {
switch (app_service_permission_type) {
case apps::PermissionType::kCamera:
arc_permission = arc::mojom::AppPermission::CAMERA;
return true;
case apps::PermissionType::kLocation:
arc_permission = arc::mojom::AppPermission::LOCATION;
return true;
case apps::PermissionType::kMicrophone:
arc_permission = arc::mojom::AppPermission::MICROPHONE;
return true;
case apps::PermissionType::kNotifications:
arc_permission = arc::mojom::AppPermission::NOTIFICATIONS;
return true;
case apps::PermissionType::kContacts:
arc_permission = arc::mojom::AppPermission::CONTACTS;
return true;
case apps::PermissionType::kStorage:
arc_permission = arc::mojom::AppPermission::STORAGE;
return true;
case apps::PermissionType::kUnknown:
case apps::PermissionType::kPrinting:
case apps::PermissionType::kFileHandling:
return false;
}
}
apps::Permissions CreatePermissions(
const base::flat_map<arc::mojom::AppPermission,
arc::mojom::PermissionStatePtr>& arc_permissions) {
apps::Permissions permissions;
for (const auto& [arc_permission_type, arc_permission_state] :
arc_permissions) {
apps::TriState value = arc_permission_state->granted
? apps::TriState::kAllow
: apps::TriState::kBlock;
// Permissions in the one-time state will ask for permission again the next
// time they are used.
if (arc_permission_state->one_time) {
value = apps::TriState::kAsk;
}
permissions.push_back(std::make_unique<apps::Permission>(
GetPermissionType(arc_permission_type), value,
arc_permission_state->managed, arc_permission_state->details));
}
return permissions;
}
std::optional<arc::UserInteractionType> GetUserInterationType(
apps::LaunchSource launch_source) {
auto user_interaction_type = arc::UserInteractionType::NOT_USER_INITIATED;
switch (launch_source) {
// kUnknown is not set anywhere, this case is not valid.
case apps::LaunchSource::kUnknown:
return std::nullopt;
case apps::LaunchSource::kFromChromeInternal:
user_interaction_type = arc::UserInteractionType::NOT_USER_INITIATED;
break;
case apps::LaunchSource::kFromAppListGrid:
user_interaction_type =
arc::UserInteractionType::APP_STARTED_FROM_LAUNCHER;
break;
case apps::LaunchSource::kFromAppListGridContextMenu:
user_interaction_type =
arc::UserInteractionType::APP_STARTED_FROM_LAUNCHER_CONTEXT_MENU;
break;
case apps::LaunchSource::kFromAppListQuery:
user_interaction_type =
arc::UserInteractionType::APP_STARTED_FROM_LAUNCHER_SEARCH;
break;
case apps::LaunchSource::kFromAppListQueryContextMenu:
user_interaction_type = arc::UserInteractionType::
APP_STARTED_FROM_LAUNCHER_SEARCH_CONTEXT_MENU;
break;
case apps::LaunchSource::kFromAppListRecommendation:
user_interaction_type =
arc::UserInteractionType::APP_STARTED_FROM_LAUNCHER_SUGGESTED_APP;
break;
case apps::LaunchSource::kFromParentalControls:
user_interaction_type =
arc::UserInteractionType::APP_STARTED_FROM_SETTINGS;
break;
case apps::LaunchSource::kFromShelf:
user_interaction_type = arc::UserInteractionType::APP_STARTED_FROM_SHELF;
break;
case apps::LaunchSource::kFromFileManager:
user_interaction_type =
arc::UserInteractionType::APP_STARTED_FROM_FILE_MANAGER;
break;
case apps::LaunchSource::kFromLink:
user_interaction_type = arc::UserInteractionType::APP_STARTED_FROM_LINK;
break;
case apps::LaunchSource::kFromOmnibox:
user_interaction_type =
arc::UserInteractionType::APP_STARTED_FROM_OMNIBOX;
break;
case apps::LaunchSource::kFromSharesheet:
user_interaction_type =
arc::UserInteractionType::APP_STARTED_FROM_SHARESHEET;
break;
case apps::LaunchSource::kFromFullRestore:
user_interaction_type =
arc::UserInteractionType::APP_STARTED_FROM_FULL_RESTORE;
break;
case apps::LaunchSource::kFromSmartTextContextMenu:
user_interaction_type = arc::UserInteractionType::
APP_STARTED_FROM_SMART_TEXT_SELECTION_CONTEXT_MENU;
break;
case apps::LaunchSource::kFromOtherApp:
user_interaction_type =
arc::UserInteractionType::APP_STARTED_FROM_OTHER_APP;
break;
case apps::LaunchSource::kFromInstaller:
user_interaction_type =
arc::UserInteractionType::APP_STARTED_FROM_INSTALLER;
break;
case apps::LaunchSource::kFromKeyboard:
case apps::LaunchSource::kFromMenu:
case apps::LaunchSource::kFromInstalledNotification:
case apps::LaunchSource::kFromTest:
case apps::LaunchSource::kFromArc:
case apps::LaunchSource::kFromReleaseNotesNotification:
case apps::LaunchSource::kFromDiscoverTabNotification:
case apps::LaunchSource::kFromManagementApi:
case apps::LaunchSource::kFromKiosk:
case apps::LaunchSource::kFromCommandLine:
case apps::LaunchSource::kFromBackgroundMode:
case apps::LaunchSource::kFromNewTabPage:
case apps::LaunchSource::kFromIntentUrl:
case apps::LaunchSource::kFromOsLogin:
case apps::LaunchSource::kFromProtocolHandler:
case apps::LaunchSource::kFromUrlHandler:
case apps::LaunchSource::kFromLockScreen:
case apps::LaunchSource::kFromAppHomePage:
case apps::LaunchSource::kFromReparenting:
case apps::LaunchSource::kFromProfileMenu:
case apps::LaunchSource::kFromSysTrayCalendar:
case apps::LaunchSource::kFromFirstRun:
case apps::LaunchSource::kFromWelcomeTour:
case apps::LaunchSource::kFromFocusMode:
case apps::LaunchSource::kFromSparky:
// These LaunchSources do not launch ARC apps. When adding a new
// LaunchSource, if it is expected to launch ARC apps, add a new
// UserInteractionType above. Otherwise, add it here.
NOTREACHED() << "Must define an ARC UserInteractionType for LaunchSource "
<< static_cast<uint32_t>(launch_source);
}
return user_interaction_type;
}
bool ShouldShow(const ArcAppListPrefs::AppInfo& app_info) {
return app_info.show_in_launcher;
}
void RequestDomainVerificationStatusUpdate(ArcAppListPrefs* prefs) {
auto* arc_service_manager = arc::ArcServiceManager::Get();
arc::mojom::IntentHelperInstance* instance = nullptr;
if (arc_service_manager) {
instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->intent_helper(),
RequestDomainVerificationStatusUpdate);
}
if (!instance) {
return;
}
instance->RequestDomainVerificationStatusUpdate();
}
arc::mojom::ActionType GetArcActionType(const std::string& action) {
if (action == apps_util::kIntentActionView) {
return arc::mojom::ActionType::VIEW;
} else if (action == apps_util::kIntentActionSend) {
return arc::mojom::ActionType::SEND;
} else if (action == apps_util::kIntentActionSendMultiple) {
return arc::mojom::ActionType::SEND_MULTIPLE;
} else if (action == apps_util::kIntentActionEdit) {
return arc::mojom::ActionType::EDIT;
} else {
return arc::mojom::ActionType::VIEW;
}
}
// Constructs an OpenUrlsRequest to be passed to
// FileSystemInstance.OpenUrlsWithPermissionAndWindowInfo.
arc::mojom::OpenUrlsRequestPtr ConstructOpenUrlsRequest(
const apps::IntentPtr& intent,
const arc::mojom::ActivityNamePtr& activity,
const std::vector<GURL>& content_urls) {
arc::mojom::OpenUrlsRequestPtr request = arc::mojom::OpenUrlsRequest::New();
request->action_type = GetArcActionType(intent->action);
request->activity_name = activity.Clone();
DCHECK_EQ(content_urls.size(), intent->files.size());
for (size_t i = 0; i < content_urls.size(); i++) {
auto content_url = content_urls[i];
arc::mojom::ContentUrlWithMimeTypePtr url_with_type =
arc::mojom::ContentUrlWithMimeType::New();
url_with_type->content_url = content_url;
DCHECK(intent->files[i]->mime_type.has_value() ||
intent->mime_type.has_value());
// Save the file's original mimetype to the URL if it exists. Otherwise, use
// the common intent mime type instead.
url_with_type->mime_type = intent->files[i]->mime_type.has_value()
? intent->files[i]->mime_type.value()
: intent->mime_type.value();
request->urls.push_back(std::move(url_with_type));
}
if (intent->share_text.has_value() || intent->share_title.has_value() ||
!intent->extras.empty()) {
request->extras = apps_util::CreateArcIntentExtras(intent);
}
return request;
}
void OnContentUrlResolved(const base::FilePath& file_path,
const std::string& app_id,
int32_t event_flags,
apps::IntentPtr intent,
arc::mojom::ActivityNamePtr activity,
apps::WindowInfoPtr window_info,
base::OnceCallback<void(bool)> callback,
const std::vector<GURL>& content_urls) {
for (const auto& content_url : content_urls) {
if (!content_url.is_valid()) {
LOG(ERROR) << "Share files failed, file urls are not valid";
std::move(callback).Run(/*success=*/false);
return;
}
}
auto* arc_service_manager = arc::ArcServiceManager::Get();
if (!arc_service_manager) {
std::move(callback).Run(/*success=*/false);
return;
}
DCHECK(window_info);
int32_t session_id = window_info->window_id;
int64_t display_id = window_info->display_id;
arc::mojom::FileSystemInstance* arc_file_system = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->file_system(),
OpenUrlsWithPermissionAndWindowInfo);
if (!arc_file_system) {
LOG(ERROR) << "Failed to open urls, ARC File System not found";
std::move(callback).Run(/*success=*/false);
return;
}
arc_file_system->OpenUrlsWithPermissionAndWindowInfo(
ConstructOpenUrlsRequest(intent, activity, content_urls),
apps::MakeArcWindowInfo(std::move(window_info)), base::DoNothing());
::full_restore::SaveAppLaunchInfo(
file_path,
std::make_unique<app_restore::AppLaunchInfo>(
app_id, event_flags, std::move(intent), session_id, display_id));
std::move(callback).Run(/*success=*/true);
}
// Sets the session id for |window_info|. If the full restore feature is
// disabled, or the session id has been set, returns |window_info|. Otherwise,
// fetches a new ARC session id, and sets to window_id for |window_info|.
apps::WindowInfoPtr SetSessionId(apps::WindowInfoPtr window_info) {
if (!window_info) {
window_info =
std::make_unique<apps::WindowInfo>(display::kInvalidDisplayId);
}
if (window_info->window_id != -1) {
return window_info;
}
window_info->window_id =
::full_restore::FullRestoreSaveHandler::GetInstance()->GetArcSessionId();
return window_info;
}
std::optional<bool> GetResizeLocked(ArcAppListPrefs* prefs,
const std::string& app_id) {
// Set null to resize lock state until the Mojo connection to ARC++ has been
// established. This prevents Chrome and ARC++ from having inconsistent
// states.
auto* arc_service_manager = arc::ArcServiceManager::Get();
if (!arc_service_manager) {
return std::nullopt;
}
// If we don't have the connection (e.g. for non-supported Android versions),
// returns null.
auto* compatibility_mode =
arc_service_manager->arc_bridge_service()->compatibility_mode();
if (!compatibility_mode->IsConnected()) {
return std::nullopt;
}
// Check if |SetResizeLockState| is available to see if Android is ready to
// be synchronized. Otherwise we need to hide the corresponding setting by
// returning null.
auto* instance =
ARC_GET_INSTANCE_FOR_METHOD(compatibility_mode, SetResizeLockState);
if (!instance) {
return std::nullopt;
}
auto resize_lock_state = prefs->GetResizeLockState(app_id);
switch (resize_lock_state) {
case arc::mojom::ArcResizeLockState::ON:
return true;
case arc::mojom::ArcResizeLockState::OFF:
return false;
case arc::mojom::ArcResizeLockState::UNDEFINED:
case arc::mojom::ArcResizeLockState::READY:
// FULLY_LOCKED means the resize-lock-related features are not available
// including the resizability toggle in the app management page.
case arc::mojom::ArcResizeLockState::FULLY_LOCKED:
return std::nullopt;
}
}
bool IsWebAppShellPackage(Profile* profile,
const ArcAppListPrefs::AppInfo& app_info) {
ash::ApkWebAppService* apk_web_app_service =
ash::ApkWebAppService::Get(profile);
return apk_web_app_service &&
apk_web_app_service->IsWebAppShellPackage(app_info.package_name);
}
bool IntentHasFilesAndMimeTypes(const apps::IntentPtr& intent) {
if (intent->files.empty()) {
return false;
}
bool all_files_have_mime_type = base::ranges::all_of(
intent->files,
[](apps::IntentFilePtr& file) { return file->mime_type.has_value(); });
return all_files_have_mime_type || intent->mime_type.has_value();
}
// Returns true if the app with the given |app_id| should open supported links
// inside the app by default.
bool AppShouldDefaultHandleLinksInApp(const std::string& app_id) {
// Play Store provides core system functionality and should handle links
// inside the app rather than in the browser.
return app_id == arc::kPlayStoreAppId;
}
// Returns true if the package with the given |package_name| should open
// supported links inside the browser by default, on managed devices.
bool PackageShouldDefaultHandleLinksInBrowser(const std::string& package_name) {
constexpr auto allowlist = base::MakeFixedFlatSet<std::string_view>({
"com.google.android.apps.docs", // Google Drive
"com.google.android.apps.docs.editors.docs", // Google Docs
"com.google.android.apps.docs.editors.sheets", // Google Sheets
"com.google.android.apps.docs.editors.slides", // Google Slides
});
return allowlist.contains(package_name);
}
// Returns true if the given `profile` is managed, and therefore should open
// supported links inside the app by default.
bool IsProfileManaged(Profile* profile) {
// TODO(crbug.com/40272292): Remove once we have policy control over link
// capturing behavior.
return profile->GetProfilePolicyConnector()->IsManaged();
}
// Returns the hard-coded Play Store intent filters. This is a stop-gap solution
// to handle Play Store URLs before ARC gets ready.
// TODO(b/259205050): Remove this once intent filters are properly cached.
std::vector<apps::IntentFilterPtr> GetHardcodedPlayStoreIntentFilters() {
const std::vector<std::string> actions = {arc::kIntentActionView};
const std::vector<std::string> schemes = {"http", "https"};
const std::vector<std::string> mime_types;
std::vector<arc::IntentFilter::AuthorityEntry> authorities;
authorities.emplace_back("play.google.com", -1);
std::vector<arc::IntentFilter::PatternMatcher> paths;
paths.emplace_back("", arc::mojom::PatternType::PATTERN_LITERAL);
paths.emplace_back("/", arc::mojom::PatternType::PATTERN_LITERAL);
paths.emplace_back("/store", arc::mojom::PatternType::PATTERN_PREFIX);
paths.emplace_back("/redeem", arc::mojom::PatternType::PATTERN_PREFIX);
paths.emplace_back("/wishlist", arc::mojom::PatternType::PATTERN_PREFIX);
paths.emplace_back("/apps/test/", arc::mojom::PatternType::PATTERN_PREFIX);
paths.emplace_back("/apps", arc::mojom::PatternType::PATTERN_LITERAL);
paths.emplace_back("/apps/launch", arc::mojom::PatternType::PATTERN_LITERAL);
paths.emplace_back("/protect/home", arc::mojom::PatternType::PATTERN_PREFIX);
std::vector<apps::IntentFilterPtr> intent_filters;
apps::IntentFilterPtr filter = apps_util::CreateIntentFilterForArc(
arc::IntentFilter(arc::kPlayStorePackage, actions, std::move(authorities),
std::move(paths), schemes, mime_types));
if (filter) {
intent_filters.push_back(std::move(filter));
}
return intent_filters;
}
apps::InstallReason GetInstallReason(const ArcAppListPrefs* prefs,
const std::string& app_id,
const ArcAppListPrefs::AppInfo& app_info) {
if (prefs->IsControlledByPolicy(app_info.package_name)) {
return apps::InstallReason::kPolicy;
}
// Sticky represents apps that cannot be uninstalled and are installed by the
// system. Policy installed apps are also considered sticky, so kPolicy must
// be first.
if (app_info.sticky) {
return apps::InstallReason::kSystem;
}
if (prefs->IsOem(app_id)) {
return apps::InstallReason::kOem;
}
if (prefs->IsDefault(app_id)) {
return apps::InstallReason::kDefault;
}
return apps::InstallReason::kUser;
}
bool ArcVersionEligibleForPromiseIcons() {
return g_test_arc_version_.value_or(arc::GetArcAndroidSdkVersionAsInt()) >=
arc::kArcVersionR;
}
} // namespace
namespace apps {
void ArcApps::SetArcVersionForTesting(int version) {
g_test_arc_version_ = version;
}
// static
ArcApps* ArcApps::Get(Profile* profile) {
return ArcAppsFactory::GetForProfile(profile);
}
ArcApps::ArcApps(AppServiceProxy* proxy)
: AppPublisher(proxy), profile_(proxy->profile()) {}
ArcApps::~ArcApps() {
proxy()->UnregisterPublisher(AppType::kArc);
}
void ArcApps::Initialize() {
if (!arc::IsArcAllowedForProfile(profile_) ||
(arc::ArcServiceManager::Get() == nullptr)) {
return;
}
// Make some observee-observer connections.
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
prefs->AddObserver(this);
proxy()->SetArcIsRegistered();
auto* intent_helper_bridge =
arc::ArcIntentHelperBridge::GetForBrowserContext(profile_);
if (intent_helper_bridge) {
intent_helper_bridge->SetAdaptiveIconDelegate(
&arc_activity_adaptive_icon_impl_);
arc_intent_helper_observation_.Observe(intent_helper_bridge);
}
// There is no MessageCenterController for unit tests, so observe when the
// MessageCenterController is created in production code.
if (ash::ArcNotificationsHostInitializer::Get()) {
notification_initializer_observation_.Observe(
ash::ArcNotificationsHostInitializer::Get());
}
auto* arc_bridge_service =
arc::ArcPrivacyItemsBridge::GetForBrowserContext(profile_);
if (arc_bridge_service) {
arc_privacy_items_bridge_observation_.Observe(arc_bridge_service);
}
auto* instance_registry = &proxy()->InstanceRegistry();
if (instance_registry) {
instance_registry_observation_.Observe(instance_registry);
}
if (web_app::AreWebAppsEnabled(profile_)) {
web_apk_manager_ = std::make_unique<apps::WebApkManager>(profile_);
}
RegisterPublisher(AppType::kArc);
std::vector<AppPtr> apps;
for (const auto& app_id : prefs->GetAppIds()) {
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
if (app_info) {
apps.push_back(CreateApp(prefs, app_id, *app_info));
}
}
AppPublisher::Publish(std::move(apps), AppType::kArc,
/*should_notify_initialized=*/true);
ObserveDisabledSystemFeaturesPolicy();
}
void ArcApps::Shutdown() {
// Disconnect the observee-observer connections that we made during the
// constructor.
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (prefs) {
prefs->RemoveObserver(this);
}
auto* intent_helper_bridge =
arc::ArcIntentHelperBridge::GetForBrowserContext(profile_);
if (intent_helper_bridge) {
intent_helper_bridge->SetAdaptiveIconDelegate(nullptr);
}
arc_intent_helper_observation_.Reset();
arc_privacy_items_bridge_observation_.Reset();
}
void ArcApps::GetCompressedIconData(const std::string& app_id,
int32_t size_in_dip,
ui::ResourceScaleFactor scale_factor,
LoadIconCallback callback) {
GetArcAppCompressedIconData(profile_, app_id, size_in_dip, scale_factor,
std::move(callback));
}
void ArcApps::Launch(const std::string& app_id,
int32_t event_flags,
LaunchSource launch_source,
WindowInfoPtr window_info) {
auto user_interaction_type = GetUserInterationType(launch_source);
if (!user_interaction_type.has_value()) {
return;
}
if (app_id == arc::kPlayStoreAppId &&
apps_util::IsHumanLaunch(launch_source)) {
arc::RecordPlayStoreLaunchWithinAWeek(profile_->GetPrefs(),
/*launched=*/true);
}
auto new_window_info = SetSessionId(std::move(window_info));
int32_t session_id = new_window_info->window_id;
int64_t display_id = new_window_info->display_id;
arc::LaunchApp(profile_, app_id, event_flags, user_interaction_type.value(),
MakeArcWindowInfo(std::move(new_window_info)));
full_restore::SaveAppLaunchInfo(
profile_->GetPath(), std::make_unique<app_restore::AppLaunchInfo>(
app_id, event_flags, session_id, display_id));
}
void ArcApps::LaunchAppWithIntent(const std::string& app_id,
int32_t event_flags,
IntentPtr intent,
LaunchSource launch_source,
WindowInfoPtr window_info,
LaunchCallback callback) {
auto user_interaction_type = GetUserInterationType(launch_source);
if (!user_interaction_type.has_value()) {
std::move(callback).Run(LaunchResult(State::kFailed));
return;
}
if (app_id == arc::kPlayStoreAppId &&
apps_util::IsHumanLaunch(launch_source)) {
arc::RecordPlayStoreLaunchWithinAWeek(profile_->GetPrefs(),
/*launched=*/true);
}
arc::ArcMetricsService::RecordArcUserInteraction(
profile_, user_interaction_type.value());
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
std::move(callback).Run(LaunchResult(State::kFailed));
return;
}
const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
prefs->GetApp(app_id);
if (!app_info) {
LOG(ERROR) << "Launch App failed, could not find app with id " << app_id;
std::move(callback).Run(LaunchResult(State::kFailed));
return;
}
arc::mojom::ActivityNamePtr activity = arc::mojom::ActivityName::New();
activity->package_name = app_info->package_name;
if (intent->activity_name.has_value() &&
!intent->activity_name.value().empty()) {
activity->activity_name = intent->activity_name.value();
}
auto new_window_info = SetSessionId(std::move(window_info));
int32_t session_id = new_window_info->window_id;
int64_t display_id = new_window_info->display_id;
// Check if the intent has files, and whether the intent has a mime type or
// all the individual files have mime types.
if (IntentHasFilesAndMimeTypes(intent)) {
if (app_info->ready) {
std::vector<GURL> file_urls;
for (const auto& file : intent->files) {
file_urls.push_back(file->url);
}
arc::ConvertToContentUrlsAndShare(
profile_, apps::GetFileSystemURL(profile_, file_urls),
base::BindOnce(&OnContentUrlResolved, profile_->GetPath(), app_id,
event_flags, std::move(intent), std::move(activity),
std::move(new_window_info),
base::BindOnce(
[](LaunchCallback callback, bool success) {
std::move(callback).Run(
ConvertBoolToLaunchResult(success));
},
std::move(callback))));
return;
}
} else {
auto intent_for_full_restore = intent->Clone();
if (!arc::LaunchAppWithIntent(
profile_, app_id, std::move(intent), event_flags,
user_interaction_type.value(),
MakeArcWindowInfo(std::move(new_window_info)))) {
VLOG(2) << "Failed to launch app: " + app_id + ".";
std::move(callback).Run(LaunchResult(State::kFailed));
return;
}
full_restore::SaveAppLaunchInfo(
profile_->GetPath(),
std::make_unique<app_restore::AppLaunchInfo>(
app_id, event_flags, std::move(intent_for_full_restore), session_id,
display_id));
std::move(callback).Run(LaunchResult(State::kSuccess));
return;
}
if (arc::IsArcPlayStoreEnabledForProfile(profile_)) {
// Handle the case when default app tries to re-activate OptIn flow.
if (arc::IsArcPlayStoreEnabledPreferenceManagedForProfile(profile_) &&
!arc::ArcSessionManager::Get()->enable_requested() &&
prefs->IsDefault(app_id)) {
arc::SetArcPlayStoreEnabledForProfile(profile_, true);
// PlayStore item has special handling for shelf controllers. In order
// to avoid unwanted initial animation for PlayStore item do not create
// deferred launch request when PlayStore item enables Google Play
// Store.
if (app_id == arc::kPlayStoreAppId) {
prefs->SetLastLaunchTime(app_id);
std::move(callback).Run(LaunchResult(State::kSuccess));
return;
}
}
} else {
if (prefs->IsDefault(app_id)) {
// The setting can fail if the preference is managed. However, the
// caller is responsible to not call this function in such case. DCHECK
// is here to prevent possible mistake.
if (!arc::SetArcPlayStoreEnabledForProfile(profile_, true)) {
std::move(callback).Run(LaunchResult(State::kFailed));
return;
}
DCHECK(arc::IsArcPlayStoreEnabledForProfile(profile_));
// PlayStore item has special handling for shelf controllers. In order
// to avoid unwanted initial animation for PlayStore item do not create
// deferred launch request when PlayStore item enables Google Play
// Store.
if (app_id == arc::kPlayStoreAppId) {
prefs->SetLastLaunchTime(app_id);
std::move(callback).Run(LaunchResult(State::kFailed));
return;
}
} else {
// Only reachable when ARC always starts.
DCHECK(arc::ShouldArcAlwaysStart());
}
}
std::move(callback).Run(LaunchResult(State::kSuccess));
}
void ArcApps::LaunchAppWithParams(AppLaunchParams&& params,
LaunchCallback callback) {
auto event_flags = apps::GetEventFlags(params.disposition,
/*prefer_container=*/false);
if (params.intent) {
LaunchAppWithIntent(params.app_id, event_flags, std::move(params.intent),
params.launch_source,
std::make_unique<WindowInfo>(params.display_id),
std::move(callback));
} else {
Launch(params.app_id, event_flags, params.launch_source,
std::make_unique<WindowInfo>(params.display_id));
// TODO(crbug.com/40787924): Add launch return value.
std::move(callback).Run(LaunchResult());
}
}
void ArcApps::LaunchShortcut(const std::string& app_id,
const std::string& shortcut_id,
int64_t display_id) {
arc::ExecuteArcShortcutCommand(profile_, app_id, shortcut_id, display_id);
}
void ArcApps::SetPermission(const std::string& app_id,
PermissionPtr permission) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
prefs->GetApp(app_id);
if (!app_info) {
LOG(ERROR) << "SetPermission failed, could not find app with id " << app_id;
return;
}
auto* arc_service_manager = arc::ArcServiceManager::Get();
if (!arc_service_manager) {
LOG(WARNING) << "SetPermission failed, ArcServiceManager not available.";
return;
}
// TODO(crbug.com/40760689): Add unknown type for arc permissions enum.
arc::mojom::AppPermission permission_type = arc::mojom::AppPermission::CAMERA;
if (!GetArcPermissionType(permission->permission_type, permission_type)) {
LOG(ERROR) << "SetPermission failed, permission type not supported by ARC.";
return;
}
if (permission->IsPermissionEnabled()) {
auto* permissions_instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->app_permissions(),
GrantPermission);
if (permissions_instance) {
permissions_instance->GrantPermission(app_info->package_name,
permission_type);
}
} else {
auto* permissions_instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->app_permissions(),
RevokePermission);
if (permissions_instance) {
permissions_instance->RevokePermission(app_info->package_name,
permission_type);
}
}
}
void ArcApps::Uninstall(const std::string& app_id,
UninstallSource uninstall_source,
bool clear_site_data,
bool report_abuse) {
arc::UninstallArcApp(app_id, profile_);
}
void ArcApps::GetMenuModel(const std::string& app_id,
MenuType menu_type,
int64_t display_id,
base::OnceCallback<void(MenuItems)> callback) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
std::move(callback).Run(MenuItems());
return;
}
const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
prefs->GetApp(app_id);
if (!app_info) {
std::move(callback).Run(MenuItems());
return;
}
MenuItems menu_items;
// Add Open item if the app is not opened and not suspended.
if (!base::Contains(app_id_to_task_ids_, app_id) && !app_info->suspended) {
AddCommandItem(ash::LAUNCH_NEW, IDS_APP_CONTEXT_MENU_ACTIVATE_ARC,
menu_items);
}
if (app_info->shortcut) {
AddCommandItem(ash::UNINSTALL, IDS_APP_LIST_REMOVE_SHORTCUT, menu_items);
} else if (app_info->ready && !app_info->sticky) {
AddCommandItem(ash::UNINSTALL, IDS_APP_LIST_UNINSTALL_ITEM, menu_items);
}
// App Info item.
if (app_info->ready && ShouldShow(*app_info)) {
AddCommandItem(ash::SHOW_APP_INFO, IDS_APP_CONTEXT_MENU_SHOW_INFO,
menu_items);
}
if (menu_type == MenuType::kShelf &&
base::Contains(app_id_to_task_ids_, app_id)) {
AddCommandItem(ash::MENU_CLOSE, IDS_SHELF_CONTEXT_MENU_CLOSE, menu_items);
}
BuildMenuForShortcut(app_info->package_name, std::move(menu_items),
std::move(callback));
}
void ArcApps::SetResizeLocked(const std::string& app_id, bool locked) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
prefs->SetResizeLockState(app_id, locked
? arc::mojom::ArcResizeLockState::ON
: arc::mojom::ArcResizeLockState::OFF);
}
void ArcApps::SetAppLocale(const std::string& app_id,
const std::string& locale_tag) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!profile_->GetPrefs() || !prefs) {
return;
}
const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
prefs->GetApp(app_id);
if (!app_info) {
LOG(ERROR) << "SetAppLocale failed, could not find app with id " << app_id;
return;
}
if (app_info->package_name.empty()) {
LOG(ERROR) << "SetAppLocale failed, package name is empty with app_id "
<< app_id;
return;
}
// Set app locale and update last-set app locale.
arc::mojom::AppInstance* app_instance =
(arc::ArcServiceManager::Get()
? ARC_GET_INSTANCE_FOR_METHOD(
arc::ArcServiceManager::Get()->arc_bridge_service()->app(),
SetAppLocale)
: nullptr);
if (app_instance) {
app_instance->SetAppLocale(app_info->package_name, locale_tag);
} else {
// If AppInstance is not ready, we still want to update the prefs to ensure
// good UX. To ensure eventual-correctness between ARC settings and Chrome
// settings, on ARC boot, ARC will always sends its latest-set locale to
// Chrome. If there's a mismatch, Chrome will then send back its latest-set
// locale to ARC, both settings are still synchronized.
prefs->SetAppLocale(app_info->package_name, locale_tag);
}
// Update the last-set locale, unless the locale tag is the system language.
if (!locale_tag.empty()) {
profile_->GetPrefs()->SetString(arc::prefs::kArcLastSetAppLocale,
locale_tag);
}
}
void ArcApps::PauseApp(const std::string& app_id) {
if (paused_apps_.MaybeAddApp(app_id)) {
SetIconEffect(app_id);
}
AppPublisher::Publish(paused_apps_.CreateAppWithPauseStatus(
AppType::kArc, app_id, /*paused=*/true));
CloseTasks(app_id);
}
void ArcApps::UnpauseApp(const std::string& app_id) {
if (paused_apps_.MaybeRemoveApp(app_id)) {
SetIconEffect(app_id);
}
AppPublisher::Publish(paused_apps_.CreateAppWithPauseStatus(
AppType::kArc, app_id, /*paused=*/false));
}
void ArcApps::BlockApp(const std::string& app_id) {
if (base::Contains(blocked_app_ids_, app_id)) {
return;
}
blocked_app_ids_.insert(app_id);
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
CHECK(prefs);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
if (!app_info) {
return;
}
auto app = std::make_unique<App>(AppType::kArc, app_id);
app->readiness = GetReadiness(app_id, *app_info);
app->icon_key = IconKey(GetIconEffects(app_id, *app_info));
AppPublisher::Publish(std::move(app));
CloseTasks(app_id);
}
void ArcApps::UnblockApp(const std::string& app_id) {
if (!base::Contains(blocked_app_ids_, app_id)) {
return;
}
blocked_app_ids_.erase(app_id);
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
CHECK(prefs);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
if (!app_info) {
return;
}
auto app = std::make_unique<App>(AppType::kArc, app_id);
app->readiness = GetReadiness(app_id, *app_info);
app->icon_key = IconKey(GetIconEffects(app_id, *app_info));
AppPublisher::Publish(std::move(app));
}
void ArcApps::StopApp(const std::string& app_id) {
CloseTasks(app_id);
}
void ArcApps::UpdateAppSize(const std::string& app_id) {
arc::mojom::AppInstance* app_instance =
(arc::ArcServiceManager::Get()
? ARC_GET_INSTANCE_FOR_METHOD(
arc::ArcServiceManager::Get()->arc_bridge_service()->app(),
UpdateAppDetails)
: nullptr);
if (!app_instance) {
return;
}
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
prefs->GetApp(app_id);
if (!app_info) {
return;
}
if (app_info->package_name.empty()) {
return;
}
// A request is made to simultaneously update all of the app's details,
// inclusive of the app size, for simplicity
app_instance->UpdateAppDetails(app_info->package_name);
}
void ArcApps::ExecuteContextMenuCommand(const std::string& app_id,
int command_id,
const std::string& shortcut_id,
int64_t display_id) {
arc::ExecuteArcShortcutCommand(profile_, app_id, shortcut_id, display_id);
}
void ArcApps::OpenNativeSettings(const std::string& app_id) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
prefs->GetApp(app_id);
if (!app_info) {
LOG(ERROR) << "Cannot open native settings for " << app_id
<< ". App is not found.";
return;
}
if (app_info->package_name.empty()) {
LOG(ERROR) << "Cannot open native settings for " << app_id
<< ". Package name is empty.";
return;
}
const auto page = arc::IsReadOnlyPermissionsEnabled()
? arc::mojom::ShowPackageInfoPage::MANAGE_PERMISSIONS
: arc::mojom::ShowPackageInfoPage::MAIN;
arc::ShowPackageInfo(app_info->package_name, page,
display::Screen::GetScreen()->GetPrimaryDisplay().id());
}
void ArcApps::OnSupportedLinksPreferenceChanged(const std::string& app_id,
bool open_in_app) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
if (!app_info) {
return;
}
arc::mojom::IntentHelperInstance* instance = nullptr;
auto* arc_service_manager = arc::ArcServiceManager::Get();
if (arc_service_manager) {
instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->intent_helper(),
SetVerifiedLinks);
}
if (!instance) {
return;
}
instance->SetVerifiedLinks({app_info->package_name}, open_in_app);
}
void ArcApps::OnAppRegistered(const std::string& app_id,
const ArcAppListPrefs::AppInfo& app_info) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (prefs && !IsWebAppShellPackage(profile_, app_info)) {
AppPublisher::Publish(CreateApp(prefs, app_id, app_info));
}
}
void ArcApps::OnAppStatesChanged(const std::string& app_id,
const ArcAppListPrefs::AppInfo& app_info) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs || IsWebAppShellPackage(profile_, app_info)) {
return;
}
AppPublisher::Publish(CreateApp(prefs, app_id, app_info));
}
void ArcApps::OnAppRemoved(const std::string& app_id) {
app_notifications_.RemoveNotificationsForApp(app_id);
paused_apps_.MaybeRemoveApp(app_id);
blocked_app_ids_.erase(app_id);
if (base::Contains(app_id_to_task_ids_, app_id)) {
for (int task_id : app_id_to_task_ids_[app_id]) {
task_id_to_app_id_.erase(task_id);
}
app_id_to_task_ids_.erase(app_id);
}
auto app = std::make_unique<App>(AppType::kArc, app_id);
app->readiness = Readiness::kUninstalledByUser;
AppPublisher::Publish(std::move(app));
}
void ArcApps::OnAppNameUpdated(const std::string& app_id,
const std::string& name) {
auto app = std::make_unique<App>(AppType::kArc, app_id);
app->name = name;
AppPublisher::Publish(std::move(app));
}
void ArcApps::OnAppLastLaunchTimeUpdated(const std::string& app_id) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
if (!app_info || IsWebAppShellPackage(profile_, *app_info)) {
return;
}
auto app = std::make_unique<App>(AppType::kArc, app_id);
app->last_launch_time = app_info->last_launch_time;
AppPublisher::Publish(std::move(app));
}
void ArcApps::OnPackageInstalled(
const arc::mojom::ArcPackageInfo& package_info) {
ConvertAndPublishPackageApps(package_info);
}
void ArcApps::OnPackageModified(
const arc::mojom::ArcPackageInfo& package_info) {
static constexpr bool update_icon = false;
ConvertAndPublishPackageApps(package_info, update_icon);
}
void ArcApps::OnPackageListInitialRefreshed() {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
// This method is called when ARC++ finishes booting. Do not update the icon;
// it should be impossible for the icon to have changed since ARC++ has not
// been running.
static constexpr bool update_icon = false;
for (const auto& app_id : prefs->GetAppIds()) {
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
if (app_info && !IsWebAppShellPackage(profile_, *app_info)) {
AppPublisher::Publish(CreateApp(prefs, app_id, *app_info, update_icon));
}
}
}
void ArcApps::OnTaskCreated(int32_t task_id,
const std::string& package_name,
const std::string& activity,
const std::string& intent,
int32_t session_id) {
const std::string app_id = ArcAppListPrefs::GetAppId(package_name, activity);
app_id_to_task_ids_[app_id].insert(task_id);
task_id_to_app_id_[task_id] = app_id;
}
void ArcApps::OnTaskDestroyed(int32_t task_id) {
auto it = task_id_to_app_id_.find(task_id);
if (it == task_id_to_app_id_.end()) {
return;
}
const std::string app_id = it->second;
task_id_to_app_id_.erase(it);
DCHECK(base::Contains(app_id_to_task_ids_, app_id));
app_id_to_task_ids_[app_id].erase(task_id);
if (app_id_to_task_ids_[app_id].empty()) {
app_id_to_task_ids_.erase(app_id);
}
}
void ArcApps::OnIntentFiltersUpdated(
const std::optional<std::string>& package_name) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
auto GetAppInfoAndPublish = [prefs, this](std::string app_id) {
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
if (app_info) {
AppPublisher::Publish(
CreateApp(prefs, app_id, *app_info, false /* update_icon */));
}
};
// If there is no specific package_name, update all apps, otherwise update
// apps for the package.
// Note: Cannot combine the two for-loops because the return type of
// GetAppIds() is std::vector<std::string> and the return type of
// GetAppsForPackage() is std::unordered_set<std::string>.
if (package_name == std::nullopt) {
for (const auto& app_id : prefs->GetAppIds()) {
GetAppInfoAndPublish(app_id);
}
} else {
for (const auto& app_id : prefs->GetAppsForPackage(package_name.value())) {
GetAppInfoAndPublish(app_id);
}
}
}
void ArcApps::OnArcSupportedLinksChanged(
const std::vector<arc::mojom::SupportedLinksPackagePtr>& added,
const std::vector<arc::mojom::SupportedLinksPackagePtr>& removed,
arc::mojom::SupportedLinkChangeSource source) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
for (const auto& supported_link : added) {
std::string app_id =
prefs->GetAppIdByPackageName(supported_link->package_name);
if (app_id.empty()) {
continue;
}
// ARC apps may handle links by default on the ARC side, but do not handle
// links by default on the Ash side. This means that the default setting may
// be different between Ash and ARC. Any user action to change the setting
// will make it the same between both sides.
//
// To make this work, we need to ignore request from the ARC system to
// update the supported links setting. We allow updates in the following
// cases:
bool allow_update =
// When the user explicitly changes the setting in Android Settings.
source == arc::mojom::SupportedLinkChangeSource::kUserPreference ||
// If the app is already marked as preferred on the Ash side.
proxy()->PreferredAppsList().IsPreferredAppForSupportedLinks(app_id) ||
// If the app is specifically allowed to handle links by default.
AppShouldDefaultHandleLinksInApp(app_id);
// Managed users are temporarily opted out of this behavior (b/280056133)
// and always apply updates from the ARC side, except for an allowlist of
// apps which handle links in the browser to improve the user experience.
if (IsProfileManaged(profile_) && !PackageShouldDefaultHandleLinksInBrowser(
supported_link->package_name)) {
allow_update = true;
}
if (!allow_update) {
continue;
}
proxy()->SetSupportedLinksPreference(app_id);
}
for (const auto& supported_link : removed) {
std::string app_id =
prefs->GetAppIdByPackageName(supported_link->package_name);
if (app_id.empty()) {
continue;
}
proxy()->RemoveSupportedLinksPreference(app_id);
}
}
void ArcApps::OnSetArcNotificationsInstance(
ash::ArcNotificationManagerBase* arc_notification_manager) {
DCHECK(arc_notification_manager);
notification_observation_.Observe(arc_notification_manager);
}
void ArcApps::OnArcNotificationInitializerDestroyed(
ash::ArcNotificationsHostInitializer* initializer) {
DCHECK(notification_initializer_observation_.IsObservingSource(initializer));
notification_initializer_observation_.Reset();
}
void ArcApps::OnNotificationUpdated(const std::string& notification_id,
const std::string& app_id) {
if (app_id.empty()) {
return;
}
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
prefs->GetApp(app_id);
if (!app_info) {
return;
}
app_notifications_.AddNotification(app_id, notification_id);
AppPublisher::Publish(
app_notifications_.CreateAppWithHasBadgeStatus(AppType::kArc, app_id));
}
void ArcApps::OnNotificationRemoved(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) {
AppPublisher::Publish(
app_notifications_.CreateAppWithHasBadgeStatus(AppType::kArc, app_id));
}
}
void ArcApps::OnArcNotificationManagerDestroyed(
ash::ArcNotificationManagerBase* notification_manager) {
DCHECK(notification_observation_.IsObservingSource(notification_manager));
notification_observation_.Reset();
}
void ArcApps::OnPrivacyItemsChanged(
const std::vector<arc::mojom::PrivacyItemPtr>& privacy_items) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
// Get the existing accessing app ids from `accessing_apps_`, and set all of
// them as false to explicitly update `AppCapabilityAccessCache` to ensure the
// access is stopped when they are not list in `privacy_items`. If they are
// still accessing, they will exist in `privacy_items`, and be set as true in
// the next loop for `privacy_items`.
base::flat_map<std::string, CapabilityAccessPtr> capability_accesses;
for (const auto& app_id : accessing_apps_) {
auto access = std::make_unique<CapabilityAccess>(app_id);
access->app_id = app_id;
access->camera = false;
access->microphone = false;
capability_accesses[app_id] = std::move(access);
}
accessing_apps_.clear();
// Check the new items in `privacy_items`, and update `capability_accesses` to
// set the access item as true, if the camera or the microphone is still in
// use.
for (const auto& item : privacy_items) {
arc::mojom::AppPermissionGroup permission = item->permission_group;
if (permission != arc::mojom::AppPermissionGroup::CAMERA &&
permission != arc::mojom::AppPermissionGroup::MICROPHONE) {
continue;
}
auto package_name = item->privacy_application->package_name;
for (const auto& app_id : prefs->GetAppsForPackage(package_name)) {
accessing_apps_.insert(app_id);
auto [it, inserted] = capability_accesses.try_emplace(
app_id, std::make_unique<CapabilityAccess>(app_id));
if (permission == arc::mojom::AppPermissionGroup::CAMERA) {
it->second->camera = true;
}
if (permission == arc::mojom::AppPermissionGroup::MICROPHONE) {
it->second->microphone = true;
}
}
}
// Write the record to `AppCapabilityAccessCache`.
std::vector<CapabilityAccessPtr> accesses;
for (auto& item : capability_accesses) {
accesses.push_back(std::move(item.second));
}
proxy()->OnCapabilityAccesses(std::move(accesses));
}
void ArcApps::OnInstanceUpdate(const apps::InstanceUpdate& update) {
if (!update.StateChanged()) {
return;
}
if (update.AppId() != arc::kSettingsAppId) {
return;
}
if (update.State() & apps::InstanceState::kActive) {
settings_app_is_active_ = true;
} else if (settings_app_is_active_) {
settings_app_is_active_ = false;
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
RequestDomainVerificationStatusUpdate(prefs);
}
}
void ArcApps::OnInstanceRegistryWillBeDestroyed(
apps::InstanceRegistry* instance_registry) {
DCHECK(instance_registry_observation_.IsObservingSource(instance_registry));
instance_registry_observation_.Reset();
}
AppPtr ArcApps::CreateApp(ArcAppListPrefs* prefs,
const std::string& app_id,
const ArcAppListPrefs::AppInfo& app_info,
bool update_icon,
bool raw_icon_updated) {
auto install_reason = GetInstallReason(prefs, app_id, app_info);
auto app = AppPublisher::MakeApp(
AppType::kArc, app_id, GetReadiness(app_id, app_info), app_info.name,
install_reason,
install_reason == InstallReason::kSystem ? InstallSource::kSystem
: InstallSource::kPlayStore);
app->publisher_id = app_info.package_name;
app->installer_package_id =
PackageId(PackageType::kArc, app_info.package_name);
app->policy_ids = {app_info.package_name};
if (update_icon) {
app->icon_key = IconKey(raw_icon_updated, GetIconEffects(app_id, app_info));
}
app->version = app_info.version_name;
app->last_launch_time = app_info.last_launch_time;
app->install_time = app_info.install_time;
std::unique_ptr<ArcAppListPrefs::PackageInfo> package =
prefs->GetPackage(app_info.package_name);
if (package) {
app->permissions = CreatePermissions(package->permissions);
if (package->locale_info) {
app->supported_locales = package->locale_info->supported_locales;
app->selected_locale = package->locale_info->selected_locale;
}
}
auto show = ShouldShow(app_info);
// All published ARC apps are launchable. All launchable apps should be
// permitted to be shown on the shelf, and have their pins on the shelf
// persisted.
app->show_in_shelf = true;
app->show_in_launcher = show;
if (app_id == arc::kPlayGamesAppId && !show) {
// Play Games should only be hidden in the launcher.
app->show_in_search = true;
app->show_in_management = true;
} else {
app->show_in_search = show;
app->show_in_management = show;
}
// Package Installer is hidden from the launcher, search and management but
// should still handle intents.
if (app_id == arc::kPackageInstallerAppId) {
app->handles_intents = true;
} else {
app->handles_intents = show;
}
app->allow_uninstall = app_info.ready && !app_info.sticky;
app->allow_close = true;
app->has_badge = app_notifications_.HasNotification(app_id);
app->paused = paused_apps_.IsPaused(app_id);
auto* intent_helper_bridge =
arc::ArcIntentHelperBridge::GetForBrowserContext(profile_);
if (intent_helper_bridge &&
app_info.package_name != arc::kArcIntentHelperPackageName) {
app->intent_filters = apps_util::CreateIntentFiltersFromArcBridge(
app_info.package_name, intent_helper_bridge);
}
// Set hard-coded Play Store intent filters if not set. This is a stop-gap
// solution to handle Play Store URLs before ARC gets ready.
// TODO(b/259205050): Remove this once intent filters are properly cached.
if (app->intent_filters.empty() && app_id == arc::kPlayStoreAppId) {
app->intent_filters = GetHardcodedPlayStoreIntentFilters();
}
app->resize_locked = GetResizeLocked(prefs, app_id);
app->app_size_in_bytes = app_info.app_size_in_bytes;
app->data_size_in_bytes = app_info.data_size_in_bytes;
return app;
}
void ArcApps::ConvertAndPublishPackageApps(
const arc::mojom::ArcPackageInfo& package_info,
bool update_icon) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
for (const auto& app_id :
prefs->GetAppsForPackage(package_info.package_name)) {
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
if (app_info && !IsWebAppShellPackage(profile_, *app_info)) {
// If the package is added or modified, the app icon files might be
// modified, so set `update_icon` and `raw_icon_updated` as true to update
// icon files in the icon folders.
AppPublisher::Publish(CreateApp(prefs, app_id, *app_info,
/*update_icon=*/true,
/*raw_icon_updated=*/true));
}
}
}
IconEffects ArcApps::GetIconEffects(const std::string& app_id,
const ArcAppListPrefs::AppInfo& app_info) {
IconEffects icon_effects = IconEffects::kNone;
if (GetReadiness(app_id, app_info) != Readiness::kReady) {
icon_effects =
static_cast<IconEffects>(icon_effects | IconEffects::kBlocked);
}
if (paused_apps_.IsPaused(app_id)) {
icon_effects =
static_cast<IconEffects>(icon_effects | IconEffects::kPaused);
}
return icon_effects;
}
void ArcApps::SetIconEffect(const std::string& app_id) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
if (!prefs) {
return;
}
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
if (!app_info || IsWebAppShellPackage(profile_, *app_info)) {
return;
}
auto app = std::make_unique<App>(AppType::kArc, app_id);
app->icon_key = IconKey(GetIconEffects(app_id, *app_info));
AppPublisher::Publish(std::move(app));
}
void ArcApps::CloseTasks(const std::string& app_id) {
if (!base::Contains(app_id_to_task_ids_, app_id)) {
return;
}
for (int task_id : app_id_to_task_ids_[app_id]) {
arc::CloseTask(task_id);
task_id_to_app_id_.erase(task_id);
}
app_id_to_task_ids_.erase(app_id);
}
void ArcApps::BuildMenuForShortcut(
const std::string& package_name,
MenuItems menu_items,
base::OnceCallback<void(MenuItems)> callback) {
// The previous request is cancelled, and start a new request if the callback
// of the previous request is not called.
arc_app_shortcuts_request_ =
std::make_unique<arc::ArcAppShortcutsRequest>(base::BindOnce(
&ArcApps::OnGetAppShortcutItems, weak_ptr_factory_.GetWeakPtr(),
std::move(menu_items), std::move(callback)));
arc_app_shortcuts_request_->StartForPackage(package_name);
}
void ArcApps::OnGetAppShortcutItems(
MenuItems menu_items,
base::OnceCallback<void(MenuItems)> callback,
std::unique_ptr<apps::AppShortcutItems> app_shortcut_items) {
if (!app_shortcut_items || app_shortcut_items->empty()) {
// No need log time for empty requests.
std::move(callback).Run(std::move(menu_items));
arc_app_shortcuts_request_.reset();
return;
}
apps::AppShortcutItems& items = *app_shortcut_items;
// Sort the shortcuts based on two rules: (1) Static (declared in manifest)
// shortcuts and then dynamic shortcuts; (2) Within each shortcut type
// (static and dynamic), shortcuts are sorted in order of increasing rank.
std::sort(items.begin(), items.end(),
[](const apps::AppShortcutItem& item1,
const apps::AppShortcutItem& item2) {
return std::tie(item1.type, item1.rank) <
std::tie(item2.type, item2.rank);
});
AddSeparator(ui::DOUBLE_SEPARATOR, menu_items);
int command_id = ash::LAUNCH_APP_SHORTCUT_FIRST;
for (const auto& item : items) {
if (command_id != ash::LAUNCH_APP_SHORTCUT_FIRST) {
AddSeparator(ui::PADDED_SEPARATOR, menu_items);
}
AddShortcutCommandItem(command_id++, item.shortcut_id, item.short_label,
item.icon, menu_items);
}
std::move(callback).Run(std::move(menu_items));
arc_app_shortcuts_request_.reset();
}
void ArcApps::OnInstallationStarted(const std::string& package_name) {
if (ash::features::ArePromiseIconsEnabled() &&
ArcVersionEligibleForPromiseIcons()) {
PromiseAppPtr promise_app = AppPublisher::MakePromiseApp(
PackageId(PackageType::kArc, package_name));
// All ARC installations start as "Pending".
promise_app->status = PromiseStatus::kPending;
AppPublisher::PublishPromiseApp(std::move(promise_app));
}
}
void ArcApps::OnInstallationProgressChanged(const std::string& package_name,
float progress) {
if (ash::features::ArePromiseIconsEnabled()) {
PackageId package_id = PackageId(PackageType::kArc, package_name);
const PromiseApp* existing_promise_app =
proxy()->PromiseAppRegistryCache()->GetPromiseApp(package_id);
if (!existing_promise_app) {
LOG(ERROR) << "Cannot update installation progress value for "
<< package_name
<< ", as there is no promise app registered for this package.";
return;
}
PromiseAppPtr promise_app = AppPublisher::MakePromiseApp(package_id);
promise_app->progress = progress;
// Update the status to reflect that the app is actively downloading/
// installing. We update the status here on the first progress update
// instead of in OnInstallationActiveChanged, due to some conflicts with
// what the ARC active status indicates and what we need.
if (existing_promise_app->status == PromiseStatus::kPending) {
promise_app->status = PromiseStatus::kInstalling;
}
AppPublisher::PublishPromiseApp(std::move(promise_app));
}
}
void ArcApps::OnInstallationActiveChanged(const std::string& package_name,
bool active) {
if (ash::features::ArePromiseIconsEnabled()) {
PackageId package_id(PackageType::kArc, package_name);
if (!proxy()->PromiseAppRegistryCache()->HasPromiseApp(package_id)) {
LOG(ERROR) << "Cannot update installation active status for "
<< package_name
<< ", as there is no promise app registered for this package.";
return;
}
// TODO(b/261907409): Set PromiseStatus to kPending if the installation is
// no longer active, i.e. if active=false after there has been at least one
// progress change.
}
}
void ArcApps::OnInstallationFinished(const std::string& package_name,
bool success,
bool is_launchable_app) {
if (ash::features::ArePromiseIconsEnabled() &&
ArcVersionEligibleForPromiseIcons()) {
if (success && is_launchable_app) {
return;
}
// Remove the promise app of any failed installation or non-launchable
// package.
PackageId package_id(PackageType::kArc, package_name);
if (!proxy()->PromiseAppRegistryCache()->HasPromiseApp(package_id)) {
return;
}
PromiseAppPtr promise_app = AppPublisher::MakePromiseApp(package_id);
promise_app->status = PromiseStatus::kCancelled;
AppPublisher::PublishPromiseApp(std::move(promise_app));
}
}
void ArcApps::OnAppConnectionClosed() {
std::vector<PromiseAppPtr> promise_apps =
proxy()->PromiseAppRegistryCache()->GetAllPromiseApps();
for (auto& promise_app : promise_apps) {
if (promise_app->package_id.package_type() != PackageType::kArc) {
continue;
}
promise_app->status = PromiseStatus::kCancelled;
AppPublisher::PublishPromiseApp(std::move(promise_app));
}
}
void ArcApps::ObserveDisabledSystemFeaturesPolicy() {
PrefService* const local_state = g_browser_process->local_state();
if (!local_state) { // Sometimes it's not available in tests.
return;
}
local_state_pref_change_registrar_.Init(local_state);
local_state_pref_change_registrar_.Add(
policy::policy_prefs::kSystemFeaturesDisableList,
base::BindRepeating(&ArcApps::OnDisableListPolicyChanged,
base::Unretained(this)));
}
void ArcApps::OnDisableListPolicyChanged() {
PrefService* const local_state = g_browser_process->local_state();
if (!local_state) {
return;
}
const base::Value::List& disabled_system_features_pref =
local_state->GetList(policy::policy_prefs::kSystemFeaturesDisableList);
bool disable_arc_settings = false;
for (const auto& entry : disabled_system_features_pref) {
if (static_cast<policy::SystemFeature>(entry.GetInt()) ==
policy::SystemFeature::kOsSettings) {
disable_arc_settings = true;
break;
}
}
ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile_);
if (!arc_prefs) {
return;
}
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
arc_prefs->GetApp(arc::kSettingsAppId);
if (!app_info) {
return;
}
bool is_disabled = false;
bool found = proxy()->AppRegistryCache().ForOneApp(
arc::kSettingsAppId, [&is_disabled](const apps::AppUpdate& update) {
is_disabled = apps_util::IsDisabled(update.Readiness());
});
if (!found) {
return;
}
if (disable_arc_settings == is_disabled) {
return;
}
auto app = std::make_unique<App>(AppType::kArc, arc::kSettingsAppId);
if (disable_arc_settings) {
settings_app_is_disabled_ = true;
app->icon_key = IconKey(/*raw_icon_updated=*/false, IconEffects::kBlocked);
} else {
settings_app_is_disabled_ = false;
app->icon_key = IconKey(GetIconEffects(arc::kSettingsAppId, *app_info));
}
app->readiness = GetReadiness(arc::kSettingsAppId, *app_info);
AppPublisher::Publish(std::move(app));
}
bool ArcApps::IsAppSuspended(const std::string& app_id,
const ArcAppListPrefs::AppInfo& app_info) {
if (app_id == arc::kSettingsAppId && settings_app_is_disabled_) {
return true;
}
return app_info.suspended;
}
Readiness ArcApps::GetReadiness(const std::string& app_id,
const ArcAppListPrefs::AppInfo& app_info) {
if (IsAppSuspended(app_id, app_info)) {
return Readiness::kDisabledByPolicy;
}
if (base::Contains(blocked_app_ids_, app_id)) {
return Readiness::kDisabledByLocalSettings;
}
return Readiness::kReady;
}
} // namespace apps