Add Uninstall UI for Crostini Apps
Add UI to handle the "uninstall" menu option when right-clicking on a
Crostini app. This change brings up a confirmation dialog; if the user
confirms, it starts up the container and sends an uninstall message.
UI mocks https://docs.google.com/presentation/d/1akjEdjidRcMH54SK9zMmQVIPaPkwVuatXuKZLJKUqzA/edit
Several known bugs:
* chromium:909071
* chromium:909063
* chromium:898295
so this is behind a flag (CrostiniAppUninstallGui)
BUG=chromium:822514
TEST=Uninstalled multiple applications. Uninstalled applications at the same time for
queuing. Closed notifications while applications were uninstalling or queued. Installed
and uninstalled applications at the same time.
Change-Id: If5bb80cb867e55c3f082c40f89357f05db151985
Reviewed-on: https://chromium-review.googlesource.com/c/1275292
Commit-Queue: Ian Barkley-Yeung <iby@chromium.org>
Reviewed-by: Timothy Loh <timloh@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: Anand Mistry <amistry@chromium.org>
Reviewed-by: Dan Erat <derat@chromium.org>
Cr-Commit-Position: refs/heads/master@{#616567}
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 381a398..e5c8729 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -3590,6 +3590,41 @@
An error occurred during installation of your Linux application.
</message>
+ <!-- Linux application uninstaller messages -->
+ <message name="IDS_CROSTINI_APPLICATION_UNINSTALL_CONFIRM_TITLE" desc="Title of the Crostini application uninstaller, a confirmation dialog for uninstalling a Linux application.">
+ Uninstall app?
+ </message>
+ <message name="IDS_CROSTINI_APPLICATION_UNINSTALL_CONFIRM_BODY" desc="Description for the Crostini application uninstaller, a confirmation dialog for uninstalling a Linux application.">
+ <ph name="LINUX_APP_NAME">$1<ex>GIMP</ex></ph> and the data associated with it will be removed from this device.
+ </message>
+ <message name="IDS_CROSTINI_APPLICATION_UNINSTALL_UNINSTALL_BUTTON" desc="Label for the button in the Linux application uninstaller dialog to uninstall the application">
+ Uninstall
+ </message>
+ <message name="IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_DISPLAY_SOURCE" desc="Source of the Notification for Linux application uninstallation.">
+ Linux uninstaller
+ </message>
+ <message name="IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_QUEUED_TITLE" desc="Title of the Notification when an Linux application is queued waiting for other operations to finish.">
+ <ph name="LINUX_APP_NAME">$1<ex>GIMP</ex></ph>
+ </message>
+ <message name="IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_QUEUED_MESSAGE" desc="Message of the Notification when an Linux application is queued waiting for other operations to finish.">
+ Uninstall pending
+ </message>
+ <message name="IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_IN_PROGRESS_TITLE" desc="Title of the Notification for an in-progress Linux application uninstallation.">
+ Uninstalling <ph name="LINUX_APP_NAME">$1<ex>GIMP</ex></ph>...
+ </message>
+ <message name="IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_COMPLETED_TITLE" desc="Title of the Notification for Linux application uninstallation once uninstallation has completed successfully.">
+ <ph name="LINUX_APP_NAME">$1<ex>GIMP</ex></ph>
+ </message>
+ <message name="IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_COMPLETED_MESSAGE" desc="Message of the Notification for Linux application uninstallation once uninstallation has completed successfully.">
+ Uninstall complete
+ </message>
+ <message name="IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_ERROR_TITLE" desc="Title of the Notification for Linux application uninstallation when there is an error during uninstallation.">
+ <ph name="LINUX_APP_NAME">$1<ex>GIMP</ex></ph>
+ </message>
+ <message name="IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_ERROR_MESSAGE" desc="Message in the Notification for Linux application uninstallation when there is an error during uninstallation.">
+ An error occurred during uninstallation. Please uninstall through the Terminal.
+ </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/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 348f15e..1ca0e4d 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -622,10 +622,11 @@
"crostini/crostini_mime_types_service.h",
"crostini/crostini_mime_types_service_factory.cc",
"crostini/crostini_mime_types_service_factory.h",
- "crostini/crostini_package_installer_notification.cc",
- "crostini/crostini_package_installer_notification.h",
- "crostini/crostini_package_installer_service.cc",
- "crostini/crostini_package_installer_service.h",
+ "crostini/crostini_package_notification.cc",
+ "crostini/crostini_package_notification.h",
+ "crostini/crostini_package_operation_status.h",
+ "crostini/crostini_package_service.cc",
+ "crostini/crostini_package_service.h",
"crostini/crostini_pref_names.cc",
"crostini/crostini_pref_names.h",
"crostini/crostini_registry_service.cc",
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc
index cb093c0..2c59762e 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -1006,8 +1006,7 @@
// detect when the install completes, successfully or otherwise.
LOG(ERROR)
<< "Attempted to install package when progress signal not connected.";
- std::move(callback).Run(CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED,
- std::string());
+ std::move(callback).Run(CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED);
return;
}
@@ -1023,6 +1022,32 @@
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
+void CrostiniManager::UninstallPackageOwningFile(
+ std::string vm_name,
+ std::string container_name,
+ std::string desktop_file_id,
+ UninstallPackageOwningFileCallback callback) {
+ if (!GetCiceroneClient()->IsUninstallPackageProgressSignalConnected()) {
+ // Technically we could still start the uninstall, but we wouldn't be able
+ // to detect when the uninstall completes, successfully or otherwise.
+ LOG(ERROR)
+ << "Attempted to uninstall package when progress signal not connected.";
+ std::move(callback).Run(CrostiniResult::UNINSTALL_PACKAGE_FAILED);
+ return;
+ }
+
+ vm_tools::cicerone::UninstallPackageOwningFileRequest request;
+ request.set_owner_id(owner_id_);
+ request.set_vm_name(std::move(vm_name));
+ request.set_container_name(std::move(container_name));
+ request.set_desktop_file_id(std::move(desktop_file_id));
+
+ GetCiceroneClient()->UninstallPackageOwningFile(
+ std::move(request),
+ base::BindOnce(&CrostiniManager::OnUninstallPackageOwningFile,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
void CrostiniManager::GetContainerSshKeys(
std::string vm_name,
std::string container_name,
@@ -1187,14 +1212,14 @@
remove_crostini_callbacks_.emplace_back(std::move(remove_callback));
}
-void CrostiniManager::AddInstallLinuxPackageProgressObserver(
- InstallLinuxPackageProgressObserver* observer) {
- install_linux_package_progress_observers_.AddObserver(observer);
+void CrostiniManager::AddLinuxPackageOperationProgressObserver(
+ LinuxPackageOperationProgressObserver* observer) {
+ linux_package_operation_progress_observers_.AddObserver(observer);
}
-void CrostiniManager::RemoveInstallLinuxPackageProgressObserver(
- InstallLinuxPackageProgressObserver* observer) {
- install_linux_package_progress_observers_.RemoveObserver(observer);
+void CrostiniManager::RemoveLinuxPackageOperationProgressObserver(
+ LinuxPackageOperationProgressObserver* observer) {
+ linux_package_operation_progress_observers_.RemoveObserver(observer);
}
void CrostiniManager::OnCreateDiskImage(
@@ -1419,6 +1444,7 @@
break;
case vm_tools::cicerone::InstallLinuxPackageProgressSignal::FAILED:
status = InstallLinuxPackageProgressStatus::FAILED;
+ LOG(ERROR) << "Install failed: " << signal.failure_details();
break;
case vm_tools::cicerone::InstallLinuxPackageProgressSignal::DOWNLOADING:
status = InstallLinuxPackageProgressStatus::DOWNLOADING;
@@ -1430,13 +1456,79 @@
NOTREACHED();
}
- for (auto& observer : install_linux_package_progress_observers_) {
- observer.OnInstallLinuxPackageProgress(
- signal.vm_name(), signal.container_name(), status,
- signal.progress_percent(), signal.failure_details());
+ for (auto& observer : linux_package_operation_progress_observers_) {
+ observer.OnInstallLinuxPackageProgress(signal.vm_name(),
+ signal.container_name(), status,
+ signal.progress_percent());
}
}
+void CrostiniManager::OnUninstallPackageProgress(
+ const vm_tools::cicerone::UninstallPackageProgressSignal& signal) {
+ if (signal.owner_id() != owner_id_)
+ return;
+
+ if (signal.progress_percent() < 0 || signal.progress_percent() > 100) {
+ LOG(ERROR) << "Received uninstall progress with invalid progress of "
+ << signal.progress_percent() << "%.";
+ return;
+ }
+
+ UninstallPackageProgressStatus status;
+ switch (signal.status()) {
+ case vm_tools::cicerone::UninstallPackageProgressSignal::SUCCEEDED:
+ status = UninstallPackageProgressStatus::SUCCEEDED;
+ break;
+ case vm_tools::cicerone::UninstallPackageProgressSignal::FAILED:
+ status = UninstallPackageProgressStatus::FAILED;
+ LOG(ERROR) << "Uninstalled failed: " << signal.failure_details();
+ break;
+ case vm_tools::cicerone::UninstallPackageProgressSignal::UNINSTALLING:
+ status = UninstallPackageProgressStatus::UNINSTALLING;
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ for (auto& observer : linux_package_operation_progress_observers_) {
+ observer.OnUninstallPackageProgress(signal.vm_name(),
+ signal.container_name(), status,
+ signal.progress_percent());
+ }
+}
+
+void CrostiniManager::OnUninstallPackageOwningFile(
+ UninstallPackageOwningFileCallback callback,
+ base::Optional<vm_tools::cicerone::UninstallPackageOwningFileResponse>
+ reply) {
+ if (!reply.has_value()) {
+ LOG(ERROR) << "Failed to uninstall Linux package. Empty response.";
+ std::move(callback).Run(CrostiniResult::UNINSTALL_PACKAGE_FAILED);
+ return;
+ }
+ vm_tools::cicerone::UninstallPackageOwningFileResponse response =
+ reply.value();
+
+ if (response.status() ==
+ vm_tools::cicerone::UninstallPackageOwningFileResponse::FAILED) {
+ LOG(ERROR) << "Failed to uninstall Linux package: "
+ << response.failure_reason();
+ std::move(callback).Run(CrostiniResult::UNINSTALL_PACKAGE_FAILED);
+ return;
+ }
+
+ if (response.status() ==
+ vm_tools::cicerone::UninstallPackageOwningFileResponse::
+ BLOCKING_OPERATION_IN_PROGRESS) {
+ LOG(WARNING) << "Failed to uninstall Linux package, another operation is "
+ "already active.";
+ std::move(callback).Run(CrostiniResult::BLOCKING_OPERATION_ALREADY_ACTIVE);
+ return;
+ }
+
+ std::move(callback).Run(CrostiniResult::SUCCESS);
+}
+
void CrostiniManager::OnCreateLxdContainer(
std::string vm_name,
std::string container_name,
@@ -1671,8 +1763,8 @@
base::Optional<vm_tools::cicerone::InstallLinuxPackageResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to install Linux package. Empty response.";
- std::move(callback).Run(CrostiniResult::LAUNCH_CONTAINER_APPLICATION_FAILED,
- std::string());
+ std::move(callback).Run(
+ CrostiniResult::LAUNCH_CONTAINER_APPLICATION_FAILED);
return;
}
vm_tools::cicerone::InstallLinuxPackageResponse response = reply.value();
@@ -1681,20 +1773,18 @@
vm_tools::cicerone::InstallLinuxPackageResponse::FAILED) {
LOG(ERROR) << "Failed to install Linux package: "
<< response.failure_reason();
- std::move(callback).Run(CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED,
- response.failure_reason());
+ std::move(callback).Run(CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED);
return;
}
if (response.status() ==
vm_tools::cicerone::InstallLinuxPackageResponse::INSTALL_ALREADY_ACTIVE) {
LOG(WARNING) << "Failed to install Linux package, install already active.";
- std::move(callback).Run(
- CrostiniResult::INSTALL_LINUX_PACKAGE_ALREADY_ACTIVE, std::string());
+ std::move(callback).Run(CrostiniResult::BLOCKING_OPERATION_ALREADY_ACTIVE);
return;
}
- std::move(callback).Run(CrostiniResult::SUCCESS, std::string());
+ std::move(callback).Run(CrostiniResult::SUCCESS);
}
void CrostiniManager::OnGetContainerSshKeys(
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.h b/chrome/browser/chromeos/crostini/crostini_manager.h
index 468ce0c2..dbba332 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.h
+++ b/chrome/browser/chromeos/crostini/crostini_manager.h
@@ -47,7 +47,8 @@
CONTAINER_START_FAILED,
LAUNCH_CONTAINER_APPLICATION_FAILED,
INSTALL_LINUX_PACKAGE_FAILED,
- INSTALL_LINUX_PACKAGE_ALREADY_ACTIVE,
+ BLOCKING_OPERATION_ALREADY_ACTIVE,
+ UNINSTALL_PACKAGE_FAILED,
SSHFS_MOUNT_ERROR,
OFFLINE_WHEN_UPGRADE_REQUIRED,
LOAD_COMPONENT_FAILED,
@@ -67,6 +68,12 @@
STOPPING,
};
+enum class UninstallPackageProgressStatus {
+ SUCCEEDED,
+ FAILED,
+ UNINSTALLING, // In progress
+};
+
// Return type when getting app icons from within a container.
struct Icon {
std::string desktop_file_id;
@@ -91,19 +98,24 @@
std::string description;
};
-class InstallLinuxPackageProgressObserver {
+class LinuxPackageOperationProgressObserver {
public:
// A successfully started package install will continually fire progress
// events until it returns a status of SUCCEEDED or FAILED. The
// |progress_percent| field is given as a percentage of the given step,
- // DOWNLOADING or INSTALLING. |failure_reason| is returned from the container
- // for a FAILED case, and not necessarily localized.
+ // DOWNLOADING or INSTALLING.
virtual void OnInstallLinuxPackageProgress(
const std::string& vm_name,
const std::string& container_name,
InstallLinuxPackageProgressStatus status,
- int progress_percent,
- const std::string& failure_reason) = 0;
+ int progress_percent) = 0;
+
+ // A successfully started package uninstall will continually fire progress
+ // events until it returns a status of SUCCEEDED or FAILED.
+ virtual void OnUninstallPackageProgress(const std::string& vm_name,
+ const std::string& container_name,
+ UninstallPackageProgressStatus status,
+ int progress_percent) = 0;
};
// CrostiniManager is a singleton which is used to check arguments for
@@ -152,11 +164,9 @@
using GetLinuxPackageInfoCallback =
base::OnceCallback<void(const LinuxPackageInfo&)>;
// The type of the callback for CrostiniManager::InstallLinuxPackage.
- // |failure_reason| is returned from the container upon failure
- // (INSTALL_LINUX_PACKAGE_FAILED), and not necessarily localized.
- using InstallLinuxPackageCallback =
- base::OnceCallback<void(CrostiniResult result,
- const std::string& failure_reason)>;
+ using InstallLinuxPackageCallback = CrostiniResultCallback;
+ // The type of the callback for CrostiniManager::UninstallPackageOwningFile.
+ using UninstallPackageOwningFileCallback = CrostiniResultCallback;
// The type of the callback for CrostiniManager::GetContainerSshKeys.
using GetContainerSshKeysCallback =
base::OnceCallback<void(CrostiniResult result,
@@ -319,12 +329,22 @@
// Begin installation of a Linux Package inside the container. If the
// installation is successfully started, further updates will be sent to
- // added InstallLinuxPackageProgressObservers.
+ // added LinuxPackageOperationProgressObservers.
void InstallLinuxPackage(std::string vm_name,
std::string container_name,
std::string package_path,
InstallLinuxPackageCallback callback);
+ // Begin uninstallation of a Linux Package inside the container. The package
+ // is identified by its associated .desktop file's ID; we don't use package_id
+ // to avoid problems with stale package_ids (such as after upgrades). If the
+ // uninstallation is successfully started, further updates will be sent to
+ // added LinuxPackageOperationProgressObservers.
+ void UninstallPackageOwningFile(std::string vm_name,
+ std::string container_name,
+ std::string desktop_file_id,
+ UninstallPackageOwningFileCallback callback);
+
// Asynchronously gets SSH server public key of container and trusted SSH
// client private key which can be used to connect to the container.
// |callback| is called after the method call finishes.
@@ -375,11 +395,11 @@
// Adds a callback to receive uninstall notification.
void AddRemoveCrostiniCallback(RemoveCrostiniCallback remove_callback);
- // Add/remove observers for package install progress.
- void AddInstallLinuxPackageProgressObserver(
- InstallLinuxPackageProgressObserver* observer);
- void RemoveInstallLinuxPackageProgressObserver(
- InstallLinuxPackageProgressObserver* observer);
+ // Add/remove observers for package install and uninstall progress.
+ void AddLinuxPackageOperationProgressObserver(
+ LinuxPackageOperationProgressObserver* observer);
+ void RemoveLinuxPackageOperationProgressObserver(
+ LinuxPackageOperationProgressObserver* observer);
// ConciergeClient::Observer:
void OnContainerStartupFailed(
@@ -393,6 +413,9 @@
void OnInstallLinuxPackageProgress(
const vm_tools::cicerone::InstallLinuxPackageProgressSignal& signal)
override;
+ void OnUninstallPackageProgress(
+ const vm_tools::cicerone::UninstallPackageProgressSignal& signal)
+ override;
void OnLxdContainerCreated(
const vm_tools::cicerone::LxdContainerCreatedSignal& signal) override;
void OnLxdContainerDownloading(
@@ -530,6 +553,12 @@
InstallLinuxPackageCallback callback,
base::Optional<vm_tools::cicerone::InstallLinuxPackageResponse> reply);
+ // Callback for CrostiniManager::UninstallPackageOwningFile.
+ void OnUninstallPackageOwningFile(
+ UninstallPackageOwningFileCallback callback,
+ base::Optional<vm_tools::cicerone::UninstallPackageOwningFileResponse>
+ reply);
+
// Callback for CrostiniManager::GetContainerSshKeys. Called after the
// Concierge service finishes.
void OnGetContainerSshKeys(
@@ -591,8 +620,8 @@
std::vector<RemoveCrostiniCallback> remove_crostini_callbacks_;
- base::ObserverList<InstallLinuxPackageProgressObserver>::Unchecked
- install_linux_package_progress_observers_;
+ base::ObserverList<LinuxPackageOperationProgressObserver>::Unchecked
+ linux_package_operation_progress_observers_;
// Restarts by <vm_name, container_name>. Only one restarter flow is actually
// running for a given container, other restarters will just have their
diff --git a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
index 3c4a751..2589c0b9 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
@@ -119,11 +119,15 @@
void InstallLinuxPackageCallback(base::OnceClosure closure,
CrostiniResult expected_result,
- const std::string& expected_failure_reason,
- CrostiniResult result,
- const std::string& failure_reason) {
+ CrostiniResult result) {
EXPECT_EQ(expected_result, result);
- EXPECT_EQ(expected_failure_reason, failure_reason);
+ std::move(closure).Run();
+ }
+
+ void UninstallPackageOwningFileCallback(base::OnceClosure closure,
+ CrostiniResult expected_result,
+ CrostiniResult result) {
+ EXPECT_EQ(expected_result, result);
std::move(closure).Run();
}
@@ -314,8 +318,7 @@
kVmName, kContainerName, "/tmp/package.deb",
base::BindOnce(&CrostiniManagerTest::InstallLinuxPackageCallback,
base::Unretained(this), run_loop()->QuitClosure(),
- CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED,
- std::string()));
+ CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED));
run_loop()->Run();
}
@@ -327,7 +330,7 @@
kVmName, kContainerName, "/tmp/package.deb",
base::BindOnce(&CrostiniManagerTest::InstallLinuxPackageCallback,
base::Unretained(this), run_loop()->QuitClosure(),
- CrostiniResult::SUCCESS, std::string()));
+ CrostiniResult::SUCCESS));
run_loop()->Run();
}
@@ -341,8 +344,70 @@
kVmName, kContainerName, "/tmp/package.deb",
base::BindOnce(&CrostiniManagerTest::InstallLinuxPackageCallback,
base::Unretained(this), run_loop()->QuitClosure(),
- CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED,
- failure_reason));
+ CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED));
+ run_loop()->Run();
+}
+
+TEST_F(CrostiniManagerTest, InstallLinuxPackageSignalOperationBlocked) {
+ vm_tools::cicerone::InstallLinuxPackageResponse response;
+ response.set_status(
+ vm_tools::cicerone::InstallLinuxPackageResponse::INSTALL_ALREADY_ACTIVE);
+ fake_cicerone_client_->set_install_linux_package_response(response);
+ crostini_manager()->InstallLinuxPackage(
+ kVmName, kContainerName, "/tmp/package.deb",
+ base::BindOnce(&CrostiniManagerTest::InstallLinuxPackageCallback,
+ base::Unretained(this), run_loop()->QuitClosure(),
+ CrostiniResult::BLOCKING_OPERATION_ALREADY_ACTIVE));
+ run_loop()->Run();
+}
+
+TEST_F(CrostiniManagerTest, UninstallPackageOwningFileSignalNotConnectedError) {
+ fake_cicerone_client_->set_uninstall_package_progress_signal_connected(false);
+ crostini_manager()->UninstallPackageOwningFile(
+ kVmName, kContainerName, "emacs",
+ base::BindOnce(&CrostiniManagerTest::UninstallPackageOwningFileCallback,
+ base::Unretained(this), run_loop()->QuitClosure(),
+ CrostiniResult::UNINSTALL_PACKAGE_FAILED));
+ run_loop()->Run();
+}
+
+TEST_F(CrostiniManagerTest, UninstallPackageOwningFileSignalSuccess) {
+ vm_tools::cicerone::UninstallPackageOwningFileResponse response;
+ response.set_status(
+ vm_tools::cicerone::UninstallPackageOwningFileResponse::STARTED);
+ fake_cicerone_client_->set_uninstall_package_owning_file_response(response);
+ crostini_manager()->UninstallPackageOwningFile(
+ kVmName, kContainerName, "emacs",
+ base::BindOnce(&CrostiniManagerTest::UninstallPackageOwningFileCallback,
+ base::Unretained(this), run_loop()->QuitClosure(),
+ CrostiniResult::SUCCESS));
+ run_loop()->Run();
+}
+
+TEST_F(CrostiniManagerTest, UninstallPackageOwningFileSignalFailure) {
+ vm_tools::cicerone::UninstallPackageOwningFileResponse response;
+ response.set_status(
+ vm_tools::cicerone::UninstallPackageOwningFileResponse::FAILED);
+ response.set_failure_reason("Didn't feel like it");
+ fake_cicerone_client_->set_uninstall_package_owning_file_response(response);
+ crostini_manager()->UninstallPackageOwningFile(
+ kVmName, kContainerName, "emacs",
+ base::BindOnce(&CrostiniManagerTest::UninstallPackageOwningFileCallback,
+ base::Unretained(this), run_loop()->QuitClosure(),
+ CrostiniResult::UNINSTALL_PACKAGE_FAILED));
+ run_loop()->Run();
+}
+
+TEST_F(CrostiniManagerTest, UninstallPackageOwningFileSignalOperationBlocked) {
+ vm_tools::cicerone::UninstallPackageOwningFileResponse response;
+ response.set_status(vm_tools::cicerone::UninstallPackageOwningFileResponse::
+ BLOCKING_OPERATION_IN_PROGRESS);
+ fake_cicerone_client_->set_uninstall_package_owning_file_response(response);
+ crostini_manager()->UninstallPackageOwningFile(
+ kVmName, kContainerName, "emacs",
+ base::BindOnce(&CrostiniManagerTest::UninstallPackageOwningFileCallback,
+ base::Unretained(this), run_loop()->QuitClosure(),
+ CrostiniResult::BLOCKING_OPERATION_ALREADY_ACTIVE));
run_loop()->Run();
}
diff --git a/chrome/browser/chromeos/crostini/crostini_package_installer_notification.cc b/chrome/browser/chromeos/crostini/crostini_package_installer_notification.cc
deleted file mode 100644
index 3594aa82..0000000
--- a/chrome/browser/chromeos/crostini/crostini_package_installer_notification.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2018 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_package_installer_notification.h"
-
-#include "ash/public/cpp/notification_utils.h"
-#include "ash/public/cpp/vector_icons/vector_icons.h"
-#include "chrome/browser/chromeos/crostini/crostini_package_installer_service.h"
-#include "chrome/browser/notifications/notification_display_service.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 kNotifierCrostiniPackageInstaller[] =
- "crostini.package_installer";
-
-} // namespace
-
-CrostiniPackageInstallerNotification::CrostiniPackageInstallerNotification(
- Profile* profile,
- const std::string& notification_id,
- CrostiniPackageInstallerService* installer_service)
- : installer_service_(installer_service),
- profile_(profile),
- weak_ptr_factory_(this) {
- message_center::RichNotificationData rich_notification_data;
- rich_notification_data.vector_small_image = &ash::kNotificationLinuxIcon;
- rich_notification_data.never_timeout = true;
- rich_notification_data.accent_color = ash::kSystemNotificationColorNormal;
-
- notification_ = std::make_unique<message_center::Notification>(
- message_center::NOTIFICATION_TYPE_PROGRESS, notification_id,
- l10n_util::GetStringUTF16(
- IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_IN_PROGRESS_TITLE),
- base::string16(), // body
- gfx::Image(), // icon
- l10n_util::GetStringUTF16(
- IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_DISPLAY_SOURCE),
- GURL(), // origin_url
- message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
- kNotifierCrostiniPackageInstaller),
- rich_notification_data,
- base::MakeRefCounted<message_center::ThunkNotificationDelegate>(
- weak_ptr_factory_.GetWeakPtr()));
-
- UpdateDisplayedNotification();
-}
-
-CrostiniPackageInstallerNotification::~CrostiniPackageInstallerNotification() =
- default;
-
-// TODO(timloh): This doesn't get called if the user shuts down Crostini, so
-// the notification will be stuck at whatever percentage it is at.
-void CrostiniPackageInstallerNotification::UpdateProgress(
- InstallLinuxPackageProgressStatus result,
- int progress_percent,
- const std::string& failure_reason) {
- if (result == InstallLinuxPackageProgressStatus::SUCCEEDED ||
- result == InstallLinuxPackageProgressStatus::FAILED) {
- // The package installer service will stop sending us updates after this.
- int title_id = IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_COMPLETED_TITLE;
- int message_id =
- IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_COMPLETED_MESSAGE;
- if (result != InstallLinuxPackageProgressStatus::SUCCEEDED) {
- title_id = IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_ERROR_TITLE;
- message_id = IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_ERROR_MESSAGE;
- notification_->set_accent_color(
- ash::kSystemNotificationColorCriticalWarning);
- }
- notification_->set_title(l10n_util::GetStringUTF16(title_id));
- notification_->set_message(l10n_util::GetStringUTF16(message_id));
- notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
- notification_->set_never_timeout(false);
- } else {
- int display_progress = progress_percent / 2;
- if (result == InstallLinuxPackageProgressStatus::INSTALLING)
- display_progress += 50;
- else
- DCHECK_EQ(InstallLinuxPackageProgressStatus::DOWNLOADING, result);
-
- notification_->set_progress(display_progress);
- }
-
- UpdateDisplayedNotification();
-}
-
-void CrostiniPackageInstallerNotification::Close(bool by_user) {
- // This call deletes us.
- installer_service_->NotificationClosed(this);
-}
-
-void CrostiniPackageInstallerNotification::UpdateDisplayedNotification() {
- NotificationDisplayService* display_service =
- NotificationDisplayService::GetForProfile(profile_);
- display_service->Display(NotificationHandler::Type::TRANSIENT,
- *notification_);
-}
-
-} // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_package_installer_notification.h b/chrome/browser/chromeos/crostini/crostini_package_installer_notification.h
deleted file mode 100644
index d95f98c..0000000
--- a/chrome/browser/chromeos/crostini/crostini_package_installer_notification.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2018 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_PACKAGE_INSTALLER_NOTIFICATION_H_
-#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_PACKAGE_INSTALLER_NOTIFICATION_H_
-
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/chromeos/crostini/crostini_manager.h"
-#include "ui/message_center/public/cpp/notification_delegate.h"
-
-namespace message_center {
-class Notification;
-}
-
-namespace crostini {
-
-class CrostiniPackageInstallerService;
-
-class CrostiniPackageInstallerNotification
- : public message_center::NotificationObserver {
- public:
- CrostiniPackageInstallerNotification(
- Profile* profile,
- const std::string& notification_id,
- CrostiniPackageInstallerService* installer_service);
- virtual ~CrostiniPackageInstallerNotification();
-
- void UpdateProgress(InstallLinuxPackageProgressStatus result,
- int progress_percent,
- const std::string& failure_reason);
-
- // message_center::NotificationObserver:
- void Close(bool by_user) override;
-
- private:
- void UpdateDisplayedNotification();
-
- // These notifications are owned by the installer service.
- CrostiniPackageInstallerService* installer_service_;
- Profile* profile_;
-
- std::unique_ptr<message_center::Notification> notification_;
-
- base::WeakPtrFactory<CrostiniPackageInstallerNotification> weak_ptr_factory_;
-
- DISALLOW_COPY_AND_ASSIGN(CrostiniPackageInstallerNotification);
-};
-
-} // namespace crostini
-
-#endif // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_PACKAGE_INSTALLER_NOTIFICATION_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_package_installer_service.cc b/chrome/browser/chromeos/crostini/crostini_package_installer_service.cc
deleted file mode 100644
index 646ad50..0000000
--- a/chrome/browser/chromeos/crostini/crostini_package_installer_service.cc
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2018 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_package_installer_service.h"
-
-#include "base/bind.h"
-#include "base/files/file_path.h"
-#include "base/no_destructor.h"
-#include "chrome/browser/chromeos/crostini/crostini_manager_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
-
-namespace crostini {
-
-namespace {
-
-class CrostiniPackageInstallerServiceFactory
- : public BrowserContextKeyedServiceFactory {
- public:
- static CrostiniPackageInstallerService* GetForProfile(Profile* profile) {
- return static_cast<CrostiniPackageInstallerService*>(
- GetInstance()->GetServiceForBrowserContext(profile, true));
- }
-
- static CrostiniPackageInstallerServiceFactory* GetInstance() {
- static base::NoDestructor<CrostiniPackageInstallerServiceFactory> factory;
- return factory.get();
- }
-
- private:
- friend class base::NoDestructor<CrostiniPackageInstallerServiceFactory>;
-
- CrostiniPackageInstallerServiceFactory()
- : BrowserContextKeyedServiceFactory(
- "CrostiniPackageInstallerService",
- BrowserContextDependencyManager::GetInstance()) {
- DependsOn(CrostiniManagerFactory::GetInstance());
- }
-
- ~CrostiniPackageInstallerServiceFactory() override = default;
-
- // BrowserContextKeyedServiceFactory:
- KeyedService* BuildServiceInstanceFor(
- content::BrowserContext* context) const override {
- Profile* profile = Profile::FromBrowserContext(context);
- return new CrostiniPackageInstallerService(profile);
- }
-};
-
-} // namespace
-
-CrostiniPackageInstallerService* CrostiniPackageInstallerService::GetForProfile(
- Profile* profile) {
- return CrostiniPackageInstallerServiceFactory::GetForProfile(profile);
-}
-
-CrostiniPackageInstallerService::CrostiniPackageInstallerService(
- Profile* profile)
- : profile_(profile), weak_ptr_factory_(this) {
- CrostiniManager::GetForProfile(profile)
- ->AddInstallLinuxPackageProgressObserver(this);
-}
-
-CrostiniPackageInstallerService::~CrostiniPackageInstallerService() = default;
-
-void CrostiniPackageInstallerService::Shutdown() {
- CrostiniManager::GetForProfile(profile_)
- ->RemoveInstallLinuxPackageProgressObserver(this);
-}
-
-void CrostiniPackageInstallerService::NotificationClosed(
- CrostiniPackageInstallerNotification* notification) {
- for (auto it = running_notifications_.begin();
- it != running_notifications_.end(); ++it) {
- if (it->second.get() == notification) {
- running_notifications_.erase(it);
- return;
- }
- }
-
- for (auto it = finished_notifications_.begin();
- it != finished_notifications_.end(); ++it) {
- if (it->get() == notification) {
- finished_notifications_.erase(it);
- return;
- }
- }
-
- NOTREACHED();
-}
-
-void CrostiniPackageInstallerService::GetLinuxPackageInfo(
- const std::string& vm_name,
- const std::string& container_name,
- const std::string& package_path,
- CrostiniManager::GetLinuxPackageInfoCallback callback) {
- CrostiniManager::GetForProfile(profile_)->GetLinuxPackageInfo(
- profile_, vm_name, container_name, package_path,
- base::BindOnce(&CrostiniPackageInstallerService::OnGetLinuxPackageInfo,
- weak_ptr_factory_.GetWeakPtr(), vm_name, container_name,
- std::move(callback)));
-}
-
-void CrostiniPackageInstallerService::InstallLinuxPackage(
- const std::string& vm_name,
- const std::string& container_name,
- const std::string& package_path,
- CrostiniManager::InstallLinuxPackageCallback callback) {
- CrostiniManager::GetForProfile(profile_)->InstallLinuxPackage(
- vm_name, container_name, package_path,
- base::BindOnce(&CrostiniPackageInstallerService::OnInstallLinuxPackage,
- weak_ptr_factory_.GetWeakPtr(), vm_name, container_name,
- std::move(callback)));
-}
-
-void CrostiniPackageInstallerService::OnInstallLinuxPackageProgress(
- const std::string& vm_name,
- const std::string& container_name,
- InstallLinuxPackageProgressStatus result,
- int progress_percent,
- const std::string& failure_reason) {
- auto it =
- running_notifications_.find(std::make_pair(vm_name, container_name));
- if (it == running_notifications_.end())
- return;
- it->second->UpdateProgress(result, progress_percent, failure_reason);
-
- if (result == InstallLinuxPackageProgressStatus::SUCCEEDED ||
- result == InstallLinuxPackageProgressStatus::FAILED) {
- finished_notifications_.emplace_back(std::move(it->second));
- running_notifications_.erase(it);
- }
-}
-
-void CrostiniPackageInstallerService::OnGetLinuxPackageInfo(
- const std::string& vm_name,
- const std::string& container_name,
- CrostiniManager::GetLinuxPackageInfoCallback callback,
- const LinuxPackageInfo& linux_package_info) {
- std::move(callback).Run(linux_package_info);
- if (!linux_package_info.success)
- return;
-}
-
-void CrostiniPackageInstallerService::OnInstallLinuxPackage(
- const std::string& vm_name,
- const std::string& container_name,
- CrostiniManager::InstallLinuxPackageCallback callback,
- CrostiniResult result,
- const std::string& failure_reason) {
- std::move(callback).Run(result, failure_reason);
- if (result != CrostiniResult::SUCCESS)
- return;
-
- std::unique_ptr<CrostiniPackageInstallerNotification>& notification =
- running_notifications_[std::make_pair(vm_name, container_name)];
- if (notification) {
- // We could reach this if the final progress update signal from a previous
- // package install doesn't get sent, so we wouldn't end up moving the
- // previous notification out of running_notifications_.
- LOG(ERROR) << "Notification for package install already exists.";
- return;
- }
-
- notification = std::make_unique<CrostiniPackageInstallerNotification>(
- profile_, GetUniqueNotificationId(), this);
-}
-
-std::string CrostiniPackageInstallerService::GetUniqueNotificationId() {
- return base::StringPrintf("crostini_package_install_%d",
- next_notification_id++);
-}
-
-} // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_package_installer_service.h b/chrome/browser/chromeos/crostini/crostini_package_installer_service.h
deleted file mode 100644
index fdc5926..0000000
--- a/chrome/browser/chromeos/crostini/crostini_package_installer_service.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2018 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_PACKAGE_INSTALLER_SERVICE_H_
-#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_PACKAGE_INSTALLER_SERVICE_H_
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/chromeos/crostini/crostini_manager.h"
-#include "chrome/browser/chromeos/crostini/crostini_package_installer_notification.h"
-#include "components/keyed_service/core/keyed_service.h"
-
-namespace crostini {
-
-class CrostiniPackageInstallerService
- : public KeyedService,
- public InstallLinuxPackageProgressObserver {
- public:
- static CrostiniPackageInstallerService* GetForProfile(Profile* profile);
-
- explicit CrostiniPackageInstallerService(Profile* profile);
- ~CrostiniPackageInstallerService() override;
-
- // KeyedService:
- void Shutdown() override;
-
- void NotificationClosed(CrostiniPackageInstallerNotification* notification);
-
- // The package installer service caches the most recent retrieved package
- // info, for use in a package install notification.
- // TODO(timloh): Actually cache the values.
- void GetLinuxPackageInfo(
- const std::string& vm_name,
- const std::string& container_name,
- const std::string& package_path,
- CrostiniManager::GetLinuxPackageInfoCallback callback);
-
- // Install a Linux package. If successfully started, a system notification
- // will be used to display further updates.
- void InstallLinuxPackage(
- const std::string& vm_name,
- const std::string& container_name,
- const std::string& package_path,
- CrostiniManager::InstallLinuxPackageCallback callback);
-
- // InstallLinuxPackageProgressObserver:
- void OnInstallLinuxPackageProgress(
- const std::string& vm_name,
- const std::string& container_name,
- InstallLinuxPackageProgressStatus result,
- int progress_percent,
- const std::string& failure_reason) override;
-
- private:
- // Wraps the callback provided in GetLinuxPackageInfo().
- void OnGetLinuxPackageInfo(
- const std::string& vm_name,
- const std::string& container_name,
- CrostiniManager::GetLinuxPackageInfoCallback callback,
- const LinuxPackageInfo& linux_package_info);
-
- // Wraps the callback provided in InstallLinuxPackage().
- void OnInstallLinuxPackage(
- const std::string& vm_name,
- const std::string& container_name,
- CrostiniManager::InstallLinuxPackageCallback callback,
- CrostiniResult result,
- const std::string& failure_reason);
-
- std::string GetUniqueNotificationId();
-
- Profile* profile_;
-
- // Keyed on <vm_name, container_name>. A container can only have one install
- // running at a time, but we need to keep notifications around until they're
- // dismissed.
- std::map<std::pair<std::string, std::string>,
- std::unique_ptr<CrostiniPackageInstallerNotification>>
- running_notifications_;
- std::vector<std::unique_ptr<CrostiniPackageInstallerNotification>>
- finished_notifications_;
-
- int next_notification_id = 0;
-
- base::WeakPtrFactory<CrostiniPackageInstallerService> weak_ptr_factory_;
-
- DISALLOW_COPY_AND_ASSIGN(CrostiniPackageInstallerService);
-};
-
-} // namespace crostini
-
-#endif // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_PACKAGE_INSTALLER_SERVICE_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_package_notification.cc b/chrome/browser/chromeos/crostini/crostini_package_notification.cc
new file mode 100644
index 0000000..4746209
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_package_notification.cc
@@ -0,0 +1,231 @@
+// Copyright 2018 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_package_notification.h"
+
+#include "ash/public/cpp/notification_utils.h"
+#include "ash/public/cpp/vector_icons/vector_icons.h"
+#include "chrome/browser/chromeos/crostini/crostini_package_service.h"
+#include "chrome/browser/notifications/notification_display_service.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/time_format.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 kNotifierCrostiniPackageOperation[] =
+ "crostini.package_operation";
+
+} // namespace
+
+CrostiniPackageNotification::NotificationSettings::NotificationSettings() {}
+CrostiniPackageNotification::NotificationSettings::NotificationSettings(
+ const NotificationSettings& rhs) = default;
+CrostiniPackageNotification::NotificationSettings::~NotificationSettings() {}
+
+CrostiniPackageNotification::CrostiniPackageNotification(
+ Profile* profile,
+ NotificationType notification_type,
+ PackageOperationStatus status,
+ const base::string16& app_name,
+ const std::string& notification_id,
+ CrostiniPackageService* package_service)
+ : notification_type_(notification_type),
+ current_status_(status),
+ package_service_(package_service),
+ profile_(profile),
+ notification_settings_(
+ GetNotificationSettingsForTypeAndAppName(notification_type,
+ app_name)),
+ weak_ptr_factory_(this) {
+ if (status == PackageOperationStatus::RUNNING) {
+ running_start_time_ = base::Time::Now();
+ }
+ message_center::RichNotificationData rich_notification_data;
+ rich_notification_data.vector_small_image = &ash::kNotificationLinuxIcon;
+ rich_notification_data.never_timeout = true;
+ 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
+ notification_settings_.source,
+ GURL(), // origin_url
+ message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
+ kNotifierCrostiniPackageOperation),
+ rich_notification_data,
+ base::MakeRefCounted<message_center::ThunkNotificationDelegate>(
+ weak_ptr_factory_.GetWeakPtr()));
+
+ // Sets title and body
+ UpdateProgress(status, 0 /*progress_percent*/);
+}
+
+CrostiniPackageNotification::~CrostiniPackageNotification() = default;
+
+// static
+CrostiniPackageNotification::NotificationSettings
+CrostiniPackageNotification::GetNotificationSettingsForTypeAndAppName(
+ NotificationType notification_type,
+ const base::string16& app_name) {
+ NotificationSettings result;
+
+ switch (notification_type) {
+ case NotificationType::PACKAGE_INSTALL:
+ DCHECK(app_name.empty());
+ result.source = l10n_util::GetStringUTF16(
+ IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_DISPLAY_SOURCE);
+ result.progress_title = l10n_util::GetStringUTF16(
+ IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_IN_PROGRESS_TITLE);
+ result.progress_body.clear();
+ result.success_title = l10n_util::GetStringUTF16(
+ IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_COMPLETED_TITLE);
+ result.success_body = l10n_util::GetStringUTF16(
+ IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_COMPLETED_MESSAGE);
+ result.failure_title = l10n_util::GetStringUTF16(
+ IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_ERROR_TITLE);
+ result.failure_body = l10n_util::GetStringUTF16(
+ IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_ERROR_MESSAGE);
+ break;
+
+ case NotificationType::APPLICATION_UNINSTALL:
+ result.source = l10n_util::GetStringUTF16(
+ IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_DISPLAY_SOURCE);
+ result.queued_title = l10n_util::GetStringFUTF16(
+ IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_QUEUED_TITLE,
+ app_name);
+ result.queued_body = l10n_util::GetStringUTF16(
+ IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_QUEUED_MESSAGE);
+ result.progress_title = l10n_util::GetStringFUTF16(
+ IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_IN_PROGRESS_TITLE,
+ app_name);
+ result.success_title = l10n_util::GetStringFUTF16(
+ IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_COMPLETED_TITLE,
+ app_name);
+ result.success_body = l10n_util::GetStringUTF16(
+ IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_COMPLETED_MESSAGE);
+ result.failure_title = l10n_util::GetStringFUTF16(
+ IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_ERROR_TITLE,
+ app_name);
+ result.failure_body = l10n_util::GetStringUTF16(
+ IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_ERROR_MESSAGE);
+ break;
+
+ default:
+ NOTREACHED();
+ }
+
+ return result;
+}
+
+// TODO(timloh): This doesn't get called if the user shuts down Crostini, so
+// the notification will be stuck at whatever percentage it is at.
+void CrostiniPackageNotification::UpdateProgress(PackageOperationStatus status,
+ int progress_percent) {
+ if (status == PackageOperationStatus::RUNNING &&
+ current_status_ != PackageOperationStatus::RUNNING) {
+ running_start_time_ = base::Time::Now();
+ }
+ current_status_ = status;
+
+ base::string16 title;
+ base::string16 body;
+ message_center::NotificationType notification_type =
+ message_center::NOTIFICATION_TYPE_SIMPLE;
+ bool never_timeout = false;
+
+ switch (status) {
+ case PackageOperationStatus::SUCCEEDED:
+ title = notification_settings_.success_title;
+ body = notification_settings_.success_body;
+ break;
+
+ case PackageOperationStatus::FAILED:
+ title = notification_settings_.failure_title;
+ body = notification_settings_.failure_body;
+ notification_->set_accent_color(
+ ash::kSystemNotificationColorCriticalWarning);
+ break;
+
+ case PackageOperationStatus::RUNNING:
+ never_timeout = true;
+ notification_type = message_center::NOTIFICATION_TYPE_PROGRESS;
+ title = notification_settings_.progress_title;
+ if (notification_type_ == NotificationType::APPLICATION_UNINSTALL) {
+ // Uninstalls have a time remaining instead of a fixed message.
+ base::TimeDelta time_since_started_running =
+ base::Time::Now() - running_start_time_;
+
+ // Don't estimate if we don't have enough data yet. At the moment we
+ // start the uninstall, we have no idea how long it will take. Only
+ // estimate once we've spent at least 3 seconds OR gotten 10% of the
+ // way through the uninstall.
+ constexpr base::TimeDelta kMinTimeForEstimate =
+ base::TimeDelta::FromSeconds(3);
+ constexpr base::TimeDelta kTimeDeltaZero =
+ base::TimeDelta::FromSeconds(0);
+ constexpr int kMinPercentForEstimate = 10;
+ if ((time_since_started_running >= kMinTimeForEstimate &&
+ progress_percent > 0) ||
+ (progress_percent >= kMinPercentForEstimate &&
+ time_since_started_running > kTimeDeltaZero)) {
+ base::TimeDelta total_time_expected =
+ (time_since_started_running * 100) / progress_percent;
+ base::TimeDelta time_remaining =
+ total_time_expected - time_since_started_running;
+ body = ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING,
+ ui::TimeFormat::LENGTH_SHORT,
+ time_remaining);
+ }
+ // else leave body blank
+ } else {
+ body = notification_settings_.progress_body;
+ }
+ break;
+
+ case PackageOperationStatus::QUEUED:
+ // We don't have queued strings for some NotificationTypes; we shouldn't
+ // be asked to move to QUEUED status for those,
+ DCHECK(!notification_settings_.queued_title.empty());
+ DCHECK(!notification_settings_.queued_body.empty());
+ title = notification_settings_.queued_title;
+ body = notification_settings_.queued_body;
+ break;
+
+ default:
+ NOTREACHED();
+ }
+
+ notification_->set_title(title);
+ notification_->set_message(body);
+ notification_->set_type(notification_type);
+ notification_->set_progress(progress_percent);
+ notification_->set_never_timeout(never_timeout);
+ UpdateDisplayedNotification();
+}
+
+void CrostiniPackageNotification::ForceAllowAutoHide() {
+ notification_->set_never_timeout(false);
+ UpdateDisplayedNotification();
+}
+
+void CrostiniPackageNotification::Close(bool by_user) {
+ // This call deletes us.
+ package_service_->NotificationClosed(this);
+}
+
+void CrostiniPackageNotification::UpdateDisplayedNotification() {
+ NotificationDisplayService* display_service =
+ NotificationDisplayService::GetForProfile(profile_);
+ display_service->Display(NotificationHandler::Type::TRANSIENT,
+ *notification_);
+}
+
+} // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_package_notification.h b/chrome/browser/chromeos/crostini/crostini_package_notification.h
new file mode 100644
index 0000000..f94f3d99
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_package_notification.h
@@ -0,0 +1,98 @@
+// Copyright 2018 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_PACKAGE_NOTIFICATION_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_PACKAGE_NOTIFICATION_H_
+
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager.h"
+#include "chrome/browser/chromeos/crostini/crostini_package_operation_status.h"
+#include "ui/message_center/public/cpp/notification_delegate.h"
+
+namespace message_center {
+class Notification;
+}
+
+namespace crostini {
+
+class CrostiniPackageService;
+
+// Notification for various Crostini package operations, such as installing
+// from a package or uninstalling an existing app.
+class CrostiniPackageNotification
+ : public message_center::NotificationObserver {
+ public:
+ enum class NotificationType { PACKAGE_INSTALL, APPLICATION_UNINSTALL };
+
+ // |app_name| should be empty for PACKAGE_INSTALL, non-empty for
+ // APPLICATION_UNINSTALL.
+ CrostiniPackageNotification(Profile* profile,
+ NotificationType notification_type,
+ PackageOperationStatus status,
+ const base::string16& app_name,
+ const std::string& notification_id,
+ CrostiniPackageService* installer_service);
+ virtual ~CrostiniPackageNotification();
+
+ void UpdateProgress(PackageOperationStatus status, int progress_percent);
+
+ void ForceAllowAutoHide();
+
+ // message_center::NotificationObserver:
+ void Close(bool by_user) override;
+
+ private:
+ // A type giving the string, etc displayed for each notification type. Note
+ // that we have the complete strings here, not just the string IDs, because
+ // the call needed to generate the strings is slightly different between
+ // notification types (specifically, uninstall notification strings usually
+ // need an app_name, while installs do not).
+ struct NotificationSettings {
+ NotificationSettings();
+ NotificationSettings(const NotificationSettings& rhs);
+ ~NotificationSettings();
+ base::string16 source;
+ base::string16 queued_title;
+ base::string16 queued_body;
+ base::string16 progress_title;
+ base::string16 progress_body;
+ base::string16 success_title;
+ base::string16 success_body;
+ base::string16 failure_title;
+ base::string16 failure_body;
+ };
+
+ void UpdateDisplayedNotification();
+
+ static NotificationSettings GetNotificationSettingsForTypeAndAppName(
+ NotificationType notification_type,
+ const base::string16& app_name);
+
+ const NotificationType notification_type_;
+ PackageOperationStatus current_status_;
+
+ // The most-recent time we entered the "RUNNING" state. Used for
+ // guesstimating when we'll be done.
+ base::Time running_start_time_;
+
+ // These notifications are owned by the package service.
+ CrostiniPackageService* package_service_;
+ Profile* profile_;
+ const NotificationSettings notification_settings_;
+
+ std::unique_ptr<message_center::Notification> notification_;
+
+ base::WeakPtrFactory<CrostiniPackageNotification> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrostiniPackageNotification);
+};
+
+} // namespace crostini
+
+#endif // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_PACKAGE_NOTIFICATION_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_package_operation_status.h b/chrome/browser/chromeos/crostini/crostini_package_operation_status.h
new file mode 100644
index 0000000..69bcda82
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_package_operation_status.h
@@ -0,0 +1,18 @@
+// Copyright 2018 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_PACKAGE_OPERATION_STATUS_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_PACKAGE_OPERATION_STATUS_H_
+
+#include <ostream>
+
+namespace crostini {
+
+// Status of an operation in CrostiniPackageService &
+// CrostiniPackageNotification.
+enum class PackageOperationStatus { QUEUED, SUCCEEDED, FAILED, RUNNING };
+
+} // namespace crostini
+
+#endif // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_PACKAGE_OPERATION_STATUS_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_package_service.cc b/chrome/browser/chromeos/crostini/crostini_package_service.cc
new file mode 100644
index 0000000..615dbd1
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_package_service.cc
@@ -0,0 +1,437 @@
+// Copyright 2018 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_package_service.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/no_destructor.h"
+#include "base/strings/strcat.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace crostini {
+
+namespace {
+
+class CrostiniPackageServiceFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static CrostiniPackageService* GetForProfile(Profile* profile) {
+ return static_cast<CrostiniPackageService*>(
+ GetInstance()->GetServiceForBrowserContext(profile, true));
+ }
+
+ static CrostiniPackageServiceFactory* GetInstance() {
+ static base::NoDestructor<CrostiniPackageServiceFactory> factory;
+ return factory.get();
+ }
+
+ private:
+ friend class base::NoDestructor<CrostiniPackageServiceFactory>;
+
+ CrostiniPackageServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "CrostiniPackageService",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(CrostiniManagerFactory::GetInstance());
+ }
+
+ ~CrostiniPackageServiceFactory() override = default;
+
+ // BrowserContextKeyedServiceFactory:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override {
+ Profile* profile = Profile::FromBrowserContext(context);
+ return new CrostiniPackageService(profile);
+ }
+};
+
+PackageOperationStatus InstallStatusToOperationStatus(
+ InstallLinuxPackageProgressStatus status) {
+ switch (status) {
+ case InstallLinuxPackageProgressStatus::SUCCEEDED:
+ return PackageOperationStatus::SUCCEEDED;
+ case InstallLinuxPackageProgressStatus::FAILED:
+ return PackageOperationStatus::FAILED;
+ case InstallLinuxPackageProgressStatus::DOWNLOADING:
+ case InstallLinuxPackageProgressStatus::INSTALLING:
+ return PackageOperationStatus::RUNNING;
+ default:
+ NOTREACHED();
+ }
+}
+
+PackageOperationStatus UninstallStatusToOperationStatus(
+ UninstallPackageProgressStatus status) {
+ switch (status) {
+ case UninstallPackageProgressStatus::SUCCEEDED:
+ return PackageOperationStatus::SUCCEEDED;
+ case UninstallPackageProgressStatus::FAILED:
+ return PackageOperationStatus::FAILED;
+ case UninstallPackageProgressStatus::UNINSTALLING:
+ return PackageOperationStatus::RUNNING;
+ default:
+ NOTREACHED();
+ }
+}
+
+} // namespace
+
+struct CrostiniPackageService::QueuedUninstall {
+ QueuedUninstall(
+ const std::string& app_id,
+ std::unique_ptr<CrostiniPackageNotification> notification_argument)
+ : app_id(app_id), notification(std::move(notification_argument)) {}
+ ~QueuedUninstall() = default;
+
+ // App to uninstall
+ std::string app_id;
+
+ // Notification displaying "uninstall queued"
+ std::unique_ptr<CrostiniPackageNotification> notification;
+};
+
+CrostiniPackageService* CrostiniPackageService::GetForProfile(
+ Profile* profile) {
+ return CrostiniPackageServiceFactory::GetForProfile(profile);
+}
+
+CrostiniPackageService::CrostiniPackageService(Profile* profile)
+ : profile_(profile), weak_ptr_factory_(this) {
+ CrostiniManager* manager = CrostiniManager::GetForProfile(profile);
+
+ manager->AddLinuxPackageOperationProgressObserver(this);
+}
+
+CrostiniPackageService::~CrostiniPackageService() = default;
+
+void CrostiniPackageService::Shutdown() {
+ CrostiniManager* manager = CrostiniManager::GetForProfile(profile_);
+ manager->RemoveLinuxPackageOperationProgressObserver(this);
+}
+
+void CrostiniPackageService::NotificationClosed(
+ CrostiniPackageNotification* notification) {
+ for (auto it = running_notifications_.begin();
+ it != running_notifications_.end(); ++it) {
+ if (it->second.get() == notification) {
+ running_notifications_.erase(it);
+ return;
+ }
+ }
+
+ for (auto it = finished_notifications_.begin();
+ it != finished_notifications_.end(); ++it) {
+ if (it->get() == notification) {
+ finished_notifications_.erase(it);
+ return;
+ }
+ }
+
+ for (auto it = queued_uninstalls_.begin(); it != queued_uninstalls_.end();
+ ++it) {
+ std::deque<QueuedUninstall>& queue = it->second;
+ for (auto it2 = queue.begin(); it2 != queue.end(); ++it2) {
+ if (it2->notification.get() == notification) {
+ // We need to delete the notification, but we still want to run the
+ // uninstall, so don't do erase(it2).
+ it2->notification.reset();
+ return;
+ }
+ }
+ }
+ NOTREACHED();
+}
+
+void CrostiniPackageService::GetLinuxPackageInfo(
+ const std::string& vm_name,
+ const std::string& container_name,
+ const std::string& package_path,
+ CrostiniManager::GetLinuxPackageInfoCallback callback) {
+ CrostiniManager::GetForProfile(profile_)->GetLinuxPackageInfo(
+ profile_, vm_name, container_name, package_path,
+ base::BindOnce(&CrostiniPackageService::OnGetLinuxPackageInfo,
+ weak_ptr_factory_.GetWeakPtr(), vm_name, container_name,
+ std::move(callback)));
+}
+
+void CrostiniPackageService::InstallLinuxPackage(
+ const std::string& vm_name,
+ const std::string& container_name,
+ const std::string& package_path,
+ CrostiniManager::InstallLinuxPackageCallback callback) {
+ CrostiniManager::GetForProfile(profile_)->InstallLinuxPackage(
+ vm_name, container_name, package_path,
+ base::BindOnce(&CrostiniPackageService::OnInstallLinuxPackage,
+ weak_ptr_factory_.GetWeakPtr(), vm_name, container_name,
+ std::move(callback)));
+}
+
+void CrostiniPackageService::OnInstallLinuxPackageProgress(
+ const std::string& vm_name,
+ const std::string& container_name,
+ InstallLinuxPackageProgressStatus status,
+ int progress_percent) {
+ // Linux package install has two phases, downloading and installing, which we
+ // map to a single progess percentage amount by dividing the range in half --
+ // 0-50% for the downloading phase, 51-100% for the installing phase.
+ int display_progress = progress_percent / 2;
+ if (status == InstallLinuxPackageProgressStatus::INSTALLING)
+ display_progress += 50; // Second phase
+
+ UpdatePackageOperationStatus(std::make_pair(vm_name, container_name),
+ InstallStatusToOperationStatus(status),
+ display_progress);
+}
+
+void CrostiniPackageService::OnUninstallPackageProgress(
+ const std::string& vm_name,
+ const std::string& container_name,
+ UninstallPackageProgressStatus status,
+ int progress_percent) {
+ UpdatePackageOperationStatus(ContainerIdentifier(vm_name, container_name),
+ UninstallStatusToOperationStatus(status),
+ progress_percent);
+}
+
+void CrostiniPackageService::QueueUninstallApplication(
+ const std::string& app_id) {
+ auto registration =
+ CrostiniRegistryServiceFactory::GetForProfile(profile_)->GetRegistration(
+ app_id);
+ DCHECK(registration);
+ const std::string vm_name = registration->VmName();
+ const std::string container_name = registration->ContainerName();
+ const std::string app_name = registration->Name();
+
+ const ContainerIdentifier container_id(vm_name, container_name);
+ if (ContainerHasRunningOperation(container_id)) {
+ CreateQueuedUninstall(container_id, app_id, app_name);
+ return;
+ }
+
+ CreateRunningNotification(
+ container_id,
+ CrostiniPackageNotification::NotificationType::APPLICATION_UNINSTALL,
+ app_name);
+ containers_with_running_operations_.insert(container_id);
+
+ UninstallApplication(*registration, app_id);
+}
+
+std::string CrostiniPackageService::ContainerIdentifierToString(
+ const ContainerIdentifier& container_id) const {
+ return base::StrCat(
+ {"(", container_id.first, ", ", container_id.second, ")"});
+}
+
+bool CrostiniPackageService::ContainerHasRunningOperation(
+ const ContainerIdentifier& container_id) const {
+ return containers_with_running_operations_.find(container_id) !=
+ containers_with_running_operations_.end();
+}
+
+void CrostiniPackageService::CreateRunningNotification(
+ const ContainerIdentifier& container_id,
+ CrostiniPackageNotification::NotificationType notification_type,
+ const std::string& app_name) {
+ { // Scope limit for |it|, which will become invalid shortly.
+ auto it = running_notifications_.find(container_id);
+ if (it != running_notifications_.end()) {
+ // We could reach this if the final progress update signal from a previous
+ // operation doesn't get sent, so we wouldn't end up moving the
+ // previous notification out of running_notifications_. Clear it out by
+ // moving to finished_notifications_.
+ LOG(ERROR) << "Notification for package operation already exists.";
+ it->second->ForceAllowAutoHide();
+ finished_notifications_.emplace_back(std::move(it->second));
+ running_notifications_.erase(it);
+ }
+ }
+
+ running_notifications_[container_id] =
+ std::make_unique<CrostiniPackageNotification>(
+ profile_, notification_type, PackageOperationStatus::RUNNING,
+ base::UTF8ToUTF16(app_name), GetUniqueNotificationId(), this);
+}
+
+void CrostiniPackageService::CreateQueuedUninstall(
+ const ContainerIdentifier& container_id,
+ const std::string& app_id,
+ const std::string& app_name) {
+ queued_uninstalls_[container_id].emplace_back(
+ app_id,
+ std::make_unique<CrostiniPackageNotification>(
+ profile_,
+ CrostiniPackageNotification::NotificationType::APPLICATION_UNINSTALL,
+ PackageOperationStatus::QUEUED, base::UTF8ToUTF16(app_name),
+ GetUniqueNotificationId(), this));
+}
+
+void CrostiniPackageService::UpdatePackageOperationStatus(
+ const ContainerIdentifier& container_id,
+ PackageOperationStatus status,
+ int progress_percent) {
+ // Update the notification window, if any. User may have closed it while it
+ // was in progress, so don't complain if not found.
+ auto it = running_notifications_.find(container_id);
+ if (it != running_notifications_.end()) {
+ DCHECK(it->second) << ContainerIdentifierToString(container_id)
+ << " has null notification pointer";
+ it->second->UpdateProgress(status, progress_percent);
+
+ if (status == PackageOperationStatus::SUCCEEDED ||
+ status == PackageOperationStatus::FAILED) {
+ finished_notifications_.emplace_back(std::move(it->second));
+ running_notifications_.erase(it);
+ }
+ }
+
+ // Update our state and kick off the next operation if we just finished an
+ // operation.
+ DCHECK(ContainerHasRunningOperation(container_id))
+ << "containers_with_running_operations_["
+ << ContainerIdentifierToString(container_id) << "] not found";
+ if (status == PackageOperationStatus::SUCCEEDED ||
+ status == PackageOperationStatus::FAILED) {
+ auto queued_iter = queued_uninstalls_.find(container_id);
+ if (queued_iter == queued_uninstalls_.end() ||
+ queued_iter->second.empty()) {
+ containers_with_running_operations_.erase(container_id);
+ } else {
+ StartQueuedUninstall(container_id);
+ }
+ }
+}
+
+void CrostiniPackageService::OnGetLinuxPackageInfo(
+ const std::string& vm_name,
+ const std::string& container_name,
+ CrostiniManager::GetLinuxPackageInfoCallback callback,
+ const LinuxPackageInfo& linux_package_info) {
+ std::move(callback).Run(linux_package_info);
+}
+
+void CrostiniPackageService::OnInstallLinuxPackage(
+ const std::string& vm_name,
+ const std::string& container_name,
+ CrostiniManager::InstallLinuxPackageCallback callback,
+ CrostiniResult result) {
+ std::move(callback).Run(result);
+ if (result != CrostiniResult::SUCCESS)
+ return;
+ const ContainerIdentifier container_id(vm_name, container_name);
+ CreateRunningNotification(
+ container_id,
+ CrostiniPackageNotification::NotificationType::PACKAGE_INSTALL,
+ "" /* app_name */);
+ containers_with_running_operations_.insert(container_id);
+}
+
+void CrostiniPackageService::UninstallApplication(
+ const CrostiniRegistryService::Registration& registration,
+ const std::string& app_id) {
+ const std::string vm_name = registration.VmName();
+ const std::string container_name = registration.ContainerName();
+ const ContainerIdentifier container_id(vm_name, container_name);
+
+ // Policies can change under us, and crostini may now be forbidden.
+ if (!IsCrostiniUIAllowedForProfile(profile_)) {
+ LOG(ERROR) << "Can't uninstall because policy no longer allows Crostini";
+ UpdatePackageOperationStatus(container_id, PackageOperationStatus::FAILED,
+ 0);
+ return;
+ }
+
+ // If Crostini is not running, launch it. This is a no-op if Crostini is
+ // already running.
+ CrostiniManager::GetForProfile(profile_)->RestartCrostini(
+ vm_name, container_name,
+ base::BindOnce(&CrostiniPackageService::OnCrostiniRunningForUninstall,
+ weak_ptr_factory_.GetWeakPtr(), container_id,
+ registration.DesktopFileId()));
+}
+
+void CrostiniPackageService::OnCrostiniRunningForUninstall(
+ const ContainerIdentifier& container_id,
+ const std::string& desktop_file_id,
+ CrostiniResult result) {
+ if (result != CrostiniResult::SUCCESS) {
+ LOG(ERROR) << "Failed to launch Crostini; uninstall aborted";
+ UpdatePackageOperationStatus(container_id, PackageOperationStatus::FAILED,
+ 0);
+ return;
+ }
+ const std::string& vm_name = container_id.first;
+ const std::string& container_name = container_id.second;
+
+ CrostiniManager::GetForProfile(profile_)->UninstallPackageOwningFile(
+ vm_name, container_name, desktop_file_id,
+ base::BindOnce(&CrostiniPackageService::OnUninstallPackageOwningFile,
+ weak_ptr_factory_.GetWeakPtr(), container_id));
+}
+
+void CrostiniPackageService::OnUninstallPackageOwningFile(
+ const ContainerIdentifier& container_id,
+ CrostiniResult result) {
+ if (result != CrostiniResult::SUCCESS) {
+ // Let user know the uninstall failed.
+ UpdatePackageOperationStatus(container_id, PackageOperationStatus::FAILED,
+ 0);
+ return;
+ }
+ // Otherwise, just leave the notification alone in the "running" state.
+ // SUCCESS just means we successfully *started* the uninstall.
+}
+
+void CrostiniPackageService::StartQueuedUninstall(
+ const ContainerIdentifier& container_id) {
+ std::string app_id;
+ auto uninstall_queue_iter = queued_uninstalls_.find(container_id);
+ if (uninstall_queue_iter == queued_uninstalls_.end()) {
+ return;
+ }
+ std::deque<QueuedUninstall>& uninstall_queue = uninstall_queue_iter->second;
+ { // Scope |next|; it becomes an invalid reference when we pop_front()
+ QueuedUninstall& next = uninstall_queue.front();
+
+ // User may have closed notification while still queued; don't complain if
+ // notification is nullptr.
+ if (next.notification) {
+ next.notification->UpdateProgress(PackageOperationStatus::RUNNING, 0);
+ running_notifications_.emplace(container_id,
+ std::move(next.notification));
+ }
+
+ app_id = next.app_id;
+ uninstall_queue.pop_front(); // Invalidates |next|
+ }
+ // containers_with_running_operations_ should be set from before and not
+ // cleared.
+ DCHECK(ContainerHasRunningOperation(container_id));
+
+ auto registration =
+ CrostiniRegistryServiceFactory::GetForProfile(profile_)->GetRegistration(
+ app_id);
+ DCHECK(registration);
+ UninstallApplication(*registration, app_id);
+
+ // Clean up memory.
+ if (uninstall_queue.empty()) {
+ queued_uninstalls_.erase(container_id);
+ // Invalidates uninstall_queue
+ }
+}
+
+std::string CrostiniPackageService::GetUniqueNotificationId() {
+ return base::StringPrintf("crostini_package_operation_%d",
+ next_notification_id_++);
+}
+
+} // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_package_service.h b/chrome/browser/chromeos/crostini/crostini_package_service.h
new file mode 100644
index 0000000..7c24538e
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_package_service.h
@@ -0,0 +1,176 @@
+// Copyright 2018 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_PACKAGE_SERVICE_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_PACKAGE_SERVICE_H_
+
+#include <deque>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager.h"
+#include "chrome/browser/chromeos/crostini/crostini_package_notification.h"
+#include "chrome/browser/chromeos/crostini/crostini_package_operation_status.h"
+#include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace crostini {
+
+class CrostiniPackageService : public KeyedService,
+ public LinuxPackageOperationProgressObserver {
+ public:
+ static CrostiniPackageService* GetForProfile(Profile* profile);
+
+ explicit CrostiniPackageService(Profile* profile);
+ ~CrostiniPackageService() override;
+
+ // KeyedService:
+ void Shutdown() override;
+
+ void NotificationClosed(CrostiniPackageNotification* notification);
+
+ // The package installer service caches the most recent retrieved package
+ // info, for use in a package install notification.
+ // TODO(timloh): Actually cache the values.
+ void GetLinuxPackageInfo(
+ const std::string& vm_name,
+ const std::string& container_name,
+ const std::string& package_path,
+ CrostiniManager::GetLinuxPackageInfoCallback callback);
+
+ // Install a Linux package. If successfully started, a system notification
+ // will be used to display further updates.
+ void InstallLinuxPackage(
+ const std::string& vm_name,
+ const std::string& container_name,
+ const std::string& package_path,
+ CrostiniManager::InstallLinuxPackageCallback callback);
+
+ // LinuxPackageOperationProgressObserver:
+ void OnInstallLinuxPackageProgress(const std::string& vm_name,
+ const std::string& container_name,
+ InstallLinuxPackageProgressStatus status,
+ int progress_percent) override;
+
+ void OnUninstallPackageProgress(const std::string& vm_name,
+ const std::string& container_name,
+ UninstallPackageProgressStatus status,
+ int progress_percent) override;
+
+ // (Eventually) uninstall the package identified by |app_id|. If successfully
+ // started, a system notification will be used to display further updates.
+ void QueueUninstallApplication(const std::string& app_id);
+
+ private:
+ // A unique identifier for our containers. This is <vm_name, container_name>.
+ using ContainerIdentifier = std::pair<std::string, std::string>;
+
+ // The user can request new uninstalls while a different operation is in
+ // progress. Rather than sending a request which will fail, just queue the
+ // request until the previous one is done.
+ struct QueuedUninstall;
+
+ std::string ContainerIdentifierToString(
+ const ContainerIdentifier& container_id) const;
+
+ bool ContainerHasRunningOperation(
+ const ContainerIdentifier& container_id) const;
+
+ // Creates a new notification and adds it to running_notifications_.
+ // |app_name| is the name of the application being modified, if any -- for
+ // installs, it will be blank, but for uninstalls, it will have the localized
+ // name of the application in UTF8.
+ // If there is a running notification, it will be set to error state. Caller
+ // should check before calling this if a different behavior is desired.
+ void CreateRunningNotification(
+ const ContainerIdentifier& container_id,
+ CrostiniPackageNotification::NotificationType notification_type,
+ const std::string& app_name);
+
+ // Creates a new uninstall notification and adds it to queued_uninstalls_.
+ void CreateQueuedUninstall(const ContainerIdentifier& container_id,
+ const std::string& app_id,
+ const std::string& app_name);
+
+ // Sets the operation status of the current operation. Sets the notification
+ // window's current state and updates containers_with_running_operations_.
+ // Note that if status is |SUCCEEDED| or |FAILED|, this may kick off another
+ // operation from the queued_uninstalls_ list.
+ void UpdatePackageOperationStatus(const ContainerIdentifier& container_id,
+ PackageOperationStatus status,
+ int progress_percent);
+
+ // Wraps the callback provided in GetLinuxPackageInfo().
+ void OnGetLinuxPackageInfo(
+ const std::string& vm_name,
+ const std::string& container_name,
+ CrostiniManager::GetLinuxPackageInfoCallback callback,
+ const LinuxPackageInfo& linux_package_info);
+
+ // Wraps the callback provided in InstallLinuxPackage().
+ void OnInstallLinuxPackage(
+ const std::string& vm_name,
+ const std::string& container_name,
+ CrostiniManager::InstallLinuxPackageCallback callback,
+ CrostiniResult result);
+
+ // Kicks off an uninstall of the given app. Never queues the operation. Helper
+ // for QueueUninstallApplication (if the operation can be performed
+ // immediately) and StartQueuedUninstall.
+ void UninstallApplication(
+ const CrostiniRegistryService::Registration& registration,
+ const std::string& app_id);
+
+ // Callback when the Crostini container is up and ready to accept messages.
+ // Used by the uninstall flow only.
+ void OnCrostiniRunningForUninstall(const ContainerIdentifier& container_id,
+ const std::string& desktop_file_id,
+ CrostiniResult result);
+
+ // Callback for CrostiniManager::UninstallPackageOwningFile().
+ void OnUninstallPackageOwningFile(const ContainerIdentifier& container_id,
+ CrostiniResult result);
+
+ // Kick off the next operation in the queue for the given container.
+ void StartQueuedUninstall(const ContainerIdentifier& container_id);
+
+ std::string GetUniqueNotificationId();
+
+ Profile* profile_;
+
+ // The notifications in the RUNNING state for each container.
+ std::map<ContainerIdentifier, std::unique_ptr<CrostiniPackageNotification>>
+ running_notifications_;
+
+ // A list of containers with running operations. Generally, matches the list
+ // of containers with notifications, but we need a separate copy of the state
+ // in case the user closes the notification while still running.
+ std::set<ContainerIdentifier> containers_with_running_operations_;
+
+ // Uninstalls we want to run when the current one is done. Operations are
+ // queued in FIFO order (but we can't use std::queue because we sometimes need
+ // to erase a notification window pointer in the middle of the queue).
+ std::map<ContainerIdentifier, std::deque<QueuedUninstall>> queued_uninstalls_;
+
+ // Notifications in a finished state (either SUCCEEDED or FAILED). We need
+ // to keep notifications around until they are dismissed even if we don't
+ // update them any more.
+ std::vector<std::unique_ptr<CrostiniPackageNotification>>
+ finished_notifications_;
+
+ int next_notification_id_ = 0;
+
+ base::WeakPtrFactory<CrostiniPackageService> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrostiniPackageService);
+};
+
+} // namespace crostini
+
+#endif // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_PACKAGE_SERVICE_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_util.h b/chrome/browser/chromeos/crostini/crostini_util.h
index 86c0364c..e1a6fae 100644
--- a/chrome/browser/chromeos/crostini/crostini_util.h
+++ b/chrome/browser/chromeos/crostini/crostini_util.h
@@ -6,6 +6,7 @@
#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_UTIL_H_
#include <string>
+#include <vector>
#include "base/callback.h"
#include "base/optional.h"
@@ -107,6 +108,9 @@
// Shows the Crostini Uninstaller dialog.
void ShowCrostiniUninstallerView(Profile* profile,
CrostiniUISurface ui_surface);
+// Shows the Crostini App Uninstaller dialog.
+void ShowCrostiniAppUninstallerView(Profile* profile,
+ const std::string& app_id);
// Shows the Crostini Upgrade dialog.
void ShowCrostiniUpgradeView(Profile* profile, CrostiniUISurface ui_surface);
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
index 41e67ce..fca554ec 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -20,7 +20,7 @@
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
-#include "chrome/browser/chromeos/crostini/crostini_package_installer_service.h"
+#include "chrome/browser/chromeos/crostini/crostini_package_service.h"
#include "chrome/browser/chromeos/crostini/crostini_share_path.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/chromeos/drive/drive_integration_service.h"
@@ -784,14 +784,12 @@
return RespondNow(Error("Invalid url: " + params->url));
}
- crostini::CrostiniPackageInstallerService::GetForProfile(profile)
- ->GetLinuxPackageInfo(
- crostini::kCrostiniDefaultVmName,
- crostini::kCrostiniDefaultContainerName, path.value(),
- base::BindOnce(
- &FileManagerPrivateInternalGetLinuxPackageInfoFunction::
- OnGetLinuxPackageInfo,
- this));
+ crostini::CrostiniPackageService::GetForProfile(profile)->GetLinuxPackageInfo(
+ crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
+ path.value(),
+ base::BindOnce(&FileManagerPrivateInternalGetLinuxPackageInfoFunction::
+ OnGetLinuxPackageInfo,
+ this));
return RespondLater();
}
@@ -832,20 +830,17 @@
return RespondNow(Error("Invalid url: " + params->url));
}
- crostini::CrostiniPackageInstallerService::GetForProfile(profile)
- ->InstallLinuxPackage(
- crostini::kCrostiniDefaultVmName,
- crostini::kCrostiniDefaultContainerName, path.value(),
- base::BindOnce(
- &FileManagerPrivateInternalInstallLinuxPackageFunction::
- OnInstallLinuxPackage,
- this));
+ crostini::CrostiniPackageService::GetForProfile(profile)->InstallLinuxPackage(
+ crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
+ path.value(),
+ base::BindOnce(&FileManagerPrivateInternalInstallLinuxPackageFunction::
+ OnInstallLinuxPackage,
+ this));
return RespondLater();
}
void FileManagerPrivateInternalInstallLinuxPackageFunction::
- OnInstallLinuxPackage(crostini::CrostiniResult result,
- const std::string& failure_reason) {
+ OnInstallLinuxPackage(crostini::CrostiniResult result) {
extensions::api::file_manager_private::InstallLinuxPackageResponse response;
switch (result) {
case crostini::CrostiniResult::SUCCESS:
@@ -856,16 +851,15 @@
response = extensions::api::file_manager_private::
INSTALL_LINUX_PACKAGE_RESPONSE_FAILED;
break;
- case crostini::CrostiniResult::INSTALL_LINUX_PACKAGE_ALREADY_ACTIVE:
+ case crostini::CrostiniResult::BLOCKING_OPERATION_ALREADY_ACTIVE:
response = extensions::api::file_manager_private::
INSTALL_LINUX_PACKAGE_RESPONSE_INSTALL_ALREADY_ACTIVE;
break;
default:
NOTREACHED();
}
- Respond(ArgumentList(
- extensions::api::file_manager_private_internal::InstallLinuxPackage::
- Results::Create(response, failure_reason)));
+ Respond(ArgumentList(extensions::api::file_manager_private_internal::
+ InstallLinuxPackage::Results::Create(response)));
}
FileManagerPrivateInternalGetCustomActionsFunction::
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h
index 95af4da..59e36d91 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h
@@ -399,8 +399,7 @@
private:
ResponseAction Run() override;
- void OnInstallLinuxPackage(crostini::CrostiniResult result,
- const std::string& failure_reason);
+ void OnInstallLinuxPackage(crostini::CrostiniResult result);
DISALLOW_COPY_AND_ASSIGN(
FileManagerPrivateInternalInstallLinuxPackageFunction);
};
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 0fb40a6..1972d17 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3286,6 +3286,8 @@
"views/arc_data_removal_dialog_view.cc",
"views/crostini/crostini_app_restart_view.cc",
"views/crostini/crostini_app_restart_view.h",
+ "views/crostini/crostini_app_uninstaller_view.cc",
+ "views/crostini/crostini_app_uninstaller_view.h",
"views/crostini/crostini_installer_view.cc",
"views/crostini/crostini_installer_view.h",
"views/crostini/crostini_uninstaller_view.cc",
diff --git a/chrome/browser/ui/app_list/crostini/crostini_app_context_menu.cc b/chrome/browser/ui/app_list/crostini/crostini_app_context_menu.cc
index 57a57ea3..ef73e52d 100644
--- a/chrome/browser/ui/app_list/crostini/crostini_app_context_menu.cc
+++ b/chrome/browser/ui/app_list/crostini/crostini_app_context_menu.cc
@@ -76,9 +76,10 @@
if (app_id() == crostini::kCrostiniTerminalId) {
crostini::ShowCrostiniUninstallerView(
profile(), crostini::CrostiniUISurface::kAppList);
- return;
+ } else {
+ crostini::ShowCrostiniAppUninstallerView(profile(), app_id());
}
- break;
+ return;
case ash::STOP_APP:
if (app_id() == crostini::kCrostiniTerminalId) {
diff --git a/chrome/browser/ui/browser_dialogs.h b/chrome/browser/ui/browser_dialogs.h
index 5efd8e2..b3c7567 100644
--- a/chrome/browser/ui/browser_dialogs.h
+++ b/chrome/browser/ui/browser_dialogs.h
@@ -269,6 +269,7 @@
HATS_BUBBLE = 90,
CROSTINI_APP_RESTART = 91,
INCOGNITO_WINDOW_COUNTER = 92,
+ CROSTINI_APP_UNINSTALLER = 93,
MAX_VALUE
};
diff --git a/chrome/browser/ui/views/crostini/crostini_app_uninstaller_view.cc b/chrome/browser/ui/views/crostini/crostini_app_uninstaller_view.cc
new file mode 100644
index 0000000..2f583ab
--- /dev/null
+++ b/chrome/browser/ui/views/crostini/crostini_app_uninstaller_view.cc
@@ -0,0 +1,109 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/crostini/crostini_app_uninstaller_view.h"
+
+#include <memory>
+
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/chromeos/crostini/crostini_package_service.h"
+#include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
+#include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/strings/grit/ui_strings.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace crostini {
+
+// Declaration in crostini_util.h, definition here. Needed because of include
+// restrictions.
+void ShowCrostiniAppUninstallerView(Profile* profile,
+ const std::string& app_id) {
+ CrostiniAppUninstallerView::Show(profile, app_id);
+}
+
+} // namespace crostini
+
+// static
+void CrostiniAppUninstallerView::Show(Profile* profile,
+ const std::string& app_id) {
+ DCHECK(crostini::IsCrostiniUIAllowedForProfile(profile));
+ views::DialogDelegate::CreateDialogWidget(
+ new CrostiniAppUninstallerView(profile, app_id), nullptr, nullptr)
+ ->Show();
+}
+
+int CrostiniAppUninstallerView::GetDialogButtons() const {
+ return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
+}
+
+base::string16 CrostiniAppUninstallerView::GetDialogButtonLabel(
+ ui::DialogButton button) const {
+ if (button == ui::DIALOG_BUTTON_OK)
+ return l10n_util::GetStringUTF16(
+ IDS_CROSTINI_APPLICATION_UNINSTALL_UNINSTALL_BUTTON);
+ DCHECK_EQ(button, ui::DIALOG_BUTTON_CANCEL);
+ return l10n_util::GetStringUTF16(IDS_APP_CANCEL);
+}
+
+base::string16 CrostiniAppUninstallerView::GetWindowTitle() const {
+ return l10n_util::GetStringUTF16(
+ IDS_CROSTINI_APPLICATION_UNINSTALL_CONFIRM_TITLE);
+}
+
+bool CrostiniAppUninstallerView::ShouldShowCloseButton() const {
+ return false;
+}
+
+bool CrostiniAppUninstallerView::Accept() {
+ // Switch over to the notification service to uninstall the package and
+ // display notifications related to the uninstall.
+ crostini::CrostiniPackageService::GetForProfile(profile_)
+ ->QueueUninstallApplication(app_id_);
+ return true; // Should close the dialog
+}
+
+gfx::Size CrostiniAppUninstallerView::CalculatePreferredSize() const {
+ const int dialog_width = ChromeLayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH) -
+ margins().width();
+ return gfx::Size(dialog_width, GetHeightForWidth(dialog_width));
+}
+
+CrostiniAppUninstallerView::CrostiniAppUninstallerView(
+ Profile* profile,
+ const std::string& app_id)
+ : profile_(profile), app_id_(app_id), weak_ptr_factory_(this) {
+ views::LayoutProvider* provider = views::LayoutProvider::Get();
+ SetLayoutManager(std::make_unique<views::BoxLayout>(
+ views::BoxLayout::kVertical,
+ provider->GetInsetsMetric(views::InsetsMetric::INSETS_DIALOG),
+ provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
+ set_margins(provider->GetDialogInsetsForContentType(
+ views::DialogContentType::TEXT, views::DialogContentType::TEXT));
+
+ crostini::CrostiniRegistryService* registry =
+ crostini::CrostiniRegistryServiceFactory::GetForProfile(profile);
+ DCHECK(registry);
+ auto app_registration = registry->GetRegistration(app_id);
+ DCHECK(app_registration);
+ const base::string16 app_name = base::UTF8ToUTF16(app_registration->Name());
+
+ const base::string16 message = l10n_util::GetStringFUTF16(
+ IDS_CROSTINI_APPLICATION_UNINSTALL_CONFIRM_BODY, app_name);
+ auto* message_label = new views::Label(message);
+ message_label->SetMultiLine(true);
+ message_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ AddChildView(message_label);
+
+ chrome::RecordDialogCreation(
+ chrome::DialogIdentifier::CROSTINI_APP_UNINSTALLER);
+}
+
+CrostiniAppUninstallerView::~CrostiniAppUninstallerView() = default;
diff --git a/chrome/browser/ui/views/crostini/crostini_app_uninstaller_view.h b/chrome/browser/ui/views/crostini/crostini_app_uninstaller_view.h
new file mode 100644
index 0000000..b468e7c
--- /dev/null
+++ b/chrome/browser/ui/views/crostini/crostini_app_uninstaller_view.h
@@ -0,0 +1,43 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_CROSTINI_CROSTINI_APP_UNINSTALLER_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_CROSTINI_CROSTINI_APP_UNINSTALLER_VIEW_H_
+
+#include <string>
+
+#include "ui/views/window/dialog_delegate.h"
+
+class Profile;
+
+// The Crostini application uninstaller. Displays a confirmation prompt,
+// and kicks off the uninstall if the user confirms that they want the app
+// uninstalled. Subsequent notifications are handled by CrostiniPackageService.
+class CrostiniAppUninstallerView : public views::DialogDelegateView {
+ public:
+ // Show the "are you sure?"-style confirmation prompt. |app_id| should be an
+ // ID understood by CrostiniRegistryService::GetRegistration().
+ static void Show(Profile* profile, const std::string& app_id);
+
+ // views::DialogDelegateView:
+ int GetDialogButtons() const override;
+ base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
+ base::string16 GetWindowTitle() const override;
+ bool ShouldShowCloseButton() const override;
+ bool Accept() override;
+ gfx::Size CalculatePreferredSize() const override;
+
+ private:
+ CrostiniAppUninstallerView(Profile* profile, const std::string& app_id);
+ ~CrostiniAppUninstallerView() override;
+
+ Profile* profile_;
+ std::string app_id_;
+
+ base::WeakPtrFactory<CrostiniAppUninstallerView> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrostiniAppUninstallerView);
+};
+
+#endif // CHROME_BROWSER_UI_VIEWS_CROSTINI_CROSTINI_APP_UNINSTALLER_VIEW_H_
diff --git a/chrome/common/extensions/api/file_manager_private_internal.idl b/chrome/common/extensions/api/file_manager_private_internal.idl
index 9fb75d3..97bfd0b 100644
--- a/chrome/common/extensions/api/file_manager_private_internal.idl
+++ b/chrome/common/extensions/api/file_manager_private_internal.idl
@@ -37,8 +37,7 @@
callback GetLinuxPackageInfoCallback =
void(fileManagerPrivate.LinuxPackageInfo linux_package_info);
callback InstallLinuxPackageCallback =
- void(fileManagerPrivate.InstallLinuxPackageResponse response,
- optional DOMString failure_reason);
+ void(fileManagerPrivate.InstallLinuxPackageResponse response);
callback GetThumbnailCallback = void(DOMString ThumbnailDataUrl);
interface Functions {
diff --git a/chromeos/dbus/cicerone_client.cc b/chromeos/dbus/cicerone_client.cc
index 16837d4..d6fb71d 100644
--- a/chromeos/dbus/cicerone_client.cc
+++ b/chromeos/dbus/cicerone_client.cc
@@ -4,6 +4,9 @@
#include "chromeos/dbus/cicerone_client.h"
+#include <string>
+#include <utility>
+
#include "base/bind.h"
#include "base/location.h"
#include "base/observer_list.h"
@@ -51,6 +54,10 @@
return is_install_linux_package_progress_signal_connected_;
}
+ bool IsUninstallPackageProgressSignalConnected() override {
+ return is_uninstall_package_progress_signal_connected_;
+ }
+
bool IsLxdContainerCreatedSignalConnected() override {
return is_lxd_container_created_signal_connected_;
}
@@ -157,6 +164,31 @@
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
+ void UninstallPackageOwningFile(
+ const vm_tools::cicerone::UninstallPackageOwningFileRequest& request,
+ DBusMethodCallback<vm_tools::cicerone::UninstallPackageOwningFileResponse>
+ callback) override {
+ dbus::MethodCall method_call(
+ vm_tools::cicerone::kVmCiceroneInterface,
+ vm_tools::cicerone::kUninstallPackageOwningFileMethod);
+ dbus::MessageWriter writer(&method_call);
+
+ if (!writer.AppendProtoAsArrayOfBytes(request)) {
+ LOG(ERROR)
+ << "Failed to encode UninstallPackageOwningFileRequest protobuf";
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), base::nullopt));
+ return;
+ }
+
+ cicerone_proxy_->CallMethod(
+ &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+ base::BindOnce(
+ &CiceroneClientImpl::OnDBusProtoResponse<
+ vm_tools::cicerone::UninstallPackageOwningFileResponse>,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+ }
+
void CreateLxdContainer(
const vm_tools::cicerone::CreateLxdContainerRequest& request,
DBusMethodCallback<vm_tools::cicerone::CreateLxdContainerResponse>
@@ -286,6 +318,14 @@
weak_ptr_factory_.GetWeakPtr()));
cicerone_proxy_->ConnectToSignal(
vm_tools::cicerone::kVmCiceroneInterface,
+ vm_tools::cicerone::kUninstallPackageProgressSignal,
+ base::BindRepeating(
+ &CiceroneClientImpl::OnUninstallPackageProgressSignal,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::BindOnce(&CiceroneClientImpl::OnSignalConnected,
+ weak_ptr_factory_.GetWeakPtr()));
+ cicerone_proxy_->ConnectToSignal(
+ vm_tools::cicerone::kVmCiceroneInterface,
vm_tools::cicerone::kLxdContainerCreatedSignal,
base::BindRepeating(&CiceroneClientImpl::OnLxdContainerCreatedSignal,
weak_ptr_factory_.GetWeakPtr()),
@@ -362,6 +402,18 @@
}
}
+ void OnUninstallPackageProgressSignal(dbus::Signal* signal) {
+ vm_tools::cicerone::UninstallPackageProgressSignal proto;
+ dbus::MessageReader reader(signal);
+ if (!reader.PopArrayOfBytesAsProto(&proto)) {
+ LOG(ERROR) << "Failed to parse proto from DBus Signal";
+ return;
+ }
+ for (auto& observer : observer_list_) {
+ observer.OnUninstallPackageProgress(proto);
+ }
+ }
+
void OnLxdContainerCreatedSignal(dbus::Signal* signal) {
vm_tools::cicerone::LxdContainerCreatedSignal proto;
dbus::MessageReader reader(signal);
@@ -413,6 +465,9 @@
} else if (signal_name ==
vm_tools::cicerone::kInstallLinuxPackageProgressSignal) {
is_install_linux_package_progress_signal_connected_ = is_connected;
+ } else if (signal_name ==
+ vm_tools::cicerone::kUninstallPackageProgressSignal) {
+ is_uninstall_package_progress_signal_connected_ = is_connected;
} else if (signal_name == vm_tools::cicerone::kLxdContainerCreatedSignal) {
is_lxd_container_created_signal_connected_ = is_connected;
} else if (signal_name ==
@@ -432,6 +487,7 @@
bool is_container_started_signal_connected_ = false;
bool is_container_shutdown_signal_connected_ = false;
bool is_install_linux_package_progress_signal_connected_ = false;
+ bool is_uninstall_package_progress_signal_connected_ = false;
bool is_lxd_container_created_signal_connected_ = false;
bool is_lxd_container_downloading_signal_connected_ = false;
bool is_tremplin_started_signal_connected_ = false;
diff --git a/chromeos/dbus/cicerone_client.h b/chromeos/dbus/cicerone_client.h
index 6b832e9..aa74506 100644
--- a/chromeos/dbus/cicerone_client.h
+++ b/chromeos/dbus/cicerone_client.h
@@ -36,6 +36,11 @@
const vm_tools::cicerone::InstallLinuxPackageProgressSignal&
signal) = 0;
+ // This is signaled from the container while a package is being uninstalled
+ // via UninstallPackageOwningFile.
+ virtual void OnUninstallPackageProgress(
+ const vm_tools::cicerone::UninstallPackageProgressSignal& signal) = 0;
+
// OnLxdContainerCreated is signaled from Cicerone when the long running
// creation of an Lxd container is complete.
virtual void OnLxdContainerCreated(
@@ -75,6 +80,9 @@
// This should be true prior to calling InstallLinuxPackage.
virtual bool IsInstallLinuxPackageProgressSignalConnected() = 0;
+ // This should be true prior to calling UninstallPackageOwningFile.
+ virtual bool IsUninstallPackageProgressSignalConnected() = 0;
+
// This should be true prior to calling CreateLxdContainer or
// StartLxdContainer.
virtual bool IsLxdContainerCreatedSignalConnected() = 0;
@@ -115,6 +123,13 @@
DBusMethodCallback<vm_tools::cicerone::InstallLinuxPackageResponse>
callback) = 0;
+ // Uninstalls the package that owns the indicated .desktop file.
+ // |callback| is called after the method call finishes.
+ virtual void UninstallPackageOwningFile(
+ const vm_tools::cicerone::UninstallPackageOwningFileRequest& request,
+ DBusMethodCallback<vm_tools::cicerone::UninstallPackageOwningFileResponse>
+ callback) = 0;
+
// Creates a new Lxd Container.
// |callback| is called to indicate creation status.
// |Observer::OnLxdContainerCreated| will be called on completion.
diff --git a/chromeos/dbus/fake_cicerone_client.cc b/chromeos/dbus/fake_cicerone_client.cc
index 6404da7..f972995 100644
--- a/chromeos/dbus/fake_cicerone_client.cc
+++ b/chromeos/dbus/fake_cicerone_client.cc
@@ -11,29 +11,24 @@
namespace chromeos {
FakeCiceroneClient::FakeCiceroneClient() {
- launch_container_application_response_.Clear();
launch_container_application_response_.set_success(true);
- container_app_icon_response_.Clear();
-
- get_linux_package_info_response_.Clear();
get_linux_package_info_response_.set_success(true);
get_linux_package_info_response_.set_package_id("Fake Package;1.0;x86-64");
get_linux_package_info_response_.set_summary("A package that is fake");
- install_linux_package_response_.Clear();
install_linux_package_response_.set_status(
vm_tools::cicerone::InstallLinuxPackageResponse::STARTED);
- create_lxd_container_response_.Clear();
+ uninstall_package_owning_file_response_.set_status(
+ vm_tools::cicerone::UninstallPackageOwningFileResponse::STARTED);
+
create_lxd_container_response_.set_status(
vm_tools::cicerone::CreateLxdContainerResponse::CREATING);
- start_lxd_container_response_.Clear();
start_lxd_container_response_.set_status(
vm_tools::cicerone::StartLxdContainerResponse::STARTED);
- setup_lxd_container_user_response_.Clear();
setup_lxd_container_user_response_.set_status(
vm_tools::cicerone::SetUpLxdContainerUserResponse::SUCCESS);
}
@@ -72,6 +67,10 @@
return is_install_linux_package_progress_signal_connected_;
}
+bool FakeCiceroneClient::IsUninstallPackageProgressSignalConnected() {
+ return is_uninstall_package_progress_signal_connected_;
+}
+
void FakeCiceroneClient::LaunchContainerApplication(
const vm_tools::cicerone::LaunchContainerApplicationRequest& request,
DBusMethodCallback<vm_tools::cicerone::LaunchContainerApplicationResponse>
@@ -106,6 +105,15 @@
base::BindOnce(std::move(callback), install_linux_package_response_));
}
+void FakeCiceroneClient::UninstallPackageOwningFile(
+ const vm_tools::cicerone::UninstallPackageOwningFileRequest& request,
+ DBusMethodCallback<vm_tools::cicerone::UninstallPackageOwningFileResponse>
+ callback) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback),
+ uninstall_package_owning_file_response_));
+}
+
void FakeCiceroneClient::WaitForServiceToBeAvailable(
dbus::ObjectProxy::WaitForServiceToBeAvailableCallback callback) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
diff --git a/chromeos/dbus/fake_cicerone_client.h b/chromeos/dbus/fake_cicerone_client.h
index c81875c..fa62094 100644
--- a/chromeos/dbus/fake_cicerone_client.h
+++ b/chromeos/dbus/fake_cicerone_client.h
@@ -32,6 +32,9 @@
// This should be true prior to calling InstallLinuxPackage.
bool IsInstallLinuxPackageProgressSignalConnected() override;
+ // This should be true prior to calling UninstallPackageOwningFile.
+ bool IsUninstallPackageProgressSignalConnected() override;
+
// This should be true prior to calling CreateLxdContainer or
// StartLxdContainer.
bool IsLxdContainerCreatedSignalConnected() override;
@@ -73,6 +76,14 @@
DBusMethodCallback<vm_tools::cicerone::InstallLinuxPackageResponse>
callback) override;
+ // Fake version of the method that uninstalls an application inside a running
+ // Container. |callback| is called after the method call finishes. This does
+ // not cause progress events to be fired.
+ void UninstallPackageOwningFile(
+ const vm_tools::cicerone::UninstallPackageOwningFileRequest& request,
+ DBusMethodCallback<vm_tools::cicerone::UninstallPackageOwningFileResponse>
+ callback) override;
+
// Fake version of the method that creates a new Container.
// |callback| is called to indicate creation status.
void CreateLxdContainer(
@@ -121,6 +132,11 @@
is_install_linux_package_progress_signal_connected_ = connected;
}
+ // Set IsUninstallPackageProgressSignalConnected state
+ void set_uninstall_package_progress_signal_connected(bool connected) {
+ is_uninstall_package_progress_signal_connected_ = connected;
+ }
+
// Set LxdContainerCreatedSignalConnected state
void set_lxd_container_created_signal_connected(bool connected) {
is_lxd_container_created_signal_connected_ = connected;
@@ -165,6 +181,13 @@
install_linux_package_response_ = install_linux_package_response;
}
+ void set_uninstall_package_owning_file_response(
+ const vm_tools::cicerone::UninstallPackageOwningFileResponse&
+ uninstall_package_owning_file_response) {
+ uninstall_package_owning_file_response_ =
+ uninstall_package_owning_file_response;
+ }
+
void set_create_lxd_container_response(
const vm_tools::cicerone::CreateLxdContainerResponse&
create_lxd_container_response) {
@@ -204,6 +227,7 @@
bool is_container_started_signal_connected_ = true;
bool is_container_shutdown_signal_connected_ = true;
bool is_install_linux_package_progress_signal_connected_ = true;
+ bool is_uninstall_package_progress_signal_connected_ = true;
bool is_lxd_container_created_signal_connected_ = true;
bool is_lxd_container_downloading_signal_connected_ = true;
bool is_tremplin_started_signal_connected_ = true;
@@ -218,6 +242,8 @@
vm_tools::cicerone::LinuxPackageInfoResponse get_linux_package_info_response_;
vm_tools::cicerone::InstallLinuxPackageResponse
install_linux_package_response_;
+ vm_tools::cicerone::UninstallPackageOwningFileResponse
+ uninstall_package_owning_file_response_;
vm_tools::cicerone::CreateLxdContainerResponse create_lxd_container_response_;
vm_tools::cicerone::StartLxdContainerResponse start_lxd_container_response_;
vm_tools::cicerone::GetLxdContainerUsernameResponse