Commit 9a88453f authored by danielng's avatar danielng Committed by Commit Bot

borealis: Adding borealis installer

Implementing an installer for borealis, based on the install flow of
Plugin VM and Crostini. Currently there is no way of accessing the
installer (during testing I attached it to a dummy app entry I created).
Whilst the install process is quite lean right now, I left in some
structures, inspired by the other installers, that will be useful when
the installation flow gets more complex and/or when tests are added.
I haven't added any tests since things may be scrapped when proper UX
is decided, the installer is only being used internally and it exists
in a vacuum.

For more context into the installer, and Borealis, please see the
attached bug.

Tests: Tested manually on my machine.
Bug: b:161650651
Change-Id: I552c39f2ff4e0da86bc0f113bef6c4129e671a51
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2338275
Commit-Queue: Daniel Ng <danielng@google.com>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarNic Hollingum <hollingum@google.com>
Reviewed-by: default avatarDana Fried <dfried@chromium.org>
Cr-Commit-Position: refs/heads/master@{#797571}
parent 1daff4ec
......@@ -5432,6 +5432,66 @@
Hide password
</message>
<!-- Borealis strings -->
<!-- TODO(danielng): Add string descriptions and remove translateable tags
when strings are finalized. -->
<message name="IDS_BOREALIS_APP_NAME" desc="" translateable="false">
Borealis
</message>
<message name="IDS_BOREALIS_INSTALLER_CONFIRMATION_TITLE" desc="" translateable="false">
Set up <ph name="APP_NAME">$1<ex>BOREALIS</ex></ph>
</message>
<message name="IDS_BOREALIS_INSTALLER_CONFIRMATION_MESSAGE" desc="" translateable="false">
Set up Borealis on your device.
</message>
<message name="IDS_BOREALIS_INSTALLER_INSTALL_BUTTON" desc="" translateable="false">
Install
</message>
<message name="IDS_BOREALIS_INSTALLER_ENVIRONMENT_SETTING_TITLE" desc="" translateable="false">
Setting up <ph name="APP_NAME">$1<ex>BOREALIS</ex></ph>...
</message>
<message name="IDS_BOREALIS_INSTALLER_ERROR_TITLE" desc="" translateable="false">
Setup couldn't complete
</message>
<message name="IDS_BOREALIS_INSTALLER_FINISHED_TITLE" desc="" translateable="false">
Setup complete
</message>
<message name="IDS_BOREALIS_INSTALLER_IMPORTING_MESSAGE" desc="" translateable="false">
Configuring the virtual machine. This may take a few minutes.
</message>
<message name="IDS_BOREALIS_INSTALLER_IMPORTED_MESSAGE" desc="" translateable="false">
<ph name="APP_NAME">$1<ex>BOREALIS</ex></ph> is ready to use.
</message>
<message name="IDS_BOREALIS_INSTALLER_ERROR_MESSAGE" desc="" translateable="false">
Something went wrong.
</message>
<message name="IDS_BOREALIS_INSTALLER_RETRY_BUTTON" desc="" translateable="false">
Retry
</message>
<message name="IDS_BOREALIS_INSTALLER_LAUNCH_BUTTON" desc="" translateable="false">
Launch
</message>
<message name="IDS_BOREALIS_INSTALLER_NOT_ALLOWED_TITLE" desc="" translateable="false">
<ph name="APP_NAME">$1<ex>BOREALIS</ex></ph> needs permission to run
</message>
<message name="IDS_BOREALIS_INSTALLER_NOT_ALLOWED_MESSAGE" desc="" translateable="false">
<ph name="APP_NAME">$1<ex>BOREALIS</ex></ph> isn't allowed on this device. Contact your administrator. Error code: <ph name="ERROR_CODE">$2<ex>7</ex></ph>.
</message>
<message name="IDS_BOREALIS_DLC_INTERNAL_FAILED_MESSAGE" desc="" translateable="false">
Your device needs to be updated before you can use <ph name="APP_NAME">$1<ex>BOREALIS</ex></ph>.
</message>
<message name="IDS_BOREALIS_DLC_BUSY_FAILED_MESSAGE" desc="" translateable="false">
Something went wrong. Please wait a few minutes and run <ph name="APP_NAME">$1<ex>BOREALIS</ex></ph> again.
</message>
<message name="IDS_BOREALIS_DLC_NEED_REBOOT_FAILED_MESSAGE" desc="" translateable="false">
Please restart your device to use <ph name="APP_NAME">$1<ex>BOREALIS</ex></ph>.
</message>
<message name="IDS_BOREALIS_INSUFFICIENT_DISK_SPACE_MESSAGE" desc="" translateable="false">
Your device is low on storage. To increase free space, delete files from device.
</message>
<message name="IDS_BOREALIS_GENERIC_ERROR_MESSAGE" desc="" translateable="false">
Couldn't install <ph name="APP_NAME">$1<ex>BOREALIS</ex></ph>. Please try again later.
</message>
<!--
==================================================================================
If you change any IDS_EDU_LOGIN_INFO_* string, update kConsentScreenTextVersion in
......
......@@ -809,6 +809,14 @@ source_set("chromeos") {
"bluetooth/debug_logs_manager_factory.h",
"boot_times_recorder.cc",
"boot_times_recorder.h",
"borealis/borealis_installer.cc",
"borealis/borealis_installer.h",
"borealis/borealis_installer_factory.cc",
"borealis/borealis_installer_factory.h",
"borealis/borealis_installer_impl.cc",
"borealis/borealis_installer_impl.h",
"borealis/borealis_util.cc",
"borealis/borealis_util.h",
"browser_context_keyed_service_factories.cc",
"browser_context_keyed_service_factories.h",
"camera_detector.cc",
......@@ -3054,6 +3062,8 @@ source_set("unit_tests") {
"authpolicy/authpolicy_helper.unittest.cc",
"base/file_flusher_unittest.cc",
"bluetooth/debug_logs_manager_unittest.cc",
"borealis/borealis_installer_mock.cc",
"borealis/borealis_installer_mock.h",
"cert_provisioning/cert_provisioning_invalidator_unittest.cc",
"cert_provisioning/cert_provisioning_platform_keys_helpers_unittest.cc",
"cert_provisioning/cert_provisioning_scheduler_unittest.cc",
......
cpelling@google.com
danielng@google.com
hollingum@google.com
// 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/chromeos/borealis/borealis_installer.h"
namespace borealis {
BorealisInstaller::BorealisInstaller() = default;
BorealisInstaller::~BorealisInstaller() = default;
// static
std::string BorealisInstaller::GetInstallingStateName(InstallingState state) {
switch (state) {
case InstallingState::kInactive:
return "kInactive";
case InstallingState::kInstallingDlc:
return "kInstallingDlc";
}
}
void BorealisInstaller::AddObserver(Observer* observer) {
DCHECK(!observer);
observers_.AddObserver(observer);
}
void BorealisInstaller::RemoveObserver(Observer* observer) {
DCHECK(!observer);
observers_.RemoveObserver(observer);
}
} // namespace borealis
// 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.
#ifndef CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_H_
#define CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_H_
#include <memory>
#include <string>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "components/keyed_service/core/keyed_service.h"
namespace borealis {
class BorealisInstaller : public KeyedService {
public:
enum class InstallationResult {
kCompleted,
kCancelled,
kNotAllowed,
kOperationInProgress,
kDlcInternal,
kDlcUnsupported,
kDlcBusy,
kDlcNeedReboot,
kDlcNeedSpace,
kDlcUnknown,
};
enum class InstallingState {
kInactive,
kInstallingDlc,
};
// Observer class for the Borealis installation related events.
class Observer : public base::CheckedObserver {
public:
virtual void OnProgressUpdated(double fraction_complete) = 0;
virtual void OnStateUpdated(InstallingState new_state) = 0;
virtual void OnInstallationEnded(InstallationResult result) = 0;
virtual void OnCancelInitiated() = 0;
};
BorealisInstaller();
static std::string GetInstallingStateName(InstallingState state);
// Checks if an installation process is already running.
virtual bool IsProcessing() = 0;
// Start the installation process.
virtual void Start() = 0;
// Cancels the installation process.
virtual void Cancel() = 0;
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
protected:
~BorealisInstaller() override;
base::ObserverList<Observer> observers_;
private:
base::WeakPtrFactory<BorealisInstaller> weak_ptr_factory_{this};
};
} // namespace borealis
#endif // CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_H_
// 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/chromeos/borealis/borealis_installer_factory.h"
#include "chrome/browser/chromeos/borealis/borealis_installer_impl.h"
#include "chrome/browser/download/download_service_factory.h"
#include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
namespace borealis {
// static
BorealisInstaller* BorealisInstallerFactory::GetForProfile(Profile* profile) {
return static_cast<BorealisInstaller*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
// static
BorealisInstallerFactory* BorealisInstallerFactory::GetInstance() {
static base::NoDestructor<BorealisInstallerFactory> factory;
return factory.get();
}
BorealisInstallerFactory::BorealisInstallerFactory()
: BrowserContextKeyedServiceFactory(
"BorealisInstaller",
BrowserContextDependencyManager::GetInstance()) {
DependsOn(DownloadServiceFactory::GetInstance());
}
BorealisInstallerFactory::~BorealisInstallerFactory() = default;
KeyedService* BorealisInstallerFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
return new BorealisInstallerImpl();
}
content::BrowserContext* BorealisInstallerFactory::GetBrowserContextToUse(
content::BrowserContext* context) const {
return chrome::GetBrowserContextRedirectedInIncognito(context);
}
} // namespace borealis
// 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.
#ifndef CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_FACTORY_H_
#define CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_FACTORY_H_
#include "base/macros.h"
#include "base/no_destructor.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
namespace content {
class BrowserContext;
} // namespace content
class Profile;
namespace borealis {
class BorealisInstaller;
class BorealisInstallerFactory : public BrowserContextKeyedServiceFactory {
public:
static BorealisInstaller* GetForProfile(Profile* profile);
static BorealisInstallerFactory* GetInstance();
private:
friend base::NoDestructor<BorealisInstallerFactory>;
BorealisInstallerFactory();
~BorealisInstallerFactory() override;
// BrowserContextKeyedServiceFactory implementation.
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override;
content::BrowserContext* GetBrowserContextToUse(
content::BrowserContext* context) const override;
DISALLOW_COPY_AND_ASSIGN(BorealisInstallerFactory);
};
} // namespace borealis
#endif // CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_FACTORY_H_
// 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/chromeos/borealis/borealis_installer_impl.h"
#include "base/bind.h"
#include "chrome/browser/chromeos/borealis/borealis_util.h"
#include "content/public/browser/browser_thread.h"
namespace borealis {
BorealisInstallerImpl::BorealisInstallerImpl() = default;
BorealisInstallerImpl::~BorealisInstallerImpl() = default;
bool BorealisInstallerImpl::IsProcessing() {
return state_ != State::kIdle;
}
void BorealisInstallerImpl::Start() {
if (!IsBorealisAllowed()) {
LOG(ERROR) << "Installation of Borealis cannot be started because "
<< "Borealis is not allowed.";
InstallationEnded(InstallationResult::kNotAllowed);
return;
}
if (IsProcessing()) {
LOG(ERROR) << "Installation of Borealis is already in progress.";
InstallationEnded(InstallationResult::kOperationInProgress);
return;
}
progress_ = 0;
StartDlcInstallation();
}
void BorealisInstallerImpl::Cancel() {
state_ = State::kCancelling;
for (auto& observer : observers_) {
observer.OnCancelInitiated();
}
}
void BorealisInstallerImpl::StartDlcInstallation() {
state_ = State::kInstalling;
UpdateInstallingState(InstallingState::kInstallingDlc);
chromeos::DlcserviceClient::Get()->Install(
"borealis-dlc",
base::BindOnce(&BorealisInstallerImpl::OnDlcInstallationCompleted,
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(
&BorealisInstallerImpl::OnDlcInstallationProgressUpdated,
weak_ptr_factory_.GetWeakPtr()));
}
void BorealisInstallerImpl::InstallationEnded(InstallationResult result) {
state_ = State::kIdle;
installing_state_ = InstallingState::kInactive;
for (auto& observer : observers_) {
observer.OnInstallationEnded(result);
}
}
void BorealisInstallerImpl::UpdateProgress(double state_progress) {
DCHECK_EQ(state_, State::kInstalling);
if (state_progress < 0 || state_progress > 1) {
LOG(ERROR) << "Unexpected progress value " << state_progress
<< " in installing state "
<< GetInstallingStateName(installing_state_);
return;
}
double start_range = 0;
double end_range = 0;
switch (installing_state_) {
case InstallingState::kInstallingDlc:
start_range = 0;
end_range = 1;
break;
default:
NOTREACHED();
}
double new_progress =
start_range + (end_range - start_range) * state_progress;
if (new_progress < progress_) {
LOG(ERROR) << "Progress went backwards from " << progress_ << " to "
<< progress_;
return;
}
progress_ = new_progress;
for (auto& observer : observers_) {
observer.OnProgressUpdated(new_progress);
}
}
void BorealisInstallerImpl::UpdateInstallingState(
InstallingState installing_state) {
DCHECK_NE(installing_state, InstallingState::kInactive);
installing_state_ = installing_state;
for (auto& observer : observers_) {
observer.OnStateUpdated(installing_state_);
}
}
void BorealisInstallerImpl::OnDlcInstallationProgressUpdated(double progress) {
DCHECK_EQ(installing_state_, InstallingState::kInstallingDlc);
if (state_ == State::kCancelling)
return;
UpdateProgress(progress);
}
void BorealisInstallerImpl::OnDlcInstallationCompleted(
const chromeos::DlcserviceClient::InstallResult& install_result) {
DCHECK_EQ(installing_state_, InstallingState::kInstallingDlc);
if (state_ == State::kCancelling) {
// Since DLC installation is currently the only step of installation,
// Borealis has actually been installed by this stage. This is calling
// CancelFinished() instead of InstallFinished(), to help provide clarity
// and also make it easier to add new installation steps in the future.
InstallationEnded(InstallationResult::kCancelled);
return;
}
// If success, continue to the next state.
if (install_result.error == dlcservice::kErrorNone) {
InstallationEnded(InstallationResult::kCompleted);
return;
}
// At this point, the Borealis DLC installation has failed.
InstallationResult result = InstallationResult::kDlcUnknown;
if (install_result.error == dlcservice::kErrorInternal) {
LOG(ERROR) << "Something went wrong internally with DlcService.";
result = InstallationResult::kDlcInternal;
} else if (install_result.error == dlcservice::kErrorInvalidDlc) {
LOG(ERROR) << "Borealis DLC is not supported, need to enable Borealis DLC.";
result = InstallationResult::kDlcUnsupported;
} else if (install_result.error == dlcservice::kErrorBusy) {
LOG(ERROR)
<< "Borealis DLC is not able to be installed as dlcservice is busy.";
result = InstallationResult::kDlcBusy;
} else if (install_result.error == dlcservice::kErrorNeedReboot) {
LOG(ERROR)
<< "Device has pending update and needs a reboot to use Borealis DLC.";
result = InstallationResult::kDlcBusy;
} else if (install_result.error == dlcservice::kErrorAllocation) {
LOG(ERROR) << "Device needs to free space to use Borealis DLC.";
result = InstallationResult::kDlcNeedSpace;
} else {
LOG(ERROR) << "Failed to install Borealis DLC: " << install_result.error;
}
InstallationEnded(result);
}
} // namespace borealis
// 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.
#ifndef CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_IMPL_H_
#define CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_IMPL_H_
#include "chrome/browser/chromeos/borealis/borealis_installer.h"
#include "chromeos/dbus/dlcservice/dlcservice_client.h"
namespace borealis {
// This class is responsible for installing the Borealis VM. Currently
// the only installation requirements for Borealis is to install the
// relevant DLC component. The installer works with closesly with
// chrome/browser/ui/views/borealis/borealis_installer_view.h.
class BorealisInstallerImpl : public BorealisInstaller {
public:
BorealisInstallerImpl();
// Disallow copy and assign.
BorealisInstallerImpl(const BorealisInstallerImpl&) = delete;
BorealisInstallerImpl& operator=(const BorealisInstallerImpl&) = delete;
// Checks if an installation process is already running.
bool IsProcessing() override;
// Start the installation process.
void Start() override;
// Cancels the installation process.
void Cancel() override;
private:
enum class State {
kIdle,
kInstalling,
kCancelling,
};
~BorealisInstallerImpl() override;
void StartDlcInstallation();
void InstallationEnded(InstallationResult result);
void UpdateProgress(double state_progress);
void UpdateInstallingState(InstallingState installing_state);
void OnDlcInstallationProgressUpdated(double progress);
void OnDlcInstallationCompleted(
const chromeos::DlcserviceClient::InstallResult& install_result);
State state_ = State::kIdle;
InstallingState installing_state_ = InstallingState::kInactive;
double progress_ = 0;
base::WeakPtrFactory<BorealisInstallerImpl> weak_ptr_factory_{this};
};
} // namespace borealis
#endif // CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_IMPL_H_
// 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/chromeos/borealis/borealis_installer_mock.h"
namespace borealis {
namespace test {
BorealisInstallerMock::BorealisInstallerMock() = default;
BorealisInstallerMock::~BorealisInstallerMock() = default;
} // namespace test
} // namespace borealis
// 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.
#ifndef CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_MOCK_H_
#define CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_MOCK_H_
#include "chrome/browser/chromeos/borealis/borealis_installer.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace borealis {
namespace test {
class BorealisInstallerMock : public BorealisInstaller {
public:
BorealisInstallerMock();
~BorealisInstallerMock();
BorealisInstallerMock(const BorealisInstallerMock&) = delete;
BorealisInstallerMock& operator=(const BorealisInstallerMock&) = delete;
MOCK_METHOD(bool, IsProcessing, (), ());
MOCK_METHOD(void, Start, (), ());
MOCK_METHOD(void, Cancel, (), ());
};
} // namespace test
} // namespace borealis
#endif // CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_INSTALLER_MOCK_H_
// 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/chromeos/borealis/borealis_util.h"
#include "chrome/common/chrome_features.h"
namespace borealis {
bool IsBorealisAllowed() {
// Check that the Borealis feature is enabled.
return base::FeatureList::IsEnabled(features::kBorealis);
}
} // namespace borealis
// 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.
#ifndef CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_UTIL_H_
#define CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_UTIL_H_
class Profile;
namespace borealis {
// Checks if Borealis is allowed to run in the current environment.
bool IsBorealisAllowed();
// Shows the Borealis installer (borealis_installer_view).
void ShowBorealisInstallerView(Profile* profile);
} // namespace borealis
#endif // CHROME_BROWSER_CHROMEOS_BOREALIS_BOREALIS_UTIL_H_
......@@ -2000,6 +2000,8 @@ static_library("ui") {
"views/apps/chrome_native_app_window_views_aura_ash.h",
"views/arc_app_dialog_view.cc",
"views/arc_data_removal_dialog_view.cc",
"views/borealis/borealis_installer_view.cc",
"views/borealis/borealis_installer_view.h",
"views/chrome_views_delegate_chromeos.cc",
"views/crostini/crostini_ansible_software_config_view.cc",
"views/crostini/crostini_ansible_software_config_view.h",
......
file://chrome/browser/chromeos/borealis/OWNERS
# Please also consider adding an owner from chrome/browser/ui/views/OWNERS.
// 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();
}
// 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.
#ifndef CHROME_BROWSER_UI_VIEWS_BOREALIS_BOREALIS_INSTALLER_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_BOREALIS_BOREALIS_INSTALLER_VIEW_H_
#include "base/callback.h"
#include "base/macros.h"
#include "chrome/browser/chromeos/borealis/borealis_installer_impl.h"
#include "ui/views/window/dialog_delegate.h"
namespace views {
class BoxLayout;
class ImageView;
class Label;
class ProgressBar;
} // namespace views
class Profile;
// The front end for the Borealis installation process, works closely with
// "chrome/browser/chromeos/borealis/borealis_installer.h".
class BorealisInstallerView : public views::DialogDelegateView,
public borealis::BorealisInstaller::Observer {
public:
explicit BorealisInstallerView(Profile* profile);
// Disallow copy and assign.
BorealisInstallerView(const BorealisInstallerView&) = delete;
BorealisInstallerView& operator=(const BorealisInstallerView&) = delete;
static BorealisInstallerView* GetActiveViewForTesting();
// views::DialogDelegateView implementation.
bool ShouldShowCloseButton() const override;
bool ShouldShowWindowTitle() const override;
bool Accept() override;
bool Cancel() override;
gfx::Size CalculatePreferredSize() const override;
// borealis::BorealisInstaller::Observer implementation.
void OnStateUpdated(
borealis::BorealisInstaller::InstallingState new_state) override;
void OnProgressUpdated(double fraction_complete) override;
void OnInstallationEnded(
borealis::BorealisInstaller::InstallationResult result) override;
void OnCancelInitiated() override {}
// Public for testing purposes.
base::string16 GetPrimaryMessage() const;
base::string16 GetSecondaryMessage() const;
private:
class TitleLabel;
enum class State {
kConfirmInstall, // Waiting for user to start installation.
kInstalling, // Installation in progress.
kCompleted, // Installation process completed.
kError, // Something unexpected happened.
};
using InstallingState = borealis::BorealisInstaller::InstallingState;
~BorealisInstallerView() override;
// Returns the dialog buttons that should be displayed, based on the current
// |state_| and error |reason_| (if relevant).
int GetCurrentDialogButtons() const;
// Returns the label for a dialog |button|, based on the current |state_|
// and error |reason_| (if relevant).
base::string16 GetCurrentDialogButtonLabel(ui::DialogButton button) const;
void OnStateUpdated();
// views::DialogDelegateView implementation.
void AddedToWidget() override;
void SetPrimaryMessageLabel();
void SetSecondaryMessageLabel();
void SetImage();
void StartInstallation();
base::string16 app_name_;
views::Label* primary_message_label_ = nullptr;
views::Label* secondary_message_label_ = nullptr;
views::ProgressBar* progress_bar_ = nullptr;
views::Label* installation_progress_message_label_ = nullptr;
views::BoxLayout* lower_container_layout_ = nullptr;
views::ImageView* big_image_ = nullptr;
borealis::BorealisInstaller* borealis_installer_ = nullptr;
State state_ = State::kConfirmInstall;
InstallingState installing_state_ = InstallingState::kInactive;
base::Optional<borealis::BorealisInstaller::InstallationResult> result_;
};
#endif // CHROME_BROWSER_UI_VIEWS_BOREALIS_BOREALIS_INSTALLER_VIEW_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment