blob: 614487ca35ceb22a54b2ebc450cef0e8b7e6d605 [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/ui/views/borealis/borealis_installer_view.h"
#include <memory>
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/window_properties.h"
#include "base/bind.h"
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chromeos/borealis/borealis_installer_factory.h"
#include "chrome/browser/chromeos/borealis/borealis_util.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/grit/chrome_unscaled_resources.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/border.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/progress_bar.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view_class_properties.h"
namespace {
BorealisInstallerView* g_borealis_installer_view = nullptr;
constexpr gfx::Insets kButtonRowInsets(0, 64, 32, 64);
constexpr int kWindowWidth = 768;
constexpr int kWindowHeight = 636;
} // namespace
// Defined in chrome/browser/chromeos/borealis/borealis_util.h.
void borealis::ShowBorealisInstallerView(Profile* profile) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!g_borealis_installer_view) {
g_borealis_installer_view = new BorealisInstallerView(profile);
views::DialogDelegate::CreateDialogWidget(g_borealis_installer_view,
nullptr, nullptr);
// TODO(danielng): link dialog to shelf item with real values.
g_borealis_installer_view->GetWidget()->GetNativeWindow()->SetProperty(
ash::kShelfIDKey,
ash::ShelfID("lgjpclljbbmphhnalkeplcmnborealis").Serialize());
}
g_borealis_installer_view->SetButtonRowInsets(kButtonRowInsets);
g_borealis_installer_view->GetWidget()->Show();
}
// We need a separate class so that we can alert screen readers appropriately
// when the text changes.
class BorealisInstallerView::TitleLabel : public views::Label {
public:
using Label::Label;
TitleLabel() {}
~TitleLabel() override {}
void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
node_data->SetName(GetText());
node_data->role = ax::mojom::Role::kStatus;
}
};
// TODO(danielng):revisit UI elements when UX input is provided.
// Currently using the UI specs that the Plugin VM installer use.
BorealisInstallerView::BorealisInstallerView(Profile* profile)
: app_name_(l10n_util::GetStringUTF16(IDS_BOREALIS_APP_NAME)),
borealis_installer_(
borealis::BorealisInstallerFactory::GetForProfile(profile)) {
// Layout constants from the spec used for the plugin vm installer.
gfx::Insets kDialogInsets(60, 64, 0, 64);
const int kPrimaryMessageHeight = views::style::GetLineHeight(
CONTEXT_HEADLINE, views::style::STYLE_PRIMARY);
const int kSecondaryMessageHeight = views::style::GetLineHeight(
CONTEXT_BODY_TEXT_LARGE, views::style::STYLE_SECONDARY);
const int kInstallationProgressMessageHeight = views::style::GetLineHeight(
CONTEXT_BODY_TEXT_SMALL, views::style::STYLE_SECONDARY);
constexpr int kProgressBarHeight = 5;
constexpr int kProgressBarTopMargin = 32;
SetDefaultButton(ui::DIALOG_BUTTON_OK);
SetCanMinimize(true);
set_draggable(true);
// Removed margins so dialog insets specify it instead.
set_margins(gfx::Insets());
views::BoxLayout* layout =
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, kDialogInsets));
views::View* upper_container_view =
AddChildView(std::make_unique<views::View>());
upper_container_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets()));
AddChildView(upper_container_view);
views::View* lower_container_view =
AddChildView(std::make_unique<views::View>());
lower_container_layout_ =
lower_container_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
AddChildView(lower_container_view);
primary_message_label_ = new TitleLabel(GetPrimaryMessage(), CONTEXT_HEADLINE,
views::style::STYLE_PRIMARY);
primary_message_label_->SetProperty(
views::kMarginsKey, gfx::Insets(kPrimaryMessageHeight, 0, 0, 0));
primary_message_label_->SetMultiLine(false);
primary_message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
upper_container_view->AddChildView(primary_message_label_);
views::View* secondary_message_container_view =
AddChildView(std::make_unique<views::View>());
secondary_message_container_view->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets(kSecondaryMessageHeight, 0, 0, 0)));
upper_container_view->AddChildView(secondary_message_container_view);
secondary_message_label_ =
new views::Label(GetSecondaryMessage(), CONTEXT_BODY_TEXT_LARGE,
views::style::STYLE_SECONDARY);
secondary_message_label_->SetMultiLine(true);
secondary_message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
secondary_message_container_view->AddChildView(secondary_message_label_);
progress_bar_ = new views::ProgressBar(kProgressBarHeight);
progress_bar_->SetProperty(
views::kMarginsKey,
gfx::Insets(kProgressBarTopMargin - kProgressBarHeight, 0, 0, 0));
upper_container_view->AddChildView(progress_bar_);
installation_progress_message_label_ = new views::Label(
base::string16(), CONTEXT_BODY_TEXT_SMALL, views::style::STYLE_SECONDARY);
installation_progress_message_label_->SetEnabledColor(gfx::kGoogleGrey700);
installation_progress_message_label_->SetProperty(
views::kMarginsKey,
gfx::Insets(kInstallationProgressMessageHeight, 0, 0, 0));
installation_progress_message_label_->SetMultiLine(false);
installation_progress_message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
upper_container_view->AddChildView(installation_progress_message_label_);
big_image_ = new views::ImageView();
lower_container_view->AddChildView(big_image_);
// Make sure the lower_container_view is pinned to the bottom of the dialog.
lower_container_layout_->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kEnd);
layout->SetFlexForView(lower_container_view, 1, true);
}
BorealisInstallerView::~BorealisInstallerView() {
borealis_installer_->RemoveObserver(this);
g_borealis_installer_view = nullptr;
}
// static
BorealisInstallerView* BorealisInstallerView::GetActiveViewForTesting() {
return g_borealis_installer_view;
}
bool BorealisInstallerView::ShouldShowCloseButton() const {
return true;
}
bool BorealisInstallerView::ShouldShowWindowTitle() const {
return false;
}
bool BorealisInstallerView::Accept() {
if (state_ == State::kConfirmInstall) {
StartInstallation();
return false;
}
if (state_ == State::kCompleted) {
// Launch button has been clicked.
// TODO(danielng): Link to launch VM command, once implemented.
return true;
}
DCHECK_EQ(state_, State::kError);
// Retry button has been clicked to retry setting of Borealis environment
// after error occurred.
StartInstallation();
return false;
}
bool BorealisInstallerView::Cancel() {
if (state_ == State::kConfirmInstall || state_ == State::kInstalling) {
borealis_installer_->Cancel();
}
return true;
}
void BorealisInstallerView::OnStateUpdated(InstallingState new_state) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::kInstalling);
DCHECK_NE(new_state, InstallingState::kInactive);
installing_state_ = new_state;
OnStateUpdated();
}
void BorealisInstallerView::OnProgressUpdated(double fraction_complete) {
progress_bar_->SetValue(fraction_complete);
}
void BorealisInstallerView::OnInstallationEnded(
borealis::BorealisInstaller::InstallationResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
switch (result) {
using ResultEnum = borealis::BorealisInstaller::InstallationResult;
case ResultEnum::kCompleted:
DCHECK_EQ(installing_state_, InstallingState::kInstallingDlc);
state_ = State::kCompleted;
break;
case ResultEnum::kCancelled:
break;
// At this point we know an error has occurred.
default:
state_ = State::kError;
result_ = result;
break;
}
installing_state_ = InstallingState::kInactive;
OnStateUpdated();
}
gfx::Size BorealisInstallerView::CalculatePreferredSize() const {
return gfx::Size(kWindowWidth, kWindowHeight);
}
base::string16 BorealisInstallerView::GetPrimaryMessage() const {
switch (state_) {
case State::kConfirmInstall:
return l10n_util::GetStringFUTF16(
IDS_BOREALIS_INSTALLER_CONFIRMATION_TITLE, app_name_);
case State::kInstalling:
return l10n_util::GetStringFUTF16(
IDS_BOREALIS_INSTALLER_ENVIRONMENT_SETTING_TITLE, app_name_);
case State::kCompleted:
return l10n_util::GetStringUTF16(IDS_BOREALIS_INSTALLER_FINISHED_TITLE);
case State::kError:
DCHECK(result_);
switch (*result_) {
case borealis::BorealisInstaller::InstallationResult::kNotAllowed:
return l10n_util::GetStringFUTF16(
IDS_BOREALIS_INSTALLER_NOT_ALLOWED_TITLE, app_name_);
default:
return l10n_util::GetStringUTF16(IDS_BOREALIS_INSTALLER_ERROR_TITLE);
}
}
}
base::string16 BorealisInstallerView::GetSecondaryMessage() const {
switch (state_) {
case State::kConfirmInstall:
return l10n_util::GetStringUTF16(
IDS_BOREALIS_INSTALLER_CONFIRMATION_MESSAGE);
case State::kInstalling:
return l10n_util::GetStringUTF16(
IDS_BOREALIS_INSTALLER_IMPORTING_MESSAGE);
case State::kCompleted:
return l10n_util::GetStringFUTF16(IDS_BOREALIS_INSTALLER_IMPORTED_MESSAGE,
app_name_);
case State::kError:
using ResultEnum = borealis::BorealisInstaller::InstallationResult;
DCHECK(result_);
switch (*result_) {
default:
case ResultEnum::kOperationInProgress:
return l10n_util::GetStringFUTF16(
IDS_BOREALIS_GENERIC_ERROR_MESSAGE, app_name_,
base::NumberToString16(
static_cast<std::underlying_type_t<ResultEnum>>(*result_)));
case ResultEnum::kNotAllowed:
case ResultEnum::kDlcUnsupported:
return l10n_util::GetStringFUTF16(
IDS_BOREALIS_INSTALLER_NOT_ALLOWED_MESSAGE, app_name_,
base::NumberToString16(
static_cast<std::underlying_type_t<ResultEnum>>(*result_)));
// DLC Failures.
case ResultEnum::kDlcInternal:
return l10n_util::GetStringFUTF16(
IDS_BOREALIS_DLC_INTERNAL_FAILED_MESSAGE, app_name_);
case ResultEnum::kDlcBusy:
return l10n_util::GetStringFUTF16(
IDS_BOREALIS_DLC_BUSY_FAILED_MESSAGE, app_name_);
case ResultEnum::kDlcNeedReboot:
return l10n_util::GetStringFUTF16(
IDS_BOREALIS_DLC_NEED_REBOOT_FAILED_MESSAGE, app_name_);
case ResultEnum::kDlcNeedSpace:
return l10n_util::GetStringFUTF16(
IDS_BOREALIS_INSUFFICIENT_DISK_SPACE_MESSAGE, app_name_);
case ResultEnum::kDlcUnknown:
return l10n_util::GetStringFUTF16(IDS_BOREALIS_GENERIC_ERROR_MESSAGE,
app_name_);
}
}
}
int BorealisInstallerView::GetCurrentDialogButtons() const {
switch (state_) {
case State::kInstalling:
return ui::DIALOG_BUTTON_CANCEL;
case State::kConfirmInstall:
case State::kCompleted:
return ui::DIALOG_BUTTON_CANCEL | ui::DIALOG_BUTTON_OK;
case State::kError:
DCHECK(result_);
switch (*result_) {
case borealis::BorealisInstaller::InstallationResult::kNotAllowed:
return ui::DIALOG_BUTTON_CANCEL;
default:
return ui::DIALOG_BUTTON_CANCEL | ui::DIALOG_BUTTON_OK;
}
}
}
base::string16 BorealisInstallerView::GetCurrentDialogButtonLabel(
ui::DialogButton button) const {
switch (state_) {
case State::kConfirmInstall:
return l10n_util::GetStringUTF16(
button == ui::DIALOG_BUTTON_OK ? IDS_BOREALIS_INSTALLER_INSTALL_BUTTON
: IDS_APP_CANCEL);
case State::kInstalling:
DCHECK_EQ(button, ui::DIALOG_BUTTON_CANCEL);
return l10n_util::GetStringUTF16(IDS_APP_CANCEL);
case State::kCompleted: {
return l10n_util::GetStringUTF16(
button == ui::DIALOG_BUTTON_OK ? IDS_BOREALIS_INSTALLER_LAUNCH_BUTTON
: IDS_APP_CLOSE);
}
case State::kError: {
DCHECK(result_);
switch (*result_) {
case borealis::BorealisInstaller::InstallationResult::kNotAllowed:
DCHECK_EQ(button, ui::DIALOG_BUTTON_CANCEL);
return l10n_util::GetStringUTF16(IDS_APP_CANCEL);
default:
return l10n_util::GetStringUTF16(
button == ui::DIALOG_BUTTON_OK
? IDS_BOREALIS_INSTALLER_RETRY_BUTTON
: IDS_APP_CANCEL);
}
}
}
}
void BorealisInstallerView::OnStateUpdated() {
SetPrimaryMessageLabel();
SetSecondaryMessageLabel();
SetImage();
// todo(danielng): ensure button labels meet a11y requirements.
int buttons = GetCurrentDialogButtons();
SetButtons(buttons);
if (buttons & ui::DIALOG_BUTTON_OK) {
SetButtonLabel(ui::DIALOG_BUTTON_OK,
GetCurrentDialogButtonLabel(ui::DIALOG_BUTTON_OK));
}
if (buttons & ui::DIALOG_BUTTON_CANCEL) {
SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
GetCurrentDialogButtonLabel(ui::DIALOG_BUTTON_CANCEL));
}
const bool progress_bar_visible = state_ == State::kInstalling;
progress_bar_->SetVisible(progress_bar_visible);
DialogModelChanged();
primary_message_label_->NotifyAccessibilityEvent(
ax::mojom::Event::kLiveRegionChanged,
/* send_native_event = */ true);
}
void BorealisInstallerView::AddedToWidget() {
// At this point GetWidget() is guaranteed to return non-null.
OnStateUpdated();
}
void BorealisInstallerView::SetPrimaryMessageLabel() {
primary_message_label_->SetText(GetPrimaryMessage());
primary_message_label_->SetVisible(true);
primary_message_label_->NotifyAccessibilityEvent(
ax::mojom::Event::kTextChanged, true);
}
void BorealisInstallerView::SetSecondaryMessageLabel() {
secondary_message_label_->SetText(GetSecondaryMessage());
secondary_message_label_->SetVisible(true);
secondary_message_label_->NotifyAccessibilityEvent(
ax::mojom::Event::kTextChanged, true);
}
void BorealisInstallerView::SetImage() {
constexpr gfx::Size kRegularImageSize(314, 191);
constexpr gfx::Size kErrorImageSize(264, 264);
constexpr int kRegularImageBottomInset = 52 + 57;
constexpr int kErrorImageBottomInset = 52;
auto setImage = [this](int image_id, gfx::Size size, int bottom_inset) {
big_image_->SetImageSize(size);
lower_container_layout_->set_inside_border_insets(
gfx::Insets(0, 0, bottom_inset, 0));
big_image_->SetImage(
ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(image_id));
};
// todo(danielng):Use Borealis images.
if (state_ == State::kError) {
setImage(IDR_PLUGIN_VM_INSTALLER_ERROR, kErrorImageSize,
kErrorImageBottomInset);
return;
}
setImage(IDR_PLUGIN_VM_INSTALLER, kRegularImageSize,
kRegularImageBottomInset);
}
void BorealisInstallerView::StartInstallation() {
state_ = State::kInstalling;
progress_bar_->SetValue(0);
OnStateUpdated();
borealis_installer_->AddObserver(this);
borealis_installer_->Start();
}