// 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/app_service_proxy_ash.h"
#include <memory>
#include <optional>
#include <utility>
#include "ash/constants/ash_features.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/not_fatal_until.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_util.h"
#include "chrome/browser/apps/app_service/app_install/app_install_service.h"
#include "chrome/browser/apps/app_service/instance_registry_updater.h"
#include "chrome/browser/apps/app_service/metrics/app_platform_metrics.h"
#include "chrome/browser/apps/app_service/metrics/app_platform_metrics_service.h"
#include "chrome/browser/apps/app_service/metrics/app_service_metrics.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/promise_apps/promise_app_service.h"
#include "chrome/browser/apps/app_service/publishers/app_publisher.h"
#include "chrome/browser/apps/app_service/publishers/standalone_browser_apps.h"
#include "chrome/browser/apps/app_service/uninstall_dialog.h"
#include "chrome/browser/apps/browser_instance/browser_app_instance_registry.h"
#include "chrome/browser/apps/browser_instance/browser_app_instance_tracker.h"
#include "chrome/browser/ash/app_restore/full_restore_service.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_time_limit_interface.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
#include "chrome/browser/ash/policy/dlp/dlp_files_controller_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/grit/browser_resources.h"
#include "chrome/grit/chrome_unscaled_resources.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/account_id/account_id.h"
#include "components/app_constants/constants.h"
#include "components/app_restore/full_restore_save_handler.h"
#include "components/app_restore/full_restore_utils.h"
#include "components/grit/components_resources.h"
#include "components/services/app_service/public/cpp/app_capability_access_cache_wrapper.h"
#include "components/services/app_service/public/cpp/app_registry_cache_wrapper.h"
#include "components/services/app_service/public/cpp/features.h"
#include "components/services/app_service/public/cpp/icon_effects.h"
#include "components/services/app_service/public/cpp/package_id.h"
#include "components/services/app_service/public/cpp/preferred_apps_impl.h"
#include "components/services/app_service/public/cpp/preferred_apps_list.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/grit/extensions_browser_resources.h"
namespace {
constexpr int32_t kAppDialogIconSize = 48;
} // namespace
namespace apps {
AppServiceProxyAsh::OnAppsRequest::OnAppsRequest(std::vector<AppPtr> deltas,
AppType app_type,
bool should_notify_initialized)
: deltas_(std::move(deltas)),
app_type_(app_type),
should_notify_initialized_(should_notify_initialized) {}
AppServiceProxyAsh::OnAppsRequest::~OnAppsRequest() = default;
AppServiceProxyAsh::AppServiceProxyAsh(Profile* profile)
: AppServiceProxyBase(profile),
icon_reader_(profile),
icon_writer_(profile) {
if (crosapi::browser_util::IsLacrosEnabled()) {
browser_app_instance_tracker_ =
std::make_unique<apps::BrowserAppInstanceTracker>(profile_,
app_registry_cache_);
browser_app_instance_registry_ =
std::make_unique<apps::BrowserAppInstanceRegistry>(
*browser_app_instance_tracker_);
browser_app_instance_app_service_updater_ =
std::make_unique<apps::InstanceRegistryUpdater>(
*browser_app_instance_registry_, instance_registry_);
}
instance_registry_observer_.Observe(&instance_registry_);
}
AppServiceProxyAsh::~AppServiceProxyAsh() {
if (IsValidProfile()) {
::full_restore::FullRestoreSaveHandler::GetInstance()->SetAppRegistryCache(
profile_->GetPath(), nullptr);
}
AppCapabilityAccessCacheWrapper::Get().RemoveAppCapabilityAccessCache(
&app_capability_access_cache_);
AppRegistryCacheWrapper::Get().RemoveAppRegistryCache(&app_registry_cache_);
}
bool AppServiceProxyAsh::IsValidProfile() {
if (!profile_) {
return false;
}
// Use OTR profile for Guest Session.
if (profile_->IsGuestSession()) {
return profile_->IsOffTheRecord();
}
return AppServiceProxyBase::IsValidProfile();
}
void AppServiceProxyAsh::Initialize() {
if (!IsValidProfile()) {
return;
}
const user_manager::User* user =
ash::ProfileHelper::Get()->GetUserByProfile(profile_);
if (user) {
const AccountId& account_id = user->GetAccountId();
app_registry_cache_.SetAccountId(account_id);
AppRegistryCacheWrapper::Get().AddAppRegistryCache(account_id,
&app_registry_cache_);
app_capability_access_cache_.SetAccountId(account_id);
AppCapabilityAccessCacheWrapper::Get().AddAppCapabilityAccessCache(
account_id, &app_capability_access_cache_);
}
if (user == user_manager::UserManager::Get()->GetPrimaryUser()) {
::full_restore::SetPrimaryProfilePath(profile_->GetPath());
// In Multi-Profile mode, only set for the primary user. For other users,
// active profile path is set when switch users.
::full_restore::SetActiveProfilePath(profile_->GetPath());
}
::full_restore::FullRestoreSaveHandler::GetInstance()->SetAppRegistryCache(
profile_->GetPath(), &app_registry_cache_);
AppServiceProxyBase::Initialize();
auto* cache = &AppRegistryCache();
if (!app_registry_cache_observer_.IsObservingSource(cache)) {
app_registry_cache_observer_.Reset();
app_registry_cache_observer_.Observe(cache);
}
publisher_host_ = std::make_unique<PublisherHost>(this);
if (crosapi::browser_util::IsLacrosEnabled() &&
ash::ProfileHelper::IsPrimaryProfile(profile_)) {
auto* browser_manager = crosapi::BrowserManager::Get();
// In unit tests, it is possible that the browser manager is not created.
if (browser_manager) {
keep_alive_ = browser_manager->KeepAlive(
crosapi::BrowserManager::Feature::kAppService);
}
}
if (!profile_->AsTestingProfile() &&
(!::ash::features::IsShimlessRMA3pDiagnosticsEnabled() ||
!::ash::IsShimlessRmaAppBrowserContext(profile_))) {
app_platform_metrics_service_ =
std::make_unique<apps::AppPlatformMetricsService>(profile_);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&AppServiceProxyAsh::InitAppPlatformMetrics,
weak_ptr_factory_.GetWeakPtr()));
}
if (ash::features::ArePromiseIconsEnabled()) {
promise_app_service_ = std::make_unique<apps::PromiseAppService>(
profile_, app_registry_cache_);
}
app_install_service_ = AppInstallService::Create(*profile_);
}
apps::InstanceRegistry& AppServiceProxyAsh::InstanceRegistry() {
return instance_registry_;
}
apps::AppPlatformMetrics* AppServiceProxyAsh::AppPlatformMetrics() {
return app_platform_metrics_service_
? app_platform_metrics_service_->AppPlatformMetrics()
: nullptr;
}
apps::AppPlatformMetricsService*
AppServiceProxyAsh::AppPlatformMetricsService() {
return app_platform_metrics_service_ ? app_platform_metrics_service_.get()
: nullptr;
}
apps::BrowserAppInstanceTracker*
AppServiceProxyAsh::BrowserAppInstanceTracker() {
return browser_app_instance_tracker_.get();
}
apps::BrowserAppInstanceRegistry*
AppServiceProxyAsh::BrowserAppInstanceRegistry() {
return browser_app_instance_registry_.get();
}
apps::StandaloneBrowserApps* AppServiceProxyAsh::StandaloneBrowserApps() {
return publisher_host_ ? publisher_host_->StandaloneBrowserApps() : nullptr;
}
apps::AppInstallService& AppServiceProxyAsh::AppInstallService() {
return *app_install_service_;
}
void AppServiceProxyAsh::RegisterCrosApiSubScriber(
SubscriberCrosapi* subscriber) {
crosapi_subscriber_ = subscriber;
crosapi_subscriber_->InitializeApps();
// Initialise the Preferred Apps in the `crosapi_subscriber_` on register.
if (preferred_apps_impl_ &&
preferred_apps_impl_->preferred_apps_list().IsInitialized()) {
crosapi_subscriber_->InitializePreferredApps(
preferred_apps_impl_->preferred_apps_list().GetValue());
}
}
void AppServiceProxyAsh::SetPublisherUnavailable(AppType app_type) {
UnregisterPublisher(app_type);
// Remove all apps for `app_type` in AppRegistryCache and AppStorage. Related
// launch requests, icon spinners will be removed too in OnAppUpdate when apps
// are removed.
OnApps(std::vector<AppPtr>{}, app_type, /*should_notify_initialized=*/true);
}
void AppServiceProxyAsh::OnApps(std::vector<AppPtr> deltas,
AppType app_type,
bool should_notify_initialized) {
// Delete app icon folders for uninstalled apps or the icon updated app.
std::vector<std::string> app_ids;
for (const auto& delta : deltas) {
if ((delta->readiness != Readiness::kUnknown &&
!apps_util::IsInstalled(delta->readiness)) ||
(delta->icon_key.has_value() && delta->icon_key->HasUpdatedVersion())) {
// If there's already a deletion in progress, skip the deletion request.
// For app types, not using AppService icon cache, e.g. remote apps, skip
// the deletion request.
if (base::Contains(pending_read_icon_requests_, delta->app_id) ||
!ShouldReadIcons(app_type)) {
continue;
}
app_ids.push_back(delta->app_id);
pending_read_icon_requests_[delta->app_id] =
std::vector<base::OnceCallback<void()>>();
}
}
if (!app_ids.empty()) {
ScheduleIconFoldersDeletion(
profile_->GetPath(), app_ids,
base::BindOnce(&AppServiceProxyAsh::PostIconFoldersDeletion,
weak_ptr_factory_.GetWeakPtr(), app_ids));
}
// Close uninstall dialogs for any uninstalled apps.
for (const AppPtr& delta : deltas) {
if (delta->readiness != Readiness::kUnknown &&
!apps_util::IsInstalled(delta->readiness) &&
base::Contains(uninstall_dialogs_, delta->app_id)) {
uninstall_dialogs_[delta->app_id]->CloseDialog();
}
}
if (crosapi_subscriber_) {
crosapi_subscriber_->OnApps(deltas, app_type, should_notify_initialized);
}
AppServiceProxyBase::OnApps(std::move(deltas), app_type,
should_notify_initialized);
}
void AppServiceProxyAsh::Uninstall(const std::string& app_id,
UninstallSource uninstall_source,
gfx::NativeWindow parent_window) {
UninstallImpl(app_id, uninstall_source, parent_window, base::DoNothing());
}
void AppServiceProxyAsh::PauseApps(
const std::map<std::string, PauseData>& pause_data) {
for (auto& data : pause_data) {
auto app_type = app_registry_cache_.GetAppType(data.first);
if (app_type == AppType::kUnknown) {
continue;
}
app_registry_cache_.ForOneApp(
data.first, [this](const apps::AppUpdate& update) {
if (!update.Paused().value_or(false)) {
pending_pause_requests_.MaybeAddApp(update.AppId());
}
});
// The app pause dialog can't be loaded for unit tests.
if (!data.second.should_show_pause_dialog || is_using_testing_profile_) {
auto* publisher = GetPublisher(app_type);
if (publisher) {
publisher->PauseApp(data.first);
}
continue;
}
app_registry_cache_.ForOneApp(
data.first, [this, &data](const apps::AppUpdate& update) {
LoadIconForDialog(
update,
base::BindOnce(&AppServiceProxyAsh::OnLoadIconForPauseDialog,
weak_ptr_factory_.GetWeakPtr(), update.AppType(),
update.AppId(), update.Name(), data.second));
});
}
}
void AppServiceProxyAsh::UnpauseApps(const std::set<std::string>& app_ids) {
for (auto& app_id : app_ids) {
auto app_type = app_registry_cache_.GetAppType(app_id);
if (app_type == AppType::kUnknown) {
continue;
}
pending_pause_requests_.MaybeRemoveApp(app_id);
auto* publisher = GetPublisher(app_type);
if (publisher) {
publisher->UnpauseApp(app_id);
}
}
}
void AppServiceProxyAsh::BlockApps(const std::set<std::string>& app_ids,
bool show_block_dialog) {
for (auto& app_id : app_ids) {
auto app_type = app_registry_cache_.GetAppType(app_id);
if (app_type == AppType::kUnknown) {
continue;
}
auto* publisher = GetPublisher(app_type);
if (publisher) {
publisher->BlockApp(app_id);
}
if (show_block_dialog) {
app_registry_cache_.ForOneApp(app_id, [](const apps::AppUpdate& update) {
AppServiceProxyAsh::CreateLocalBlockDialog(update.Name());
});
}
}
}
void AppServiceProxyAsh::UnblockApps(const std::set<std::string>& app_ids) {
for (auto& app_id : app_ids) {
auto app_type = app_registry_cache_.GetAppType(app_id);
if (app_type == AppType::kUnknown) {
continue;
}
pending_pause_requests_.MaybeRemoveApp(app_id);
auto* publisher = GetPublisher(app_type);
if (publisher) {
publisher->UnblockApp(app_id);
}
}
}
void AppServiceProxyAsh::SetResizeLocked(const std::string& app_id,
bool locked) {
auto* publisher = GetPublisher(app_registry_cache_.GetAppType(app_id));
if (publisher) {
publisher->SetResizeLocked(app_id, locked);
}
}
void AppServiceProxyAsh::SetArcIsRegistered() {
if (arc_is_registered_) {
return;
}
arc_is_registered_ = true;
if (publisher_host_) {
publisher_host_->SetArcIsRegistered();
}
}
void AppServiceProxyAsh::LaunchAppWithIntent(const std::string& app_id,
int32_t event_flags,
IntentPtr intent,
LaunchSource launch_source,
WindowInfoPtr window_info,
LaunchCallback callback) {
apps::IntentPtr intent_copy = intent->Clone();
base::OnceCallback launch_callback = base::BindOnce(
&AppServiceProxyAsh::LaunchAppWithIntentIfAllowed,
weak_ptr_factory_.GetWeakPtr(), app_id, event_flags, std::move(intent),
std::move(launch_source), std::move(window_info), std::move(callback));
policy::DlpFilesControllerAsh* files_controller =
policy::DlpFilesControllerAsh::GetForPrimaryProfile();
if (files_controller) {
auto app_found = app_registry_cache_.ForOneApp(
app_id, [&files_controller, &intent_copy,
&launch_callback](const apps::AppUpdate& update) {
files_controller->CheckIfLaunchAllowed(update, std::move(intent_copy),
std::move(launch_callback));
});
if (!app_found) {
std::move(launch_callback).Run(/*is_allowed=*/true);
}
} else {
std::move(launch_callback).Run(/*is_allowed=*/true);
}
}
base::WeakPtr<AppServiceProxyAsh> AppServiceProxyAsh::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void AppServiceProxyAsh::ReInitializeCrostiniForTesting() {
if (publisher_host_) {
publisher_host_->ReInitializeCrostiniForTesting(this); // IN-TEST
}
}
void AppServiceProxyAsh::SetDialogCreatedCallbackForTesting(
base::OnceClosure callback) {
dialog_created_callback_ = std::move(callback);
}
void AppServiceProxyAsh::UninstallForTesting(
const std::string& app_id,
gfx::NativeWindow parent_window,
OnUninstallForTestingCallback callback) {
UninstallImpl(app_id, UninstallSource::kUnknown, parent_window,
std::move(callback));
}
void AppServiceProxyAsh::SetAppPlatformMetricsServiceForTesting(
std::unique_ptr<apps::AppPlatformMetricsService>
app_platform_metrics_service) {
app_platform_metrics_service_ = std::move(app_platform_metrics_service);
}
void AppServiceProxyAsh::RegisterPublishersForTesting() {
if (publisher_host_) {
publisher_host_->RegisterPublishersForTesting();
}
}
void AppServiceProxyAsh::ReadIconsForTesting(AppType app_type,
const std::string& app_id,
int32_t size_in_dip,
const IconKey& icon_key,
IconType icon_type,
LoadIconCallback callback) {
ReadIcons(app_type, app_id, size_in_dip, icon_key.Clone(), icon_type,
std::move(callback));
}
apps::PromiseAppRegistryCache* AppServiceProxyAsh::PromiseAppRegistryCache() {
if (!promise_app_service_) {
return nullptr;
}
return promise_app_service_->PromiseAppRegistryCache();
}
apps::PromiseAppService* AppServiceProxyAsh::PromiseAppService() {
if (!promise_app_service_) {
return nullptr;
}
return promise_app_service_.get();
}
void AppServiceProxyAsh::OnPromiseApp(PromiseAppPtr delta) {
if (!promise_app_service_) {
return;
}
promise_app_service_->OnPromiseApp(std::move(delta));
}
void AppServiceProxyAsh::LoadPromiseIcon(const PackageId& package_id,
int32_t size_hint_in_dip,
IconEffects icon_effects,
apps::LoadIconCallback callback) {
PromiseAppService()->LoadIcon(package_id, size_hint_in_dip, icon_effects,
std::move(callback));
}
void AppServiceProxyAsh::LoadDefaultIcon(AppType app_type,
int32_t size_in_dip,
IconEffects icon_effects,
IconType icon_type,
LoadIconCallback callback) {
auto* publisher = GetPublisher(app_type);
int default_icon_resource_id = IDR_APP_DEFAULT_ICON;
if (publisher) {
default_icon_resource_id = publisher->DefaultIconResourceId();
}
LoadIconFromResource(
profile_, std::nullopt, icon_type, size_in_dip, default_icon_resource_id,
/*is_placeholder_icon=*/false, icon_effects, std::move(callback));
}
void AppServiceProxyAsh::SetAppLocale(const std::string& app_id,
const std::string& locale_tag) {
auto* publisher = GetPublisher(app_registry_cache_.GetAppType(app_id));
if (publisher) {
publisher->SetAppLocale(app_id, locale_tag);
}
}
void AppServiceProxyAsh::Shutdown() {
crosapi_subscriber_ = nullptr;
app_platform_metrics_service_.reset();
uninstall_dialogs_.clear();
if (publisher_host_) {
publisher_host_->Shutdown();
}
}
void AppServiceProxyAsh::UninstallImpl(const std::string& app_id,
UninstallSource uninstall_source,
gfx::NativeWindow parent_window,
OnUninstallForTestingCallback callback) {
// If the dialog exists for the app id, we bring the dialog to the front
auto it = uninstall_dialogs_.find(app_id);
if (it != uninstall_dialogs_.end()) {
if (it->second->GetWidget()) {
it->second->GetWidget()->Show();
}
if (!callback.is_null()) {
std::move(callback).Run(false);
}
return;
}
app_registry_cache_.ForOneApp(app_id, [this, uninstall_source, parent_window,
&callback](
const apps::AppUpdate& update) {
auto icon_key = update.IconKey();
DCHECK(icon_key.has_value());
auto app_type = update.AppType();
auto uninstall_dialog_ptr = std::make_unique<UninstallDialog>(
profile_, app_type, update.AppId(), update.Name(), parent_window,
base::BindOnce(&AppServiceProxyAsh::OnUninstallDialogClosed,
weak_ptr_factory_.GetWeakPtr(), app_type, update.AppId(),
uninstall_source));
UninstallDialog* uninstall_dialog = uninstall_dialog_ptr.get();
uninstall_dialog_ptr->SetDialogCreatedCallbackForTesting(
std::move(callback));
uninstall_dialogs_.emplace(update.AppId(), std::move(uninstall_dialog_ptr));
uninstall_dialog->PrepareToShow(std::move(icon_key.value()),
this->app_icon_loader(),
kAppDialogIconSize);
});
}
void AppServiceProxyAsh::OnUninstallDialogClosed(
apps::AppType app_type,
const std::string& app_id,
UninstallSource uninstall_source,
bool uninstall,
bool clear_site_data,
bool report_abuse,
UninstallDialog* uninstall_dialog) {
if (uninstall) {
auto* publisher = GetPublisher(app_type);
DCHECK(publisher);
publisher->Uninstall(app_id, uninstall_source, clear_site_data,
report_abuse);
PerformPostUninstallTasks(app_type, app_id, uninstall_source);
}
DCHECK(uninstall_dialog);
auto it = uninstall_dialogs_.find(app_id);
CHECK(it != uninstall_dialogs_.end(), base::NotFatalUntil::M130);
uninstall_dialogs_.erase(it);
}
void AppServiceProxyAsh::InitializePreferredAppsForAllSubscribers() {
AppServiceProxyBase::InitializePreferredAppsForAllSubscribers();
if (crosapi_subscriber_ && preferred_apps_impl_) {
crosapi_subscriber_->InitializePreferredApps(
preferred_apps_impl_->preferred_apps_list().GetValue());
}
}
void AppServiceProxyAsh::OnPreferredAppsChanged(
PreferredAppChangesPtr changes) {
if (!crosapi_subscriber_) {
AppServiceProxyBase::OnPreferredAppsChanged(std::move(changes));
return;
}
DCHECK(changes);
AppServiceProxyBase::OnPreferredAppsChanged(changes->Clone());
crosapi_subscriber_->OnPreferredAppsChanged(std::move(changes));
}
bool AppServiceProxyAsh::MaybeShowLaunchPreventionDialog(
const apps::AppUpdate& update) {
if (update.AppId() == app_constants::kChromeAppId) {
return false;
}
// Return true and load the icon for the app block dialog when the app
// is blocked by policy.
if (update.Readiness() == apps::Readiness::kDisabledByPolicy) {
LoadIconForDialog(
update, base::BindOnce(&AppServiceProxyAsh::OnLoadIconForBlockDialog,
weak_ptr_factory_.GetWeakPtr(), update.Name()));
return true;
}
// Return true and load the icon for the app local block dialog when the app
// is blocked by local settings.
if (update.Readiness() == apps::Readiness::kDisabledByLocalSettings) {
AppServiceProxyAsh::CreateLocalBlockDialog(update.Name());
// For browser tests, call the dialog created callback to stop the run loop.
if (!dialog_created_callback_.is_null()) {
// Post task to the UI thread so local block dialog matches the
// asynchronicity of the other dialogs.
content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
->PostTask(FROM_HERE, std::move(dialog_created_callback_));
}
return true;
}
// Return true, and load the icon for the app pause dialog when the app
// is paused.
if (update.Paused().value_or(false) ||
pending_pause_requests_.IsPaused(update.AppId())) {
ash::app_time::AppTimeLimitInterface* app_limit =
ash::app_time::AppTimeLimitInterface::Get(profile_);
DCHECK(app_limit);
auto time_limit =
app_limit->GetTimeLimitForApp(update.AppId(), update.AppType());
if (!time_limit.has_value()) {
NOTREACHED_IN_MIGRATION();
return true;
}
PauseData pause_data;
pause_data.hours = time_limit.value().InHours();
pause_data.minutes = time_limit.value().InMinutes() % 60;
LoadIconForDialog(
update, base::BindOnce(&AppServiceProxyAsh::OnLoadIconForPauseDialog,
weak_ptr_factory_.GetWeakPtr(), update.AppType(),
update.AppId(), update.Name(), pause_data));
return true;
}
// The app is not prevented from launching and we didn't show any dialog.
return false;
}
void AppServiceProxyAsh::OnLaunched(LaunchCallback callback,
LaunchResult&& launch_result) {
base::RepeatingCallback<bool(void)> ready_to_run_callback =
base::BindRepeating(&AppServiceProxyAsh::CanRunLaunchCallback,
base::Unretained(this), launch_result.instance_ids);
base::OnceClosure launch_callback =
base::BindOnce(std::move(callback), std::move(launch_result));
if (ready_to_run_callback.Run()) {
std::move(launch_callback).Run();
} else {
callback_list_.emplace_back(
std::make_pair(ready_to_run_callback, std::move(launch_callback)));
}
}
bool AppServiceProxyAsh::ShouldExcludeBrowserTabApps(
bool exclude_browser_tab_apps,
WindowMode window_mode) {
return exclude_browser_tab_apps && window_mode == WindowMode::kBrowser;
}
void AppServiceProxyAsh::LoadIconForDialog(const apps::AppUpdate& update,
apps::LoadIconCallback callback) {
constexpr bool kAllowPlaceholderIcon = false;
auto icon_type = IconType::kStandard;
// For browser tests, load the app icon, because there is no family link
// logo for browser tests.
//
// For non_child profile, load the app icon, because the app is blocked by
// admin.
if (!dialog_created_callback_.is_null() || !profile_->IsChild()) {
LoadIcon(update.AppId(), icon_type, kAppDialogIconSize,
kAllowPlaceholderIcon, std::move(callback));
return;
}
// Load the family link kite logo icon for the app pause dialog or the app
// block dialog for the child profile.
LoadIconFromResource(/*profile=*/nullptr, /*app_id=*/std::nullopt, icon_type,
kAppDialogIconSize, IDR_SUPERVISED_USER_ICON,
kAllowPlaceholderIcon, IconEffects::kNone,
std::move(callback));
}
void AppServiceProxyAsh::OnLoadIconForBlockDialog(const std::string& app_name,
IconValuePtr icon_value) {
if (icon_value->icon_type != IconType::kStandard) {
return;
}
AppServiceProxyAsh::CreateBlockDialog(app_name, icon_value->uncompressed,
profile_);
// For browser tests, call the dialog created callback to stop the run loop.
if (!dialog_created_callback_.is_null()) {
std::move(dialog_created_callback_).Run();
}
}
void AppServiceProxyAsh::OnLoadIconForPauseDialog(apps::AppType app_type,
const std::string& app_id,
const std::string& app_name,
const PauseData& pause_data,
IconValuePtr icon_value) {
if (icon_value->icon_type != IconType::kStandard) {
OnPauseDialogClosed(app_type, app_id);
return;
}
AppServiceProxyAsh::CreatePauseDialog(
app_type, app_name, icon_value->uncompressed, pause_data,
base::BindOnce(&AppServiceProxyAsh::OnPauseDialogClosed,
weak_ptr_factory_.GetWeakPtr(), app_type, app_id));
// For browser tests, call the dialog created callback to stop the run loop.
if (!dialog_created_callback_.is_null()) {
std::move(dialog_created_callback_).Run();
}
}
void AppServiceProxyAsh::OnPauseDialogClosed(apps::AppType app_type,
const std::string& app_id) {
bool should_pause_app = pending_pause_requests_.IsPaused(app_id);
if (!should_pause_app) {
app_registry_cache_.ForOneApp(
app_id, [&should_pause_app](const apps::AppUpdate& update) {
if (update.Paused().value_or(false)) {
should_pause_app = true;
}
});
}
if (should_pause_app) {
auto* publisher = GetPublisher(app_type);
if (publisher) {
publisher->PauseApp(app_id);
}
}
}
void AppServiceProxyAsh::OnAppUpdate(const apps::AppUpdate& update) {
if ((update.PausedChanged() && update.Paused().value_or(false)) ||
(update.ReadinessChanged() &&
!apps_util::IsInstalled(update.Readiness()))) {
pending_pause_requests_.MaybeRemoveApp(update.AppId());
}
}
void AppServiceProxyAsh::OnAppRegistryCacheWillBeDestroyed(
apps::AppRegistryCache* cache) {
app_registry_cache_observer_.Reset();
}
void AppServiceProxyAsh::RecordAppPlatformMetrics(
Profile* profile,
const apps::AppUpdate& update,
apps::LaunchSource launch_source,
apps::LaunchContainer container) {
RecordAppLaunchMetrics(profile, update.AppType(), update.AppId(),
launch_source, container);
}
void AppServiceProxyAsh::InitAppPlatformMetrics() {
if (app_platform_metrics_service_) {
app_platform_metrics_service_->Start(
app_registry_cache_, instance_registry_, app_capability_access_cache_);
}
}
void AppServiceProxyAsh::PerformPostUninstallTasks(
apps::AppType app_type,
const std::string& app_id,
UninstallSource uninstall_source) {
if (app_platform_metrics_service_ &&
app_platform_metrics_service_->AppPlatformMetrics()) {
app_platform_metrics_service_->AppPlatformMetrics()->RecordAppUninstallUkm(
app_type, app_id, uninstall_source);
}
}
void AppServiceProxyAsh::PerformPostLaunchTasks(
apps::LaunchSource launch_source) {
if (apps_util::IsHumanLaunch(launch_source)) {
ash::full_restore::FullRestoreService::MaybeCloseNotification(profile_);
}
}
void AppServiceProxyAsh::OnInstanceUpdate(const apps::InstanceUpdate& update) {
if (!update.IsCreation()) {
return;
}
callback_list_.remove_if([](std::pair<base::RepeatingCallback<bool(void)>,
base::OnceClosure>& callbacks) {
if (callbacks.first.Run()) {
std::move(callbacks.second).Run();
return true;
}
return false;
});
}
void AppServiceProxyAsh::OnInstanceRegistryWillBeDestroyed(
apps::InstanceRegistry* cache) {
instance_registry_observer_.Reset();
}
bool AppServiceProxyAsh::CanRunLaunchCallback(
const std::vector<base::UnguessableToken>& instance_ids) {
for (const base::UnguessableToken& instance_id : instance_ids) {
bool exists = false;
InstanceRegistry().ForOneInstance(
instance_id,
[&exists](const apps::InstanceUpdate& update) { exists = true; });
if (!exists) {
return false;
}
}
return true;
}
void AppServiceProxyAsh::LaunchAppWithIntentIfAllowed(
const std::string& app_id,
int32_t event_flags,
IntentPtr intent,
LaunchSource launch_source,
WindowInfoPtr window_info,
LaunchCallback callback,
bool is_allowed) {
if (!is_allowed) {
std::move(callback).Run(LaunchResult(State::kFailed));
return;
}
AppServiceProxyBase::LaunchAppWithIntent(
app_id, event_flags, std::move(intent), std::move(launch_source),
std::move(window_info), std::move(callback));
}
bool AppServiceProxyAsh::ShouldReadIcons(AppType app_type) {
// Exclude the remote apps, because remote apps regenerate app id for each
// user login session. So we can't save the remote app icon image files in the
// app id directories.
return app_type != AppType::kRemote;
}
void AppServiceProxyAsh::ReadIcons(AppType app_type,
const std::string& app_id,
int32_t size_in_dip,
std::unique_ptr<IconKey> icon_key,
IconType icon_type,
LoadIconCallback callback) {
auto it = pending_read_icon_requests_.find(app_id);
if (it != pending_read_icon_requests_.end()) {
// The icon folder is being deleted, so add the `ReadIcons` request to
// `pending_read_icon_requests_` to wait for the deletion.
it->second.push_back(base::BindOnce(
&AppServiceProxyAsh::ReadIcons, weak_ptr_factory_.GetWeakPtr(),
app_type, app_id, size_in_dip, std::move(icon_key), icon_type,
std::move(callback)));
return;
}
icon_reader_.ReadIcons(
app_id, size_in_dip, *icon_key, icon_type,
base::BindOnce(&AppServiceProxyAsh::OnIconRead,
weak_ptr_factory_.GetWeakPtr(), app_type, app_id,
size_in_dip,
static_cast<IconEffects>(icon_key->icon_effects),
icon_type, std::move(callback)));
}
void AppServiceProxyAsh::OnIconRead(AppType app_type,
const std::string& app_id,
int32_t size_in_dip,
IconEffects icon_effects,
IconType icon_type,
LoadIconCallback callback,
IconValuePtr iv) {
if (!iv || (iv->uncompressed.isNull() && iv->compressed.empty())) {
auto* publisher = GetPublisher(app_type);
if (!publisher) {
LOG(WARNING) << "No publisher for requested icon";
LoadIconFromResource(
profile_, app_id, icon_type, size_in_dip, IDR_APP_DEFAULT_ICON,
/*is_placeholder_icon=*/false, icon_effects, std::move(callback));
return;
}
icon_writer_.InstallIcon(
publisher, app_id, size_in_dip,
base::BindOnce(&AppServiceProxyAsh::OnIconInstalled,
weak_ptr_factory_.GetWeakPtr(), app_type, app_id,
size_in_dip, icon_effects, icon_type,
publisher->DefaultIconResourceId(),
std::move(callback)));
return;
}
std::move(callback).Run(std::move(iv));
}
void AppServiceProxyAsh::OnIconInstalled(AppType app_type,
const std::string& app_id,
int32_t size_in_dip,
IconEffects icon_effects,
IconType icon_type,
int default_icon_resource_id,
LoadIconCallback callback,
bool install_success) {
if (!install_success) {
LoadIconFromResource(
profile_, app_id, icon_type, size_in_dip, default_icon_resource_id,
/*is_placeholder_icon=*/false, icon_effects, std::move(callback));
return;
}
IconKey icon_key;
icon_key.icon_effects = icon_effects;
icon_reader_.ReadIcons(app_id, size_in_dip, icon_key, icon_type,
std::move(callback));
}
void AppServiceProxyAsh::PostIconFoldersDeletion(
const std::vector<std::string>& ids) {
for (const auto& id : ids) {
auto it = pending_read_icon_requests_.find(id);
if (it == pending_read_icon_requests_.end()) {
continue;
}
// The saved `ReadIcons` requests in `pending_read_icon_requests_` are run
// to load the icon for `app_id`.
std::vector<base::OnceCallback<void()>> callbacks = std::move(it->second);
pending_read_icon_requests_.erase(it);
for (auto& callback : callbacks) {
std::move(callback).Run();
}
}
}
IntentLaunchInfo AppServiceProxyAsh::CreateIntentLaunchInfo(
const apps::IntentPtr& intent,
const apps::IntentFilterPtr& filter,
const apps::AppUpdate& update) {
IntentLaunchInfo entry =
AppServiceProxyBase::CreateIntentLaunchInfo(intent, filter, update);
if (policy::DlpFilesControllerAsh* files_controller =
policy::DlpFilesControllerAsh::GetForPrimaryProfile()) {
entry.is_dlp_blocked = files_controller->IsLaunchBlocked(update, intent);
}
return entry;
}
} // namespace apps