Commit 7a1d56b0 authored by Nicholas Verne's avatar Nicholas Verne Committed by Commit Bot

Refactor Crostini Export/Import business logic.

This is to allow different UI treatments.

Currently, Export/Import progress is surfaced in ChromeOS notifications.
The current design for the container upgrade needs to show export progress in
main dialog rather than a notification. This refactor enables both UI options.

Bug: 1024693
Change-Id: Icb39ae531dab34b673ad0e5f86ee4de804665c8b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1930271
Auto-Submit: Nicholas Verne <nverne@chromium.org>
Commit-Queue: Nicholas Verne <nverne@chromium.org>
Reviewed-by: default avatarJulian Watson <juwa@google.com>
Cr-Commit-Position: refs/heads/master@{#720366}
parent ca55861f
......@@ -820,6 +820,8 @@ source_set("chromeos") {
"crostini/crostini_export_import.h",
"crostini/crostini_export_import_notification.cc",
"crostini/crostini_export_import_notification.h",
"crostini/crostini_export_import_status_tracker.cc",
"crostini/crostini_export_import_status_tracker.h",
"crostini/crostini_features.cc",
"crostini/crostini_features.h",
"crostini/crostini_force_close_watcher.cc",
......
......@@ -68,6 +68,21 @@ class CrostiniExportImport : public KeyedService,
bool in_progress) = 0;
};
using TrackerFactory =
base::OnceCallback<CrostiniExportImportStatusTracker*(ExportImportType,
base::FilePath)>;
struct OperationData {
OperationData(ExportImportType type,
ContainerId id,
TrackerFactory factory);
~OperationData();
ExportImportType type;
ContainerId container_id;
TrackerFactory tracker_factory;
};
static CrostiniExportImport* GetForProfile(Profile* profile);
explicit CrostiniExportImport(Profile* profile);
......@@ -122,13 +137,19 @@ class CrostiniExportImport : public KeyedService,
TestImportFailArchitecture);
FRIEND_TEST_ALL_PREFIXES(CrostiniExportImportTest, TestImportFailSpace);
OperationData* NewOperationData(ExportImportType type,
ContainerId id,
TrackerFactory cb);
OperationData* NewOperationData(ExportImportType type, ContainerId id);
OperationData* NewOperationData(ExportImportType type);
// ui::SelectFileDialog::Listener implementation.
void FileSelected(const base::FilePath& path,
int index,
void* params) override;
void FileSelectionCanceled(void* params) override;
void Start(ExportImportType type,
ContainerId container_id,
void Start(OperationData* params,
base::FilePath path,
CrostiniManager::CrostiniResultCallback callback);
......@@ -175,20 +196,24 @@ class CrostiniExportImport : public KeyedService,
CrostiniManager::CrostiniResultCallback callback,
CrostiniResult result);
void OpenFileDialog(ExportImportType type,
void OpenFileDialog(OperationData* params,
content::WebContents* web_contents);
std::string GetUniqueNotificationId();
CrostiniExportImportNotification& RemoveNotification(
std::map<ContainerId, CrostiniExportImportNotification*>::iterator it);
CrostiniExportImportStatusTracker& RemoveTracker(
std::map<ContainerId, CrostiniExportImportStatusTracker*>::iterator it);
Profile* profile_;
scoped_refptr<ui::SelectFileDialog> select_folder_dialog_;
std::map<ContainerId, CrostiniExportImportNotification*> notifications_;
// Notifications must have unique-per-profile identifiers.
std::map<ContainerId, CrostiniExportImportStatusTracker*> status_trackers_;
// |operation_data_storage_| persists the data required to complete an
// operation while the file selection dialog is open/operation is in progress.
std::unordered_map<OperationData*, std::unique_ptr<OperationData>>
operation_data_storage_;
// Trackers must have unique-per-profile identifiers.
// A non-static member on a profile-keyed-service will suffice.
int next_notification_id_;
int next_status_tracker_id_;
base::ObserverList<Observer> observers_;
// weak_ptr_factory_ should always be last member.
base::WeakPtrFactory<CrostiniExportImport> weak_ptr_factory_{this};
......
......@@ -38,12 +38,9 @@ CrostiniExportImportNotification::CrostiniExportImportNotification(
const std::string& notification_id,
base::FilePath path,
ContainerId container_id)
: profile_(profile),
type_(type),
path_(std::move(path)),
: CrostiniExportImportStatusTracker(type, std::move(path)),
profile_(profile),
container_id_(std::move(container_id)) {
DCHECK(type == ExportImportType::EXPORT || type == ExportImportType::IMPORT);
message_center::RichNotificationData rich_notification_data;
rich_notification_data.vector_small_image = &kNotificationLinuxIcon;
rich_notification_data.accent_color = ash::kSystemNotificationColorNormal;
......@@ -60,7 +57,6 @@ CrostiniExportImportNotification::CrostiniExportImportNotification(
rich_notification_data,
base::MakeRefCounted<message_center::ThunkNotificationDelegate>(
weak_ptr_factory_.GetWeakPtr()));
SetStatusRunning(0);
}
......@@ -74,17 +70,8 @@ void CrostiniExportImportNotification::ForceRedisplay() {
/*metadata=*/nullptr);
}
void CrostiniExportImportNotification::SetStatusRunning(int progress_percent) {
DCHECK(status_ == Status::RUNNING || status_ == Status::CANCELLING);
// Progress updates can still be received while the notification is being
// cancelled. These should not be displayed, as the operation will eventually
// cancel (or fail to cancel).
if (status_ == Status::CANCELLING) {
return;
}
status_ = Status::RUNNING;
void CrostiniExportImportNotification::SetStatusRunningUI(
int progress_percent) {
if (hidden_) {
return;
}
......@@ -92,7 +79,7 @@ void CrostiniExportImportNotification::SetStatusRunning(int progress_percent) {
notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS);
notification_->set_accent_color(ash::kSystemNotificationColorNormal);
notification_->set_title(l10n_util::GetStringUTF16(
type_ == ExportImportType::EXPORT
type() == ExportImportType::EXPORT
? IDS_CROSTINI_EXPORT_NOTIFICATION_TITLE_RUNNING
: IDS_CROSTINI_IMPORT_NOTIFICATION_TITLE_RUNNING));
notification_->set_message(
......@@ -106,11 +93,7 @@ void CrostiniExportImportNotification::SetStatusRunning(int progress_percent) {
ForceRedisplay();
}
void CrostiniExportImportNotification::SetStatusCancelling() {
DCHECK(status_ == Status::RUNNING);
status_ = Status::CANCELLING;
void CrostiniExportImportNotification::SetStatusCancellingUI() {
if (hidden_) {
return;
}
......@@ -118,7 +101,7 @@ void CrostiniExportImportNotification::SetStatusCancelling() {
notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS);
notification_->set_accent_color(ash::kSystemNotificationColorNormal);
notification_->set_title(l10n_util::GetStringUTF16(
type_ == ExportImportType::EXPORT
type() == ExportImportType::EXPORT
? IDS_CROSTINI_EXPORT_NOTIFICATION_TITLE_CANCELLING
: IDS_CROSTINI_IMPORT_NOTIFICATION_TITLE_CANCELLING));
notification_->set_message({});
......@@ -130,20 +113,15 @@ void CrostiniExportImportNotification::SetStatusCancelling() {
ForceRedisplay();
}
void CrostiniExportImportNotification::SetStatusDone() {
DCHECK(status_ == Status::RUNNING ||
(type_ == ExportImportType::IMPORT && status_ == Status::CANCELLING));
status_ = Status::DONE;
void CrostiniExportImportNotification::SetStatusDoneUI() {
notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
notification_->set_accent_color(ash::kSystemNotificationColorNormal);
notification_->set_title(l10n_util::GetStringUTF16(
type_ == ExportImportType::EXPORT
type() == ExportImportType::EXPORT
? IDS_CROSTINI_EXPORT_NOTIFICATION_TITLE_DONE
: IDS_CROSTINI_IMPORT_NOTIFICATION_TITLE_DONE));
notification_->set_message(l10n_util::GetStringUTF16(
type_ == ExportImportType::EXPORT
type() == ExportImportType::EXPORT
? IDS_CROSTINI_EXPORT_NOTIFICATION_MESSAGE_DONE
: IDS_CROSTINI_IMPORT_NOTIFICATION_MESSAGE_DONE));
notification_->set_buttons({});
......@@ -153,64 +131,18 @@ void CrostiniExportImportNotification::SetStatusDone() {
ForceRedisplay();
}
void CrostiniExportImportNotification::SetStatusCancelled() {
DCHECK(status_ == Status::CANCELLING);
status_ = Status::CANCELLED;
void CrostiniExportImportNotification::SetStatusCancelledUI() {
NotificationDisplayService::GetForProfile(profile_)->Close(
NotificationHandler::Type::TRANSIENT, notification_->id());
}
void CrostiniExportImportNotification::SetStatusFailed() {
SetStatusFailed(Status::FAILED_UNKNOWN_REASON,
l10n_util::GetStringUTF16(
type_ == ExportImportType::EXPORT
? IDS_CROSTINI_EXPORT_NOTIFICATION_MESSAGE_FAILED
: IDS_CROSTINI_IMPORT_NOTIFICATION_MESSAGE_FAILED));
}
void CrostiniExportImportNotification::SetStatusFailedArchitectureMismatch(
const std::string& architecture_container,
const std::string& architecture_device) {
DCHECK(type_ == ExportImportType::IMPORT);
SetStatusFailed(
Status::FAILED_ARCHITECTURE_MISMATCH,
l10n_util::GetStringFUTF16(
IDS_CROSTINI_IMPORT_NOTIFICATION_MESSAGE_FAILED_ARCHITECTURE,
base::ASCIIToUTF16(architecture_container),
base::ASCIIToUTF16(architecture_device)));
}
void CrostiniExportImportNotification::SetStatusFailedInsufficientSpace(
uint64_t additional_required_space) {
DCHECK(type_ == ExportImportType::IMPORT);
SetStatusFailed(Status::FAILED_INSUFFICIENT_SPACE,
l10n_util::GetStringFUTF16(
IDS_CROSTINI_IMPORT_NOTIFICATION_MESSAGE_FAILED_SPACE,
ui::FormatBytes(additional_required_space)));
}
void CrostiniExportImportNotification::SetStatusFailedConcurrentOperation(
ExportImportType in_progress_operation_type) {
SetStatusFailed(
Status::FAILED_CONCURRENT_OPERATION,
l10n_util::GetStringUTF16(
in_progress_operation_type == ExportImportType::EXPORT
? IDS_CROSTINI_EXPORT_NOTIFICATION_MESSAGE_FAILED_IN_PROGRESS
: IDS_CROSTINI_IMPORT_NOTIFICATION_MESSAGE_FAILED_IN_PROGRESS));
}
void CrostiniExportImportNotification::SetStatusFailed(
void CrostiniExportImportNotification::SetStatusFailedWithMessageUI(
Status status,
const base::string16& message) {
DCHECK(status_ == Status::RUNNING || status_ == Status::CANCELLING);
status_ = status;
notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
notification_->set_accent_color(ash::kSystemNotificationColorCriticalWarning);
notification_->set_title(l10n_util::GetStringUTF16(
type_ == ExportImportType::EXPORT
type() == ExportImportType::EXPORT
? IDS_CROSTINI_EXPORT_NOTIFICATION_TITLE_FAILED
: IDS_CROSTINI_IMPORT_NOTIFICATION_TITLE_FAILED));
notification_->set_message(message);
......@@ -222,7 +154,7 @@ void CrostiniExportImportNotification::SetStatusFailed(
}
void CrostiniExportImportNotification::Close(bool by_user) {
switch (status_) {
switch (status()) {
case Status::RUNNING:
case Status::CANCELLING:
hidden_ = true;
......@@ -243,18 +175,18 @@ void CrostiniExportImportNotification::Close(bool by_user) {
void CrostiniExportImportNotification::Click(
const base::Optional<int>& button_index,
const base::Optional<base::string16>&) {
switch (status_) {
switch (status()) {
case Status::RUNNING:
if (button_index) {
DCHECK(*button_index == 1);
CrostiniExportImport::GetForProfile(profile_)->CancelOperation(
type_, container_id_);
type(), container_id_);
}
return;
case Status::DONE:
DCHECK(!button_index);
if (type_ == ExportImportType::EXPORT) {
platform_util::ShowItemInFolder(profile_, path_);
if (type() == ExportImportType::EXPORT) {
platform_util::ShowItemInFolder(profile_, path());
}
return;
case Status::FAILED_UNKNOWN_REASON:
......
......@@ -10,6 +10,7 @@
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/crostini/crostini_export_import_status_tracker.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "ui/message_center/public/cpp/notification_delegate.h"
......@@ -25,53 +26,24 @@ enum class ExportImportType;
// Notification for Crostini export and import.
class CrostiniExportImportNotification
: public message_center::NotificationObserver {
: public CrostiniExportImportStatusTracker,
public message_center::NotificationObserver {
public:
enum class Status {
RUNNING,
CANCELLING,
DONE,
CANCELLED,
FAILED_UNKNOWN_REASON,
FAILED_ARCHITECTURE_MISMATCH,
FAILED_INSUFFICIENT_SPACE,
FAILED_CONCURRENT_OPERATION,
};
// Used to construct CrostiniExportImportNotification to ensure it controls
// its lifetime.
static CrostiniExportImportNotification* Create(
static CrostiniExportImportStatusTracker* Create(
Profile* profile,
ExportImportType type,
ContainerId container_id,
const std::string& notification_id,
base::FilePath path,
ContainerId container_id) {
ExportImportType type,
base::FilePath path) {
return new CrostiniExportImportNotification(profile, type, notification_id,
std::move(path),
std::move(container_id));
}
virtual ~CrostiniExportImportNotification();
// Can be used to draw attention to the notification without changing its
// status, even if it has been hidden.
void ForceRedisplay();
void SetStatusRunning(int progress_percent);
void SetStatusCancelling();
void SetStatusDone();
void SetStatusCancelled();
void SetStatusFailed();
void SetStatusFailedArchitectureMismatch(
const std::string& architecture_container,
const std::string& architecture_device);
void SetStatusFailedInsufficientSpace(uint64_t additional_required_space);
void SetStatusFailedConcurrentOperation(
ExportImportType in_progress_operation_type);
Status status() const { return status_; }
ExportImportType type() const { return type_; }
const base::FilePath& path() const { return path_; }
~CrostiniExportImportNotification() override;
// Getters for testing.
message_center::Notification* get_notification() {
return notification_.get();
......@@ -89,13 +61,18 @@ class CrostiniExportImportNotification
base::FilePath path,
ContainerId container_id);
void SetStatusFailed(Status status, const base::string16& message);
// CrostiniExportImportStatusTracker:
void ForceRedisplay() override;
void SetStatusRunningUI(int progress_percent) override;
void SetStatusCancellingUI() override;
void SetStatusDoneUI() override;
void SetStatusCancelledUI() override;
void SetStatusFailedWithMessageUI(Status status,
const base::string16& message) override;
Profile* profile_;
ExportImportType type_;
base::FilePath path_;
Profile* profile_; // Not owned.
ContainerId container_id_;
Status status_ = Status::RUNNING;
// Time when the operation started. Used for estimating time remaining.
base::TimeTicks started_ = base::TimeTicks::Now();
std::unique_ptr<message_center::Notification> notification_;
......
// Copyright 2019 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/crostini/crostini_export_import_status_tracker.h"
#include "base/logging.h"
#include "chrome/browser/chromeos/crostini/crostini_export_import.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/text/bytes_formatting.h"
namespace crostini {
CrostiniExportImportStatusTracker::CrostiniExportImportStatusTracker(
ExportImportType type,
base::FilePath path)
: type_(type), path_(path) {
DCHECK(type == ExportImportType::EXPORT || type == ExportImportType::IMPORT);
}
CrostiniExportImportStatusTracker::~CrostiniExportImportStatusTracker() =
default;
void CrostiniExportImportStatusTracker::SetStatusRunning(int progress_percent) {
DCHECK(status_ == Status::RUNNING || status_ == Status::CANCELLING);
// Progress updates can still be received while the notification is being
// cancelled. These should not be displayed, as the operation will eventually
// cancel (or fail to cancel).
if (status_ == Status::CANCELLING) {
return;
}
status_ = Status::RUNNING;
SetStatusRunningUI(progress_percent);
}
void CrostiniExportImportStatusTracker::SetStatusCancelling() {
DCHECK(status_ == Status::RUNNING);
status_ = Status::CANCELLING;
SetStatusCancellingUI();
}
void CrostiniExportImportStatusTracker::SetStatusDone() {
DCHECK(status_ == Status::RUNNING ||
(type() == ExportImportType::IMPORT && status_ == Status::CANCELLING));
status_ = Status::DONE;
SetStatusDoneUI();
}
void CrostiniExportImportStatusTracker::SetStatusCancelled() {
DCHECK(status_ == Status::CANCELLING);
status_ = Status::CANCELLED;
SetStatusCancelledUI();
}
void CrostiniExportImportStatusTracker::SetStatusFailed() {
SetStatusFailedWithMessage(
Status::FAILED_UNKNOWN_REASON,
l10n_util::GetStringUTF16(
type() == ExportImportType::EXPORT
? IDS_CROSTINI_EXPORT_NOTIFICATION_MESSAGE_FAILED
: IDS_CROSTINI_IMPORT_NOTIFICATION_MESSAGE_FAILED));
}
void CrostiniExportImportStatusTracker::SetStatusFailedArchitectureMismatch(
const std::string& architecture_container,
const std::string& architecture_device) {
DCHECK(type() == ExportImportType::IMPORT);
SetStatusFailedWithMessage(
Status::FAILED_ARCHITECTURE_MISMATCH,
l10n_util::GetStringFUTF16(
IDS_CROSTINI_IMPORT_NOTIFICATION_MESSAGE_FAILED_ARCHITECTURE,
base::ASCIIToUTF16(architecture_container),
base::ASCIIToUTF16(architecture_device)));
}
void CrostiniExportImportStatusTracker::SetStatusFailedInsufficientSpace(
uint64_t additional_required_space) {
DCHECK(type() == ExportImportType::IMPORT);
SetStatusFailedWithMessage(
Status::FAILED_INSUFFICIENT_SPACE,
l10n_util::GetStringFUTF16(
IDS_CROSTINI_IMPORT_NOTIFICATION_MESSAGE_FAILED_SPACE,
ui::FormatBytes(additional_required_space)));
}
void CrostiniExportImportStatusTracker::SetStatusFailedConcurrentOperation(
ExportImportType in_progress_operation_type) {
SetStatusFailedWithMessage(
Status::FAILED_CONCURRENT_OPERATION,
l10n_util::GetStringUTF16(
in_progress_operation_type == ExportImportType::EXPORT
? IDS_CROSTINI_EXPORT_NOTIFICATION_MESSAGE_FAILED_IN_PROGRESS
: IDS_CROSTINI_IMPORT_NOTIFICATION_MESSAGE_FAILED_IN_PROGRESS));
}
void CrostiniExportImportStatusTracker::SetStatusFailedWithMessage(
Status status,
const base::string16& message) {
DCHECK(status_ == Status::RUNNING || status_ == Status::CANCELLING);
status_ = status;
SetStatusFailedWithMessageUI(status, message);
}
} // namespace crostini
// Copyright 2019 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_CROSTINI_CROSTINI_EXPORT_IMPORT_STATUS_TRACKER_H_
#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_STATUS_TRACKER_H_
#include <memory>
#include <string>
#include "base/files/file_path.h"
#include "base/strings/string16.h"
namespace crostini {
enum class ExportImportType;
class CrostiniExportImportStatusTracker {
public:
enum class Status {
RUNNING,
CANCELLING,
DONE,
CANCELLED,
FAILED_UNKNOWN_REASON,
FAILED_ARCHITECTURE_MISMATCH,
FAILED_INSUFFICIENT_SPACE,
FAILED_CONCURRENT_OPERATION,
};
CrostiniExportImportStatusTracker(ExportImportType type, base::FilePath path);
virtual ~CrostiniExportImportStatusTracker();
Status status() const { return status_; }
ExportImportType type() const { return type_; }
const base::FilePath& path() const { return path_; }
// Can be used to draw attention to the UI without changing its
// status, even if it has been hidden.
virtual void ForceRedisplay() {}
virtual void SetStatusRunningUI(int progress_percent) = 0;
virtual void SetStatusCancellingUI() = 0;
virtual void SetStatusDoneUI() = 0;
virtual void SetStatusCancelledUI() = 0;
virtual void SetStatusFailedWithMessageUI(Status status,
const base::string16& message) = 0;
void SetStatusRunning(int progress_percent);
void SetStatusCancelling();
void SetStatusDone();
void SetStatusCancelled();
void SetStatusFailed();
void SetStatusFailedArchitectureMismatch(
const std::string& architecture_container,
const std::string& architecture_device);
void SetStatusFailedInsufficientSpace(uint64_t additional_required_space);
void SetStatusFailedConcurrentOperation(
ExportImportType in_progress_operation_type);
private:
void SetStatusFailedWithMessage(Status status, const base::string16& message);
ExportImportType type_;
base::FilePath path_;
Status status_ = Status::RUNNING;
};
} // namespace crostini
#endif // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_STATUS_TRACKER_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