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(