blob: 85d22c98ce8c288137b1093aeff589e17fb0836a [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/apps/app_service/plugin_vm_apps.h"
#include <utility>
#include <vector>
#include "ash/public/cpp/app_menu_constants.h"
#include "base/metrics/user_metrics.h"
#include "base/time/time.h"
#include "chrome/browser/apps/app_service/app_icon_factory.h"
#include "chrome/browser/apps/app_service/app_service_metrics.h"
#include "chrome/browser/apps/app_service/menu_util.h"
#include "chrome/browser/chromeos/guest_os/guest_os_registry_service.h"
#include "chrome/browser/chromeos/guest_os/guest_os_registry_service_factory.h"
#include "chrome/browser/chromeos/plugin_vm/plugin_vm_features.h"
#include "chrome/browser/chromeos/plugin_vm/plugin_vm_manager_factory.h"
#include "chrome/browser/chromeos/plugin_vm/plugin_vm_pref_names.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/app_management/app_management.mojom.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/chrome_unscaled_resources.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/mojom/types.mojom.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
struct PermissionInfo {
app_management::mojom::PluginVmPermissionType permission;
const char* pref_name;
};
struct PluginVmHandledPermissionInfo {
app_management::mojom::PluginVmPermissionType permission;
plugin_vm::PermissionType permission_type;
};
constexpr PermissionInfo permission_infos[] = {
{app_management::mojom::PluginVmPermissionType::PRINTING,
plugin_vm::prefs::kPluginVmPrintersAllowed},
};
constexpr PluginVmHandledPermissionInfo plugin_vm_handled_permission_infos[] = {
{app_management::mojom::PluginVmPermissionType::CAMERA,
plugin_vm::PermissionType::kCamera},
{app_management::mojom::PluginVmPermissionType::MICROPHONE,
plugin_vm::PermissionType::kMicrophone},
};
const char* PermissionToPrefName(
app_management::mojom::PluginVmPermissionType permission) {
for (const PermissionInfo& info : permission_infos) {
if (info.permission == permission) {
return info.pref_name;
}
}
return nullptr;
}
void SetAppAllowed(apps::mojom::App* app, bool allowed) {
app->readiness = allowed ? apps::mojom::Readiness::kReady
: apps::mojom::Readiness::kDisabledByPolicy;
const apps::mojom::OptionalBool opt_allowed =
allowed ? apps::mojom::OptionalBool::kTrue
: apps::mojom::OptionalBool::kFalse;
app->recommendable = opt_allowed;
app->searchable = opt_allowed;
app->show_in_launcher = opt_allowed;
app->show_in_shelf = opt_allowed;
app->show_in_search = opt_allowed;
}
void SetShowInAppManagement(apps::mojom::App* app, bool installed) {
// Show when installed, even if disabled by policy, to give users the choice
// to uninstall and free up space.
app->show_in_management = installed ? apps::mojom::OptionalBool::kTrue
: apps::mojom::OptionalBool::kFalse;
}
void PopulatePermissions(apps::mojom::App* app,
Profile* profile,
bool allowed) {
for (const PermissionInfo& info : permission_infos) {
auto permission = apps::mojom::Permission::New();
permission->permission_id = static_cast<uint32_t>(info.permission);
permission->value_type = apps::mojom::PermissionValueType::kBool;
permission->value =
static_cast<uint32_t>(profile->GetPrefs()->GetBoolean(info.pref_name));
permission->is_managed = false;
app->permissions.push_back(std::move(permission));
}
for (const PluginVmHandledPermissionInfo& info :
plugin_vm_handled_permission_infos) {
auto permission = apps::mojom::Permission::New();
permission->permission_id = static_cast<uint32_t>(info.permission);
permission->value_type = apps::mojom::PermissionValueType::kBool;
if (allowed) {
permission->value = static_cast<uint32_t>(
plugin_vm::PluginVmManagerFactory::GetForProfile(profile)
->GetPermission(info.permission_type));
} else {
permission->value = static_cast<uint32_t>(false);
}
permission->is_managed = false;
app->permissions.push_back(std::move(permission));
}
}
apps::mojom::AppPtr GetPluginVmApp(Profile* profile, bool allowed) {
apps::mojom::AppPtr app = apps::PublisherBase::MakeApp(
apps::mojom::AppType::kPluginVm, plugin_vm::kPluginVmShelfAppId,
allowed ? apps::mojom::Readiness::kReady
: apps::mojom::Readiness::kDisabledByPolicy,
l10n_util::GetStringUTF8(IDS_PLUGIN_VM_APP_NAME),
apps::mojom::InstallSource::kUser);
app->icon_key = apps::mojom::IconKey::New(
apps::mojom::IconKey::kDoesNotChangeOverTime,
IDR_LOGO_PLUGIN_VM_DEFAULT_192, apps::IconEffects::kNone);
SetShowInAppManagement(
app.get(), plugin_vm::PluginVmFeatures::Get()->IsConfigured(profile));
PopulatePermissions(app.get(), profile, allowed);
SetAppAllowed(app.get(), allowed);
return app;
}
} // namespace
namespace apps {
PluginVmApps::PluginVmApps(
const mojo::Remote<apps::mojom::AppService>& app_service,
Profile* profile)
: profile_(profile), registry_(nullptr), permissions_observer_(this) {
// Don't show anything for non-primary profiles. We can't use
// `PluginVmFeatures::Get()->IsAllowed()` here because we still let the user
// uninstall Plugin VM when it isn't allowed for some other reasons (e.g.
// policy).
if (!chromeos::ProfileHelper::IsPrimaryProfile(profile)) {
return;
}
registry_ = guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile_);
if (!registry_) {
return;
}
registry_->AddObserver(this);
PublisherBase::Initialize(app_service, apps::mojom::AppType::kPluginVm);
// Register for Plugin VM changes to policy and installed state, so that we
// can update the availability and status of the Plugin VM app. Unretained is
// safe as these are cleaned up upon destruction.
policy_subscription_ =
std::make_unique<plugin_vm::PluginVmPolicySubscription>(
profile_, base::BindRepeating(&PluginVmApps::OnPluginVmAllowedChanged,
base::Unretained(this)));
pref_registrar_.Init(profile_->GetPrefs());
pref_registrar_.Add(
plugin_vm::prefs::kPluginVmImageExists,
base::BindRepeating(&PluginVmApps::OnPluginVmConfiguredChanged,
base::Unretained(this)));
is_allowed_ = plugin_vm::PluginVmFeatures::Get()->IsAllowed(profile_);
if (is_allowed_) {
permissions_observer_.Add(
plugin_vm::PluginVmManagerFactory::GetForProfile(profile_));
}
}
PluginVmApps::~PluginVmApps() {
if (registry_) {
registry_->RemoveObserver(this);
}
}
void PluginVmApps::Connect(
mojo::PendingRemote<apps::mojom::Subscriber> subscriber_remote,
apps::mojom::ConnectOptionsPtr opts) {
std::vector<apps::mojom::AppPtr> apps;
apps.push_back(GetPluginVmApp(profile_, is_allowed_));
for (const auto& pair :
registry_->GetRegisteredApps(guest_os::GuestOsRegistryService::VmType::
ApplicationList_VmType_PLUGIN_VM)) {
const guest_os::GuestOsRegistryService::Registration& registration =
pair.second;
apps.push_back(Convert(registration, /*new_icon_key=*/true));
}
mojo::Remote<apps::mojom::Subscriber> subscriber(
std::move(subscriber_remote));
subscriber->OnApps(std::move(apps));
subscribers_.Add(std::move(subscriber));
}
void PluginVmApps::LoadIcon(const std::string& app_id,
apps::mojom::IconKeyPtr icon_key,
apps::mojom::IconType icon_type,
int32_t size_hint_in_dip,
bool allow_placeholder_icon,
LoadIconCallback callback) {
registry_->LoadIcon(app_id, std::move(icon_key), icon_type, size_hint_in_dip,
allow_placeholder_icon,
apps::mojom::IconKey::kInvalidResourceId,
std::move(callback));
}
void PluginVmApps::Launch(const std::string& app_id,
int32_t event_flags,
apps::mojom::LaunchSource launch_source,
int64_t display_id) {
DCHECK_EQ(plugin_vm::kPluginVmShelfAppId, app_id);
if (plugin_vm::PluginVmFeatures::Get()->IsEnabled(profile_)) {
plugin_vm::PluginVmManagerFactory::GetForProfile(profile_)->LaunchPluginVm(
base::DoNothing());
} else {
plugin_vm::ShowPluginVmInstallerView(profile_);
}
}
void PluginVmApps::SetPermission(const std::string& app_id,
apps::mojom::PermissionPtr permission_ptr) {
auto permission = static_cast<app_management::mojom::PluginVmPermissionType>(
permission_ptr->permission_id);
const char* pref_name = PermissionToPrefName(permission);
if (!pref_name) {
return;
}
profile_->GetPrefs()->SetBoolean(pref_name, permission_ptr->value);
apps::mojom::AppPtr app = apps::mojom::App::New();
app->app_type = apps::mojom::AppType::kPluginVm;
app->app_id = plugin_vm::kPluginVmShelfAppId;
PopulatePermissions(app.get(), profile_, is_allowed_);
Publish(std::move(app), subscribers_);
}
void PluginVmApps::Uninstall(const std::string& app_id,
apps::mojom::UninstallSource uninstall_source,
bool clear_site_data,
bool report_abuse) {
guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile_)
->ClearApplicationList(guest_os::GuestOsRegistryService::VmType::
ApplicationList_VmType_PLUGIN_VM,
plugin_vm::kPluginVmName, "");
plugin_vm::PluginVmManagerFactory::GetForProfile(profile_)
->UninstallPluginVm();
}
void PluginVmApps::GetMenuModel(const std::string& app_id,
apps::mojom::MenuType menu_type,
int64_t display_id,
GetMenuModelCallback callback) {
apps::mojom::MenuItemsPtr menu_items = apps::mojom::MenuItems::New();
if (ShouldAddOpenItem(app_id, menu_type, profile_)) {
AddCommandItem(ash::MENU_OPEN_NEW, IDS_APP_CONTEXT_MENU_ACTIVATE_ARC,
&menu_items);
}
if (ShouldAddCloseItem(app_id, menu_type, profile_)) {
AddCommandItem(ash::MENU_CLOSE, IDS_SHELF_CONTEXT_MENU_CLOSE, &menu_items);
}
if (app_id == plugin_vm::kPluginVmShelfAppId &&
plugin_vm::IsPluginVmRunning(profile_)) {
AddCommandItem(ash::SHUTDOWN_GUEST_OS, IDS_PLUGIN_VM_SHUT_DOWN_MENU_ITEM,
&menu_items);
}
std::move(callback).Run(std::move(menu_items));
}
void PluginVmApps::OnRegistryUpdated(
guest_os::GuestOsRegistryService* registry_service,
guest_os::GuestOsRegistryService::VmType vm_type,
const std::vector<std::string>& updated_apps,
const std::vector<std::string>& removed_apps,
const std::vector<std::string>& inserted_apps) {
if (vm_type != guest_os::GuestOsRegistryService::VmType::
ApplicationList_VmType_PLUGIN_VM) {
return;
}
for (const std::string& app_id : updated_apps) {
if (auto registration = registry_->GetRegistration(app_id)) {
Publish(Convert(*registration, /*new_icon_key=*/false), subscribers_);
}
}
for (const std::string& app_id : removed_apps) {
apps::mojom::AppPtr app = apps::mojom::App::New();
app->app_type = apps::mojom::AppType::kPluginVm;
app->app_id = app_id;
app->readiness = apps::mojom::Readiness::kUninstalledByUser;
Publish(std::move(app), subscribers_);
}
for (const std::string& app_id : inserted_apps) {
if (auto registration = registry_->GetRegistration(app_id)) {
Publish(Convert(*registration, /*new_icon_key=*/true), subscribers_);
}
}
}
apps::mojom::AppPtr PluginVmApps::Convert(
const guest_os::GuestOsRegistryService::Registration& registration,
bool new_icon_key) {
apps::mojom::AppPtr app = PublisherBase::MakeApp(
apps::mojom::AppType::kPluginVm, registration.app_id(),
apps::mojom::Readiness::kReady, registration.Name(),
apps::mojom::InstallSource::kUser);
if (new_icon_key) {
auto icon_effects =
base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)
? IconEffects::kCrOsStandardIcon
: IconEffects::kNone;
app->icon_key = icon_key_factory_.MakeIconKey(icon_effects);
}
app->last_launch_time = registration.LastLaunchTime();
app->install_time = registration.InstallTime();
app->show_in_launcher = apps::mojom::OptionalBool::kFalse;
app->show_in_search = apps::mojom::OptionalBool::kFalse;
app->show_in_shelf = apps::mojom::OptionalBool::kFalse;
app->show_in_management = apps::mojom::OptionalBool::kFalse;
return app;
}
void PluginVmApps::OnPluginVmAllowedChanged(bool is_allowed) {
// Republish the Plugin VM app when policy changes have changed
// its availability. Only changed fields need to be republished.
is_allowed_ = is_allowed;
apps::mojom::AppPtr app = apps::mojom::App::New();
app->app_type = apps::mojom::AppType::kPluginVm;
app->app_id = plugin_vm::kPluginVmShelfAppId;
SetAppAllowed(app.get(), is_allowed);
Publish(std::move(app), subscribers_);
}
void PluginVmApps::OnPluginVmConfiguredChanged() {
// Only changed fields need to be republished.
apps::mojom::AppPtr app = apps::mojom::App::New();
app->app_type = apps::mojom::AppType::kPluginVm;
app->app_id = plugin_vm::kPluginVmShelfAppId;
SetShowInAppManagement(
app.get(), plugin_vm::PluginVmFeatures::Get()->IsConfigured(profile_));
Publish(std::move(app), subscribers_);
}
void PluginVmApps::OnPluginVmPermissionsChanged(
plugin_vm::PermissionType permission_type,
bool allowed) {
apps::mojom::AppPtr app = apps::mojom::App::New();
app->app_type = apps::mojom::AppType::kPluginVm;
app->app_id = plugin_vm::kPluginVmShelfAppId;
PopulatePermissions(app.get(), profile_, is_allowed_);
Publish(std::move(app), subscribers_);
}
} // namespace apps