// Copyright 2023 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/bruschetta_apps.h"
#include <string>
#include <utility>
#include <vector>
#include "ash/public/cpp/app_menu_constants.h"
#include "base/functional/callback_helpers.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/menu_util.h"
#include "chrome/browser/ash/bruschetta/bruschetta_launcher.h"
#include "chrome/browser/ash/bruschetta/bruschetta_service.h"
#include "chrome/browser/ash/bruschetta/bruschetta_util.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/guest_os/guest_os_launcher.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
#include "chrome/browser/ash/guest_os/guest_os_share_path.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chrome/browser/ui/ash/shelf/shelf_spinner_controller.h"
#include "chrome/browser/ui/ash/shelf/shelf_spinner_item_controller.h"
#include "chrome/grit/chrome_unscaled_resources.h"
#include "chrome/grit/generated_resources.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/intent.h"
namespace apps {
namespace {
void AddSpinner(const std::string& app_id) {
if (auto* chrome_controller = ChromeShelfController::instance()) {
chrome_controller->GetShelfSpinnerController()->AddSpinnerToShelf(
app_id, std::make_unique<ShelfSpinnerItemController>(app_id));
}
}
// It's safe to remove a non-existent spinner.
void RemoveSpinner(const std::string& app_id) {
if (auto* chrome_controller = ChromeShelfController::instance()) {
chrome_controller->GetShelfSpinnerController()->CloseSpinner(app_id);
}
}
void OnLaunchFailed(const std::string& app_id,
LaunchCallback callback,
const std::string& reason) {
LOG(ERROR) << "Failed to launch Bruschetta app " << app_id << ": " << reason;
RemoveSpinner(app_id);
std::move(callback).Run(ConvertBoolToLaunchResult(false));
}
void OnSharePathForLaunchApplication(
Profile* profile,
const std::string& app_id,
guest_os::GuestOsRegistryService::Registration registration,
const guest_os::GuestId container_id,
int64_t display_id,
const std::vector<std::string>& args,
LaunchCallback callback,
bool success,
const std::string& failure_reason) {
if (!success) {
OnLaunchFailed(app_id, std::move(callback),
"Failed to share paths with Bruschetta: " + failure_reason);
return;
}
guest_os::launcher::LaunchApplication(
profile, container_id, std::move(registration), display_id, args,
base::BindOnce(
[](const std::string& app_id, LaunchCallback callback, bool success,
const std::string& failure_reason) {
if (!success) {
OnLaunchFailed(app_id, std::move(callback), failure_reason);
return;
}
RemoveSpinner(app_id);
std::move(callback).Run(ConvertBoolToLaunchResult(success));
},
app_id, std::move(callback)));
}
void LaunchApplication(
Profile* profile,
const std::string& app_id,
guest_os::GuestOsRegistryService::Registration registration,
int64_t display_id,
const std::vector<guest_os::LaunchArg> args,
LaunchCallback callback) {
// TODO(b/265601951): Handle window permissions. Crostini uses
// AppServiceAppWindowCrostiniTracker::OnAppLaunchRequested for this.
// Get vm_info because we need seneschal_server_handle.
const std::string& vm_name = registration.VmName();
auto vm_info =
guest_os::GuestOsSessionTracker::GetForProfile(profile)->GetVmInfo(
vm_name);
if (!vm_info) {
OnLaunchFailed(app_id, std::move(callback),
"Bruschetta VM not running: " + vm_name);
return;
}
const guest_os::GuestId container_id(registration.ToGuestId());
auto* share_path = guest_os::GuestOsSharePath::GetForProfile(profile);
auto paths_or_error = share_path->ConvertArgsToPathsToShare(
registration, args, bruschetta::BruschettaChromeOSBaseDirectory(),
/*map_crostini_home=*/false);
if (absl::holds_alternative<std::string>(paths_or_error)) {
OnLaunchFailed(app_id, std::move(callback),
absl::get<std::string>(paths_or_error));
return;
}
const auto& paths =
absl::get<guest_os::GuestOsSharePath::PathsToShare>(paths_or_error);
share_path->SharePaths(
vm_name, vm_info->seneschal_server_handle(),
std::move(paths.paths_to_share),
base::BindOnce(OnSharePathForLaunchApplication, profile, app_id,
std::move(registration), std::move(container_id),
display_id, std::move(paths.launch_args),
std::move(callback)));
}
} // namespace
BruschettaApps::BruschettaApps(AppServiceProxy* proxy) : GuestOSApps(proxy) {}
bool BruschettaApps::CouldBeAllowed() const {
return true;
}
apps::AppType BruschettaApps::AppType() const {
return AppType::kBruschetta;
}
guest_os::VmType BruschettaApps::VmType() const {
return guest_os::VmType::BRUSCHETTA;
}
int BruschettaApps::DefaultIconResourceId() const {
return IDR_LOGO_BRUSCHETTA_DEFAULT;
}
void BruschettaApps::Launch(const std::string& app_id,
int32_t event_flags,
LaunchSource launch_source,
WindowInfoPtr window_info) {
LaunchAppWithIntent(app_id, event_flags, /*intent=*/nullptr, launch_source,
std::move(window_info), /*callback=*/base::DoNothing());
}
void BruschettaApps::LaunchAppWithIntent(const std::string& app_id,
int32_t event_flags,
IntentPtr intent,
LaunchSource launch_source,
WindowInfoPtr window_info,
LaunchCallback callback) {
const int64_t display_id =
window_info ? window_info->display_id : display::kInvalidDisplayId;
std::optional<guest_os::GuestOsRegistryService::Registration> registration =
registry()->GetRegistration(app_id);
if (!registration) {
// TODO(b/247638226): RecordAppLaunchHistogram(kUnknown) to collect usage
// stats for failed launches.
OnLaunchFailed(app_id, std::move(callback),
"Unknown Bruschetta app_id: " + app_id);
return;
}
// TODO(b/247638226): RecordAppLaunchHistogram(kRegisteredApp) to collect
// usage stats for successful launches.
// Update the last launched time.
registry()->AppLaunched(app_id);
// Start the bruschetta VM if necessary.
const std::string& vm_name = registration->VmName();
auto launcher =
bruschetta::BruschettaService::GetForProfile(profile())->GetLauncher(
vm_name);
if (!launcher) {
OnLaunchFailed(app_id, std::move(callback),
"Unknown Bruschetta VM name: " + vm_name);
return;
}
auto args = ArgsFromIntent(intent.get());
AddSpinner(app_id);
launcher->EnsureRunning(base::BindOnce(
[](Profile* profile, const std::string& app_id,
guest_os::GuestOsRegistryService::Registration registration,
int64_t display_id, const std::vector<guest_os::LaunchArg> args,
const std::string& vm_name, LaunchCallback callback,
bruschetta::BruschettaResult result) {
if (result != bruschetta::BruschettaResult::kSuccess) {
OnLaunchFailed(app_id, std::move(callback),
"Failed to start Bruschetta VM " + vm_name + ": " +
bruschetta::BruschettaResultString(result));
return;
}
LaunchApplication(profile, app_id, std::move(registration), display_id,
std::move(args), std::move(callback));
},
profile(), app_id, std::move(registration.value()), display_id,
std::move(args), vm_name, std::move(callback)));
}
void BruschettaApps::CreateAppOverrides(
const guest_os::GuestOsRegistryService::Registration& registration,
App* app) {
// TODO(b/247638042): Implement IsUninstallable and use it here.
}
void BruschettaApps::GetMenuModel(
const std::string& app_id,
MenuType menu_type,
int64_t display_id,
base::OnceCallback<void(MenuItems)> callback) {
MenuItems menu_items;
if (menu_type == MenuType::kShelf) {
AddCommandItem(ash::APP_CONTEXT_MENU_NEW_WINDOW, IDS_APP_LIST_NEW_WINDOW,
menu_items);
}
if (ShouldAddOpenItem(app_id, menu_type, profile())) {
AddCommandItem(ash::LAUNCH_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);
}
std::move(callback).Run(std::move(menu_items));
}
} // namespace apps