Add 'Shut Down' option to context menus for Plugin VM

This CL adds a 'Shut Down' option to the shelf and launcher context
menus for the Plugin VM app. These will only show while the app is
open. For now we simply dispatch a StopVm message, but we may want to
expand this to also close the UI window once the shut down is complete.

Bug: 940319
Change-Id: I3acbbaf540384fad1dbb68e6226420b47d586793
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1621987
Commit-Queue: Timothy Loh <timloh@chromium.org>
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#662512}
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 5757d9e..ade9da4 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -3917,6 +3917,9 @@
   <message name="IDS_PLUGIN_VM_LAUNCHER_RETRY_BUTTON" desc="Label for the button in the Plugin VM installer to retry installation on failure.">
     Retry
   </message>
+  <message name="IDS_PLUGIN_VM_SHUT_DOWN_MENU_ITEM" desc="Text shown in the launcher and shelf context menus for the Plugin VM app to shut it down.">
+    Shut down
+  </message>
 
   <!-- Strings for Account Manager welcome screen -->
   <message name="IDS_ACCOUNT_MANAGER_WELCOME_TITLE" desc="Title for the Chrome OS Account Manager Welcome screen.">
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_manager.cc b/chrome/browser/chromeos/plugin_vm/plugin_vm_manager.cc
index 1035970..bab4845 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_manager.cc
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_manager.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_manager.h"
 
+#include "base/bind_helpers.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
@@ -79,6 +80,15 @@
                          weak_ptr_factory_.GetWeakPtr()));
 }
 
