// Copyright 2021 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/subscriber_crosapi.h"
#include <optional>
#include <utility>
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/types/optional_util.h"
#include "base/unguessable_token.h"
#include "chrome/browser/apps/app_service/app_install/app_install_service.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/intent_util.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/apps/app_service/metrics/app_service_metrics.h"
#include "chrome/browser/apps/browser_instance/browser_app_instance_registry.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/webui/ash/settings/app_management/app_management_uma.h"
#include "chromeos/crosapi/mojom/app_service_types.mojom.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "ui/gfx/native_widget_types.h"
namespace apps {
namespace {
bool Accepts(AppType app_type) {
return app_type == AppType::kUnknown || app_type == AppType::kArc ||
app_type == AppType::kWeb || app_type == AppType::kSystemWeb ||
app_type == AppType::kStandaloneBrowserChromeApp;
}
std::optional<AppInstallSurface> AppInstallSurfaceFromCrosapi(
crosapi::mojom::InstallAppParams::Surface surface) {
using Surface = crosapi::mojom::InstallAppParams::Surface;
switch (surface) {
case Surface::kUnknown:
return std::nullopt;
case Surface::kAppInstallUriUnknown:
return AppInstallSurface::kAppInstallUriUnknown;
case Surface::kAppInstallUriShowoff:
return AppInstallSurface::kAppInstallUriShowoff;
case Surface::kAppInstallUriMall:
return AppInstallSurface::kAppInstallUriMall;
case Surface::kAppInstallUriGetit:
return AppInstallSurface::kAppInstallUriGetit;
case Surface::kAppInstallUriLauncher:
return AppInstallSurface::kAppInstallUriLauncher;
case Surface::kAppInstallUriPeripherals:
return AppInstallSurface::kAppInstallUriPeripherals;
}
}
} // namespace
class AppUpdate;
SubscriberCrosapi::SubscriberCrosapi(Profile* profile)
: profile_(profile),
proxy_(apps::AppServiceProxyFactory::GetForProfile(profile)) {}
SubscriberCrosapi::~SubscriberCrosapi() = default;
void SubscriberCrosapi::RegisterAppServiceProxyFromCrosapi(
mojo::PendingReceiver<crosapi::mojom::AppServiceProxy> receiver) {
// At the moment the app service subscriber will only accept one client
// connect to ash chrome. Any extra clients will be ignored.
// TODO(crbug.com/40167449): Support SxS lacros.
if (crosapi_receiver_.is_bound()) {
return;
}
crosapi_receiver_.Bind(std::move(receiver));
crosapi_receiver_.set_disconnect_handler(base::BindOnce(
&SubscriberCrosapi::OnCrosapiDisconnected, base::Unretained(this)));
}
void SubscriberCrosapi::OnApps(const std::vector<AppPtr>& deltas,
AppType app_type,
bool should_notify_initialized) {
if (!subscriber_.is_bound()) {
return;
}
if (!Accepts(app_type)) {
return;
}
std::vector<AppPtr> apps;
for (const auto& delta : deltas) {
if (Accepts(delta->app_type)) {
apps.push_back(delta->Clone());
}
}
subscriber_->OnApps(std::move(apps), app_type, should_notify_initialized);
}
void SubscriberCrosapi::InitializeApps() {
// For each app type that has already been initialized, republish their apps
// to Lacros as initialized. App types that are yet to initialize will
// initialize via OnApps() in the usual way.
// Sort apps by app type.
std::vector<AppPtr> all_apps = proxy_->AppRegistryCache().GetAllApps();
base::flat_map<AppType, std::vector<AppPtr>> app_type_apps;
for (AppPtr& app : all_apps) {
if (Accepts(app->app_type)) {
// `update_version` holds an int value when get from AppRegistryCache.
// However, at the Lacros side, AppServiceProxyLacros calls OnApps to
// publish the app as `delta` to `app_registry_cache_` directly.
// `icon_key`'s `update_version` should never hold a int32_t as `delta`
// when calling OnApps to publish apps, and there is a bool checking for
// `update_version` of `delta`'s `icon_key` in AppUpdate::Merge at the
// Lacros side. So set `update_version` as false to prevent the crash from
// the bool checking when merging to AppRegistryCache's `states_` at the
// Lacros side .
app->icon_key->update_version = false;
app_type_apps[app->app_type].push_back(std::move(app));
}
}
for (AppType app_type : proxy_->AppRegistryCache().InitializedAppTypes()) {
if (Accepts(app_type)) {
OnApps(std::move(app_type_apps[app_type]), app_type,
/*should_notify_initialized=*/true);
}
}
}
void SubscriberCrosapi::InitializePreferredApps(PreferredApps preferred_apps) {
if (subscriber_.is_bound()) {
subscriber_->InitializePreferredApps(std::move(preferred_apps));
}
}
void SubscriberCrosapi::OnPreferredAppsChanged(PreferredAppChangesPtr changes) {
if (!subscriber_.is_bound()) {
return;
}
subscriber_->OnPreferredAppsChanged(std::move(changes));
}
void SubscriberCrosapi::OnCrosapiDisconnected() {
crosapi_receiver_.reset();
subscriber_.reset();
}
void SubscriberCrosapi::RegisterAppServiceSubscriber(
mojo::PendingRemote<crosapi::mojom::AppServiceSubscriber> subscriber) {
// At the moment the app service subscriber will only accept one client
// connect to ash chrome. Any extra clients will be ignored.
// TODO(crbug.com/40167449): Support SxS lacros.
if (subscriber_.is_bound()) {
return;
}
subscriber_.Bind(std::move(subscriber));
subscriber_.set_disconnect_handler(base::BindOnce(
&SubscriberCrosapi::OnSubscriberDisconnected, base::Unretained(this)));
proxy_->RegisterCrosApiSubScriber(this);
}
void SubscriberCrosapi::Launch(crosapi::mojom::LaunchParamsPtr launch_params) {
// TODO(crbug.com/40787924): Link up the return callback.
proxy_->LaunchAppWithParams(
ConvertCrosapiToLaunchParams(launch_params, profile_), base::DoNothing());
}
void SubscriberCrosapi::LaunchWithResult(
crosapi::mojom::LaunchParamsPtr launch_params,
LaunchWithResultCallback callback) {
auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile_);
proxy->LaunchAppWithParams(
ConvertCrosapiToLaunchParams(launch_params, profile_),
MojomLaunchResultToLaunchResultCallback(std::move(callback)));
}
void SubscriberCrosapi::LoadIcon(const std::string& app_id,
IconKeyPtr icon_key,
IconType icon_type,
int32_t size_hint_in_dip,
apps::LoadIconCallback callback) {
// Currently there is no usage of custom icon_key icon loading from
// Lacros. Drop the icon key from the interface here.
// TODO(crbug.com/40255408): Update the crosapi interface to match this.
proxy_->LoadIcon(app_id, icon_type, size_hint_in_dip,
/*allow_placeholder_icon=*/false, std::move(callback));
}
void SubscriberCrosapi::AddPreferredAppDeprecated(
const std::string& app_id,
crosapi::mojom::IntentPtr intent) {
NOTIMPLEMENTED();
}
void SubscriberCrosapi::ShowAppManagementPage(const std::string& app_id) {
if (!proxy_->AppRegistryCache().ForOneApp(app_id,
[](const apps::AppUpdate&) {})) {
LOG(WARNING) << "Unknown app: " << app_id;
return;
}
chrome::ShowAppManagementPage(
profile_, app_id, ash::settings::AppManagementEntryPoint::kPageInfoView);
}
void SubscriberCrosapi::SetSupportedLinksPreference(const std::string& app_id) {
proxy_->SetSupportedLinksPreference(app_id);
}
void SubscriberCrosapi::UninstallSilently(const std::string& app_id,
UninstallSource uninstall_source) {
proxy_->UninstallSilently(app_id, uninstall_source);
}
void SubscriberCrosapi::InstallAppWithFallback(
crosapi::mojom::InstallAppParamsPtr params,
InstallAppWithFallbackCallback callback) {
bool valid = [&] {
if (!params->serialized_package_id.has_value()) {
return false;
}
std::optional<AppInstallSurface> surface =
AppInstallSurfaceFromCrosapi(params->surface);
if (!surface.has_value()) {
return false;
}
std::optional<gfx::NativeWindow> window;
if (params->window_id.has_value()) {
window = proxy_->BrowserAppInstanceRegistry()->GetWindowByInstanceId(
params->window_id.value());
}
proxy_->AppInstallService().InstallAppWithFallback(
surface.value(), std::move(params->serialized_package_id).value(),
window,
base::BindOnce(std::move(callback),
crosapi::mojom::AppInstallResult::New()));
return true;
}();
if (!valid) {
std::move(callback).Run(crosapi::mojom::AppInstallResult::New());
}
}
void SubscriberCrosapi::OnSubscriberDisconnected() {
subscriber_.reset();
}
} // namespace apps