crostini export with notifications
Exposes export and import functions which control
File dialog chooser to select file for export/import
and displays notification with progress.
BUG=912638
Change-Id: I7b5ff8151bf76f81d2483ff199ed573e42277a2e
Reviewed-on: https://chromium-review.googlesource.com/c/1462638
Commit-Queue: Joel Hockey <joelhockey@chromium.org>
Reviewed-by: Nicholas Verne <nverne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#632495}
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 974dbfa..97771c1 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -3542,6 +3542,32 @@
No description is available.
</message>
+ <!-- Crostini export and import -->
+ <message name="IDS_CROSTINI_EXPORT_TITLE" desc="Title for exporting (backing up) the crostini container.">
+ Backup
+ </message>
+ <message name="IDS_CROSTINI_EXPORT_NOTIFICATION_IN_PROGRESS" desc="Message displayed while export is in progress in the notification for exporting (backing up) the crostini container.">
+ Backup up...
+ </message>
+ <message name="IDS_CROSTINI_EXPORT_NOTIFICATION_DONE" desc="Message displayed when done in the notification for exporting (backing up) the crostini container.">
+ Backup complete
+ </message>
+ <message name="IDS_CROSTINI_EXPORT_NOTIFICATION_FAILED" desc="Message displayed when there is a failure in the notification for exporting (backing up) the crostini container.">
+ Backup failed
+ </message>
+ <message name="IDS_CROSTINI_IMPORT_TITLE" desc="Title for importing (restoring) the crostini container.">
+ Restore
+ </message>
+ <message name="IDS_CROSTINI_IMPORT_NOTIFICATION_IN_PROGRESS" desc="Message displayed while import is in progress in the notification for importing (restoring) the crostini container.">
+ Restoring...
+ </message>
+ <message name="IDS_CROSTINI_IMPORT_NOTIFICATION_DONE" desc="Message displayed when done in the notification for importing (restoring) the crostini container.">
+ Restore complete
+ </message>
+ <message name="IDS_CROSTINI_IMPORT_NOTIFICATION_FAILED" desc="Message displayed when there is a failure in the notification for importing (restoring) the crostini container.">
+ Restore failed
+ </message>
+
<!-- Time limit notification -->
<message name="IDS_SCREEN_TIME_NOTIFICATION_TITLE" desc="The title of the notification when screen usage limit reaches before locking the device.">
Almost time for a break
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_DONE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_DONE.png.sha1
new file mode 100644
index 0000000..afd7bd82
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_DONE.png.sha1
@@ -0,0 +1 @@
+f4d76e1c540780ba80e4b6568367695c93d73f68
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_FAILED.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_FAILED.png.sha1
new file mode 100644
index 0000000..afd7bd82
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_FAILED.png.sha1
@@ -0,0 +1 @@
+f4d76e1c540780ba80e4b6568367695c93d73f68
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_IN_PROGRESS.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_IN_PROGRESS.png.sha1
new file mode 100644
index 0000000..afd7bd82
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_IN_PROGRESS.png.sha1
@@ -0,0 +1 @@
+f4d76e1c540780ba80e4b6568367695c93d73f68
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_TITLE.png.sha1
new file mode 100644
index 0000000..afd7bd82
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_TITLE.png.sha1
@@ -0,0 +1 @@
+f4d76e1c540780ba80e4b6568367695c93d73f68
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_DONE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_DONE.png.sha1
new file mode 100644
index 0000000..bceb036
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_DONE.png.sha1
@@ -0,0 +1 @@
+67a7f5b5f40ee10862fdb1ce486f99f99812a5cf
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_FAILED.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_FAILED.png.sha1
new file mode 100644
index 0000000..bceb036
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_FAILED.png.sha1
@@ -0,0 +1 @@
+67a7f5b5f40ee10862fdb1ce486f99f99812a5cf
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_IN_PROGRESS.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_IN_PROGRESS.png.sha1
new file mode 100644
index 0000000..bceb036
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_IN_PROGRESS.png.sha1
@@ -0,0 +1 @@
+67a7f5b5f40ee10862fdb1ce486f99f99812a5cf
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_TITLE.png.sha1
new file mode 100644
index 0000000..bceb036
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_TITLE.png.sha1
@@ -0,0 +1 @@
+67a7f5b5f40ee10862fdb1ce486f99f99812a5cf
\ No newline at end of file
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 54125ae..ddb1759 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -646,6 +646,10 @@
"chrome_content_browser_client_chromeos_part.h",
"chrome_service_name.cc",
"chrome_service_name.h",
+ "crostini/crostini_export_import.cc",
+ "crostini/crostini_export_import.h",
+ "crostini/crostini_export_import_notification.cc",
+ "crostini/crostini_export_import_notification.h",
"crostini/crostini_manager.cc",
"crostini/crostini_manager.h",
"crostini/crostini_manager_factory.cc",
@@ -2216,6 +2220,7 @@
"child_accounts/time_limit_test_utils.cc",
"child_accounts/usage_time_limit_processor_unittest.cc",
"child_accounts/usage_time_state_notifier_unittest.cc",
+ "crostini/crostini_export_import_unittest.cc",
"crostini/crostini_manager_unittest.cc",
"crostini/crostini_package_service_unittest.cc",
"crostini/crostini_share_path_unittest.cc",
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import.cc b/chrome/browser/chromeos/crostini/crostini_export_import.cc
new file mode 100644
index 0000000..33cae00
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_export_import.cc
@@ -0,0 +1,297 @@
+// 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.h"
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_share_path.h"
+#include "chrome/browser/chromeos/crostini/crostini_share_path_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/chrome_select_file_policy.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace crostini {
+
+class CrostiniExportImportFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static CrostiniExportImport* GetForProfile(Profile* profile) {
+ return static_cast<CrostiniExportImport*>(
+ GetInstance()->GetServiceForBrowserContext(profile, true));
+ }
+
+ static CrostiniExportImportFactory* GetInstance() {
+ static base::NoDestructor<CrostiniExportImportFactory> factory;
+ return factory.get();
+ }
+
+ private:
+ friend class base::NoDestructor<CrostiniExportImportFactory>;
+
+ CrostiniExportImportFactory()
+ : BrowserContextKeyedServiceFactory(
+ "CrostiniExportImportService",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(CrostiniSharePathFactory::GetInstance());
+ DependsOn(CrostiniManagerFactory::GetInstance());
+ }
+
+ ~CrostiniExportImportFactory() override = default;
+
+ // BrowserContextKeyedServiceFactory:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override {
+ Profile* profile = Profile::FromBrowserContext(context);
+ return new CrostiniExportImport(profile);
+ }
+};
+
+CrostiniExportImport* CrostiniExportImport::GetForProfile(Profile* profile) {
+ return CrostiniExportImportFactory::GetForProfile(profile);
+}
+
+CrostiniExportImport::CrostiniExportImport(Profile* profile)
+ : profile_(profile), weak_ptr_factory_(this) {
+ CrostiniManager* manager = CrostiniManager::GetForProfile(profile_);
+ manager->AddExportContainerProgressObserver(this);
+ manager->AddImportContainerProgressObserver(this);
+}
+
+CrostiniExportImport::~CrostiniExportImport() = default;
+
+void CrostiniExportImport::Shutdown() {
+ CrostiniManager* manager = CrostiniManager::GetForProfile(profile_);
+ manager->RemoveExportContainerProgressObserver(this);
+ manager->RemoveImportContainerProgressObserver(this);
+}
+
+void CrostiniExportImport::ExportContainer(content::WebContents* web_contents) {
+ OpenFileDialog(ExportImportType::EXPORT, web_contents);
+}
+
+void CrostiniExportImport::ImportContainer(content::WebContents* web_contents) {
+ OpenFileDialog(ExportImportType::IMPORT, web_contents);
+}
+
+void CrostiniExportImport::OpenFileDialog(ExportImportType type,
+ content::WebContents* web_contents) {
+ PrefService* pref_service = profile_->GetPrefs();
+ ui::SelectFileDialog::Type file_selector_mode;
+ unsigned title = 0;
+ base::FilePath default_path;
+ ui::SelectFileDialog::FileTypeInfo file_types;
+ file_types.allowed_paths =
+ ui::SelectFileDialog::FileTypeInfo::NATIVE_OR_DRIVE_PATH;
+ file_types.extensions = {{"tar.gz", "tgz"}};
+
+ switch (type) {
+ case ExportImportType::EXPORT:
+ file_selector_mode = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
+ title = IDS_CROSTINI_EXPORT_TITLE;
+ base::Time::Exploded exploded;
+ base::Time::Now().LocalExplode(&exploded);
+ default_path = pref_service->GetFilePath(prefs::kDownloadDefaultDirectory)
+ .Append(base::StringPrintf(
+ "crostini-%04d-%02d-%02d.tar.gz", exploded.year,
+ exploded.month, exploded.day_of_month));
+ break;
+ case ExportImportType::IMPORT:
+ file_selector_mode = ui::SelectFileDialog::SELECT_OPEN_FILE,
+ title = IDS_CROSTINI_IMPORT_TITLE;
+ default_path =
+ pref_service->GetFilePath(prefs::kDownloadDefaultDirectory);
+ break;
+ }
+
+ select_folder_dialog_ = ui::SelectFileDialog::Create(
+ this, std::make_unique<ChromeSelectFilePolicy>(web_contents));
+ select_folder_dialog_->SelectFile(
+ file_selector_mode, l10n_util::GetStringUTF16(title), default_path,
+ &file_types, 0, base::FilePath::StringType(),
+ web_contents->GetTopLevelNativeWindow(), reinterpret_cast<void*>(type));
+}
+
+void CrostiniExportImport::FileSelected(const base::FilePath& path,
+ int index,
+ void* params) {
+ ExportImportType type =
+ static_cast<ExportImportType>(reinterpret_cast<uintptr_t>(params));
+ ContainerId container_id(crostini::kCrostiniDefaultVmName,
+ crostini::kCrostiniDefaultContainerName);
+ notifications_[container_id] =
+ std::make_unique<CrostiniExportImportNotification>(
+ profile_, type, GetUniqueNotificationId(), path);
+
+ switch (type) {
+ case ExportImportType::EXPORT:
+ crostini::CrostiniSharePath::GetForProfile(profile_)->SharePath(
+ crostini::kCrostiniDefaultVmName, path.DirName(), false,
+ base::BindOnce(&CrostiniExportImport::ExportAfterSharing,
+ weak_ptr_factory_.GetWeakPtr(), container_id,
+ path.BaseName()));
+ break;
+ case ExportImportType::IMPORT:
+ crostini::CrostiniSharePath::GetForProfile(profile_)->SharePath(
+ crostini::kCrostiniDefaultVmName, path, false,
+ base::BindOnce(&CrostiniExportImport::ImportAfterSharing,
+ weak_ptr_factory_.GetWeakPtr(), container_id));
+ break;
+ }
+}
+
+void CrostiniExportImport::ExportAfterSharing(
+ const ContainerId& container_id,
+ const base::FilePath& filename,
+ const base::FilePath& container_path,
+ bool result,
+ const std::string failure_reason) {
+ if (!result) {
+ LOG(ERROR) << "Error sharing for export " << container_path.value() << ": "
+ << failure_reason;
+ auto it = notifications_.find(container_id);
+ DCHECK(it != notifications_.end()) << ContainerIdToString(container_id)
+ << " has no notification to update";
+ it->second->UpdateStatus(CrostiniExportImportNotification::Status::FAILED,
+ 0);
+ return;
+ }
+ CrostiniManager::GetForProfile(profile_)->ExportLxdContainer(
+ crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
+ container_path.Append(filename),
+ base::BindOnce(&CrostiniExportImport::OnExportComplete,
+ weak_ptr_factory_.GetWeakPtr(), container_id));
+}
+
+void CrostiniExportImport::OnExportComplete(const ContainerId& container_id,
+ CrostiniResult result) {
+ auto it = notifications_.find(container_id);
+ DCHECK(it != notifications_.end())
+ << ContainerIdToString(container_id) << " has no notification to update";
+
+ if (result != crostini::CrostiniResult::SUCCESS) {
+ LOG(ERROR) << "Error exporting " << int(result);
+ it->second->UpdateStatus(CrostiniExportImportNotification::Status::FAILED,
+ 0);
+ } else {
+ it->second->UpdateStatus(CrostiniExportImportNotification::Status::DONE, 0);
+ }
+ notifications_.erase(it);
+}
+
+void CrostiniExportImport::OnExportContainerProgress(
+ const std::string& vm_name,
+ const std::string& container_name,
+ crostini::ExportContainerProgressStatus status,
+ int progress_percent,
+ uint64_t progress_speed) {
+ ContainerId container_id(vm_name, container_name);
+ auto it = notifications_.find(container_id);
+ DCHECK(it != notifications_.end())
+ << ContainerIdToString(container_id) << " has no notification to update";
+
+ switch (status) {
+ // Rescale PACK:1-100 => 0-50.
+ case crostini::ExportContainerProgressStatus::PACK:
+ it->second->UpdateStatus(
+ CrostiniExportImportNotification::Status::RUNNING,
+ progress_percent / 2);
+ break;
+ // Rescale DOWNLOAD:1-100 => 50-100.
+ case crostini::ExportContainerProgressStatus::DOWNLOAD:
+ it->second->UpdateStatus(
+ CrostiniExportImportNotification::Status::RUNNING,
+ 50 + progress_percent / 2);
+ break;
+ default:
+ LOG(WARNING) << "Unknown Export progress status " << int(status);
+ }
+}
+
+void CrostiniExportImport::ImportAfterSharing(
+ const ContainerId& container_id,
+ const base::FilePath& container_path,
+ bool result,
+ const std::string failure_reason) {
+ if (!result) {
+ LOG(ERROR) << "Error sharing for import " << container_path.value() << ": "
+ << failure_reason;
+ auto it = notifications_.find(container_id);
+ DCHECK(it != notifications_.end()) << ContainerIdToString(container_id)
+ << " has no notification to update";
+ it->second->UpdateStatus(CrostiniExportImportNotification::Status::FAILED,
+ 0);
+ return;
+ }
+ CrostiniManager::GetForProfile(profile_)->ImportLxdContainer(
+ crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
+ container_path,
+ base::BindOnce(&CrostiniExportImport::OnImportComplete,
+ weak_ptr_factory_.GetWeakPtr(), container_id));
+}
+
+void CrostiniExportImport::OnImportComplete(const ContainerId& container_id,
+ CrostiniResult result) {
+ auto it = notifications_.find(container_id);
+ DCHECK(it != notifications_.end())
+ << ContainerIdToString(container_id) << " has no notification to update";
+ if (result != crostini::CrostiniResult::SUCCESS) {
+ LOG(ERROR) << "Error importing " << int(result);
+ it->second->UpdateStatus(CrostiniExportImportNotification::Status::FAILED,
+ 0);
+ } else {
+ it->second->UpdateStatus(CrostiniExportImportNotification::Status::DONE, 0);
+ }
+ notifications_.erase(it);
+}
+
+void CrostiniExportImport::OnImportContainerProgress(
+ const std::string& vm_name,
+ const std::string& container_name,
+ crostini::ImportContainerProgressStatus status,
+ int progress_percent,
+ uint64_t progress_speed) {
+ ContainerId container_id(vm_name, container_name);
+ auto it = notifications_.find(container_id);
+ DCHECK(it != notifications_.end())
+ << ContainerIdToString(container_id) << " has no notification to update";
+
+ switch (status) {
+ // Rescale UPLOAD:1-100 => 0-50.
+ case crostini::ImportContainerProgressStatus::UPLOAD:
+ it->second->UpdateStatus(
+ CrostiniExportImportNotification::Status::RUNNING,
+ progress_percent / 2);
+ break;
+ // Rescale UNPACK:1-100 => 50-100.
+ case crostini::ImportContainerProgressStatus::UNPACK:
+ it->second->UpdateStatus(
+ CrostiniExportImportNotification::Status::RUNNING,
+ 50 + progress_percent / 2);
+ break;
+ default:
+ LOG(WARNING) << "Unknown Export progress status " << int(status);
+ }
+}
+
+std::string CrostiniExportImport::GetUniqueNotificationId() {
+ return base::StringPrintf("crostini_export_import_%d",
+ next_notification_id_++);
+}
+
+CrostiniExportImportNotification*
+CrostiniExportImport::GetNotificationForTesting(ContainerId container_id) {
+ auto it = notifications_.find(container_id);
+ return it != notifications_.end() ? it->second.get() : nullptr;
+}
+
+} // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import.h b/chrome/browser/chromeos/crostini/crostini_export_import.h
new file mode 100644
index 0000000..12f792e
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_export_import.h
@@ -0,0 +1,114 @@
+// 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_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/crostini/crostini_export_import_notification.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class Profile;
+
+namespace content {
+class WebContents;
+}
+
+namespace crostini {
+
+enum class CrostiniResult;
+
+enum class ExportImportType { EXPORT, IMPORT };
+
+// CrostiniExportImport is a keyed profile service to manage exporting and
+// importing containers with crostini. It manages a file dialog for selecting
+// files and a notification to show the progress of export/import.
+//
+// TODO(crbug.com/932339): Ensure we have enough free space before doing
+// backup or restore.
+class CrostiniExportImport : public KeyedService,
+ public ui::SelectFileDialog::Listener,
+ public crostini::ExportContainerProgressObserver,
+ public crostini::ImportContainerProgressObserver {
+ public:
+ static CrostiniExportImport* GetForProfile(Profile* profile);
+
+ explicit CrostiniExportImport(Profile* profile);
+ ~CrostiniExportImport() override;
+
+ // KeyedService:
+ void Shutdown() override;
+
+ // Export the crostini container.
+ void ExportContainer(content::WebContents* web_contents);
+ // Import the crostini container.
+ void ImportContainer(content::WebContents* web_contents);
+
+ CrostiniExportImportNotification* GetNotificationForTesting(
+ ContainerId container_id);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(CrostiniExportImportTest, TestExportSuccess);
+ FRIEND_TEST_ALL_PREFIXES(CrostiniExportImportTest, TestExportFail);
+ FRIEND_TEST_ALL_PREFIXES(CrostiniExportImportTest, TestImportSuccess);
+ FRIEND_TEST_ALL_PREFIXES(CrostiniExportImportTest, TestImportFail);
+
+ // ui::SelectFileDialog::Listener implementation.
+ void FileSelected(const base::FilePath& path,
+ int index,
+ void* params) override;
+
+ // crostini::ExportContainerProgressObserver implementation.
+ void OnExportContainerProgress(const std::string& vm_name,
+ const std::string& container_name,
+ crostini::ExportContainerProgressStatus status,
+ int progress_percent,
+ uint64_t progress_speed) override;
+
+ // crostini::ImportContainerProgressObserver implementation.
+ void OnImportContainerProgress(const std::string& vm_name,
+ const std::string& container_name,
+ crostini::ImportContainerProgressStatus status,
+ int progress_percent,
+ uint64_t progress_speed) override;
+
+ void ExportAfterSharing(const ContainerId& container_id,
+ const base::FilePath& filename,
+ const base::FilePath& container_path,
+ bool result,
+ const std::string failure_reason);
+ void OnExportComplete(const ContainerId& container_id, CrostiniResult result);
+
+ void ImportAfterSharing(const ContainerId& container_id,
+ const base::FilePath& container_path,
+ bool result,
+ const std::string failure_reason);
+ void OnImportComplete(const ContainerId& container_id, CrostiniResult result);
+
+ void OpenFileDialog(ExportImportType type,
+ content::WebContents* web_contents);
+
+ std::string GetUniqueNotificationId();
+
+ Profile* profile_;
+ scoped_refptr<ui::SelectFileDialog> select_folder_dialog_;
+ std::map<ContainerId, std::unique_ptr<CrostiniExportImportNotification>>
+ notifications_;
+ // Notifications must have unique-per-profile identifiers.
+ // A non-static member on a profile-keyed-service will suffice.
+ int next_notification_id_;
+ // weak_ptr_factory_ should always be last member.
+ base::WeakPtrFactory<CrostiniExportImport> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrostiniExportImport);
+};
+
+} // namespace crostini
+
+#endif // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import_notification.cc b/chrome/browser/chromeos/crostini/crostini_export_import_notification.cc
new file mode 100644
index 0000000..6af4316
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_export_import_notification.cc
@@ -0,0 +1,119 @@
+// 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_notification.h"
+
+#include "ash/public/cpp/notification_utils.h"
+#include "ash/public/cpp/vector_icons/vector_icons.h"
+#include "chrome/browser/chromeos/crostini/crostini_export_import.h"
+#include "chrome/browser/notifications/notification_display_service.h"
+#include "chrome/browser/platform_util_chromeos.cc"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/message_center/public/cpp/message_center_constants.h"
+#include "ui/message_center/public/cpp/notification.h"
+#include "ui/message_center/public/cpp/notification_delegate.h"
+
+namespace crostini {
+
+namespace {
+
+constexpr char kNotifierCrostiniExportImportOperation[] =
+ "crostini.export_operation";
+
+} // namespace
+
+CrostiniExportImportNotification::CrostiniExportImportNotification(
+ Profile* profile,
+ ExportImportType type,
+ const std::string& notification_id,
+ const base::FilePath& path)
+ : profile_(profile), type_(type), path_(path), weak_ptr_factory_(this) {
+ // Messages.
+ switch (type) {
+ case ExportImportType::EXPORT:
+ message_title_ = IDS_CROSTINI_EXPORT_TITLE;
+ message_running_ = IDS_CROSTINI_EXPORT_NOTIFICATION_IN_PROGRESS;
+ message_done_ = IDS_CROSTINI_EXPORT_NOTIFICATION_DONE;
+ message_failed_ = IDS_CROSTINI_EXPORT_NOTIFICATION_FAILED;
+ break;
+ case ExportImportType::IMPORT:
+ message_title_ = IDS_CROSTINI_IMPORT_TITLE;
+ message_running_ = IDS_CROSTINI_IMPORT_NOTIFICATION_IN_PROGRESS;
+ message_done_ = IDS_CROSTINI_IMPORT_NOTIFICATION_DONE;
+ message_failed_ = IDS_CROSTINI_IMPORT_NOTIFICATION_FAILED;
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ message_center::RichNotificationData rich_notification_data;
+ rich_notification_data.vector_small_image = &ash::kNotificationLinuxIcon;
+ rich_notification_data.accent_color = ash::kSystemNotificationColorNormal;
+
+ notification_ = std::make_unique<message_center::Notification>(
+ message_center::NOTIFICATION_TYPE_PROGRESS, notification_id,
+ base::string16(), base::string16(),
+ gfx::Image(), // icon
+ l10n_util::GetStringUTF16(message_title_),
+ GURL(), // origin_url
+ message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
+ kNotifierCrostiniExportImportOperation),
+ rich_notification_data,
+ base::MakeRefCounted<message_center::ThunkNotificationDelegate>(
+ weak_ptr_factory_.GetWeakPtr()));
+
+ UpdateStatus(Status::RUNNING, 0);
+}
+
+CrostiniExportImportNotification::~CrostiniExportImportNotification() = default;
+
+void CrostiniExportImportNotification::UpdateStatus(Status status,
+ int progress_percent) {
+ status_ = status;
+ switch (status) {
+ case Status::RUNNING:
+ if (closed_) {
+ return;
+ }
+ notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS);
+ notification_->set_progress(progress_percent);
+ notification_->set_message(l10n_util::GetStringUTF16(message_running_));
+ notification_->set_never_timeout(true);
+ break;
+ case Status::DONE:
+ notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
+ notification_->set_message(l10n_util::GetStringUTF16(message_done_));
+ notification_->set_never_timeout(false);
+ break;
+ case Status::FAILED:
+ notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
+ notification_->set_accent_color(
+ ash::kSystemNotificationColorCriticalWarning);
+ notification_->set_message(l10n_util::GetStringUTF16(message_failed_));
+ notification_->set_never_timeout(false);
+ break;
+ default:
+ NOTREACHED();
+ }
+ NotificationDisplayService* display_service =
+ NotificationDisplayService::GetForProfile(profile_);
+ display_service->Display(NotificationHandler::Type::TRANSIENT,
+ *notification_);
+}
+
+void CrostiniExportImportNotification::Close(bool by_user) {
+ closed_ = true;
+}
+
+void CrostiniExportImportNotification::Click(
+ const base::Optional<int>& button_index,
+ const base::Optional<base::string16>& reply) {
+ if (type_ == ExportImportType::EXPORT) {
+ platform_util::ShowItemInFolder(profile_, path_);
+ }
+}
+
+} // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import_notification.h b/chrome/browser/chromeos/crostini/crostini_export_import_notification.h
new file mode 100644
index 0000000..a7f90f0
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_export_import_notification.h
@@ -0,0 +1,68 @@
+// 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_NOTIFICATION_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_NOTIFICATION_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/message_center/public/cpp/notification_delegate.h"
+
+class Profile;
+
+namespace message_center {
+class Notification;
+}
+
+namespace crostini {
+
+enum class ExportImportType;
+
+// Notification for Crostini export and import.
+class CrostiniExportImportNotification
+ : public message_center::NotificationObserver {
+ public:
+ enum class Status { RUNNING, DONE, FAILED };
+
+ CrostiniExportImportNotification(Profile* profile,
+ ExportImportType type,
+ const std::string& notification_id,
+ const base::FilePath& path);
+ virtual ~CrostiniExportImportNotification();
+
+ void UpdateStatus(Status status, int progress_percent);
+
+ // Getters for testing.
+ Status get_status() { return status_; }
+ message_center::Notification* get_notification() {
+ return notification_.get();
+ }
+
+ // message_center::NotificationObserver:
+ void Close(bool by_user) override;
+ void Click(const base::Optional<int>& button_index,
+ const base::Optional<base::string16>& reply) override;
+
+ private:
+ Profile* profile_;
+ ExportImportType type_;
+ base::FilePath path_;
+ // These notifications are owned by the export service.
+ Status status_ = Status::RUNNING;
+ int message_title_;
+ int message_running_;
+ int message_done_;
+ int message_failed_;
+ std::unique_ptr<message_center::Notification> notification_;
+ bool closed_ = false;
+ base::WeakPtrFactory<CrostiniExportImportNotification> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(CrostiniExportImportNotification);
+};
+
+} // namespace crostini
+
+#endif // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_NOTIFICATION_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc b/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc
new file mode 100644
index 0000000..41a386c
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc
@@ -0,0 +1,227 @@
+// 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.h"
+
+#include "base/files/file_path.h"
+#include "base/run_loop.h"
+#include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/browser/chromeos/file_manager/path_util.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/fake_cicerone_client.h"
+#include "chromeos/dbus/fake_seneschal_client.h"
+#include "chromeos/dbus/seneschal/seneschal_service.pb.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "storage/browser/fileapi/external_mount_points.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/message_center/public/cpp/notification.h"
+
+namespace crostini {
+
+class CrostiniExportImportTest : public testing::Test {
+ public:
+ void SendExportProgress(
+ vm_tools::cicerone::ExportLxdContainerProgressSignal_Status status,
+ int progress_percent) {
+ vm_tools::cicerone::ExportLxdContainerProgressSignal signal;
+ signal.set_owner_id(CryptohomeIdForProfile(profile()));
+ signal.set_vm_name(kCrostiniDefaultVmName);
+ signal.set_container_name(kCrostiniDefaultContainerName);
+ signal.set_status(status);
+ signal.set_progress_percent(progress_percent);
+ fake_cicerone_client_->NotifyExportLxdContainerProgress(signal);
+ }
+
+ void SendImportProgress(
+ vm_tools::cicerone::ImportLxdContainerProgressSignal_Status status,
+ int progress_percent) {
+ vm_tools::cicerone::ImportLxdContainerProgressSignal signal;
+ signal.set_owner_id(CryptohomeIdForProfile(profile()));
+ signal.set_vm_name(kCrostiniDefaultVmName);
+ signal.set_container_name(kCrostiniDefaultContainerName);
+ signal.set_status(status);
+ signal.set_progress_percent(progress_percent);
+ fake_cicerone_client_->NotifyImportLxdContainerProgress(signal);
+ }
+
+ CrostiniExportImportTest()
+ : test_browser_thread_bundle_(
+ content::TestBrowserThreadBundle::REAL_IO_THREAD) {
+ chromeos::DBusThreadManager::Initialize();
+ fake_seneschal_client_ = static_cast<chromeos::FakeSeneschalClient*>(
+ chromeos::DBusThreadManager::Get()->GetSeneschalClient());
+ fake_cicerone_client_ = static_cast<chromeos::FakeCiceroneClient*>(
+ chromeos::DBusThreadManager::Get()->GetCiceroneClient());
+ }
+
+ ~CrostiniExportImportTest() override {
+ chromeos::DBusThreadManager::Shutdown();
+ }
+
+ void SetUp() override {
+ run_loop_ = std::make_unique<base::RunLoop>();
+ profile_ = std::make_unique<TestingProfile>();
+ crostini_export_import_ = std::make_unique<CrostiniExportImport>(profile());
+ CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
+ kCrostiniDefaultVmName);
+ container_id_ =
+ ContainerId(kCrostiniDefaultVmName, kCrostiniDefaultContainerName);
+
+ storage::ExternalMountPoints* mount_points =
+ storage::ExternalMountPoints::GetSystemInstance();
+ mount_points->RegisterFileSystem(
+ file_manager::util::GetDownloadsMountPointName(profile()),
+ storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(),
+ file_manager::util::GetMyFilesFolderForProfile(profile()));
+ tarball_ = file_manager::util::GetMyFilesFolderForProfile(profile()).Append(
+ "tarball.tar.gz");
+ }
+
+ void TearDown() override {
+ run_loop_.reset();
+ profile_.reset();
+ }
+
+ protected:
+ base::RunLoop* run_loop() { return run_loop_.get(); }
+ Profile* profile() { return profile_.get(); }
+ CrostiniExportImport* crostini_export_import() {
+ return crostini_export_import_.get();
+ }
+
+ // Owned by chromeos::DBusThreadManager
+ chromeos::FakeCiceroneClient* fake_cicerone_client_;
+ chromeos::FakeSeneschalClient* fake_seneschal_client_;
+
+ std::unique_ptr<base::RunLoop> run_loop_;
+ std::unique_ptr<TestingProfile> profile_;
+ std::unique_ptr<CrostiniExportImport> crostini_export_import_;
+ ContainerId container_id_;
+ base::FilePath tarball_;
+
+ private:
+ content::TestBrowserThreadBundle test_browser_thread_bundle_;
+ DISALLOW_COPY_AND_ASSIGN(CrostiniExportImportTest);
+};
+
+TEST_F(CrostiniExportImportTest, TestExportSuccess) {
+ crostini_export_import()->FileSelected(
+ tarball_, 0, reinterpret_cast<void*>(ExportImportType::EXPORT));
+ run_loop_->RunUntilIdle();
+ CrostiniExportImportNotification* notification =
+ crostini_export_import()->GetNotificationForTesting(container_id_);
+ EXPECT_NE(notification, nullptr);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::RUNNING);
+ EXPECT_EQ(notification->get_notification()->progress(), 0);
+
+ // 20% PACK = 10% overall.
+ SendExportProgress(vm_tools::cicerone::
+ ExportLxdContainerProgressSignal_Status_EXPORTING_PACK,
+ 20);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::RUNNING);
+ EXPECT_EQ(notification->get_notification()->progress(), 10);
+
+ // 20% DOWNLOAD = 60% overall.
+ SendExportProgress(
+ vm_tools::cicerone::
+ ExportLxdContainerProgressSignal_Status_EXPORTING_DOWNLOAD,
+ 20);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::RUNNING);
+ EXPECT_EQ(notification->get_notification()->progress(), 60);
+
+ // Close notification and update progress. Should not update notification.
+ notification->Close(false);
+ SendExportProgress(
+ vm_tools::cicerone::
+ ExportLxdContainerProgressSignal_Status_EXPORTING_DOWNLOAD,
+ 40);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::RUNNING);
+ EXPECT_EQ(notification->get_notification()->progress(), 60);
+
+ // Done.
+ SendExportProgress(
+ vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_DONE, 0);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::DONE);
+}
+
+TEST_F(CrostiniExportImportTest, TestExportFail) {
+ crostini_export_import()->FileSelected(
+ tarball_, 0, reinterpret_cast<void*>(ExportImportType::EXPORT));
+ run_loop_->RunUntilIdle();
+ CrostiniExportImportNotification* notification =
+ crostini_export_import()->GetNotificationForTesting(container_id_);
+
+ // Failed.
+ SendExportProgress(
+ vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_FAILED, 0);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::FAILED);
+}
+
+TEST_F(CrostiniExportImportTest, TestImportSuccess) {
+ crostini_export_import()->FileSelected(
+ tarball_, 0, reinterpret_cast<void*>(ExportImportType::IMPORT));
+ run_loop_->RunUntilIdle();
+ CrostiniExportImportNotification* notification =
+ crostini_export_import()->GetNotificationForTesting(container_id_);
+ EXPECT_NE(notification, nullptr);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::RUNNING);
+ EXPECT_EQ(notification->get_notification()->progress(), 0);
+
+ // 20% UPLOAD = 10% overall.
+ SendImportProgress(
+ vm_tools::cicerone::
+ ImportLxdContainerProgressSignal_Status_IMPORTING_UPLOAD,
+ 20);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::RUNNING);
+ EXPECT_EQ(notification->get_notification()->progress(), 10);
+
+ // 20% UNPACK = 60% overall.
+ SendImportProgress(
+ vm_tools::cicerone::
+ ImportLxdContainerProgressSignal_Status_IMPORTING_UNPACK,
+ 20);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::RUNNING);
+ EXPECT_EQ(notification->get_notification()->progress(), 60);
+
+ // Close notification and update progress. Should not update notification.
+ notification->Close(false);
+ SendImportProgress(
+ vm_tools::cicerone::
+ ImportLxdContainerProgressSignal_Status_IMPORTING_UNPACK,
+ 40);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::RUNNING);
+ EXPECT_EQ(notification->get_notification()->progress(), 60);
+
+ // Done.
+ SendImportProgress(
+ vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_DONE, 0);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::DONE);
+}
+
+TEST_F(CrostiniExportImportTest, TestImportFail) {
+ crostini_export_import()->FileSelected(
+ tarball_, 0, reinterpret_cast<void*>(ExportImportType::IMPORT));
+ run_loop_->RunUntilIdle();
+ CrostiniExportImportNotification* notification =
+ crostini_export_import()->GetNotificationForTesting(container_id_);
+
+ // Failed.
+ SendImportProgress(
+ vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_FAILED, 0);
+ EXPECT_EQ(notification->get_status(),
+ CrostiniExportImportNotification::Status::FAILED);
+}
+} // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc
index 64dabaa5..687bac8 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -1106,7 +1106,7 @@
return;
}
- auto key = std::make_pair(vm_name, container_name);
+ ContainerId key(vm_name, container_name);
if (export_lxd_container_callbacks_.find(key) !=
export_lxd_container_callbacks_.end()) {
LOG(ERROR) << "Export currently in progress for " << vm_name << ", "
@@ -1147,7 +1147,7 @@
std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
return;
}
- auto key = std::make_pair(vm_name, container_name);
+ ContainerId key(vm_name, container_name);
if (import_lxd_container_callbacks_.find(key) !=
import_lxd_container_callbacks_.end()) {
LOG(ERROR) << "Import currently in progress for " << vm_name << ", "
@@ -1745,8 +1745,8 @@
std::string vm_name,
std::string container_name,
ShutdownContainerCallback shutdown_callback) {
- shutdown_container_callbacks_.emplace(
- std::make_tuple(vm_name, container_name), std::move(shutdown_callback));
+ shutdown_container_callbacks_.emplace(ContainerId(vm_name, container_name),
+ std::move(shutdown_callback));
}
void CrostiniManager::AddRemoveCrostiniCallback(
@@ -2129,7 +2129,7 @@
// The callback will be called when we receive the LxdContainerCreated
// signal.
create_lxd_container_callbacks_.emplace(
- std::make_tuple(vm_name, container_name), std::move(callback));
+ ContainerId(vm_name, container_name), std::move(callback));
return;
}
if (response.status() !=
@@ -2179,7 +2179,7 @@
// The callback will be called when we receive the LxdContainerStarting
// signal.
start_lxd_container_callbacks_.emplace(
- std::make_tuple(vm_name, container_name), std::move(callback));
+ ContainerId(vm_name, container_name), std::move(callback));
break;
default:
NOTREACHED();
@@ -2210,7 +2210,7 @@
}
if (!GetContainerInfo(vm_name, container_name)) {
- start_container_callbacks_.emplace(std::make_tuple(vm_name, container_name),
+ start_container_callbacks_.emplace(ContainerId(vm_name, container_name),
std::move(callback));
return;
}
@@ -2535,7 +2535,7 @@
std::string vm_name,
std::string container_name,
base::Optional<vm_tools::cicerone::ExportLxdContainerResponse> reply) {
- auto key = std::make_pair(vm_name, container_name);
+ ContainerId key(vm_name, container_name);
auto it = export_lxd_container_callbacks_.find(key);
if (it == export_lxd_container_callbacks_.end()) {
LOG(ERROR) << "No export callback for " << vm_name << ", "
@@ -2571,14 +2571,9 @@
ExportContainerProgressStatus status;
CrostiniResult result;
switch (signal.status()) {
- case vm_tools::cicerone::ExportLxdContainerProgressSignal::EXPORTING_TAR:
+ case vm_tools::cicerone::ExportLxdContainerProgressSignal::EXPORTING_PACK:
exporting = true;
- status = ExportContainerProgressStatus::TAR;
- break;
- case vm_tools::cicerone::ExportLxdContainerProgressSignal::
- EXPORTING_COMPRESS:
- exporting = true;
- status = ExportContainerProgressStatus::COMPRESS;
+ status = ExportContainerProgressStatus::PACK;
break;
case vm_tools::cicerone::ExportLxdContainerProgressSignal::
EXPORTING_DOWNLOAD:
@@ -2620,7 +2615,7 @@
std::string vm_name,
std::string container_name,
base::Optional<vm_tools::cicerone::ImportLxdContainerResponse> reply) {
- auto key = std::make_pair(vm_name, container_name);
+ ContainerId key(vm_name, container_name);
auto it = import_lxd_container_callbacks_.find(key);
if (it == import_lxd_container_callbacks_.end()) {
LOG(ERROR) << "No import callback for " << vm_name << ", "
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.h b/chrome/browser/chromeos/crostini/crostini_manager.h
index 4b9f79a..d319fca 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.h
+++ b/chrome/browser/chromeos/crostini/crostini_manager.h
@@ -90,8 +90,7 @@
};
enum class ExportContainerProgressStatus {
- TAR,
- COMPRESS,
+ PACK,
DOWNLOAD,
};
diff --git a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
index 0b9ec266..bc0245c 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
@@ -1120,18 +1120,13 @@
base::Unretained(this), run_loop()->QuitClosure(),
CrostiniResult::SUCCESS));
- // Send signals, TAR, COMPRESS, DOWNLOAD, DONE.
+ // Send signals, PACK, DOWNLOAD, DONE.
vm_tools::cicerone::ExportLxdContainerProgressSignal signal;
signal.set_owner_id(CryptohomeIdForProfile(profile()));
signal.set_vm_name(kVmName);
signal.set_container_name(kContainerName);
signal.set_status(vm_tools::cicerone::
- ExportLxdContainerProgressSignal_Status_EXPORTING_TAR);
- fake_cicerone_client_->NotifyExportLxdContainerProgress(signal);
-
- signal.set_status(
- vm_tools::cicerone::
- ExportLxdContainerProgressSignal_Status_EXPORTING_COMPRESS);
+ ExportLxdContainerProgressSignal_Status_EXPORTING_PACK);
fake_cicerone_client_->NotifyExportLxdContainerProgress(signal);
signal.set_status(