+void PluginVmManager::StopPluginVm() {
+  vm_tools::plugin_dispatcher::StopVmRequest request;
+  request.set_owner_id(owner_id_);
+  request.set_vm_name_uuid(kPluginVmDefaultName);
+
+  chromeos::DBusThreadManager::Get()->GetVmPluginDispatcherClient()->StopVm(
+      std::move(request), base::DoNothing());
+}
+
 void PluginVmManager::OnStartPluginVmDispatcher(bool success) {
   if (!success) {
     LOG(ERROR) << "Failed to start Plugin Vm Dispatcher.";
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_manager.h b/chrome/browser/chromeos/plugin_vm/plugin_vm_manager.h
index cea291c..4b107ec 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_manager.h
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_manager.h
@@ -28,6 +28,7 @@
   ~PluginVmManager() override;
 
   void LaunchPluginVm();
+  void StopPluginVm();
 
  private:
   // The flow to launch a Plugin Vm. We'll probably want to add additional
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_util.cc b/chrome/browser/chromeos/plugin_vm/plugin_vm_util.cc
index 41f8428..9aba066 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_util.cc
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_util.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "components/exo/shell_surface_util.h"
 #include "components/prefs/pref_service.h"
 
@@ -68,6 +69,14 @@
   return IsPluginVmAllowedForProfile(profile) && IsPluginVmConfigured(profile);
 }
 
+bool IsPluginVmRunning(Profile* profile) {
+  // TODO(timloh): This should probably also check the state of the VM. Once we
+  // have a signal to update us on VM state changes, we should keep track of it
+  // for use here.
+  return ChromeLauncherController::instance()->IsOpen(
+      ash::ShelfID(kPluginVmAppId));
+}
+
 bool IsPluginVmWindow(const aura::Window* window) {
   const std::string* app_id = exo::GetShellApplicationId(window);
   if (!app_id)
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_util.h b/chrome/browser/chromeos/plugin_vm/plugin_vm_util.h
index a449b7f..29776f0 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_util.h
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_util.h
@@ -17,8 +17,6 @@
 
 namespace plugin_vm {
 
-using PluginVmStartedCallback = base::OnceCallback<void(bool)>;
-
 // Generated as crx_file::id_util::GenerateId("org.chromium.plugin_vm");
 constexpr char kPluginVmAppId[] = "lgjpclljbbmphhnalkeplcmnjpfmmaek";
 
@@ -34,14 +32,14 @@
 // Returns true if PluginVm is allowed and configured for the current profile.
 bool IsPluginVmEnabled(Profile* profile);
 
+// Determines if the default Plugin VM is running and visible.
+bool IsPluginVmRunning(Profile* profile);
+
 void ShowPluginVmLauncherView(Profile* profile);
 
 // Checks if an window is for plugin vm.
 bool IsPluginVmWindow(const aura::Window* window);
 
-void StartPluginVmForProfile(Profile* profile,
-                             PluginVmStartedCallback callback);
-
 // Retrieves the license key to be used for PluginVm. If
 // none is set this will return an empty string.
 std::string GetPluginVmLicenseKey();
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 993c2e3..d9cb417 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3402,6 +3402,8 @@
         "app_list/crostini/crostini_app_item.h",
         "app_list/crostini/crostini_app_model_builder.cc",
         "app_list/crostini/crostini_app_model_builder.h",
+        "app_list/internal_app/internal_app_context_menu.cc",
+        "app_list/internal_app/internal_app_context_menu.h",
         "app_list/internal_app/internal_app_icon_loader.cc",
         "app_list/internal_app/internal_app_icon_loader.h",
         "app_list/internal_app/internal_app_item.cc",
diff --git a/chrome/browser/ui/app_list/internal_app/internal_app_context_menu.cc b/chrome/browser/ui/app_list/internal_app/internal_app_context_menu.cc
new file mode 100644
index 0000000..b9e0ba4
--- /dev/null
+++ b/chrome/browser/ui/app_list/internal_app/internal_app_context_menu.cc
@@ -0,0 +1,50 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/app_list/internal_app/internal_app_context_menu.h"
+
+#include "ash/public/cpp/app_menu_constants.h"
+#include "chrome/browser/chromeos/plugin_vm/plugin_vm_manager.h"
+#include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
+#include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
+#include "chrome/grit/generated_resources.h"
+
+InternalAppContextMenu::InternalAppContextMenu(
+    Profile* profile,
+    const std::string& app_id,
+    AppListControllerDelegate* controller)
+    : app_list::AppContextMenu(nullptr, profile, app_id, controller) {}
+
+InternalAppContextMenu::~InternalAppContextMenu() = default;
+
+bool InternalAppContextMenu::IsCommandIdEnabled(int command_id) const {
+  if (command_id == ash::STOP_APP) {
+    DCHECK_EQ(app_list::FindInternalApp(app_id())->internal_app_name,
+              app_list::InternalAppName::kPluginVm);
+    return plugin_vm::IsPluginVmRunning(profile());
+  }
+  return app_list::AppContextMenu::IsCommandIdEnabled(command_id);
+}
+
+void InternalAppContextMenu::ExecuteCommand(int command_id, int event_flags) {
+  switch (command_id) {
+    case ash::STOP_APP:
+      DCHECK_EQ(app_list::FindInternalApp(app_id())->internal_app_name,
+                app_list::InternalAppName::kPluginVm);
+      plugin_vm::PluginVmManager::GetForProfile(profile())->StopPluginVm();
+      return;
+  }
+  app_list::AppContextMenu::ExecuteCommand(command_id, event_flags);
+}
+
+void InternalAppContextMenu::BuildMenu(ui::SimpleMenuModel* menu_model) {
+  app_list::AppContextMenu::BuildMenu(menu_model);
+
+  const auto* internal_app = app_list::FindInternalApp(app_id());
+  DCHECK(internal_app);
+  if (internal_app->internal_app_name == app_list::InternalAppName::kPluginVm) {
+    AddContextMenuOption(menu_model, ash::STOP_APP,
+                         IDS_PLUGIN_VM_SHUT_DOWN_MENU_ITEM);
+  }
+}
diff --git a/chrome/browser/ui/app_list/internal_app/internal_app_context_menu.h b/chrome/browser/ui/app_list/internal_app/internal_app_context_menu.h
new file mode 100644
index 0000000..59c95f1
--- /dev/null
+++ b/chrome/browser/ui/app_list/internal_app/internal_app_context_menu.h
@@ -0,0 +1,32 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_APP_LIST_INTERNAL_APP_INTERNAL_APP_CONTEXT_MENU_H_
+#define CHROME_BROWSER_UI_APP_LIST_INTERNAL_APP_INTERNAL_APP_CONTEXT_MENU_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "chrome/browser/ui/app_list/app_context_menu.h"
+
+class AppListControllerDelegate;
+class Profile;
+
+class InternalAppContextMenu : public app_list::AppContextMenu {
+ public:
+  InternalAppContextMenu(Profile* profile,
+                         const std::string& app_id,
+                         AppListControllerDelegate* controller);
+  ~InternalAppContextMenu() override;
+
+  // app_list::AppContextMenu:
+  bool IsCommandIdEnabled(int command_id) const override;
+  void ExecuteCommand(int command_id, int event_flags) override;
+  void BuildMenu(ui::SimpleMenuModel* menu_model) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(InternalAppContextMenu);
+};
+
+#endif  // CHROME_BROWSER_UI_APP_LIST_INTERNAL_APP_INTERNAL_APP_CONTEXT_MENU_H_
diff --git a/chrome/browser/ui/app_list/internal_app/internal_app_item.cc b/chrome/browser/ui/app_list/internal_app/internal_app_item.cc
index 6f31eed..1022795 100644
--- a/chrome/browser/ui/app_list/internal_app/internal_app_item.cc
+++ b/chrome/browser/ui/app_list/internal_app/internal_app_item.cc
@@ -6,8 +6,8 @@
 
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "base/metrics/histogram_macros.h"
-#include "chrome/browser/ui/app_list/app_context_menu.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
+#include "chrome/browser/ui/app_list/internal_app/internal_app_context_menu.h"
 #include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -55,8 +55,8 @@
 
 void InternalAppItem::GetContextMenuModel(GetMenuModelCallback callback) {
   if (!context_menu_) {
-    context_menu_ = std::make_unique<app_list::AppContextMenu>(
-        nullptr, profile(), id(), GetController());
+    context_menu_ = std::make_unique<InternalAppContextMenu>(profile(), id(),
+                                                             GetController());
   }
   context_menu_->GetMenuModel(std::move(callback));
 }
diff --git a/chrome/browser/ui/app_list/search/internal_app_result.cc b/chrome/browser/ui/app_list/search/internal_app_result.cc
index b55b4f6..5d9a733 100644
--- a/chrome/browser/ui/app_list/search/internal_app_result.cc
+++ b/chrome/browser/ui/app_list/search/internal_app_result.cc
@@ -12,8 +12,8 @@
 #include "base/metrics/histogram_macros.h"
 #include "chrome/browser/favicon/large_icon_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/app_context_menu.h"
 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
+#include "chrome/browser/ui/app_list/internal_app/internal_app_context_menu.h"
 #include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
 #include "components/favicon/core/favicon_server_fetcher_params.h"
 #include "components/favicon/core/large_icon_service.h"
@@ -173,8 +173,8 @@
   }
 
   if (!context_menu_) {
-    context_menu_ = std::make_unique<AppContextMenu>(nullptr, profile(), id(),
-                                                     controller());
+    context_menu_ =
+        std::make_unique<InternalAppContextMenu>(profile(), id(), controller());
   }
   context_menu_->GetMenuModel(std::move(callback));
 }
diff --git a/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.cc b/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.cc
index 37c2569..738913d 100644
--- a/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.cc
@@ -9,6 +9,8 @@
 
 #include "ash/public/cpp/app_menu_constants.h"
 #include "ash/public/cpp/shelf_item.h"
+#include "chrome/browser/chromeos/plugin_vm/plugin_vm_manager.h"
+#include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
 #include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/grit/generated_resources.h"
@@ -40,5 +42,28 @@
   if (app_is_open) {
     AddContextMenuOption(menu_model, ash::MENU_CLOSE,
                          IDS_LAUNCHER_CONTEXT_MENU_CLOSE);
+
+    if (internal_app->internal_app_name ==
+            app_list::InternalAppName::kPluginVm &&
+        plugin_vm::IsPluginVmRunning(controller()->profile())) {
+      AddContextMenuOption(menu_model, ash::STOP_APP,
+                           IDS_PLUGIN_VM_SHUT_DOWN_MENU_ITEM);
+    }
+  }
+}
+
+void InternalAppShelfContextMenu::ExecuteCommand(int command_id,
+                                                 int event_flags) {
+  if (ExecuteCommonCommand(command_id, event_flags))
+    return;
+
+  const auto* internal_app = app_list::FindInternalApp(item().id.app_id);
+  DCHECK(internal_app);
+  DCHECK_EQ(internal_app->internal_app_name,
+            app_list::InternalAppName::kPluginVm);
+  if (command_id == ash::STOP_APP) {
+    plugin_vm::PluginVmManager::GetForProfile(controller()->profile())
+        ->StopPluginVm();
+    return;
   }
 }
diff --git a/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h b/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h
index 4d6402a..f1bd568 100644
--- a/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h
+++ b/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h
@@ -18,6 +18,7 @@
 
   // LauncherContextMenu:
   void GetMenuModel(GetMenuModelCallback callback) override;
+  void ExecuteCommand(int command_id, int event_flags) override;
 
  private:
   void BuildMenu(ui::SimpleMenuModel* menu_model);