Commit c90764f4 authored by Julian Watson's avatar Julian Watson Committed by Commit Bot

crostini: decouple lifetime of export import controller and notification

The lifetime of CrostiniExportImportNotificationController used to be
1to1 with the lifetime of the NotificationUI. This is unnecessary and
makes it harder to reuse this functionality more broadly (see
https://chromium-review.googlesource.com/c/chromium/src/+/1985621/3 for
an example which has very complicated lifetime management due to this).

This CL separates the Controller's lifetime from the UI's lifetime by
extracting the Controller's UI event handling logic (clicking/closing)
into a delegate which the controller creates, such that its lifetime
is 1to1 with the UI, thus allowing the Controller to be destroyed at
anytime without affecting the behaviour of the UI.

Bug: 1024693
Change-Id: I6c67016f862e80dcd1c92a12d507f484c28fe4de
tbr: stevenjb@chromium.org
tbr: tengs@chromium.org
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1991060
Commit-Queue: Julian Watson <juwa@google.com>
Reviewed-by: default avatarDavid Munro <davidmunro@google.com>
Cr-Commit-Position: refs/heads/master@{#731788}
parent 9fe1b4d7
...@@ -106,8 +106,15 @@ CrostiniExportImport::OperationData* CrostiniExportImport::NewOperationData( ...@@ -106,8 +106,15 @@ CrostiniExportImport::OperationData* CrostiniExportImport::NewOperationData(
CrostiniExportImport::OperationData* CrostiniExportImport::NewOperationData( CrostiniExportImport::OperationData* CrostiniExportImport::NewOperationData(
ExportImportType type, ExportImportType type,
ContainerId container_id) { ContainerId container_id) {
TrackerFactory factory = TrackerFactory factory = base::BindOnce(
base::BindOnce(&CrostiniExportImportNotificationController::Create, [](Profile* profile, ContainerId container_id,
std::string notification_id, ExportImportType type,
base::FilePath path)
-> std::unique_ptr<CrostiniExportImportStatusTracker> {
return std::make_unique<CrostiniExportImportNotificationController>(
profile, type, std::move(notification_id), std::move(path),
std::move(container_id));
},
profile_, container_id, GetUniqueNotificationId()); profile_, container_id, GetUniqueNotificationId());
return NewOperationData(type, std::move(container_id), std::move(factory)); return NewOperationData(type, std::move(container_id), std::move(factory));
} }
...@@ -205,7 +212,7 @@ void CrostiniExportImport::Start( ...@@ -205,7 +212,7 @@ void CrostiniExportImport::Start(
return std::move(callback).Run(CrostiniResult::NOT_ALLOWED); return std::move(callback).Run(CrostiniResult::NOT_ALLOWED);
} }
auto* status_tracker = std::move(operation_data->tracker_factory) auto status_tracker = std::move(operation_data->tracker_factory)
.Run(operation_data->type, path); .Run(operation_data->type, path);
auto it = status_trackers_.find(operation_data->container_id); auto it = status_trackers_.find(operation_data->container_id);
...@@ -218,7 +225,7 @@ void CrostiniExportImport::Start( ...@@ -218,7 +225,7 @@ void CrostiniExportImport::Start(
return; return;
} else { } else {
status_trackers_.emplace_hint(it, operation_data->container_id, status_trackers_.emplace_hint(it, operation_data->container_id,
status_tracker); std::move(status_tracker));
for (auto& observer : observers_) { for (auto& observer : observers_) {
observer.OnCrostiniExportImportOperationStatusChanged(true); observer.OnCrostiniExportImportOperationStatusChanged(true);
} }
...@@ -268,7 +275,7 @@ void CrostiniExportImport::ExportAfterSharing( ...@@ -268,7 +275,7 @@ void CrostiniExportImport::ExportAfterSharing(
<< failure_reason; << failure_reason;
auto it = status_trackers_.find(container_id); auto it = status_trackers_.find(container_id);
if (it != status_trackers_.end()) { if (it != status_trackers_.end()) {
RemoveTracker(it).SetStatusFailed(); RemoveTracker(it)->SetStatusFailed();
} else { } else {
NOTREACHED() << container_id << " has no status_tracker to update"; NOTREACHED() << container_id << " has no status_tracker to update";
} }
...@@ -306,7 +313,7 @@ void CrostiniExportImport::OnExportComplete( ...@@ -306,7 +313,7 @@ void CrostiniExportImport::OnExportComplete(
base::TaskPriority::BEST_EFFORT}, base::TaskPriority::BEST_EFFORT},
base::BindOnce(base::IgnoreResult(&base::DeleteFile), base::BindOnce(base::IgnoreResult(&base::DeleteFile),
it->second->path(), false)); it->second->path(), false));
RemoveTracker(it).SetStatusCancelled(); RemoveTracker(it)->SetStatusCancelled();
break; break;
} }
case CrostiniExportImportStatusTracker::Status::RUNNING: case CrostiniExportImportStatusTracker::Status::RUNNING:
...@@ -324,7 +331,7 @@ void CrostiniExportImport::OnExportComplete( ...@@ -324,7 +331,7 @@ void CrostiniExportImport::OnExportComplete(
"Crostini.BackupSizeRatio", "Crostini.BackupSizeRatio",
std::round(compressed_size * 100.0 / container_size)); std::round(compressed_size * 100.0 / container_size));
} }
RemoveTracker(it).SetStatusDone(); RemoveTracker(it)->SetStatusDone();
break; break;
default: default:
NOTREACHED(); NOTREACHED();
...@@ -340,7 +347,7 @@ void CrostiniExportImport::OnExportComplete( ...@@ -340,7 +347,7 @@ void CrostiniExportImport::OnExportComplete(
base::TaskPriority::BEST_EFFORT}, base::TaskPriority::BEST_EFFORT},
base::BindOnce(base::IgnoreResult(&base::DeleteFile), base::BindOnce(base::IgnoreResult(&base::DeleteFile),
it->second->path(), false)); it->second->path(), false));
RemoveTracker(it).SetStatusCancelled(); RemoveTracker(it)->SetStatusCancelled();
break; break;
} }
default: default:
...@@ -370,7 +377,7 @@ void CrostiniExportImport::OnExportComplete( ...@@ -370,7 +377,7 @@ void CrostiniExportImport::OnExportComplete(
CrostiniExportImportStatusTracker::Status::RUNNING || CrostiniExportImportStatusTracker::Status::RUNNING ||
it->second->status() == it->second->status() ==
CrostiniExportImportStatusTracker::Status::CANCELLING); CrostiniExportImportStatusTracker::Status::CANCELLING);
RemoveTracker(it).SetStatusFailed(); RemoveTracker(it)->SetStatusFailed();
} }
UMA_HISTOGRAM_ENUMERATION("Crostini.Backup", enum_hist_result); UMA_HISTOGRAM_ENUMERATION("Crostini.Backup", enum_hist_result);
std::move(callback).Run(result); std::move(callback).Run(result);
...@@ -431,7 +438,7 @@ void CrostiniExportImport::ImportAfterSharing( ...@@ -431,7 +438,7 @@ void CrostiniExportImport::ImportAfterSharing(
<< failure_reason; << failure_reason;
auto it = status_trackers_.find(container_id); auto it = status_trackers_.find(container_id);
if (it != status_trackers_.end()) { if (it != status_trackers_.end()) {
RemoveTracker(it).SetStatusFailed(); RemoveTracker(it)->SetStatusFailed();
} else { } else {
NOTREACHED() << container_id << " has no status_tracker to update"; NOTREACHED() << container_id << " has no status_tracker to update";
} }
...@@ -465,7 +472,7 @@ void CrostiniExportImport::OnImportComplete( ...@@ -465,7 +472,7 @@ void CrostiniExportImport::OnImportComplete(
// natural to pretend the cancel did not happen, and instead display // natural to pretend the cancel did not happen, and instead display
// success. // success.
case CrostiniExportImportStatusTracker::Status::CANCELLING: case CrostiniExportImportStatusTracker::Status::CANCELLING:
RemoveTracker(it).SetStatusDone(); RemoveTracker(it)->SetStatusDone();
break; break;
default: default:
NOTREACHED(); NOTREACHED();
...@@ -478,7 +485,7 @@ void CrostiniExportImport::OnImportComplete( ...@@ -478,7 +485,7 @@ void CrostiniExportImport::OnImportComplete(
if (it != status_trackers_.end()) { if (it != status_trackers_.end()) {
switch (it->second->status()) { switch (it->second->status()) {
case CrostiniExportImportStatusTracker::Status::CANCELLING: case CrostiniExportImportStatusTracker::Status::CANCELLING:
RemoveTracker(it).SetStatusCancelled(); RemoveTracker(it)->SetStatusCancelled();
break; break;
default: default:
NOTREACHED(); NOTREACHED();
...@@ -514,7 +521,7 @@ void CrostiniExportImport::OnImportComplete( ...@@ -514,7 +521,7 @@ void CrostiniExportImport::OnImportComplete(
if (it != status_trackers_.end()) { if (it != status_trackers_.end()) {
DCHECK(it->second->status() == DCHECK(it->second->status() ==
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
RemoveTracker(it).SetStatusFailed(); RemoveTracker(it)->SetStatusFailed();
} else { } else {
NOTREACHED() << container_id << " has no status_tracker to update"; NOTREACHED() << container_id << " has no status_tracker to update";
} }
...@@ -558,12 +565,12 @@ void CrostiniExportImport::OnImportContainerProgress( ...@@ -558,12 +565,12 @@ void CrostiniExportImport::OnImportContainerProgress(
break; break;
// Failure, set error message. // Failure, set error message.
case ImportContainerProgressStatus::FAILURE_ARCHITECTURE: case ImportContainerProgressStatus::FAILURE_ARCHITECTURE:
RemoveTracker(it).SetStatusFailedArchitectureMismatch( RemoveTracker(it)->SetStatusFailedArchitectureMismatch(
architecture_container, architecture_device); architecture_container, architecture_device);
break; break;
case ImportContainerProgressStatus::FAILURE_SPACE: case ImportContainerProgressStatus::FAILURE_SPACE:
DCHECK_GE(minimum_required_space, available_space); DCHECK_GE(minimum_required_space, available_space);
RemoveTracker(it).SetStatusFailedInsufficientSpace( RemoveTracker(it)->SetStatusFailedInsufficientSpace(
minimum_required_space - available_space); minimum_required_space - available_space);
break; break;
default: default:
...@@ -576,10 +583,12 @@ std::string CrostiniExportImport::GetUniqueNotificationId() { ...@@ -576,10 +583,12 @@ std::string CrostiniExportImport::GetUniqueNotificationId() {
next_status_tracker_id_++); next_status_tracker_id_++);
} }
CrostiniExportImportStatusTracker& CrostiniExportImport::RemoveTracker( std::unique_ptr<CrostiniExportImportStatusTracker>
std::map<ContainerId, CrostiniExportImportStatusTracker*>::iterator it) { CrostiniExportImport::RemoveTracker(
std::map<ContainerId,
std::unique_ptr<CrostiniExportImportStatusTracker>>::iterator it) {
DCHECK(it != status_trackers_.end()); DCHECK(it != status_trackers_.end());
auto& status_tracker = *it->second; auto status_tracker = std::move(it->second);
status_trackers_.erase(it); status_trackers_.erase(it);
for (auto& observer : observers_) { for (auto& observer : observers_) {
observer.OnCrostiniExportImportOperationStatusChanged(false); observer.OnCrostiniExportImportOperationStatusChanged(false);
...@@ -623,7 +632,8 @@ CrostiniExportImport::GetNotificationControllerForTesting( ...@@ -623,7 +632,8 @@ CrostiniExportImport::GetNotificationControllerForTesting(
if (it == status_trackers_.end()) { if (it == status_trackers_.end()) {
return nullptr; return nullptr;
} }
return static_cast<CrostiniExportImportNotificationController*>(it->second) return static_cast<CrostiniExportImportNotificationController*>(
it->second.get())
->GetWeakPtr(); ->GetWeakPtr();
} }
......
...@@ -68,9 +68,8 @@ class CrostiniExportImport : public KeyedService, ...@@ -68,9 +68,8 @@ class CrostiniExportImport : public KeyedService,
bool in_progress) = 0; bool in_progress) = 0;
}; };
using TrackerFactory = using TrackerFactory = base::OnceCallback<std::unique_ptr<
base::OnceCallback<CrostiniExportImportStatusTracker*(ExportImportType, CrostiniExportImportStatusTracker>(ExportImportType, base::FilePath)>;
base::FilePath)>;
struct OperationData { struct OperationData {
OperationData(ExportImportType type, OperationData(ExportImportType type,
...@@ -201,12 +200,15 @@ class CrostiniExportImport : public KeyedService, ...@@ -201,12 +200,15 @@ class CrostiniExportImport : public KeyedService,
std::string GetUniqueNotificationId(); std::string GetUniqueNotificationId();
CrostiniExportImportStatusTracker& RemoveTracker( std::unique_ptr<CrostiniExportImportStatusTracker> RemoveTracker(
std::map<ContainerId, CrostiniExportImportStatusTracker*>::iterator it); std::map<ContainerId,
std::unique_ptr<CrostiniExportImportStatusTracker>>::iterator
it);
Profile* profile_; Profile* profile_;
scoped_refptr<ui::SelectFileDialog> select_folder_dialog_; scoped_refptr<ui::SelectFileDialog> select_folder_dialog_;
std::map<ContainerId, CrostiniExportImportStatusTracker*> status_trackers_; std::map<ContainerId, std::unique_ptr<CrostiniExportImportStatusTracker>>
status_trackers_;
// |operation_data_storage_| persists the data required to complete an // |operation_data_storage_| persists the data required to complete an
// operation while the file selection dialog is open/operation is in progress. // operation while the file selection dialog is open/operation is in progress.
std::unordered_map<OperationData*, std::unique_ptr<OperationData>> std::unordered_map<OperationData*, std::unique_ptr<OperationData>>
......
...@@ -32,6 +32,18 @@ constexpr char kNotifierCrostiniExportImportOperation[] = ...@@ -32,6 +32,18 @@ constexpr char kNotifierCrostiniExportImportOperation[] =
} // namespace } // namespace
CrostiniExportImportClickCloseDelegate::CrostiniExportImportClickCloseDelegate()
: HandleNotificationClickDelegate(
HandleNotificationClickDelegate::ButtonClickCallback()) {}
void CrostiniExportImportClickCloseDelegate::Close(bool by_user) {
if (!close_closure_.is_null())
close_closure_.Run();
}
CrostiniExportImportClickCloseDelegate::
~CrostiniExportImportClickCloseDelegate() = default;
CrostiniExportImportNotificationController:: CrostiniExportImportNotificationController::
CrostiniExportImportNotificationController( CrostiniExportImportNotificationController(
Profile* profile, Profile* profile,
...@@ -41,7 +53,13 @@ CrostiniExportImportNotificationController:: ...@@ -41,7 +53,13 @@ CrostiniExportImportNotificationController::
ContainerId container_id) ContainerId container_id)
: CrostiniExportImportStatusTracker(type, std::move(path)), : CrostiniExportImportStatusTracker(type, std::move(path)),
profile_(profile), profile_(profile),
container_id_(std::move(container_id)) { container_id_(std::move(container_id)),
delegate_(
base::MakeRefCounted<CrostiniExportImportClickCloseDelegate>()) {
delegate_->SetCloseCallback(base::BindRepeating(
&CrostiniExportImportNotificationController::on_notification_closed,
weak_ptr_factory_.GetWeakPtr()));
message_center::RichNotificationData rich_notification_data; message_center::RichNotificationData rich_notification_data;
rich_notification_data.vector_small_image = &kNotificationLinuxIcon; rich_notification_data.vector_small_image = &kNotificationLinuxIcon;
rich_notification_data.accent_color = ash::kSystemNotificationColorNormal; rich_notification_data.accent_color = ash::kSystemNotificationColorNormal;
...@@ -55,9 +73,7 @@ CrostiniExportImportNotificationController:: ...@@ -55,9 +73,7 @@ CrostiniExportImportNotificationController::
GURL(), // origin_url GURL(), // origin_url
message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT, message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
kNotifierCrostiniExportImportOperation), kNotifierCrostiniExportImportOperation),
rich_notification_data, rich_notification_data, delegate_);
base::MakeRefCounted<message_center::ThunkNotificationDelegate>(
weak_ptr_factory_.GetWeakPtr()));
SetStatusRunning(0); SetStatusRunning(0);
} }
...@@ -78,6 +94,18 @@ void CrostiniExportImportNotificationController::SetStatusRunningUI( ...@@ -78,6 +94,18 @@ void CrostiniExportImportNotificationController::SetStatusRunningUI(
return; return;
} }
delegate_->SetCallback(base::BindRepeating(
[](Profile* profile, ExportImportType type, ContainerId container_id,
base::Optional<int> button_index) {
if (!button_index.has_value()) {
return;
}
DCHECK(*button_index == 1);
CrostiniExportImport::GetForProfile(profile)->CancelOperation(
type, container_id);
},
profile_, type(), container_id_));
notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS); notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS);
notification_->set_accent_color(ash::kSystemNotificationColorNormal); notification_->set_accent_color(ash::kSystemNotificationColorNormal);
notification_->set_title(l10n_util::GetStringUTF16( notification_->set_title(l10n_util::GetStringUTF16(
...@@ -100,6 +128,9 @@ void CrostiniExportImportNotificationController::SetStatusCancellingUI() { ...@@ -100,6 +128,9 @@ void CrostiniExportImportNotificationController::SetStatusCancellingUI() {
return; return;
} }
delegate_->SetCallback(
CrostiniExportImportClickCloseDelegate::ButtonClickCallback());
notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS); notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS);
notification_->set_accent_color(ash::kSystemNotificationColorNormal); notification_->set_accent_color(ash::kSystemNotificationColorNormal);
notification_->set_title(l10n_util::GetStringUTF16( notification_->set_title(l10n_util::GetStringUTF16(
...@@ -116,6 +147,17 @@ void CrostiniExportImportNotificationController::SetStatusCancellingUI() { ...@@ -116,6 +147,17 @@ void CrostiniExportImportNotificationController::SetStatusCancellingUI() {
} }
void CrostiniExportImportNotificationController::SetStatusDoneUI() { void CrostiniExportImportNotificationController::SetStatusDoneUI() {
if (type() == ExportImportType::EXPORT) {
delegate_->SetCallback(base::BindRepeating(
[](Profile* profile, base::FilePath path) {
platform_util::ShowItemInFolder(profile, path);
},
profile_, path()));
} else {
delegate_->SetCallback(
CrostiniExportImportClickCloseDelegate::ButtonClickCallback());
}
notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE); notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
notification_->set_accent_color(ash::kSystemNotificationColorNormal); notification_->set_accent_color(ash::kSystemNotificationColorNormal);
notification_->set_title(l10n_util::GetStringUTF16( notification_->set_title(l10n_util::GetStringUTF16(
...@@ -134,6 +176,9 @@ void CrostiniExportImportNotificationController::SetStatusDoneUI() { ...@@ -134,6 +176,9 @@ void CrostiniExportImportNotificationController::SetStatusDoneUI() {
} }
void CrostiniExportImportNotificationController::SetStatusCancelledUI() { void CrostiniExportImportNotificationController::SetStatusCancelledUI() {
delegate_->SetCallback(
CrostiniExportImportClickCloseDelegate::ButtonClickCallback());
NotificationDisplayService::GetForProfile(profile_)->Close( NotificationDisplayService::GetForProfile(profile_)->Close(
NotificationHandler::Type::TRANSIENT, notification_->id()); NotificationHandler::Type::TRANSIENT, notification_->id());
} }
...@@ -141,6 +186,42 @@ void CrostiniExportImportNotificationController::SetStatusCancelledUI() { ...@@ -141,6 +186,42 @@ void CrostiniExportImportNotificationController::SetStatusCancelledUI() {
void CrostiniExportImportNotificationController::SetStatusFailedWithMessageUI( void CrostiniExportImportNotificationController::SetStatusFailedWithMessageUI(
Status status, Status status,
const base::string16& message) { const base::string16& message) {
switch (status) {
case Status::FAILED_UNKNOWN_REASON:
delegate_->SetCallback(base::BindRepeating(
[](Profile* profile) {
chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
profile, chrome::kCrostiniExportImportSubPage);
},
profile_));
break;
case Status::FAILED_ARCHITECTURE_MISMATCH:
delegate_->SetCallback(base::BindRepeating(
[](Profile* profile) {
NavigateParams params(profile,
GURL(chrome::kLinuxExportImportHelpURL),
ui::PAGE_TRANSITION_LINK);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
params.window_action = NavigateParams::SHOW_WINDOW;
Navigate(&params);
},
profile_));
break;
case Status::FAILED_INSUFFICIENT_SPACE:
delegate_->SetCallback(base::BindRepeating(
[](Profile* profile) {
chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
profile, chrome::kStorageSubPage);
},
profile_));
break;
default:
LOG(ERROR) << "Unexpected failure status "
<< static_cast<std::underlying_type_t<Status>>(status);
delegate_->SetCallback(
CrostiniExportImportClickCloseDelegate::ButtonClickCallback());
}
notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE); notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
notification_->set_accent_color(ash::kSystemNotificationColorCriticalWarning); notification_->set_accent_color(ash::kSystemNotificationColorCriticalWarning);
notification_->set_title(l10n_util::GetStringUTF16( notification_->set_title(l10n_util::GetStringUTF16(
...@@ -155,64 +236,4 @@ void CrostiniExportImportNotificationController::SetStatusFailedWithMessageUI( ...@@ -155,64 +236,4 @@ void CrostiniExportImportNotificationController::SetStatusFailedWithMessageUI(
ForceRedisplay(); ForceRedisplay();
} }
void CrostiniExportImportNotificationController::Close(bool by_user) {
switch (status()) {
case Status::RUNNING:
case Status::CANCELLING:
hidden_ = true;
return;
case Status::DONE:
case Status::CANCELLED:
case Status::FAILED_UNKNOWN_REASON:
case Status::FAILED_ARCHITECTURE_MISMATCH:
case Status::FAILED_INSUFFICIENT_SPACE:
case Status::FAILED_CONCURRENT_OPERATION:
delete this;
return;
default:
NOTREACHED();
}
}
void CrostiniExportImportNotificationController::Click(
const base::Optional<int>& button_index,
const base::Optional<base::string16>&) {
switch (status()) {
case Status::RUNNING:
if (button_index) {
DCHECK(*button_index == 1);
CrostiniExportImport::GetForProfile(profile_)->CancelOperation(
type(), container_id_);
}
return;
case Status::DONE:
DCHECK(!button_index);
if (type() == ExportImportType::EXPORT) {
platform_util::ShowItemInFolder(profile_, path());
}
return;
case Status::FAILED_UNKNOWN_REASON:
DCHECK(!button_index);
chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
profile_, chrome::kCrostiniExportImportSubPage);
return;
case Status::FAILED_ARCHITECTURE_MISMATCH: {
DCHECK(!button_index);
NavigateParams params(profile_, GURL(chrome::kLinuxExportImportHelpURL),
ui::PAGE_TRANSITION_LINK);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
params.window_action = NavigateParams::SHOW_WINDOW;
Navigate(&params);
}
return;
case Status::FAILED_INSUFFICIENT_SPACE:
DCHECK(!button_index);
chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
profile_, chrome::kStorageSubPage);
return;
default:
DCHECK(!button_index);
}
}
} // namespace crostini } // namespace crostini
...@@ -22,26 +22,40 @@ class Notification; ...@@ -22,26 +22,40 @@ class Notification;
namespace crostini { namespace crostini {
class CrostiniExportImportClickCloseDelegate
: public message_center::HandleNotificationClickDelegate {
public:
using HandleNotificationClickDelegate::ButtonClickCallback;
CrostiniExportImportClickCloseDelegate();
void SetCloseCallback(base::RepeatingClosure close_closure) {
close_closure_ = std::move(close_closure);
}
// NotificationDelegate overrides:
void Close(bool by_user) override;
private:
~CrostiniExportImportClickCloseDelegate() override;
base::RepeatingClosure close_closure_{};
};
enum class ExportImportType; enum class ExportImportType;
// Controller for Crostini's Export Import Notification UI. // Controller for Crostini's Export Import notification UI.
// It can be used to change the look of the UI, and handles actions from the UI. // Upon construction the Controller will create a new notification, displaying
// the StatusRunning UI. If the controller is freed the notification will
// continue to be shown and handle click events until the user closes it.
class CrostiniExportImportNotificationController class CrostiniExportImportNotificationController
: public CrostiniExportImportStatusTracker, : public CrostiniExportImportStatusTracker {
public message_center::NotificationObserver {
public: public:
// Used to construct CrostiniExportImportNotificationController to ensure it CrostiniExportImportNotificationController(Profile* profile,
// controls its lifetime.
static CrostiniExportImportStatusTracker* Create(
Profile* profile,
ContainerId container_id,
const std::string& notification_id,
ExportImportType type, ExportImportType type,
base::FilePath path) { const std::string& notification_id,
return new CrostiniExportImportNotificationController( base::FilePath path,
profile, type, notification_id, std::move(path), ContainerId container_id);
std::move(container_id));
}
~CrostiniExportImportNotificationController() override; ~CrostiniExportImportNotificationController() override;
...@@ -52,19 +66,11 @@ class CrostiniExportImportNotificationController ...@@ -52,19 +66,11 @@ class CrostiniExportImportNotificationController
base::WeakPtr<CrostiniExportImportNotificationController> GetWeakPtr() { base::WeakPtr<CrostiniExportImportNotificationController> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr(); return weak_ptr_factory_.GetWeakPtr();
} }
message_center::NotificationObserver* get_delegate() {
// message_center::NotificationObserver: return delegate_.get();
void Close(bool by_user) override; }
void Click(const base::Optional<int>& button_index,
const base::Optional<base::string16>& reply) override;
private: private:
CrostiniExportImportNotificationController(Profile* profile,
ExportImportType type,
const std::string& notification_id,
base::FilePath path,
ContainerId container_id);
// CrostiniExportImportStatusTracker: // CrostiniExportImportStatusTracker:
void ForceRedisplay() override; void ForceRedisplay() override;
void SetStatusRunningUI(int progress_percent) override; void SetStatusRunningUI(int progress_percent) override;
...@@ -74,15 +80,26 @@ class CrostiniExportImportNotificationController ...@@ -74,15 +80,26 @@ class CrostiniExportImportNotificationController
void SetStatusFailedWithMessageUI(Status status, void SetStatusFailedWithMessageUI(Status status,
const base::string16& message) override; const base::string16& message) override;
void on_notification_closed() { hidden_ = true; }
Profile* profile_; // Not owned. Profile* profile_; // Not owned.
ContainerId container_id_; ContainerId container_id_;
// |delegate_| is responsible for handling click events. It is separate from
// the controller because it needs to live as long as the notification is in
// the UI, but the controller's lifetime ends once the notification is in a
// final state (done, canceled, or failed).
scoped_refptr<CrostiniExportImportClickCloseDelegate> delegate_;
// Time when the operation started. Used for estimating time remaining. // Time when the operation started. Used for estimating time remaining.
base::TimeTicks started_ = base::TimeTicks::Now(); base::TimeTicks started_ = base::TimeTicks::Now();
// |notification_| acts as a handle to the notification UI, it is used to
// update the UI's progress/message/buttons. Freeing |notification_| doesn't
// close the notification UI; the UI system is responsible for closing it.
std::unique_ptr<message_center::Notification> notification_; std::unique_ptr<message_center::Notification> notification_;
bool hidden_ = false; bool hidden_ = false;
base::WeakPtrFactory<CrostiniExportImportNotificationController> base::WeakPtrFactory<CrostiniExportImportNotificationController>
weak_ptr_factory_{this}; weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(CrostiniExportImportNotificationController); DISALLOW_COPY_AND_ASSIGN(CrostiniExportImportNotificationController);
}; };
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
#include "chrome/browser/chromeos/crostini/crostini_util.h" #include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/chromeos/file_manager/path_util.h" #include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/chromeos/guest_os/guest_os_share_path.h" #include "chrome/browser/chromeos/guest_os/guest_os_share_path.h"
#include "chrome/browser/notifications/notification_display_service_factory.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/test/base/testing_profile.h" #include "chrome/test/base/testing_profile.h"
#include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_cicerone_client.h" #include "chromeos/dbus/fake_cicerone_client.h"
...@@ -47,6 +49,42 @@ class CrostiniExportImportTest : public testing::Test { ...@@ -47,6 +49,42 @@ class CrostiniExportImportTest : public testing::Test {
container_id_); container_id_);
} }
const message_center::Notification& GetNotification() {
// Assertions in this function are wrap in IILEs because you cannot assert
// in a function with a non-void return type.
const base::WeakPtr<CrostiniExportImportNotificationController>&
controller = GetController();
[&] { ASSERT_NE(controller, nullptr); }();
const message_center::Notification* controller_notification =
controller->get_notification();
[&] { ASSERT_NE(controller_notification, nullptr); }();
const base::Optional<message_center::Notification>& ui_notification =
notification_display_service_->GetNotification(
controller_notification->id());
[&] { ASSERT_NE(ui_notification, base::nullopt); }();
// The controller notification is stored on the
// CrostiniExportImportNotificationController, but copied into the
// message_center's storage whenever it changes. If they could share the
// same instance of the notification then this function wouldn't be
// necessary.
[&] { ASSERT_NE(controller_notification, &*ui_notification); }();
[&] {
ASSERT_TRUE(
controller_notification->type() == ui_notification->type() &&
controller_notification->id() == ui_notification->id() &&
controller_notification->title() == ui_notification->title() &&
controller_notification->message() == ui_notification->message() &&
controller_notification->timestamp() ==
ui_notification->timestamp() &&
controller_notification->progress() == ui_notification->progress() &&
controller_notification->never_timeout() ==
ui_notification->never_timeout() &&
controller_notification->delegate() == ui_notification->delegate());
}();
// Either notification could be returned here, they are fungible.
return *controller_notification;
}
void SendExportProgress( void SendExportProgress(
vm_tools::cicerone::ExportLxdContainerProgressSignal_Status status, vm_tools::cicerone::ExportLxdContainerProgressSignal_Status status,
const ExportProgressOptionalArguments& arguments = {}) { const ExportProgressOptionalArguments& arguments = {}) {
...@@ -96,6 +134,12 @@ class CrostiniExportImportTest : public testing::Test { ...@@ -96,6 +134,12 @@ class CrostiniExportImportTest : public testing::Test {
profile_ = std::make_unique<TestingProfile>(); profile_ = std::make_unique<TestingProfile>();
crostini_export_import_ = std::make_unique<CrostiniExportImport>(profile()); crostini_export_import_ = std::make_unique<CrostiniExportImport>(profile());
test_helper_ = std::make_unique<CrostiniTestHelper>(profile_.get()); test_helper_ = std::make_unique<CrostiniTestHelper>(profile_.get());
notification_display_service_tester_ =
std::make_unique<NotificationDisplayServiceTester>(profile());
notification_display_service_ =
static_cast<StubNotificationDisplayService*>(
NotificationDisplayServiceFactory::GetForProfile(profile()));
ASSERT_NE(notification_display_service_, nullptr);
CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting( CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
kCrostiniDefaultVmName); kCrostiniDefaultVmName);
CrostiniManager::GetForProfile(profile())->set_skip_restart_for_testing(); CrostiniManager::GetForProfile(profile())->set_skip_restart_for_testing();
...@@ -134,6 +178,9 @@ class CrostiniExportImportTest : public testing::Test { ...@@ -134,6 +178,9 @@ class CrostiniExportImportTest : public testing::Test {
std::unique_ptr<TestingProfile> profile_; std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<CrostiniExportImport> crostini_export_import_; std::unique_ptr<CrostiniExportImport> crostini_export_import_;
std::unique_ptr<CrostiniTestHelper> test_helper_; std::unique_ptr<CrostiniTestHelper> test_helper_;
std::unique_ptr<NotificationDisplayServiceTester>
notification_display_service_tester_;
StubNotificationDisplayService* notification_display_service_;
ContainerId container_id_; ContainerId container_id_;
base::FilePath tarball_; base::FilePath tarball_;
...@@ -168,11 +215,12 @@ TEST_F(CrostiniExportImportTest, TestDeprecatedExportSuccess) { ...@@ -168,11 +215,12 @@ TEST_F(CrostiniExportImportTest, TestDeprecatedExportSuccess) {
ASSERT_NE(controller, nullptr); ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
std::string notification_id;
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); notification_id = notification.id();
EXPECT_EQ(notification->progress(), 0); EXPECT_EQ(notification.progress(), 0);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// 20% PACK = 10% overall. // 20% PACK = 10% overall.
...@@ -183,10 +231,10 @@ TEST_F(CrostiniExportImportTest, TestDeprecatedExportSuccess) { ...@@ -183,10 +231,10 @@ TEST_F(CrostiniExportImportTest, TestDeprecatedExportSuccess) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), 10); EXPECT_EQ(notification.progress(), 10);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// 20% DOWNLOAD = 60% overall. // 20% DOWNLOAD = 60% overall.
...@@ -198,14 +246,14 @@ TEST_F(CrostiniExportImportTest, TestDeprecatedExportSuccess) { ...@@ -198,14 +246,14 @@ TEST_F(CrostiniExportImportTest, TestDeprecatedExportSuccess) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), 60); EXPECT_EQ(notification.progress(), 60);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// Close notification and update progress. Should not update notification. // Close notification and update progress. Should not update notification.
controller->Close(false); controller->get_delegate()->Close(false);
SendExportProgress( SendExportProgress(
vm_tools::cicerone:: vm_tools::cicerone::
ExportLxdContainerProgressSignal_Status_EXPORTING_DOWNLOAD, ExportLxdContainerProgressSignal_Status_EXPORTING_DOWNLOAD,
...@@ -214,23 +262,24 @@ TEST_F(CrostiniExportImportTest, TestDeprecatedExportSuccess) { ...@@ -214,23 +262,24 @@ TEST_F(CrostiniExportImportTest, TestDeprecatedExportSuccess) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), 60); EXPECT_EQ(notification.progress(), 60);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// Done. // Done.
SendExportProgress( SendExportProgress(
vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_DONE); vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_DONE);
ASSERT_EQ(GetController(), nullptr); EXPECT_EQ(GetController(), nullptr);
ASSERT_NE(controller, nullptr); EXPECT_EQ(controller, nullptr);
EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::DONE);
{ {
message_center::Notification* notification = controller->get_notification(); const base::Optional<message_center::Notification> ui_notification =
ASSERT_NE(notification, nullptr); notification_display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification->pinned()); ASSERT_NE(ui_notification, base::nullopt);
EXPECT_FALSE(ui_notification->pinned());
std::string msg("Linux apps & files have been successfully backed up");
EXPECT_EQ(ui_notification->message(), base::UTF8ToUTF16(msg));
} }
// CrostiniExportImport should've created the exported file. // CrostiniExportImport should've created the exported file.
...@@ -248,11 +297,12 @@ TEST_F(CrostiniExportImportTest, TestExportSuccess) { ...@@ -248,11 +297,12 @@ TEST_F(CrostiniExportImportTest, TestExportSuccess) {
ASSERT_NE(controller, nullptr); ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
std::string notification_id;
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); notification_id = notification.id();
EXPECT_EQ(notification->progress(), 0); EXPECT_EQ(notification.progress(), 0);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// STREAMING 10% bytes done + 30% files done = 20% overall. // STREAMING 10% bytes done + 30% files done = 20% overall.
...@@ -267,10 +317,10 @@ TEST_F(CrostiniExportImportTest, TestExportSuccess) { ...@@ -267,10 +317,10 @@ TEST_F(CrostiniExportImportTest, TestExportSuccess) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), 20); EXPECT_EQ(notification.progress(), 20);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// STREAMING 66% bytes done + 55% files done then floored = 60% overall. // STREAMING 66% bytes done + 55% files done then floored = 60% overall.
...@@ -285,14 +335,14 @@ TEST_F(CrostiniExportImportTest, TestExportSuccess) { ...@@ -285,14 +335,14 @@ TEST_F(CrostiniExportImportTest, TestExportSuccess) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), 60); EXPECT_EQ(notification.progress(), 60);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// Close notification and update progress. Should not update notification. // Close notification and update progress. Should not update notification.
controller->Close(false); controller->get_delegate()->Close(false);
SendExportProgress( SendExportProgress(
vm_tools::cicerone:: vm_tools::cicerone::
ExportLxdContainerProgressSignal_Status_EXPORTING_STREAMING, ExportLxdContainerProgressSignal_Status_EXPORTING_STREAMING,
...@@ -304,23 +354,24 @@ TEST_F(CrostiniExportImportTest, TestExportSuccess) { ...@@ -304,23 +354,24 @@ TEST_F(CrostiniExportImportTest, TestExportSuccess) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), 60); EXPECT_EQ(notification.progress(), 60);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// Done. // Done.
SendExportProgress( SendExportProgress(
vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_DONE); vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_DONE);
ASSERT_EQ(GetController(), nullptr); EXPECT_EQ(GetController(), nullptr);
ASSERT_NE(controller, nullptr); EXPECT_EQ(controller, nullptr);
EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::DONE);
{ {
message_center::Notification* notification = controller->get_notification(); const base::Optional<message_center::Notification> ui_notification =
ASSERT_NE(notification, nullptr); notification_display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification->pinned()); ASSERT_NE(ui_notification, base::nullopt);
EXPECT_FALSE(ui_notification->pinned());
std::string msg("Linux apps & files have been successfully backed up");
EXPECT_EQ(ui_notification->message(), base::UTF8ToUTF16(msg));
} }
// CrostiniExportImport should've created the exported file. // CrostiniExportImport should've created the exported file.
...@@ -338,24 +389,26 @@ TEST_F(CrostiniExportImportTest, TestExportFail) { ...@@ -338,24 +389,26 @@ TEST_F(CrostiniExportImportTest, TestExportFail) {
ASSERT_NE(controller, nullptr); ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
std::string notification_id;
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); notification_id = notification.id();
EXPECT_EQ(notification->progress(), 0); EXPECT_EQ(notification.progress(), 0);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// Failed. // Failed.
SendExportProgress( SendExportProgress(
vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_FAILED); vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_FAILED);
ASSERT_EQ(GetController(), nullptr); EXPECT_EQ(GetController(), nullptr);
ASSERT_NE(controller, nullptr); EXPECT_EQ(controller, nullptr);
EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::FAILED_UNKNOWN_REASON);
{ {
message_center::Notification* notification = controller->get_notification(); const base::Optional<message_center::Notification> ui_notification =
ASSERT_NE(notification, nullptr); notification_display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification->pinned()); ASSERT_NE(ui_notification, base::nullopt);
EXPECT_FALSE(ui_notification->pinned());
std::string msg("Backup couldn't be completed due to an error");
EXPECT_EQ(ui_notification->message(), base::UTF8ToUTF16(msg));
} }
// CrostiniExportImport should cleanup the file if an export fails. // CrostiniExportImport should cleanup the file if an export fails.
...@@ -373,11 +426,12 @@ TEST_F(CrostiniExportImportTest, TestExportCancelled) { ...@@ -373,11 +426,12 @@ TEST_F(CrostiniExportImportTest, TestExportCancelled) {
ASSERT_NE(controller, nullptr); ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
std::string notification_id;
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); notification_id = notification.id();
EXPECT_EQ(notification->progress(), 0); EXPECT_EQ(notification.progress(), 0);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// CANCELLING: // CANCELLING:
...@@ -387,10 +441,10 @@ TEST_F(CrostiniExportImportTest, TestExportCancelled) { ...@@ -387,10 +441,10 @@ TEST_F(CrostiniExportImportTest, TestExportCancelled) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::CANCELLING); CrostiniExportImportStatusTracker::Status::CANCELLING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), -1); EXPECT_EQ(notification.progress(), -1);
EXPECT_FALSE(notification->pinned()); EXPECT_FALSE(notification.pinned());
} }
EXPECT_TRUE(base::PathExists(tarball_)); EXPECT_TRUE(base::PathExists(tarball_));
...@@ -406,24 +460,22 @@ TEST_F(CrostiniExportImportTest, TestExportCancelled) { ...@@ -406,24 +460,22 @@ TEST_F(CrostiniExportImportTest, TestExportCancelled) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::CANCELLING); CrostiniExportImportStatusTracker::Status::CANCELLING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), -1); EXPECT_EQ(notification.progress(), -1);
EXPECT_FALSE(notification->pinned()); EXPECT_FALSE(notification.pinned());
} }
EXPECT_TRUE(base::PathExists(tarball_)); EXPECT_TRUE(base::PathExists(tarball_));
// CANCELLED: // CANCELLED:
SendExportProgress( SendExportProgress(
vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_CANCELLED); vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_CANCELLED);
ASSERT_EQ(GetController(), nullptr); EXPECT_EQ(GetController(), nullptr);
ASSERT_NE(controller, nullptr); EXPECT_EQ(controller, nullptr);
EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::CANCELLED);
{ {
message_center::Notification* notification = controller->get_notification(); const base::Optional<message_center::Notification> ui_notification =
ASSERT_NE(notification, nullptr); notification_display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification->pinned()); EXPECT_EQ(ui_notification, base::nullopt);
} }
task_environment_.RunUntilIdle(); task_environment_.RunUntilIdle();
...@@ -440,11 +492,12 @@ TEST_F(CrostiniExportImportTest, TestExportDoneBeforeCancelled) { ...@@ -440,11 +492,12 @@ TEST_F(CrostiniExportImportTest, TestExportDoneBeforeCancelled) {
ASSERT_NE(controller, nullptr); ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
std::string notification_id;
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); notification_id = notification.id();
EXPECT_EQ(notification->progress(), 0); EXPECT_EQ(notification.progress(), 0);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// CANCELLING: // CANCELLING:
...@@ -454,24 +507,22 @@ TEST_F(CrostiniExportImportTest, TestExportDoneBeforeCancelled) { ...@@ -454,24 +507,22 @@ TEST_F(CrostiniExportImportTest, TestExportDoneBeforeCancelled) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::CANCELLING); CrostiniExportImportStatusTracker::Status::CANCELLING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), -1); EXPECT_EQ(notification.progress(), -1);
EXPECT_FALSE(notification->pinned()); EXPECT_FALSE(notification.pinned());
} }
EXPECT_TRUE(base::PathExists(tarball_)); EXPECT_TRUE(base::PathExists(tarball_));
// DONE: Completed before cancel processed, file should be deleted. // DONE: Completed before cancel processed, file should be deleted.
SendExportProgress( SendExportProgress(
vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_DONE); vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_DONE);
ASSERT_EQ(GetController(), nullptr); EXPECT_EQ(GetController(), nullptr);
ASSERT_NE(controller, nullptr); EXPECT_EQ(controller, nullptr);
EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::CANCELLED);
{ {
message_center::Notification* notification = controller->get_notification(); const base::Optional<message_center::Notification> ui_notification =
ASSERT_NE(notification, nullptr); notification_display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification->pinned()); EXPECT_EQ(ui_notification, base::nullopt);
} }
task_environment_.RunUntilIdle(); task_environment_.RunUntilIdle();
...@@ -488,11 +539,12 @@ TEST_F(CrostiniExportImportTest, TestImportSuccess) { ...@@ -488,11 +539,12 @@ TEST_F(CrostiniExportImportTest, TestImportSuccess) {
ASSERT_NE(controller, nullptr); ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
std::string notification_id;
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); notification_id = notification.id();
EXPECT_EQ(notification->progress(), 0); EXPECT_EQ(notification.progress(), 0);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// 20% UPLOAD = 10% overall. // 20% UPLOAD = 10% overall.
...@@ -504,10 +556,10 @@ TEST_F(CrostiniExportImportTest, TestImportSuccess) { ...@@ -504,10 +556,10 @@ TEST_F(CrostiniExportImportTest, TestImportSuccess) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), 10); EXPECT_EQ(notification.progress(), 10);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// 20% UNPACK = 60% overall. // 20% UNPACK = 60% overall.
...@@ -519,14 +571,14 @@ TEST_F(CrostiniExportImportTest, TestImportSuccess) { ...@@ -519,14 +571,14 @@ TEST_F(CrostiniExportImportTest, TestImportSuccess) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), 60); EXPECT_EQ(notification.progress(), 60);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// Close notification and update progress. Should not update notification. // Close notification and update progress. Should not update notification.
controller->Close(false); controller->get_delegate()->Close(false);
SendImportProgress( SendImportProgress(
vm_tools::cicerone:: vm_tools::cicerone::
ImportLxdContainerProgressSignal_Status_IMPORTING_UNPACK, ImportLxdContainerProgressSignal_Status_IMPORTING_UNPACK,
...@@ -535,23 +587,24 @@ TEST_F(CrostiniExportImportTest, TestImportSuccess) { ...@@ -535,23 +587,24 @@ TEST_F(CrostiniExportImportTest, TestImportSuccess) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), 60); EXPECT_EQ(notification.progress(), 60);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// Done. // Done.
SendImportProgress( SendImportProgress(
vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_DONE); vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_DONE);
ASSERT_EQ(GetController(), nullptr); EXPECT_EQ(GetController(), nullptr);
ASSERT_NE(controller, nullptr); EXPECT_EQ(controller, nullptr);
EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::DONE);
{ {
message_center::Notification* notification = controller->get_notification(); const base::Optional<message_center::Notification> ui_notification =
ASSERT_NE(notification, nullptr); notification_display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification->pinned()); ASSERT_NE(ui_notification, base::nullopt);
EXPECT_FALSE(ui_notification->pinned());
std::string msg("Linux apps & files have been successfully replaced");
EXPECT_EQ(ui_notification->message(), base::UTF8ToUTF16(msg));
} }
} }
...@@ -565,26 +618,26 @@ TEST_F(CrostiniExportImportTest, TestImportFail) { ...@@ -565,26 +618,26 @@ TEST_F(CrostiniExportImportTest, TestImportFail) {
ASSERT_NE(controller, nullptr); ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
std::string notification_id;
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); notification_id = notification.id();
EXPECT_EQ(notification->progress(), 0); EXPECT_EQ(notification.progress(), 0);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// Failed. // Failed.
SendImportProgress( SendImportProgress(
vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_FAILED); vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_FAILED);
ASSERT_EQ(GetController(), nullptr); EXPECT_EQ(GetController(), nullptr);
ASSERT_NE(controller, nullptr); EXPECT_EQ(controller, nullptr);
EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::FAILED_UNKNOWN_REASON);
{ {
message_center::Notification* notification = controller->get_notification(); const base::Optional<message_center::Notification> ui_notification =
ASSERT_NE(notification, nullptr); notification_display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification->pinned()); ASSERT_NE(ui_notification, base::nullopt);
EXPECT_FALSE(ui_notification->pinned());
std::string msg("Restoring couldn't be completed due to an error"); std::string msg("Restoring couldn't be completed due to an error");
EXPECT_EQ(notification->message(), base::UTF8ToUTF16(msg)); EXPECT_EQ(ui_notification->message(), base::UTF8ToUTF16(msg));
} }
} }
...@@ -598,11 +651,12 @@ TEST_F(CrostiniExportImportTest, TestImportCancelled) { ...@@ -598,11 +651,12 @@ TEST_F(CrostiniExportImportTest, TestImportCancelled) {
ASSERT_NE(controller, nullptr); ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
std::string notification_id;
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); notification_id = notification.id();
EXPECT_EQ(notification->progress(), 0); EXPECT_EQ(notification.progress(), 0);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// CANCELLING: // CANCELLING:
...@@ -612,10 +666,10 @@ TEST_F(CrostiniExportImportTest, TestImportCancelled) { ...@@ -612,10 +666,10 @@ TEST_F(CrostiniExportImportTest, TestImportCancelled) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::CANCELLING); CrostiniExportImportStatusTracker::Status::CANCELLING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), -1); EXPECT_EQ(notification.progress(), -1);
EXPECT_FALSE(notification->pinned()); EXPECT_FALSE(notification.pinned());
} }
// STREAMING: should not be displayed as cancel is in progress // STREAMING: should not be displayed as cancel is in progress
...@@ -627,23 +681,21 @@ TEST_F(CrostiniExportImportTest, TestImportCancelled) { ...@@ -627,23 +681,21 @@ TEST_F(CrostiniExportImportTest, TestImportCancelled) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::CANCELLING); CrostiniExportImportStatusTracker::Status::CANCELLING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), -1); EXPECT_EQ(notification.progress(), -1);
EXPECT_FALSE(notification->pinned()); EXPECT_FALSE(notification.pinned());
} }
// CANCELLED: // CANCELLED:
SendImportProgress( SendImportProgress(
vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_CANCELLED); vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_CANCELLED);
ASSERT_EQ(GetController(), nullptr); EXPECT_EQ(GetController(), nullptr);
ASSERT_NE(controller, nullptr); EXPECT_EQ(controller, nullptr);
EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::CANCELLED);
{ {
message_center::Notification* notification = controller->get_notification(); const base::Optional<message_center::Notification> ui_notification =
ASSERT_NE(notification, nullptr); notification_display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification->pinned()); EXPECT_EQ(ui_notification, base::nullopt);
} }
} }
...@@ -657,11 +709,12 @@ TEST_F(CrostiniExportImportTest, TestImportDoneBeforeCancelled) { ...@@ -657,11 +709,12 @@ TEST_F(CrostiniExportImportTest, TestImportDoneBeforeCancelled) {
ASSERT_NE(controller, nullptr); ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
std::string notification_id;
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); notification_id = notification.id();
EXPECT_EQ(notification->progress(), 0); EXPECT_EQ(notification.progress(), 0);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// CANCELLING: // CANCELLING:
...@@ -671,23 +724,24 @@ TEST_F(CrostiniExportImportTest, TestImportDoneBeforeCancelled) { ...@@ -671,23 +724,24 @@ TEST_F(CrostiniExportImportTest, TestImportDoneBeforeCancelled) {
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::CANCELLING); CrostiniExportImportStatusTracker::Status::CANCELLING);
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); EXPECT_EQ(notification.id(), notification_id);
EXPECT_EQ(notification->progress(), -1); EXPECT_EQ(notification.progress(), -1);
EXPECT_FALSE(notification->pinned()); EXPECT_FALSE(notification.pinned());
} }
// DONE: Cancel couldn't be processed in time, done is displayed instead. // DONE: Cancel couldn't be processed in time, done is displayed instead.
SendImportProgress( SendImportProgress(
vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_DONE); vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_DONE);
ASSERT_EQ(GetController(), nullptr); EXPECT_EQ(GetController(), nullptr);
ASSERT_NE(controller, nullptr); EXPECT_EQ(controller, nullptr);
EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::DONE);
{ {
message_center::Notification* notification = controller->get_notification(); const base::Optional<message_center::Notification> ui_notification =
ASSERT_NE(notification, nullptr); notification_display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification->pinned()); ASSERT_NE(ui_notification, base::nullopt);
EXPECT_FALSE(ui_notification->pinned());
std::string msg("Linux apps & files have been successfully replaced");
EXPECT_EQ(ui_notification->message(), base::UTF8ToUTF16(msg));
} }
} }
...@@ -701,32 +755,31 @@ TEST_F(CrostiniExportImportTest, TestImportFailArchitecture) { ...@@ -701,32 +755,31 @@ TEST_F(CrostiniExportImportTest, TestImportFailArchitecture) {
ASSERT_NE(controller, nullptr); ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
std::string notification_id;
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); notification_id = notification.id();
EXPECT_EQ(notification->progress(), 0); EXPECT_EQ(notification.progress(), 0);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// Failed Architecture. // Failed Architecture.
SendImportProgress( SendImportProgress(
vm_tools::cicerone:: vm_tools::cicerone::
ImportLxdContainerProgressSignal_Status_FAILED_ARCHITECTURE); ImportLxdContainerProgressSignal_Status_FAILED_ARCHITECTURE);
ASSERT_EQ(GetController(), nullptr); EXPECT_EQ(GetController(), nullptr);
ASSERT_NE(controller, nullptr); EXPECT_EQ(controller, nullptr);
EXPECT_EQ(
controller->status(),
CrostiniExportImportStatusTracker::Status::FAILED_ARCHITECTURE_MISMATCH);
{ {
message_center::Notification* notification = controller->get_notification(); const base::Optional<message_center::Notification> ui_notification =
ASSERT_NE(notification, nullptr); notification_display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification->pinned()); ASSERT_NE(ui_notification, base::nullopt);
EXPECT_FALSE(ui_notification->pinned());
std::string msg( std::string msg(
"Cannot import container architecture type arch_con with this device " "Cannot import container architecture type arch_con with this device "
"which is arch_dev. You can try restoring this container into a " "which is arch_dev. You can try restoring this container into a "
"different device, or you can access the files inside this container " "different device, or you can access the files inside this container "
"image by opening in Files app."); "image by opening in Files app.");
EXPECT_EQ(notification->message(), base::UTF8ToUTF16(msg)); EXPECT_EQ(ui_notification->message(), base::UTF8ToUTF16(msg));
} }
} }
...@@ -740,11 +793,12 @@ TEST_F(CrostiniExportImportTest, TestImportFailSpace) { ...@@ -740,11 +793,12 @@ TEST_F(CrostiniExportImportTest, TestImportFailSpace) {
ASSERT_NE(controller, nullptr); ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller->status(), EXPECT_EQ(controller->status(),
CrostiniExportImportStatusTracker::Status::RUNNING); CrostiniExportImportStatusTracker::Status::RUNNING);
std::string notification_id;
{ {
message_center::Notification* notification = controller->get_notification(); const message_center::Notification& notification = GetNotification();
ASSERT_NE(notification, nullptr); notification_id = notification.id();
EXPECT_EQ(notification->progress(), 0); EXPECT_EQ(notification.progress(), 0);
EXPECT_TRUE(notification->pinned()); EXPECT_TRUE(notification.pinned());
} }
// Failed Space. // Failed Space.
...@@ -754,19 +808,17 @@ TEST_F(CrostiniExportImportTest, TestImportFailSpace) { ...@@ -754,19 +808,17 @@ TEST_F(CrostiniExportImportTest, TestImportFailSpace) {
.available_space = 20ul * 1'024 * 1'024 * 1'024, // 20Gb .available_space = 20ul * 1'024 * 1'024 * 1'024, // 20Gb
.min_required_space = 35ul * 1'024 * 1'024 * 1'024 // 35Gb .min_required_space = 35ul * 1'024 * 1'024 * 1'024 // 35Gb
}); });
ASSERT_EQ(GetController(), nullptr); EXPECT_EQ(GetController(), nullptr);
ASSERT_NE(controller, nullptr); EXPECT_EQ(controller, nullptr);
EXPECT_EQ(
controller->status(),
CrostiniExportImportStatusTracker::Status::FAILED_INSUFFICIENT_SPACE);
{ {
message_center::Notification* notification = controller->get_notification(); const base::Optional<message_center::Notification> ui_notification =
ASSERT_NE(notification, nullptr); notification_display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification->pinned()); ASSERT_NE(ui_notification, base::nullopt);
EXPECT_FALSE(ui_notification->pinned());
std::string msg = std::string msg =
"Cannot restore due to lack of storage space. Free up 15.0 GB from the " "Cannot restore due to lack of storage space. Free up 15.0 GB from the "
"device and try again."; "device and try again.";
EXPECT_EQ(notification->message(), base::UTF8ToUTF16(msg)); EXPECT_EQ(ui_notification->message(), base::UTF8ToUTF16(msg));
} }
} }
......
...@@ -43,7 +43,21 @@ ThunkNotificationDelegate::~ThunkNotificationDelegate() = default; ...@@ -43,7 +43,21 @@ ThunkNotificationDelegate::~ThunkNotificationDelegate() = default;
HandleNotificationClickDelegate::HandleNotificationClickDelegate( HandleNotificationClickDelegate::HandleNotificationClickDelegate(
const base::RepeatingClosure& callback) { const base::RepeatingClosure& callback) {
if (!callback.is_null()) { SetCallback(callback);
}
HandleNotificationClickDelegate::HandleNotificationClickDelegate(
const ButtonClickCallback& callback)
: callback_(callback) {}
void HandleNotificationClickDelegate::SetCallback(
const ButtonClickCallback& callback) {
callback_ = callback;
}
void HandleNotificationClickDelegate::SetCallback(
const base::RepeatingClosure& closure) {
if (!closure.is_null()) {
// Create a callback that consumes and ignores the button index parameter, // Create a callback that consumes and ignores the button index parameter,
// and just runs the provided closure. // and just runs the provided closure.
callback_ = base::BindRepeating( callback_ = base::BindRepeating(
...@@ -52,14 +66,10 @@ HandleNotificationClickDelegate::HandleNotificationClickDelegate( ...@@ -52,14 +66,10 @@ HandleNotificationClickDelegate::HandleNotificationClickDelegate(
DCHECK(!button_index); DCHECK(!button_index);
closure.Run(); closure.Run();
}, },
callback); closure);
} }
} }
HandleNotificationClickDelegate::HandleNotificationClickDelegate(
const ButtonClickCallback& callback)
: callback_(callback) {}
HandleNotificationClickDelegate::~HandleNotificationClickDelegate() {} HandleNotificationClickDelegate::~HandleNotificationClickDelegate() {}
void HandleNotificationClickDelegate::Click( void HandleNotificationClickDelegate::Click(
......
...@@ -95,6 +95,14 @@ class MESSAGE_CENTER_PUBLIC_EXPORT HandleNotificationClickDelegate ...@@ -95,6 +95,14 @@ class MESSAGE_CENTER_PUBLIC_EXPORT HandleNotificationClickDelegate
explicit HandleNotificationClickDelegate( explicit HandleNotificationClickDelegate(
const base::RepeatingClosure& closure); const base::RepeatingClosure& closure);
// Overrides the callback with one that handles clicks on a button or on the
// body.
void SetCallback(const ButtonClickCallback& callback);
// Overrides the callback with one that only handles clicks on the body of the
// notification.
void SetCallback(const base::RepeatingClosure& closure);
// NotificationDelegate overrides: // NotificationDelegate overrides:
void Click(const base::Optional<int>& button_index, void Click(const base::Optional<int>& button_index,
const base::Optional<base::string16>& reply) override; const base::Optional<base::string16>& reply) override;
......
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