// 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/ui/webui/ash/settings/pages/crostini/crostini_handler.h"
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/ash/bruschetta/bruschetta_service.h"
#include "chrome/browser/ash/bruschetta/bruschetta_util.h"
#include "chrome/browser/ash/crostini/crostini_disk.h"
#include "chrome/browser/ash/crostini/crostini_features.h"
#include "chrome/browser/ash/crostini/crostini_installer.h"
#include "chrome/browser/ash/crostini/crostini_port_forwarder.h"
#include "chrome/browser/ash/crostini/crostini_pref_names.h"
#include "chrome/browser/ash/crostini/crostini_shared_devices.h"
#include "chrome/browser/ash/crostini/crostini_types.mojom.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/guest_os/guest_os_pref_names.h"
#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
#include "chrome/browser/ash/guest_os/guest_os_terminal.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/bruschetta/bruschetta_installer_view.h"
#include "chrome/browser/ui/views/bruschetta/bruschetta_uninstaller_view.h"
#include "chrome/browser/ui/views/crostini/crostini_uninstaller_view.h"
#include "chrome/browser/ui/webui/ash/crostini_upgrader/crostini_upgrader_dialog.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/intent_util.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_ui.h"
#include "ui/display/screen.h"
namespace ash::settings {
namespace {
// These values are used for metrics and should not change.
enum class CrostiniSettingsEvent {
kEnableAdbSideloading = 0,
kDisableAdbSideloading = 1,
kMaxValue = kDisableAdbSideloading,
};
void LogEvent(CrostiniSettingsEvent action) {
base::UmaHistogramEnumeration("Crostini.SettingsEvent", action);
}
} // namespace
CrostiniHandler::CrostiniHandler(Profile* profile) : profile_(profile) {}
CrostiniHandler::~CrostiniHandler() {
DisallowJavascript();
}
void CrostiniHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"requestCrostiniInstallerView",
base::BindRepeating(&CrostiniHandler::HandleRequestCrostiniInstallerView,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"requestRemoveCrostini",
base::BindRepeating(&CrostiniHandler::HandleRequestRemoveCrostini,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"exportCrostiniContainer",
base::BindRepeating(&CrostiniHandler::HandleExportCrostiniContainer,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"importCrostiniContainer",
base::BindRepeating(&CrostiniHandler::HandleImportCrostiniContainer,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"requestCrostiniInstallerStatus",
base::BindRepeating(
&CrostiniHandler::HandleCrostiniInstallerStatusRequest,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"requestCrostiniExportImportOperationStatus",
base::BindRepeating(
&CrostiniHandler::HandleCrostiniExportImportOperationStatusRequest,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"requestArcAdbSideloadStatus",
base::BindRepeating(&CrostiniHandler::HandleQueryArcAdbRequest,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"getCanChangeArcAdbSideloading",
base::BindRepeating(
&CrostiniHandler::HandleCanChangeArcAdbSideloadingRequest,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"enableArcAdbSideload",
base::BindRepeating(&CrostiniHandler::HandleEnableArcAdbRequest,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"disableArcAdbSideload",
base::BindRepeating(&CrostiniHandler::HandleDisableArcAdbRequest,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"requestCrostiniContainerUpgradeView",
base::BindRepeating(&CrostiniHandler::HandleRequestContainerUpgradeView,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"requestCrostiniUpgraderDialogStatus",
base::BindRepeating(
&CrostiniHandler::HandleCrostiniUpgraderDialogStatusRequest,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"requestCrostiniContainerUpgradeAvailable",
base::BindRepeating(
&CrostiniHandler::HandleCrostiniContainerUpgradeAvailableRequest,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"getCrostiniDiskInfo",
base::BindRepeating(&CrostiniHandler::HandleGetCrostiniDiskInfo,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"resizeCrostiniDisk",
base::BindRepeating(&CrostiniHandler::HandleResizeCrostiniDisk,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"addCrostiniPortForward",
base::BindRepeating(&CrostiniHandler::HandleAddCrostiniPortForward,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"removeCrostiniPortForward",
base::BindRepeating(&CrostiniHandler::HandleRemoveCrostiniPortForward,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"removeAllCrostiniPortForwards",
base::BindRepeating(&CrostiniHandler::HandleRemoveAllCrostiniPortForwards,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"activateCrostiniPortForward",
base::BindRepeating(&CrostiniHandler::HandleActivateCrostiniPortForward,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"deactivateCrostiniPortForward",
base::BindRepeating(&CrostiniHandler::HandleDeactivateCrostiniPortForward,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"getCrostiniActivePorts",
base::BindRepeating(&CrostiniHandler::HandleGetCrostiniActivePorts,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"getCrostiniActiveNetworkInfo",
base::BindRepeating(&CrostiniHandler::HandleGetCrostiniActiveNetworkInfo,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"checkCrostiniIsRunning",
base::BindRepeating(&CrostiniHandler::HandleCheckCrostiniIsRunning,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"checkBruschettaIsRunning",
base::BindRepeating(&CrostiniHandler::HandleCheckBruschettaIsRunning,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"shutdownCrostini",
base::BindRepeating(&CrostiniHandler::HandleShutdownCrostini,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"shutdownBruschetta",
base::BindRepeating(&CrostiniHandler::HandleShutdownBruschetta,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"requestContainerInfo",
base::BindRepeating(&CrostiniHandler::HandleRequestContainerInfo,
handler_weak_ptr_factory_.GetWeakPtr()));
if (crostini::CrostiniFeatures::Get()->IsMultiContainerAllowed(profile_)) {
web_ui()->RegisterMessageCallback(
"createContainer",
base::BindRepeating(&CrostiniHandler::HandleCreateContainer,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"deleteContainer",
base::BindRepeating(&CrostiniHandler::HandleDeleteContainer,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"setContainerBadgeColor",
base::BindRepeating(&CrostiniHandler::HandleSetContainerBadgeColor,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"stopContainer",
base::BindRepeating(&CrostiniHandler::HandleStopContainer,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"openContainerFileSelector",
base::BindRepeating(&CrostiniHandler::HandleOpenContainerFileSelector,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"requestSharedVmDevices",
base::BindRepeating(&CrostiniHandler::HandleRequestSharedVmDevices,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"isVmDeviceShared",
base::BindRepeating(&CrostiniHandler::HandleIsVmDeviceShared,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"setVmDeviceShared",
base::BindRepeating(&CrostiniHandler::HandleSetVmDeviceShared,
handler_weak_ptr_factory_.GetWeakPtr()));
}
web_ui()->RegisterMessageCallback(
"requestBruschettaInstallerView",
base::BindRepeating(
&CrostiniHandler::HandleRequestBruschettaInstallerView,
handler_weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"requestBruschettaUninstallerView",
base::BindRepeating(
&CrostiniHandler::HandleRequestBruschettaUninstallerView,
handler_weak_ptr_factory_.GetWeakPtr()));
}
void CrostiniHandler::OnJavascriptAllowed() {
auto* crostini_manager = crostini::CrostiniManager::GetForProfile(profile_);
crostini_manager->AddCrostiniDialogStatusObserver(this);
crostini_manager->AddCrostiniContainerPropertiesObserver(this);
crostini_manager->AddContainerShutdownObserver(this);
crostini::CrostiniExportImport::GetForProfile(profile_)->AddObserver(this);
crostini::CrostiniPortForwarder::GetForProfile(profile_)->AddObserver(this);
guest_os::GuestOsSessionTracker::GetForProfile(profile_)
->AddContainerStartedObserver(this);
// Observe ADB sideloading device policy and react to its changes
adb_sideloading_device_policy_subscription_ =
CrosSettings::Get()->AddSettingsObserver(
kDeviceCrostiniArcAdbSideloadingAllowed,
base::BindRepeating(&CrostiniHandler::FetchCanChangeAdbSideloading,
handler_weak_ptr_factory_.GetWeakPtr()));
// Observe ADB sideloading user policy and react to its changes
pref_change_registrar_.Init(profile_->GetPrefs());
pref_change_registrar_.Add(
crostini::prefs::kCrostiniArcAdbSideloadingUserPref,
base::BindRepeating(&CrostiniHandler::FetchCanChangeAdbSideloading,
handler_weak_ptr_factory_.GetWeakPtr()));
// Observe changes to containers in general
pref_change_registrar_.Add(
guest_os::prefs::kGuestOsContainers,
base::BindRepeating(&CrostiniHandler::HandleRequestContainerInfo,
handler_weak_ptr_factory_.GetWeakPtr(),
base::Value::List()));
}
void CrostiniHandler::OnJavascriptDisallowed() {
auto* crostini_manager = crostini::CrostiniManager::GetForProfile(profile_);
crostini_manager->RemoveCrostiniDialogStatusObserver(this);
crostini_manager->RemoveCrostiniContainerPropertiesObserver(this);
crostini_manager->RemoveContainerShutdownObserver(this);
crostini::CrostiniExportImport::GetForProfile(profile_)->RemoveObserver(this);
crostini::CrostiniPortForwarder::GetForProfile(profile_)->RemoveObserver(
this);
guest_os::GuestOsSessionTracker::GetForProfile(profile_)
->RemoveContainerStartedObserver(this);
adb_sideloading_device_policy_subscription_ = {};
pref_change_registrar_.RemoveAll();
callback_weak_ptr_factory_.InvalidateWeakPtrs();
}
void CrostiniHandler::HandleRequestCrostiniInstallerView(
const base::Value::List& args) {
AllowJavascript();
crostini::CrostiniInstaller::GetForProfile(Profile::FromWebUI(web_ui()))
->ShowDialog(crostini::CrostiniUISurface::kSettings);
}
void CrostiniHandler::HandleRequestRemoveCrostini(
const base::Value::List& args) {
AllowJavascript();
crostini::ShowCrostiniUninstallerView(Profile::FromWebUI(web_ui()));
}
namespace {
base::Value::Dict CrostiniDiskInfoToValue(
std::unique_ptr<crostini::CrostiniDiskInfo> disk_info) {
base::Value::Dict disk_value;
if (!disk_info) {
disk_value.Set("succeeded", false);
return disk_value;
}
disk_value.Set("succeeded", true);
disk_value.Set("canResize", disk_info->can_resize);
disk_value.Set("isUserChosenSize", disk_info->is_user_chosen_size);
disk_value.Set("isLowSpaceAvailable", disk_info->is_low_space_available);
disk_value.Set("defaultIndex", disk_info->default_index);
base::Value::List ticks;
for (const auto& tick : disk_info->ticks) {
base::Value::Dict t;
t.Set("value", static_cast<double>(tick->value));
t.Set("ariaValue", tick->aria_value);
t.Set("label", tick->label);
ticks.Append(std::move(t));
}
disk_value.Set("ticks", std::move(ticks));
return disk_value;
}
} // namespace
void CrostiniHandler::HandleExportCrostiniContainer(
const base::Value::List& args) {
CHECK_EQ(1U, args.size());
guest_os::GuestId container_id(args[0]);
VLOG(1) << "Exporting = " << container_id;
crostini::CrostiniExportImport::GetForProfile(profile_)->ExportContainer(
container_id, web_ui()->GetWebContents());
}
void CrostiniHandler::HandleImportCrostiniContainer(
const base::Value::List& args) {
CHECK_EQ(1U, args.size());
guest_os::GuestId container_id(args[0]);
VLOG(1) << "Importing = " << container_id;
crostini::CrostiniExportImport::GetForProfile(profile_)->ImportContainer(
container_id, web_ui()->GetWebContents());
}
void CrostiniHandler::HandleCrostiniInstallerStatusRequest(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(0U, args.size());
bool status = crostini::CrostiniManager::GetForProfile(profile_)
->GetCrostiniDialogStatus(crostini::DialogType::INSTALLER);
OnCrostiniDialogStatusChanged(crostini::DialogType::INSTALLER, status);
}
void CrostiniHandler::HandleCrostiniExportImportOperationStatusRequest(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(0U, args.size());
bool in_progress = crostini::CrostiniExportImport::GetForProfile(profile_)
->GetExportImportOperationStatus();
OnCrostiniExportImportOperationStatusChanged(in_progress);
}
void CrostiniHandler::OnCrostiniDialogStatusChanged(
crostini::DialogType dialog_type,
bool status) {
// It's technically possible for this to be called before Javascript is
// enabled, in which case we must not call FireWebUIListener
if (IsJavascriptAllowed()) {
// Other side listens with cr.addWebUIListener
switch (dialog_type) {
case crostini::DialogType::INSTALLER:
FireWebUIListener("crostini-installer-status-changed",
base::Value(status));
break;
case crostini::DialogType::UPGRADER:
FireWebUIListener("crostini-upgrader-status-changed",
base::Value(status));
break;
case crostini::DialogType::REMOVER:
FireWebUIListener("crostini-remover-status-changed",
base::Value(status));
break;
default:
NOTREACHED_IN_MIGRATION();
break;
}
}
}
void CrostiniHandler::OnContainerOsReleaseChanged(
const guest_os::GuestId& container_id,
bool can_upgrade) {
if (crostini::CrostiniFeatures::Get()->IsContainerUpgradeUIAllowed(
profile_) &&
container_id == crostini::DefaultContainerId()) {
FireWebUIListener("crostini-container-upgrade-available-changed",
base::Value(can_upgrade));
}
}
void CrostiniHandler::OnQueryAdbSideload(
SessionManagerClient::AdbSideloadResponseCode response_code,
bool enabled) {
if (response_code != SessionManagerClient::AdbSideloadResponseCode::SUCCESS) {
LOG(ERROR) << "Failed to query adb sideload status";
enabled = false;
}
bool need_powerwash =
response_code ==
SessionManagerClient::AdbSideloadResponseCode::NEED_POWERWASH;
// Other side listens with cr.addWebUIListener
FireWebUIListener("crostini-arc-adb-sideload-status-changed",
base::Value(enabled), base::Value(need_powerwash));
}
void CrostiniHandler::HandleEnableArcAdbRequest(const base::Value::List& args) {
CHECK_EQ(0U, args.size());
crostini::CrostiniFeatures::Get()->CanChangeAdbSideloading(
profile_, base::BindOnce(&CrostiniHandler::OnCanEnableArcAdbSideloading,
handler_weak_ptr_factory_.GetWeakPtr()));
}
void CrostiniHandler::OnCanEnableArcAdbSideloading(
bool can_change_adb_sideloading) {
if (!can_change_adb_sideloading) {
return;
}
LogEvent(CrostiniSettingsEvent::kEnableAdbSideloading);
PrefService* prefs = g_browser_process->local_state();
prefs->SetBoolean(prefs::kEnableAdbSideloadingRequested, true);
prefs->CommitPendingWrite();
chrome::AttemptRelaunch();
}
void CrostiniHandler::HandleDisableArcAdbRequest(
const base::Value::List& args) {
CHECK_EQ(0U, args.size());
crostini::CrostiniFeatures::Get()->CanChangeAdbSideloading(
profile_, base::BindOnce(&CrostiniHandler::OnCanDisableArcAdbSideloading,
handler_weak_ptr_factory_.GetWeakPtr()));
}
void CrostiniHandler::OnCanDisableArcAdbSideloading(
bool can_change_adb_sideloading) {
if (!can_change_adb_sideloading) {
return;
}
LogEvent(CrostiniSettingsEvent::kDisableAdbSideloading);
PrefService* prefs = g_browser_process->local_state();
prefs->SetBoolean(prefs::kFactoryResetRequested, true);
prefs->CommitPendingWrite();
chromeos::PowerManagerClient::Get()->RequestRestart(
power_manager::REQUEST_RESTART_FOR_USER, "disable adb sideloading");
}
void CrostiniHandler::LaunchTerminal(apps::IntentPtr intent) {
guest_os::LaunchTerminalWithIntent(
profile_, display::Screen::GetScreen()->GetPrimaryDisplay().id(),
std::move(intent), base::DoNothing());
}
void CrostiniHandler::HandleRequestContainerUpgradeView(
const base::Value::List& args) {
CHECK_EQ(0U, args.size());
CrostiniUpgraderDialog::Show(
profile_,
base::BindOnce(&CrostiniHandler::LaunchTerminal,
handler_weak_ptr_factory_.GetWeakPtr(),
/*intent=*/nullptr),
// If the user cancels the upgrade, we won't need to restart Crostini and
// we don't want to run the launch closure which would launch Terminal.
/*only_run_launch_closure_on_restart=*/true);
}
void CrostiniHandler::OnCrostiniExportImportOperationStatusChanged(
bool in_progress) {
// Other side listens with cr.addWebUIListener
FireWebUIListener("crostini-export-import-operation-status-changed",
base::Value(in_progress));
}
void CrostiniHandler::HandleQueryArcAdbRequest(const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(0U, args.size());
SessionManagerClient* client = SessionManagerClient::Get();
client->QueryAdbSideload(
base::BindOnce(&CrostiniHandler::OnQueryAdbSideload,
handler_weak_ptr_factory_.GetWeakPtr()));
}
void CrostiniHandler::HandleCanChangeArcAdbSideloadingRequest(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(0U, args.size());
FetchCanChangeAdbSideloading();
}
void CrostiniHandler::FetchCanChangeAdbSideloading() {
crostini::CrostiniFeatures::Get()->CanChangeAdbSideloading(
profile_, base::BindOnce(&CrostiniHandler::OnCanChangeArcAdbSideloading,
handler_weak_ptr_factory_.GetWeakPtr()));
}
void CrostiniHandler::OnCanChangeArcAdbSideloading(
bool can_change_arc_adb_sideloading) {
FireWebUIListener("crostini-can-change-arc-adb-sideload-changed",
base::Value(can_change_arc_adb_sideloading));
}
void CrostiniHandler::HandleCrostiniUpgraderDialogStatusRequest(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(0U, args.size());
bool is_open = crostini::CrostiniManager::GetForProfile(profile_)
->GetCrostiniDialogStatus(crostini::DialogType::UPGRADER);
OnCrostiniDialogStatusChanged(crostini::DialogType::UPGRADER, is_open);
}
void CrostiniHandler::HandleCrostiniContainerUpgradeAvailableRequest(
const base::Value::List& args) {
AllowJavascript();
bool can_upgrade = crostini::ShouldAllowContainerUpgrade(profile_);
OnContainerOsReleaseChanged(crostini::DefaultContainerId(), can_upgrade);
}
void CrostiniHandler::OnActivePortsChanged(
const base::Value::List& activePorts) {
// Other side listens with cr.addWebUIListener
FireWebUIListener("crostini-port-forwarder-active-ports-changed",
activePorts);
}
void CrostiniHandler::OnActiveNetworkChanged(const base::Value& interface,
const base::Value& ipAddress) {
FireWebUIListener("crostini-active-network-info", interface, ipAddress);
}
void CrostiniHandler::HandleAddCrostiniPortForward(
const base::Value::List& args) {
CHECK_EQ(5U, args.size());
std::string callback_id = args[0].GetString();
guest_os::GuestId container_id(args[1]);
int port_number = args[2].GetInt();
int protocol_type = args[3].GetInt();
std::string label = args[4].GetString();
if (!crostini::CrostiniFeatures::Get()->IsPortForwardingAllowed(profile_)) {
OnPortForwardComplete(callback_id, false);
return;
}
crostini::CrostiniPortForwarder::GetForProfile(profile_)->AddPort(
container_id, port_number,
static_cast<crostini::CrostiniPortForwarder::Protocol>(protocol_type),
std::move(label),
base::BindOnce(&CrostiniHandler::OnPortForwardComplete,
callback_weak_ptr_factory_.GetWeakPtr(),
std::move(callback_id)));
}
void CrostiniHandler::HandleRemoveCrostiniPortForward(
const base::Value::List& args) {
const auto& list = args;
CHECK_EQ(4U, list.size());
std::string callback_id = list[0].GetString();
guest_os::GuestId container_id(list[1]);
int port_number = list[2].GetInt();
int protocol_type = list[3].GetInt();
if (!crostini::CrostiniFeatures::Get()->IsPortForwardingAllowed(profile_)) {
OnPortForwardComplete(callback_id, false);
return;
}
crostini::CrostiniPortForwarder::GetForProfile(profile_)->RemovePort(
container_id, port_number,
static_cast<crostini::CrostiniPortForwarder::Protocol>(protocol_type),
base::BindOnce(&CrostiniHandler::OnPortForwardComplete,
callback_weak_ptr_factory_.GetWeakPtr(),
std::move(callback_id)));
}
void CrostiniHandler::HandleRemoveAllCrostiniPortForwards(
const base::Value::List& args) {
CHECK_EQ(1U, args.size());
if (!crostini::CrostiniFeatures::Get()->IsPortForwardingAllowed(profile_)) {
return;
}
crostini::CrostiniPortForwarder::GetForProfile(profile_)->RemoveAllPorts(
guest_os::GuestId(args[0]));
}
void CrostiniHandler::HandleActivateCrostiniPortForward(
const base::Value::List& args) {
const auto& list = args;
CHECK_EQ(4U, list.size());
std::string callback_id = list[0].GetString();
guest_os::GuestId container_id(list[1]);
int port_number = list[2].GetInt();
int protocol_type = list[3].GetInt();
if (!crostini::CrostiniFeatures::Get()->IsPortForwardingAllowed(profile_)) {
OnPortForwardComplete(callback_id, false);
return;
}
crostini::CrostiniPortForwarder::GetForProfile(profile_)->ActivatePort(
container_id, port_number,
static_cast<crostini::CrostiniPortForwarder::Protocol>(protocol_type),
base::BindOnce(&CrostiniHandler::OnPortForwardComplete,
callback_weak_ptr_factory_.GetWeakPtr(),
std::move(callback_id)));
}
void CrostiniHandler::HandleDeactivateCrostiniPortForward(
const base::Value::List& args) {
const auto& list = args;
CHECK_EQ(4U, list.size());
std::string callback_id = list[0].GetString();
guest_os::GuestId container_id(list[1]);
int port_number = list[2].GetInt();
int protocol_type = list[3].GetInt();
if (!crostini::CrostiniFeatures::Get()->IsPortForwardingAllowed(profile_)) {
OnPortForwardComplete(callback_id, false);
return;
}
crostini::CrostiniPortForwarder::GetForProfile(profile_)->DeactivatePort(
container_id, port_number,
static_cast<crostini::CrostiniPortForwarder::Protocol>(protocol_type),
base::BindOnce(&CrostiniHandler::OnPortForwardComplete,
callback_weak_ptr_factory_.GetWeakPtr(),
std::move(callback_id)));
}
void CrostiniHandler::OnPortForwardComplete(std::string callback_id,
bool success) {
ResolveJavascriptCallback(base::Value(callback_id), base::Value(success));
}
void CrostiniHandler::ResolveGetCrostiniDiskInfoCallback(
const std::string& callback_id,
std::unique_ptr<crostini::CrostiniDiskInfo> disk_info) {
ResolveJavascriptCallback(base::Value(std::move(callback_id)),
CrostiniDiskInfoToValue(std::move(disk_info)));
}
void CrostiniHandler::HandleGetCrostiniDiskInfo(const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(3U, args.size());
std::string callback_id = args[0].GetString();
std::string vm_name = args[1].GetString();
bool full_info = args[2].GetBool();
crostini::disk::GetDiskInfo(
base::BindOnce(&CrostiniHandler::ResolveGetCrostiniDiskInfoCallback,
callback_weak_ptr_factory_.GetWeakPtr(),
std::move(callback_id)),
profile_, std::move(vm_name), full_info);
}
void CrostiniHandler::HandleResizeCrostiniDisk(const base::Value::List& args) {
CHECK_EQ(3U, args.size());
std::string callback_id = args[0].GetString();
std::string vm_name = args[1].GetString();
double bytes = args[2].GetDouble();
crostini::disk::ResizeCrostiniDisk(
profile_, std::move(vm_name), bytes,
base::BindOnce(&CrostiniHandler::ResolveResizeCrostiniDiskCallback,
callback_weak_ptr_factory_.GetWeakPtr(),
std::move(callback_id)));
}
void CrostiniHandler::ResolveResizeCrostiniDiskCallback(
const std::string& callback_id,
bool succeeded) {
ResolveJavascriptCallback(base::Value(std::move(callback_id)),
base::Value(succeeded));
}
void CrostiniHandler::HandleGetCrostiniActivePorts(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(1U, args.size());
std::string callback_id = args[0].GetString();
ResolveJavascriptCallback(
base::Value(callback_id),
crostini::CrostiniPortForwarder::GetForProfile(profile_)
->GetActivePorts());
}
void CrostiniHandler::HandleGetCrostiniActiveNetworkInfo(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(1U, args.size());
std::string callback_id = args[0].GetString();
ResolveJavascriptCallback(
base::Value(callback_id),
crostini::CrostiniPortForwarder::GetForProfile(profile_)
->GetActiveNetworkInfo());
}
void CrostiniHandler::HandleCheckCrostiniIsRunning(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(1U, args.size());
std::string callback_id = args[0].GetString();
ResolveJavascriptCallback(base::Value(callback_id),
base::Value(crostini::IsCrostiniRunning(profile_)));
}
void CrostiniHandler::HandleCheckBruschettaIsRunning(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(1U, args.size());
std::string callback_id = args[0].GetString();
ResolveJavascriptCallback(
base::Value(callback_id),
base::Value(bruschetta::IsBruschettaRunning(profile_)));
}
void CrostiniHandler::OnContainerStarted(
const guest_os::GuestId& container_id) {
if (container_id == crostini::DefaultContainerId()) {
FireWebUIListener("crostini-status-changed", base::Value(true));
}
// After other observers have run, we can send container info.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&CrostiniHandler::HandleRequestContainerInfo,
handler_weak_ptr_factory_.GetWeakPtr(),
base::Value::List()));
}
void CrostiniHandler::OnContainerShutdown(
const guest_os::GuestId& container_id) {
if (container_id == crostini::DefaultContainerId()) {
FireWebUIListener("crostini-status-changed", base::Value(false));
}
// After other observers have run, we can send container info.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&CrostiniHandler::HandleRequestContainerInfo,
handler_weak_ptr_factory_.GetWeakPtr(),
base::Value::List()));
}
void CrostiniHandler::HandleShutdownCrostini(const base::Value::List& args) {
CHECK_EQ(0U, args.size());
crostini::CrostiniManager::GetForProfile(profile_)->StopRunningVms(
base::DoNothing());
}
void CrostiniHandler::HandleShutdownBruschetta(const base::Value::List& args) {
CHECK_EQ(0U, args.size());
bruschetta::BruschettaService::GetForProfile(profile_)->StopRunningVms();
}
void CrostiniHandler::HandleCreateContainer(const base::Value::List& args) {
CHECK_EQ(4U, args.size());
guest_os::GuestId container_id(args[0]);
GURL image_server_url(args[1].GetString());
std::string image_alias(args[2].GetString());
base::FilePath container_file(args[3].GetString());
if (!crostini::CrostiniFeatures::Get()->IsMultiContainerAllowed(profile_)) {
LOG(ERROR) << "Failed to create a new Crostini container: Multi-container "
"flag not enabled.";
return;
}
if (!args[1].GetString().empty() && !image_server_url.is_valid()) {
LOG(ERROR) << "Malformed data. image_server_url=" << args[1].GetString()
<< ", image_alias=" << image_alias;
return;
}
VLOG(1) << "Creating container_id = " << container_id;
bool isContainerBackupFile =
!container_file.empty() &&
container_file.Extension() != FILE_PATH_LITERAL(".yaml");
if (isContainerBackupFile) {
VLOG(1) << "backup_file = " << container_file
<< "will be used to create a new container.";
crostini::CrostiniExportImport::GetForProfile(profile_)
->CreateContainerFromImport(
container_id, container_file,
base::BindOnce(&CrostiniHandler::OnContainerCreated,
handler_weak_ptr_factory_.GetWeakPtr(),
container_id));
return;
}
crostini::CrostiniManager::RestartOptions options;
options.restart_source = crostini::RestartSource::kMultiContainerCreation;
if (image_server_url.is_valid()) {
options.image_server_url = image_server_url.spec();
VLOG(1) << "image_server_url = " << image_server_url;
}
if (!image_alias.empty()) {
options.image_alias = image_alias;
VLOG(1) << "image_alias = " << image_alias;
}
if (!container_file.empty() &&
container_file.Extension() == FILE_PATH_LITERAL(".yaml")) {
options.ansible_playbook = container_file;
VLOG(1) << "ansible_playbook = " << container_file;
}
crostini::CrostiniManager::GetForProfile(profile_)
->RestartCrostiniWithOptions(
container_id, std::move(options),
base::BindOnce(&CrostiniHandler::OnContainerCreated,
handler_weak_ptr_factory_.GetWeakPtr(), container_id));
auto intent = std::make_unique<apps::Intent>(apps_util::kIntentActionView);
intent->extras = container_id.ToMap();
// The Terminal will be added as an observer to the above restart.
// Immediately launch the terminal to allow the window to popup and show the
// startup progress.
LaunchTerminal(std::move(intent));
}
void CrostiniHandler::OnContainerCreated(guest_os::GuestId container_id,
crostini::CrostiniResult result) {
if (result != crostini::CrostiniResult::SUCCESS) {
LOG(ERROR) << "Failed to create container: " << container_id;
return;
}
VLOG(1) << "Container was created successfully with ID: " << container_id;
}
void CrostiniHandler::HandleDeleteContainer(const base::Value::List& args) {
CHECK_EQ(1U, args.size());
if (!crostini::CrostiniFeatures::Get()->IsMultiContainerAllowed(profile_)) {
return;
}
guest_os::GuestId container_id(args[0]);
if (container_id == crostini::DefaultContainerId()) {
LOG(ERROR) << "Deleting " << container_id << " not permitted";
return;
}
VLOG(1) << "Deleting " << container_id;
auto* crostini_manager = crostini::CrostiniManager::GetForProfile(profile_);
crostini::CrostiniManager::RestartOptions options;
options.stop_after_lxd_available = true;
crostini_manager->RestartCrostiniWithOptions(
container_id, std::move(options),
base::BindOnce(
[](base::WeakPtr<crostini::CrostiniManager> crostini_manager,
guest_os::GuestId container_id, crostini::CrostiniResult result) {
if (crostini_manager &&
result == crostini::CrostiniResult::SUCCESS) {
crostini_manager->DeleteLxdContainer(container_id,
base::DoNothing());
}
},
crostini_manager->GetWeakPtr(), container_id));
}
void CrostiniHandler::HandleRequestContainerInfo(
const base::Value::List& args) {
constexpr char kIdKey[] = "id";
constexpr char kIpv4Key[] = "ipv4";
base::Value::List container_info_list;
for (const auto& container_id :
guest_os::GetContainers(profile_, guest_os::VmType::TERMINA)) {
base::Value::Dict container_info_value;
container_info_value.Set(kIdKey, container_id.ToDictValue());
auto info =
guest_os::GuestOsSessionTracker::GetForProfile(profile_)->GetInfo(
container_id);
if (info) {
container_info_value.Set(kIpv4Key, info->ipv4_address);
}
SkColor badge_color =
crostini::GetContainerBadgeColor(profile_, container_id);
std::string badge_color_str =
base::StringPrintf("#%02x%02x%02x", SkColorGetR(badge_color),
SkColorGetG(badge_color), SkColorGetB(badge_color));
container_info_value.Set("badge_color", badge_color_str);
container_info_list.Append(std::move(container_info_value));
}
FireWebUIListener("crostini-container-info", container_info_list);
}
void CrostiniHandler::HandleSetContainerBadgeColor(
const base::Value::List& args) {
CHECK_EQ(2U, args.size());
guest_os::GuestId container_id(args[0]);
SkColor badge_color(args[1].GetDict().FindDouble("value").value());
crostini::SetContainerBadgeColor(profile_, container_id, badge_color);
}
void CrostiniHandler::HandleStopContainer(const base::Value::List& args) {
CHECK_EQ(1U, args.size());
if (!crostini::CrostiniFeatures::Get()->IsMultiContainerAllowed(profile_)) {
return;
}
guest_os::GuestId container_id(args[0]);
if (crostini::ShouldStopVm(profile_, container_id)) {
crostini::CrostiniManager::GetForProfile(profile_)->StopVm(
container_id.vm_name, base::DoNothing());
} else {
crostini::CrostiniManager::GetForProfile(profile_)->StopLxdContainer(
container_id, base::DoNothing());
}
}
void CrostiniHandler::HandleOpenContainerFileSelector(
const base::Value::List& args) {
CHECK_EQ(1U, args.size());
const std::string& callback_id = args[0].GetString();
file_selector_ = std::make_unique<crostini::CrostiniFileSelector>(web_ui());
file_selector_->SelectFile(
base::BindOnce(&CrostiniHandler::OnContainerFileSelected,
callback_weak_ptr_factory_.GetWeakPtr(), callback_id),
base::DoNothing());
}
void CrostiniHandler::OnContainerFileSelected(const std::string& callback_id,
const base::FilePath& path) {
base::Value filePath(path.value());
ResolveJavascriptCallback(base::Value(callback_id), filePath);
}
void CrostiniHandler::HandleRequestSharedVmDevices(
const base::Value::List& args) {
constexpr char kIdKey[] = "id";
constexpr char kVmDevicesKey[] = "vmDevices";
constexpr char kMicrophone[] = "microphone";
auto* crostini_shared_devices =
crostini::CrostiniSharedDevices::GetForProfile(profile_);
base::Value::List shared_vmdevices;
for (const auto& container_id :
guest_os::GetContainers(profile_, guest_os::VmType::TERMINA)) {
base::Value::Dict container_shared_devices;
container_shared_devices.Set(kIdKey, container_id.ToDictValue());
base::Value::Dict device_dict;
device_dict.Set(kMicrophone, crostini_shared_devices->IsVmDeviceShared(
container_id, kMicrophone));
container_shared_devices.Set(kVmDevicesKey, std::move(device_dict));
shared_vmdevices.Append(std::move(container_shared_devices));
}
FireWebUIListener("crostini-shared-vmdevices", shared_vmdevices);
}
void CrostiniHandler::HandleIsVmDeviceShared(const base::Value::List& args) {
CHECK_EQ(3U, args.size());
const std::string& callback_id = args[0].GetString();
guest_os::GuestId container_id(args[1]);
const std::string& vm_device = args[2].GetString();
ResolveJavascriptCallback(
base::Value(callback_id),
crostini::CrostiniSharedDevices::GetForProfile(profile_)
->IsVmDeviceShared(container_id, vm_device));
}
void CrostiniHandler::HandleSetVmDeviceShared(const base::Value::List& args) {
CHECK_EQ(4U, args.size());
const std::string& callback_id = args[0].GetString();
guest_os::GuestId container_id(args[1]);
const std::string& vm_device = args[2].GetString();
bool shared = args[3].GetBool();
crostini::CrostiniSharedDevices::GetForProfile(profile_)->SetVmDeviceShared(
container_id, vm_device, shared,
base::BindOnce(
[](base::WeakPtr<CrostiniHandler> weak_this,
const std::string callback_id, bool was_applied) {
if (weak_this) {
weak_this->ResolveJavascriptCallback(base::Value(callback_id),
was_applied);
}
},
callback_weak_ptr_factory_.GetWeakPtr(), callback_id));
}
void CrostiniHandler::HandleRequestBruschettaInstallerView(
const base::Value::List& args) {
AllowJavascript();
BruschettaInstallerView::Show(Profile::FromWebUI(web_ui()),
bruschetta::GetBruschettaAlphaId());
}
void CrostiniHandler::HandleRequestBruschettaUninstallerView(
const base::Value::List& args) {
AllowJavascript();
BruschettaUninstallerView::Show(Profile::FromWebUI(web_ui()),
bruschetta::GetBruschettaAlphaId());
}
} // namespace ash::settings