Commit 296586cf authored by lgcheng's avatar lgcheng Committed by Commit bot

arc: Implement uninstall confirmation dialog for Arc app.

Add ArcAppDialogView for arc app uninstall confirmation. Add Test coverage.

BUG=661076
Test=Add browsertest. Tests passed.
Test=Manual Test.

Review-Url: https://codereview.chromium.org/2529783002
Cr-Commit-Position: refs/heads/master@{#435641}
parent 693c2b93
......@@ -3220,6 +3220,7 @@ split_static_library("ui") {
sources += [
"app_list/arc/arc_app_context_menu.cc",
"app_list/arc/arc_app_context_menu.h",
"app_list/arc/arc_app_dialog.h",
"app_list/arc/arc_app_icon.cc",
"app_list/arc/arc_app_icon.h",
"app_list/arc/arc_app_icon_loader.cc",
......@@ -3260,6 +3261,7 @@ split_static_library("ui") {
"ash/launcher/arc_launcher_context_menu.h",
"ash/launcher/launcher_arc_app_updater.cc",
"ash/launcher/launcher_arc_app_updater.h",
"views/arc_app_dialog_view.cc",
]
}
if (is_desktop_linux) {
......
......@@ -4,9 +4,11 @@
#include "chrome/browser/ui/app_list/arc/arc_app_context_menu.h"
#include "base/bind.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
#include "chrome/browser/ui/app_list/arc/arc_app_dialog.h"
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h"
......@@ -82,7 +84,7 @@ void ArcAppContextMenu::ExecuteCommand(int command_id, int event_flags) {
ArcAppWindowLauncherController::GetShelfAppIdFromArcAppId(app_id()));
break;
case UNINSTALL:
UninstallPackage();
arc::ShowArcAppUninstallDialog(profile(), controller(), app_id());
break;
case SHOW_APP_INFO:
ShowPackageInfo();
......@@ -92,23 +94,6 @@ void ArcAppContextMenu::ExecuteCommand(int command_id, int event_flags) {
}
}
void ArcAppContextMenu::UninstallPackage() {
ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile());
DCHECK(arc_prefs);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
arc_prefs->GetApp(app_id());
if (!app_info) {
VLOG(2) << "Package being uninstalled does not exist: " << app_id() << ".";
return;
}
if (app_info->shortcut) {
// for shortcut we just remove the shortcut instead of the package
arc_prefs->RemoveApp(app_id());
} else {
arc::UninstallPackage(app_info->package_name);
}
}
void ArcAppContextMenu::ShowPackageInfo() {
const ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile());
DCHECK(arc_prefs);
......
......@@ -34,7 +34,6 @@ class ArcAppContextMenu : public app_list::AppContextMenu {
private:
void IsAppOpen();
void UninstallPackage();
void ShowPackageInfo();
DISALLOW_COPY_AND_ASSIGN(ArcAppContextMenu);
......
// Copyright 2016 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_ARC_ARC_APP_DIALOG_H_
#define CHROME_BROWSER_UI_APP_LIST_ARC_ARC_APP_DIALOG_H_
#include <string>
#include "base/callback.h"
class AppListControllerDelegate;
class Profile;
namespace arc {
// Shows a dialog for user to confirm uninstallation of Arc app.
// Currently, Arc app can only be manually uninstalled from AppList. But it
// would be simple to enable the dialog to shown from other source.
void ShowArcAppUninstallDialog(Profile* profile,
AppListControllerDelegate* controller,
const std::string& app_id);
// Test purpose methods.
bool IsArcAppDialogViewAliveForTest();
bool CloseAppDialogViewAndConfirmForTest(bool confirm);
} // namespace arc
#endif // CHROME_BROWSER_UI_APP_LIST_ARC_ARC_APP_DIALOG_H_
......@@ -12,6 +12,7 @@
#include "base/json/json_writer.h"
#include "base/synchronization/waitable_event.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ui/ash/launcher/arc_app_deferred_launcher_controller.h"
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
......@@ -392,6 +393,22 @@ void UninstallPackage(const std::string& package_name) {
app_instance->UninstallPackage(package_name);
}
void UninstallArcApp(const std::string& app_id, Profile* profile) {
ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile);
DCHECK(arc_prefs);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
arc_prefs->GetApp(app_id);
if (!app_info) {
VLOG(2) << "Package being uninstalled does not exist: " << app_id << ".";
return;
}
// For shortcut we just remove the shortcut instead of the package.
if (app_info->shortcut)
arc_prefs->RemoveApp(app_id);
else
UninstallPackage(app_info->package_name);
}
void RemoveCachedIcon(const std::string& icon_resource_id) {
VLOG(2) << "Removing icon " << icon_resource_id;
......
......@@ -11,6 +11,8 @@
#include "components/arc/common/app.mojom.h"
#include "ui/gfx/geometry/rect.h"
class Profile;
namespace content {
class BrowserContext;
}
......@@ -70,6 +72,9 @@ bool CanHandleResolution(content::BrowserContext* context,
// Uninstalls the package in ARC.
void UninstallPackage(const std::string& package_name);
// Uninstalls Arc app or removes shortcut.
void UninstallArcApp(const std::string& app_id, Profile* profile);
// Removes cached app shortcut icon in ARC.
void RemoveCachedIcon(const std::string& icon_resource_id);
......
// Copyright 2016 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/arc/arc_app_dialog.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
#include "chrome/browser/ui/app_list/arc/arc_app_icon_loader.h"
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ui/native_window_tracker.h"
#include "chrome/grit/generated_resources.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_types.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_delegate.h"
namespace arc {
namespace {
const int kRightColumnWidth = 210;
const int kIconSize = 64;
using ArcAppConfirmCallback =
base::Callback<void(const std::string& app_id, Profile* profile)>;
class ArcAppDialogView : public views::DialogDelegateView,
public AppIconLoaderDelegate {
public:
ArcAppDialogView(Profile* profile,
AppListControllerDelegate* controller,
const std::string& app_id,
const base::string16& window_title,
const base::string16& heading_text,
const base::string16& confirm_button_text,
const base::string16& cancel_button_text,
ArcAppConfirmCallback confirm_callback);
~ArcAppDialogView() override;
// Public method used for test only.
void ConfirmOrCancelForTest(bool confirm);
private:
// views::WidgetDelegate:
base::string16 GetWindowTitle() const override;
void DeleteDelegate() override;
ui::ModalType GetModalType() const override;
// views::View:
gfx::Size GetPreferredSize() const override;
void Layout() override;
// views::DialogDelegate:
base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
bool Accept() override;
// AppIconLoaderDelegate:
void OnAppImageUpdated(const std::string& app_id,
const gfx::ImageSkia& image) override;
// Constructs and shows the modal dialog widget.
void Show();
bool initial_setup_ = true;
views::ImageView* icon_view_ = nullptr;
views::Label* heading_view_ = nullptr;
std::unique_ptr<ArcAppIconLoader> icon_loader_;
Profile* const profile_;
AppListControllerDelegate* controller_;
gfx::NativeWindow parent_;
// Tracks whether |parent_| got destroyed.
std::unique_ptr<NativeWindowTracker> parent_window_tracker_;
const std::string app_id_;
const base::string16 window_title_;
const base::string16 confirm_button_text_;
const base::string16 cancel_button_text_;
ArcAppConfirmCallback confirm_callback_;
DISALLOW_COPY_AND_ASSIGN(ArcAppDialogView);
};
// Browertest use only. Global pointer of ArcAppDialogView which is shown.
ArcAppDialogView* g_current_arc_app_dialog_view = nullptr;
ArcAppDialogView::ArcAppDialogView(Profile* profile,
AppListControllerDelegate* controller,
const std::string& app_id,
const base::string16& window_title,
const base::string16& heading_text,
const base::string16& confirm_button_text,
const base::string16& cancel_button_text,
ArcAppConfirmCallback confirm_callback)
: profile_(profile),
controller_(controller),
app_id_(app_id),
window_title_(window_title),
confirm_button_text_(confirm_button_text),
cancel_button_text_(cancel_button_text),
confirm_callback_(confirm_callback) {
DCHECK(controller);
parent_ = controller_->GetAppListWindow();
if (parent_)
parent_window_tracker_ = NativeWindowTracker::Create(parent_);
icon_view_ = new views::ImageView();
icon_view_->SetImageSize(gfx::Size(kIconSize, kIconSize));
AddChildView(icon_view_);
heading_view_ = new views::Label(heading_text);
heading_view_->SetMultiLine(true);
heading_view_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
heading_view_->SetAllowCharacterBreak(true);
AddChildView(heading_view_);
icon_loader_.reset(new ArcAppIconLoader(profile_, kIconSize, this));
// The dialog will show once the icon is loaded.
icon_loader_->FetchImage(app_id_);
}
ArcAppDialogView::~ArcAppDialogView() {
DCHECK_EQ(this, g_current_arc_app_dialog_view);
g_current_arc_app_dialog_view = nullptr;
}
void ArcAppDialogView::ConfirmOrCancelForTest(bool confirm) {
if (confirm)
Accept();
else
Cancel();
GetWidget()->Close();
}
base::string16 ArcAppDialogView::GetWindowTitle() const {
return window_title_;
}
void ArcAppDialogView::DeleteDelegate() {
if (controller_)
controller_->OnCloseChildDialog();
DialogDelegateView::DeleteDelegate();
}
ui::ModalType ArcAppDialogView::GetModalType() const {
return ui::MODAL_TYPE_WINDOW;
}
// TODO(lgcheng@) The code below is copied from
// ExtensionUninstallDialogDelegateView sizing and layout code. Use
// LayoutManager to relace these manual layout. See crbug.com/670110.
gfx::Size ArcAppDialogView::GetPreferredSize() const {
int width = kRightColumnWidth;
width += kIconSize;
width += views::kButtonHEdgeMarginNew * 2;
width += views::kRelatedControlHorizontalSpacing;
int height = views::kPanelVertMargin * 2;
height += heading_view_->GetHeightForWidth(kRightColumnWidth);
return gfx::Size(width,
std::max(height, kIconSize + views::kPanelVertMargin * 2));
}
void ArcAppDialogView::Layout() {
int x = views::kButtonHEdgeMarginNew;
int y = views::kPanelVertMargin;
heading_view_->SizeToFit(kRightColumnWidth);
if (heading_view_->height() <= kIconSize) {
icon_view_->SetBounds(x, y, kIconSize, kIconSize);
x += kIconSize;
x += views::kRelatedControlHorizontalSpacing;
heading_view_->SetX(x);
heading_view_->SetY(y + (kIconSize - heading_view_->height()) / 2);
} else {
icon_view_->SetBounds(x, y + (heading_view_->height() - kIconSize) / 2,
kIconSize, kIconSize);
x += kIconSize;
x += views::kRelatedControlHorizontalSpacing;
heading_view_->SetX(x);
heading_view_->SetY(y);
}
}
base::string16 ArcAppDialogView::GetDialogButtonLabel(
ui::DialogButton button) const {
return button == ui::DIALOG_BUTTON_CANCEL ? cancel_button_text_
: confirm_button_text_;
}
bool ArcAppDialogView::Accept() {
confirm_callback_.Run(app_id_, profile_);
return true;
}
void ArcAppDialogView::OnAppImageUpdated(const std::string& app_id,
const gfx::ImageSkia& image) {
DCHECK_EQ(app_id, app_id_);
DCHECK(!image.isNull());
icon_view_->SetImage(image);
if (initial_setup_)
Show();
}
void ArcAppDialogView::Show() {
initial_setup_ = false;
// The parent window was killed before the icon was loaded.
if (parent_ && parent_window_tracker_->WasNativeWindowClosed()) {
Cancel();
DialogDelegateView::DeleteDelegate();
return;
}
if (controller_)
controller_->OnShowChildDialog();
g_current_arc_app_dialog_view = this;
constrained_window::CreateBrowserModalDialogViews(this, parent_)->Show();
}
} // namespace
void ShowArcAppUninstallDialog(Profile* profile,
AppListControllerDelegate* controller,
const std::string& app_id) {
ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile);
DCHECK(arc_prefs);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
arc_prefs->GetApp(app_id);
if (!app_info)
return;
bool is_shortcut = app_info->shortcut;
base::string16 window_title = l10n_util::GetStringUTF16(
is_shortcut ? IDS_EXTENSION_UNINSTALL_PROMPT_TITLE
: IDS_APP_UNINSTALL_PROMPT_TITLE);
base::string16 heading_text = base::UTF8ToUTF16(l10n_util::GetStringFUTF8(
is_shortcut ? IDS_EXTENSION_UNINSTALL_PROMPT_HEADING
: IDS_ARC_APP_UNINSTALL_PROMPT_HEADING,
base::UTF8ToUTF16(app_info->name)));
base::string16 confirm_button_text = l10n_util::GetStringUTF16(
is_shortcut ? IDS_EXTENSION_PROMPT_UNINSTALL_BUTTON
: IDS_EXTENSION_PROMPT_UNINSTALL_APP_BUTTON);
base::string16 cancel_button_text = l10n_util::GetStringUTF16(IDS_CANCEL);
new ArcAppDialogView(profile, controller, app_id, window_title, heading_text,
confirm_button_text, cancel_button_text,
base::Bind(UninstallArcApp));
}
bool IsArcAppDialogViewAliveForTest() {
return g_current_arc_app_dialog_view != nullptr;
}
bool CloseAppDialogViewAndConfirmForTest(bool confirm) {
if (!g_current_arc_app_dialog_view)
return false;
g_current_arc_app_dialog_view->ConfirmOrCancelForTest(confirm);
return true;
}
} // namespace arc
// Copyright 2016 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/arc/arc_app_dialog.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "chrome/browser/chromeos/arc/arc_session_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
#include "chrome/browser/ui/app_list/app_list_service.h"
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs_factory.h"
#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/chromeos_switches.h"
#include "components/arc/arc_bridge_service.h"
#include "components/arc/common/app.mojom.h"
#include "components/arc/test/fake_app_instance.h"
#include "content/public/test/test_utils.h"
namespace arc {
class ArcAppUninstallDialogViewBrowserTest : public InProcessBrowserTest {
public:
ArcAppUninstallDialogViewBrowserTest() {}
// InProcessBrowserTest:
~ArcAppUninstallDialogViewBrowserTest() override {}
void SetUpAppInstance() {
profile_ = browser()->profile();
base::CommandLine::ForCurrentProcess()->AppendSwitch(
chromeos::switches::kEnableArc);
// A valid |arc_app_list_prefs_| is needed for the Arc bridge service and
// the Arc session manager.
arc_app_list_pref_ = ArcAppListPrefs::Get(profile_);
if (!arc_app_list_pref_) {
ArcAppListPrefsFactory::GetInstance()->RecreateServiceInstanceForTesting(
profile_);
}
DCHECK(ArcBridgeService::Get());
ArcSessionManager* session_manager = ArcSessionManager::Get();
DCHECK(session_manager);
ArcSessionManager::DisableUIForTesting();
session_manager->OnPrimaryUserProfilePrepared(profile_);
session_manager->EnableArc();
arc_app_list_pref_ = ArcAppListPrefs::Get(profile_);
DCHECK(arc_app_list_pref_);
base::RunLoop run_loop;
arc_app_list_pref_->SetDefaltAppsReadyCallback(run_loop.QuitClosure());
run_loop.Run();
app_instance_.reset(new arc::FakeAppInstance(arc_app_list_pref_));
arc_app_list_pref_->app_instance_holder()->SetInstance(app_instance_.get());
// In this setup, we have one app and one shortcut which share one package.
mojom::AppInfo app;
app.name = base::StringPrintf("Fake App %d", 0);
app.package_name = base::StringPrintf("fake.package.%d", 0);
app.activity = base::StringPrintf("fake.app.%d.activity", 0);
app.sticky = false;
app_instance_->SendRefreshAppList(std::vector<mojom::AppInfo>(1, app));
mojom::ShortcutInfo shortcut;
shortcut.name = base::StringPrintf("Fake Shortcut %d", 0);
shortcut.package_name = base::StringPrintf("fake.package.%d", 0);
shortcut.intent_uri = base::StringPrintf("Fake Shortcut uri %d", 0);
app_instance_->SendInstallShortcut(shortcut);
mojom::ArcPackageInfo package;
package.package_name = base::StringPrintf("fake.package.%d", 0);
package.package_version = 0;
package.last_backup_android_id = 0;
package.last_backup_time = 0;
package.sync = false;
app_instance_->SendRefreshPackageList(
std::vector<mojom::ArcPackageInfo>(1, package));
}
void TearDownOnMainThread() override {
ArcSessionManager::Get()->Shutdown();
InProcessBrowserTest::TearDownOnMainThread();
}
// Ensures the ArcAppDialogView is destoryed.
void TearDown() override { ASSERT_FALSE(IsArcAppDialogViewAliveForTest()); }
ArcAppListPrefs* arc_app_list_pref() { return arc_app_list_pref_; }
FakeAppInstance* instance() { return app_instance_.get(); }
private:
ArcAppListPrefs* arc_app_list_pref_ = nullptr;
Profile* profile_ = nullptr;
std::unique_ptr<arc::FakeAppInstance> app_instance_;
DISALLOW_COPY_AND_ASSIGN(ArcAppUninstallDialogViewBrowserTest);
};
// User confirms/cancels Arc app uninstall. Note that the shortcut is removed
// when the app and the package are uninstalled since the shortcut and the app
// share same package.
IN_PROC_BROWSER_TEST_F(ArcAppUninstallDialogViewBrowserTest,
UserConfirmsUninstall) {
SetUpAppInstance();
std::vector<std::string> app_ids = arc_app_list_pref()->GetAppIds();
EXPECT_EQ(app_ids.size(), 2u);
std::string package_name = base::StringPrintf("fake.package.%d", 0);
std::string app_activity = base::StringPrintf("fake.app.%d.activity", 0);
std::string app_id =
arc_app_list_pref()->GetAppId(package_name, app_activity);
AppListService* service = AppListService::Get();
ASSERT_TRUE(service);
service->ShowForProfile(browser()->profile());
AppListControllerDelegate* controller(service->GetControllerDelegate());
ASSERT_TRUE(controller);
ShowArcAppUninstallDialog(browser()->profile(), controller, app_id);
content::RunAllPendingInMessageLoop();
EXPECT_TRUE(CloseAppDialogViewAndConfirmForTest(false));
content::RunAllPendingInMessageLoop();
app_ids = arc_app_list_pref()->GetAppIds();
EXPECT_EQ(app_ids.size(), 2u);
ShowArcAppUninstallDialog(browser()->profile(), controller, app_id);
content::RunAllPendingInMessageLoop();
EXPECT_TRUE(CloseAppDialogViewAndConfirmForTest(true));
content::RunAllPendingInMessageLoop();
app_ids = arc_app_list_pref()->GetAppIds();
EXPECT_EQ(app_ids.size(), 0u);
controller->DismissView();
}
// User confirms/cancels Arc app shortcut removal. Note that the app is not
// uninstalled when the shortcut is removed.
IN_PROC_BROWSER_TEST_F(ArcAppUninstallDialogViewBrowserTest,
UserConfirmsUninstallShortcut) {
SetUpAppInstance();
std::vector<std::string> app_ids = arc_app_list_pref()->GetAppIds();
EXPECT_EQ(app_ids.size(), 2u);
std::string package_name = base::StringPrintf("fake.package.%d", 0);
std::string intent_uri = base::StringPrintf("Fake Shortcut uri %d", 0);
std::string app_id = arc_app_list_pref()->GetAppId(package_name, intent_uri);
AppListService* service = AppListService::Get();
ASSERT_TRUE(service);
service->ShowForProfile(browser()->profile());
AppListControllerDelegate* controller(service->GetControllerDelegate());
ASSERT_TRUE(controller);
ShowArcAppUninstallDialog(browser()->profile(), controller, app_id);
content::RunAllPendingInMessageLoop();
EXPECT_TRUE(CloseAppDialogViewAndConfirmForTest(false));
content::RunAllPendingInMessageLoop();
app_ids = arc_app_list_pref()->GetAppIds();
EXPECT_EQ(app_ids.size(), 2u);
ShowArcAppUninstallDialog(browser()->profile(), controller, app_id);
content::RunAllPendingInMessageLoop();
EXPECT_TRUE(CloseAppDialogViewAndConfirmForTest(true));
content::RunAllPendingInMessageLoop();
app_ids = arc_app_list_pref()->GetAppIds();
EXPECT_EQ(app_ids.size(), 1u);
controller->DismissView();
}
} // namespace arc
......@@ -2316,6 +2316,7 @@ test("browser_tests") {
"../browser/extensions/api/hotword_private/hotword_private_apitest.cc",
"../browser/extensions/api/vpn_provider/vpn_provider_apitest.cc",
"../browser/ui/ash/launcher/arc_app_launcher_browsertest.cc",
"../browser/ui/views/arc_app_dialog_view_browsertest.cc",
"../browser/ui/webui/options/chromeos/accounts_options_browsertest.cc",
"../browser/ui/webui/options/chromeos/guest_mode_options_browsertest.cc",
"../browser/ui/webui/options/chromeos/guest_mode_options_ui_browsertest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment