Commit 2bb2cc7e authored by Qiang Xu's avatar Qiang Xu Committed by Commit Bot

arc: add app shortcut items in ArcAppContextMenu

changes:
This CL makes ArcAppContextMenu showing Android app shortcuts in app
list context menu.
TODO list:
- Add ArcLauncherContextMenu integration for shelf item.
- Add LaunchAppShortcut support.
- Add UMA for measuring query & decode time.
- Add context menu metrics.
- UI adjustments when specs come.

video demo: go/appshortcuts-demo-1

Bug: 803291
Test: device test with android side change
Change-Id: I30f06d609eebc91c8d493af5bdb64360cdde002a
Reviewed-on: https://chromium-review.googlesource.com/1029412
Commit-Queue: Qiang Xu <warx@google.com>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarYusuke Sato <yusukes@chromium.org>
Reviewed-by: default avatarYury Khmel <khmel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555427}
parent ca449882
...@@ -305,6 +305,9 @@ source_set("chromeos") { ...@@ -305,6 +305,9 @@ source_set("chromeos") {
"arc/accessibility/arc_accessibility_util.h", "arc/accessibility/arc_accessibility_util.h",
"arc/accessibility/ax_tree_source_arc.cc", "arc/accessibility/ax_tree_source_arc.cc",
"arc/accessibility/ax_tree_source_arc.h", "arc/accessibility/ax_tree_source_arc.h",
"arc/app_shortcuts/arc_app_shortcut_item.h",
"arc/app_shortcuts/arc_app_shortcuts_request.cc",
"arc/app_shortcuts/arc_app_shortcuts_request.h",
"arc/arc_migration_constants.h", "arc/arc_migration_constants.h",
"arc/arc_migration_guide_notification.cc", "arc/arc_migration_guide_notification.cc",
"arc/arc_migration_guide_notification.h", "arc/arc_migration_guide_notification.h",
...@@ -1840,6 +1843,7 @@ source_set("unit_tests") { ...@@ -1840,6 +1843,7 @@ source_set("unit_tests") {
"apps/intent_helper/apps_navigation_throttle_unittest.cc", "apps/intent_helper/apps_navigation_throttle_unittest.cc",
"arc/accessibility/arc_accessibility_helper_bridge_unittest.cc", "arc/accessibility/arc_accessibility_helper_bridge_unittest.cc",
"arc/accessibility/ax_tree_source_arc_unittest.cc", "arc/accessibility/ax_tree_source_arc_unittest.cc",
"arc/app_shortcuts/arc_app_shortcuts_request_unittest.cc",
"arc/arc_migration_guide_notification_unittest.cc", "arc/arc_migration_guide_notification_unittest.cc",
"arc/arc_play_store_enabled_preference_handler_unittest.cc", "arc/arc_play_store_enabled_preference_handler_unittest.cc",
"arc/arc_session_manager_unittest.cc", "arc/arc_session_manager_unittest.cc",
......
// 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_ARC_APP_SHORTCUTS_ARC_APP_SHORTCUT_ITEM_H_
#define CHROME_BROWSER_CHROMEOS_ARC_APP_SHORTCUTS_ARC_APP_SHORTCUT_ITEM_H_
#include <string>
#include <vector>
#include "base/strings/string16.h"
#include "ui/gfx/image/image_skia.h"
namespace arc {
// Describes app shortcut that is published by Android's ShortcutManager.
struct ArcAppShortcutItem {
// The ID of this shortcut. Unique within each publisher app and stable across
// devices.
std::string shortcut_id;
// The short description of this shortcut.
base::string16 short_label;
// The icon for this shortcut.
gfx::ImageSkia icon;
};
using ArcAppShortcutItems = std::vector<ArcAppShortcutItem>;
} // namespace arc
#endif // CHROME_BROWSER_CHROMEOS_ARC_APP_SHORTCUTS_ARC_APP_SHORTCUT_ITEM_H_
// 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/arc/app_shortcuts/arc_app_shortcuts_request.h"
#include <utility>
#include "base/barrier_closure.h"
#include "base/callback_helpers.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chromeos/arc/icon_decode_request.h"
#include "components/arc/arc_bridge_service.h"
#include "components/arc/arc_service_manager.h"
#include "ui/views/controls/menu/menu_config.h"
namespace arc {
ArcAppShortcutsRequest::ArcAppShortcutsRequest(
GetAppShortcutItemsCallback callback)
: callback_(std::move(callback)), weak_ptr_factory_(this) {
DCHECK(callback_);
}
ArcAppShortcutsRequest::~ArcAppShortcutsRequest() = default;
void ArcAppShortcutsRequest::StartForPackage(const std::string& package_name) {
// DCHECK because it shouldn't be called more than one time for the life cycle
// of |this|.
DCHECK(!items_);
DCHECK(icon_decode_requests_.empty());
mojom::AppInstance* app_instance =
ArcServiceManager::Get()
? ARC_GET_INSTANCE_FOR_METHOD(
ArcServiceManager::Get()->arc_bridge_service()->app(),
GetAppShortcutItems)
: nullptr;
if (!app_instance) {
std::move(callback_).Run(nullptr);
return;
}
app_instance->GetAppShortcutItems(
package_name,
base::BindOnce(&ArcAppShortcutsRequest::OnGetAppShortcutItems,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcAppShortcutsRequest::OnGetAppShortcutItems(
std::vector<mojom::AppShortcutItemPtr> shortcut_items) {
// Using base::Unretained(this) here is safe since we own barrier_closure_.
barrier_closure_ = base::BarrierClosure(
shortcut_items.size(),
base::BindOnce(&ArcAppShortcutsRequest::OnAllIconDecodeRequestsDone,
base::Unretained(this)));
items_ = std::make_unique<ArcAppShortcutItems>();
const views::MenuConfig& menu_config = views::MenuConfig::instance();
for (const auto& shortcut_item_ptr : shortcut_items) {
ArcAppShortcutItem item;
item.shortcut_id = shortcut_item_ptr->shortcut_id;
item.short_label = base::UTF8ToUTF16(shortcut_item_ptr->short_label);
items_->emplace_back(std::move(item));
icon_decode_requests_.emplace_back(std::make_unique<IconDecodeRequest>(
base::BindOnce(&ArcAppShortcutsRequest::OnSingleIconDecodeRequestDone,
weak_ptr_factory_.GetWeakPtr(), items_->size() - 1),
menu_config.touchable_icon_size));
icon_decode_requests_.back()->StartWithOptions(shortcut_item_ptr->icon_png);
}
}
void ArcAppShortcutsRequest::OnAllIconDecodeRequestsDone() {
icon_decode_requests_.clear();
DCHECK(callback_);
base::ResetAndReturn(&callback_).Run(std::move(items_));
}
void ArcAppShortcutsRequest::OnSingleIconDecodeRequestDone(
size_t index,
const gfx::ImageSkia& icon) {
DCHECK(items_);
DCHECK_LT(index, items_->size());
items_->at(index).icon = icon;
barrier_closure_.Run();
}
} // namespace arc
// 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_ARC_APP_SHORTCUTS_ARC_APP_SHORTCUTS_REQUEST_H_
#define CHROME_BROWSER_CHROMEOS_ARC_APP_SHORTCUTS_ARC_APP_SHORTCUTS_REQUEST_H_
#include <memory>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/chromeos/arc/app_shortcuts/arc_app_shortcut_item.h"
#include "components/arc/common/app.mojom.h"
namespace arc {
class IconDecodeRequest;
// A helper class that sends querying app shortcuts request to Android on behalf
// of Chrome.
class ArcAppShortcutsRequest {
public:
using GetAppShortcutItemsCallback =
base::OnceCallback<void(std::unique_ptr<ArcAppShortcutItems>)>;
explicit ArcAppShortcutsRequest(GetAppShortcutItemsCallback callback);
~ArcAppShortcutsRequest();
// Starts querying app shortcuts for |package_name|. Results are retruned in
// |callback_|. It shouldn't be called more than one time for the life cycle
// of |this|.
void StartForPackage(const std::string& package_name);
private:
// Returned from Android with a list of app shortcuts.
void OnGetAppShortcutItems(
std::vector<mojom::AppShortcutItemPtr> shortcut_items);
// Bound by a barrier closure to wait all icon decode requests done.
void OnAllIconDecodeRequestsDone();
// Bound by each icon decode request.
void OnSingleIconDecodeRequestDone(size_t index, const gfx::ImageSkia& icon);
GetAppShortcutItemsCallback callback_;
// Caches the app shortcut items to be sent to |callback_| when they are
// ready.
std::unique_ptr<ArcAppShortcutItems> items_;
// A barrier closure to be run when all pending icon decode requests are done.
base::RepeatingClosure barrier_closure_;
// Icon decode request for each item.
std::vector<std::unique_ptr<IconDecodeRequest>> icon_decode_requests_;
base::WeakPtrFactory<ArcAppShortcutsRequest> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ArcAppShortcutsRequest);
};
} // namespace arc
#endif // CHROME_BROWSER_CHROMEOS_ARC_APP_SHORTCUTS_ARC_APP_SHORTCUTS_REQUEST_H_
// 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/arc/app_shortcuts/arc_app_shortcuts_request.h"
#include <memory>
#include <utility>
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "chrome/browser/chromeos/arc/app_shortcuts/arc_app_shortcut_item.h"
#include "chrome/browser/chromeos/arc/icon_decode_request.h"
#include "chrome/browser/ui/app_list/arc/arc_app_test.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace arc {
class ArcAppShortcutsRequestTest : public testing::Test {
public:
ArcAppShortcutsRequestTest() = default;
~ArcAppShortcutsRequestTest() override = default;
void SetUp() override {
profile_ = std::make_unique<TestingProfile>();
arc_app_test_.SetUp(profile_.get());
IconDecodeRequest::DisableSafeDecodingForTesting();
}
void TearDown() override {
arc_app_test_.TearDown();
profile_.reset();
}
private:
std::unique_ptr<TestingProfile> profile_;
ArcAppTest arc_app_test_;
content::TestBrowserThreadBundle thread_bundle_;
DISALLOW_COPY_AND_ASSIGN(ArcAppShortcutsRequestTest);
};
TEST_F(ArcAppShortcutsRequestTest, Basic) {
base::RunLoop run_loop;
std::unique_ptr<ArcAppShortcutItems> items;
auto arc_app_shortcuts_request =
std::make_unique<ArcAppShortcutsRequest>(base::BindLambdaForTesting(
[&](std::unique_ptr<ArcAppShortcutItems> returned_items) {
items = std::move(returned_items);
run_loop.Quit();
}));
arc_app_shortcuts_request->StartForPackage("Test");
run_loop.Run();
DCHECK(items);
for (size_t i = 0; i < items->size(); ++i) {
EXPECT_EQ(base::StringPrintf("ShortLabel %zu", i),
base::UTF16ToUTF8(items->at(i).short_label));
}
}
} // namespace arc
...@@ -25,10 +25,10 @@ class AppContextMenu : public ui::SimpleMenuModel::Delegate { ...@@ -25,10 +25,10 @@ class AppContextMenu : public ui::SimpleMenuModel::Delegate {
// Defines command ids, used in context menu of all types. // Defines command ids, used in context menu of all types.
// These are used in histograms, do not remove/renumber entries. Only add at // These are used in histograms, do not remove/renumber entries. Only add at
// the end just before USE_LAUNCH_TYPE_COMMAND_END or after INSTALL and before // the end just before USE_LAUNCH_TYPE_COMMAND_END or after INSTALL and before
// USE_LAUNCH_TYPE_COMMAND_START. If you're adding to this enum with the // USE_LAUNCH_TYPE_COMMAND_START or after LAUNCH_APP_SHORTCUT_LAST. If you're
// intention that it will be logged, add checks to ensure stability of the // adding to this enum with the intention that it will be logged, add checks
// enum and update the ChromeOSUICommands enum listing in // to ensure stability of the enum and update the ChromeOSUICommands enum
// tools/metrics/histograms/enums.xml. // listing in tools/metrics/histograms/enums.xml.
enum CommandId { enum CommandId {
LAUNCH_NEW = 100, LAUNCH_NEW = 100,
TOGGLE_PIN = 101, TOGGLE_PIN = 101,
...@@ -46,6 +46,10 @@ class AppContextMenu : public ui::SimpleMenuModel::Delegate { ...@@ -46,6 +46,10 @@ class AppContextMenu : public ui::SimpleMenuModel::Delegate {
USE_LAUNCH_TYPE_FULLSCREEN = 202, USE_LAUNCH_TYPE_FULLSCREEN = 202,
USE_LAUNCH_TYPE_WINDOW = 203, USE_LAUNCH_TYPE_WINDOW = 203,
USE_LAUNCH_TYPE_COMMAND_END, USE_LAUNCH_TYPE_COMMAND_END,
// Range of command ids reserved for launching app shortcuts from context
// menu for Android app.
LAUNCH_APP_SHORTCUT_FIRST = 1000,
LAUNCH_APP_SHORTCUT_LAST = 1999,
}; };
AppContextMenu(AppContextMenuDelegate* delegate, AppContextMenu(AppContextMenuDelegate* delegate,
......
...@@ -10,8 +10,10 @@ ...@@ -10,8 +10,10 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h" #include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "chrome/browser/chromeos/arc/icon_decode_request.h"
#include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/menu_manager_factory.h" #include "chrome/browser/extensions/menu_manager_factory.h"
#include "chrome/browser/ui/app_list/app_context_menu_delegate.h" #include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
...@@ -137,6 +139,7 @@ class AppContextMenuTest : public AppListTestBase, ...@@ -137,6 +139,7 @@ class AppContextMenuTest : public AppListTestBase,
GetParam()) { GetParam()) {
scoped_feature_list_.InitAndEnableFeature( scoped_feature_list_.InitAndEnableFeature(
features::kTouchableAppContextMenu); features::kTouchableAppContextMenu);
arc::IconDecodeRequest::DisableSafeDecodingForTesting();
} }
extensions::MenuManagerFactory::GetInstance()->SetTestingFactory( extensions::MenuManagerFactory::GetInstance()->SetTestingFactory(
profile(), MenuManagerFactory); profile(), MenuManagerFactory);
...@@ -419,9 +422,11 @@ TEST_P(AppContextMenuTest, ArcMenu) { ...@@ -419,9 +422,11 @@ TEST_P(AppContextMenuTest, ArcMenu) {
std::unique_ptr<ui::MenuModel> menu = GetContextMenuModel(&item); std::unique_ptr<ui::MenuModel> menu = GetContextMenuModel(&item);
ASSERT_NE(nullptr, menu); ASSERT_NE(nullptr, menu);
// Separators are not added to touchable app context menus. // Separators are not added to touchable app context menus. For touchable app
// context menus, arc app has three more app shortcuts provided by
// arc::FakeAppInstance.
const int expected_items = const int expected_items =
features::IsTouchableAppContextMenuEnabled() ? 4 : 6; features::IsTouchableAppContextMenuEnabled() ? 7 : 6;
ASSERT_EQ(expected_items, menu->GetItemCount()); ASSERT_EQ(expected_items, menu->GetItemCount());
int index = 0; int index = 0;
...@@ -453,9 +458,11 @@ TEST_P(AppContextMenuTest, ArcMenu) { ...@@ -453,9 +458,11 @@ TEST_P(AppContextMenuTest, ArcMenu) {
// ARC app menu requires model to be recalculated. // ARC app menu requires model to be recalculated.
menu = GetContextMenuModel(&item); menu = GetContextMenuModel(&item);
// Separators are not added to touchable app context menus. // Separators are not added to touchable app context menus. For touchable app
// context menus, arc app has three more app shortcuts provided by
// arc::FakeAppInstance.
const int expected_items_app_open = const int expected_items_app_open =
features::IsTouchableAppContextMenuEnabled() ? 3 : 4; features::IsTouchableAppContextMenuEnabled() ? 6 : 4;
ASSERT_EQ(expected_items_app_open, menu->GetItemCount()); ASSERT_EQ(expected_items_app_open, menu->GetItemCount());
index = 0; index = 0;
ValidateItemState(menu.get(), index++, ValidateItemState(menu.get(), index++,
...@@ -467,6 +474,14 @@ TEST_P(AppContextMenuTest, ArcMenu) { ...@@ -467,6 +474,14 @@ TEST_P(AppContextMenuTest, ArcMenu) {
ValidateItemState(menu.get(), index++, ValidateItemState(menu.get(), index++,
MenuState(app_list::AppContextMenu::SHOW_APP_INFO)); MenuState(app_list::AppContextMenu::SHOW_APP_INFO));
// Test arc app shortcuts provided by arc::FakeAppInstance.
if (features::IsTouchableAppContextMenuEnabled()) {
for (int i = 0; i < 3; ++i) {
EXPECT_EQ(base::StringPrintf("ShortLabel %d", i),
base::UTF16ToUTF8(menu->GetLabelAt(i + index)));
}
}
// This makes all apps non-ready. // This makes all apps non-ready.
controller()->SetAppOpen(app_id, false); controller()->SetAppOpen(app_id, false);
arc::ConnectionObserver<arc::mojom::AppInstance>* connection_observer = arc::ConnectionObserver<arc::mojom::AppInstance>* connection_observer =
...@@ -476,9 +491,10 @@ TEST_P(AppContextMenuTest, ArcMenu) { ...@@ -476,9 +491,10 @@ TEST_P(AppContextMenuTest, ArcMenu) {
menu = GetContextMenuModel(&item); menu = GetContextMenuModel(&item);
// Separators and disabled options are not added to touchable app context // Separators and disabled options are not added to touchable app context
// menus. // menus. For touchable app context menus, arc app has three more app
// shortcuts provided by arc::FakeAppInstance.
const int expected_items_reopen = const int expected_items_reopen =
features::IsTouchableAppContextMenuEnabled() ? 2 : 6; features::IsTouchableAppContextMenuEnabled() ? 5 : 6;
ASSERT_EQ(expected_items_reopen, menu->GetItemCount()); ASSERT_EQ(expected_items_reopen, menu->GetItemCount());
index = 0; index = 0;
ValidateItemState(menu.get(), index++, ValidateItemState(menu.get(), index++,
...@@ -496,6 +512,13 @@ TEST_P(AppContextMenuTest, ArcMenu) { ...@@ -496,6 +512,13 @@ TEST_P(AppContextMenuTest, ArcMenu) {
menu.get(), index++, menu.get(), index++,
MenuState(app_list::AppContextMenu::SHOW_APP_INFO, false, false)); MenuState(app_list::AppContextMenu::SHOW_APP_INFO, false, false));
} }
// Test arc app shortcuts provided by arc::FakeAppInstance.
if (features::IsTouchableAppContextMenuEnabled()) {
for (int i = 0; i < 3; ++i) {
EXPECT_EQ(base::StringPrintf("ShortLabel %d", i),
base::UTF16ToUTF8(menu->GetLabelAt(i + index)));
}
}
// Uninstall all apps. // Uninstall all apps.
arc_test.app_instance()->RefreshAppList(); arc_test.app_instance()->RefreshAppList();
...@@ -522,9 +545,11 @@ TEST_P(AppContextMenuTest, ArcMenuShortcut) { ...@@ -522,9 +545,11 @@ TEST_P(AppContextMenuTest, ArcMenuShortcut) {
std::unique_ptr<ui::MenuModel> menu = GetContextMenuModel(&item); std::unique_ptr<ui::MenuModel> menu = GetContextMenuModel(&item);
ASSERT_NE(nullptr, menu); ASSERT_NE(nullptr, menu);
// Separators are not added to touchable app context menus. // Separators are not added to touchable app context menus. For touchable app
// context menus, arc app has three more app shortcuts provided by
// arc::FakeAppInstance.
const int expected_items = const int expected_items =
features::IsTouchableAppContextMenuEnabled() ? 4 : 6; features::IsTouchableAppContextMenuEnabled() ? 7 : 6;
int index = 0; int index = 0;
ASSERT_EQ(expected_items, menu->GetItemCount()); ASSERT_EQ(expected_items, menu->GetItemCount());
ValidateItemState(menu.get(), index++, ValidateItemState(menu.get(), index++,
...@@ -539,6 +564,13 @@ TEST_P(AppContextMenuTest, ArcMenuShortcut) { ...@@ -539,6 +564,13 @@ TEST_P(AppContextMenuTest, ArcMenuShortcut) {
MenuState(app_list::AppContextMenu::UNINSTALL)); MenuState(app_list::AppContextMenu::UNINSTALL));
ValidateItemState(menu.get(), index++, ValidateItemState(menu.get(), index++,
MenuState(app_list::AppContextMenu::SHOW_APP_INFO)); MenuState(app_list::AppContextMenu::SHOW_APP_INFO));
// Test arc app shortcuts provided by arc::FakeAppInstance.
if (features::IsTouchableAppContextMenuEnabled()) {
for (int i = 0; i < 3; ++i) {
EXPECT_EQ(base::StringPrintf("ShortLabel %d", i),
base::UTF16ToUTF8(menu->GetLabelAt(i + index)));
}
}
// This makes all apps non-ready. Shortcut is still uninstall-able. // This makes all apps non-ready. Shortcut is still uninstall-able.
arc::ConnectionObserver<arc::mojom::AppInstance>* connection_observer = arc::ConnectionObserver<arc::mojom::AppInstance>* connection_observer =
...@@ -547,9 +579,9 @@ TEST_P(AppContextMenuTest, ArcMenuShortcut) { ...@@ -547,9 +579,9 @@ TEST_P(AppContextMenuTest, ArcMenuShortcut) {
menu = GetContextMenuModel(&item); menu = GetContextMenuModel(&item);
// Separators and disabled options are not added to touchable app context // Separators and disabled options are not added to touchable app context
// menus. // menus. For touchable app context menus, arc app has three more app
const int expected_items_non_ready = // shortcuts provided by arc::FakeAppInstance.
features::IsTouchableAppContextMenuEnabled() ? 3 : 6; const int expected_items_non_ready = 6;
ASSERT_EQ(expected_items_non_ready, menu->GetItemCount()); ASSERT_EQ(expected_items_non_ready, menu->GetItemCount());
index = 0; index = 0;
ValidateItemState(menu.get(), index++, ValidateItemState(menu.get(), index++,
...@@ -566,6 +598,14 @@ TEST_P(AppContextMenuTest, ArcMenuShortcut) { ...@@ -566,6 +598,14 @@ TEST_P(AppContextMenuTest, ArcMenuShortcut) {
ValidateItemState( ValidateItemState(
menu.get(), index++, menu.get(), index++,
MenuState(app_list::AppContextMenu::SHOW_APP_INFO, false, false)); MenuState(app_list::AppContextMenu::SHOW_APP_INFO, false, false));
// Test arc app shortcuts provided by arc::FakeAppInstance.
if (features::IsTouchableAppContextMenuEnabled()) {
for (int i = 0; i < 3; ++i) {
EXPECT_EQ(base::StringPrintf("ShortLabel %d", i),
base::UTF16ToUTF8(menu->GetLabelAt(i + index)));
}
}
} }
TEST_P(AppContextMenuTest, ArcMenuStickyItem) { TEST_P(AppContextMenuTest, ArcMenuStickyItem) {
...@@ -585,8 +625,10 @@ TEST_P(AppContextMenuTest, ArcMenuStickyItem) { ...@@ -585,8 +625,10 @@ TEST_P(AppContextMenuTest, ArcMenuStickyItem) {
std::unique_ptr<ui::MenuModel> menu = GetContextMenuModel(&item); std::unique_ptr<ui::MenuModel> menu = GetContextMenuModel(&item);
ASSERT_NE(nullptr, menu); ASSERT_NE(nullptr, menu);
// Separators are not added to touchable app context menus. // Separators are not added to touchable app context menus. For touchable
int expected_items = features::IsTouchableAppContextMenuEnabled() ? 3 : 5; // app context menus, arc app has three more app shortcuts provided by
// arc::FakeAppInstance.
int expected_items = features::IsTouchableAppContextMenuEnabled() ? 6 : 5;
ASSERT_EQ(expected_items, menu->GetItemCount()); ASSERT_EQ(expected_items, menu->GetItemCount());
int index = 0; int index = 0;
ValidateItemState(menu.get(), index++, ValidateItemState(menu.get(), index++,
...@@ -599,6 +641,14 @@ TEST_P(AppContextMenuTest, ArcMenuStickyItem) { ...@@ -599,6 +641,14 @@ TEST_P(AppContextMenuTest, ArcMenuStickyItem) {
ValidateItemState(menu.get(), index++, MenuState()); // separator ValidateItemState(menu.get(), index++, MenuState()); // separator
ValidateItemState(menu.get(), index++, ValidateItemState(menu.get(), index++,
MenuState(app_list::AppContextMenu::SHOW_APP_INFO)); MenuState(app_list::AppContextMenu::SHOW_APP_INFO));
// Test arc app shortcuts provided by arc::FakeAppInstance.
if (features::IsTouchableAppContextMenuEnabled()) {
for (int i = 0; i < 3; ++i) {
EXPECT_EQ(base::StringPrintf("ShortLabel %d", i),
base::UTF16ToUTF8(menu->GetLabelAt(i + index)));
}
}
} }
} }
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "chrome/browser/ui/app_list/arc/arc_app_context_menu.h" #include "chrome/browser/ui/app_list/arc/arc_app_context_menu.h"
#include <utility>
#include "base/bind.h" #include "base/bind.h"
#include "chrome/browser/profiles/profile.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_context_menu_delegate.h"
...@@ -15,16 +17,25 @@ ...@@ -15,16 +17,25 @@
#include "chrome/grit/generated_resources.h" #include "chrome/grit/generated_resources.h"
#include "ui/base/ui_base_features.h" #include "ui/base/ui_base_features.h"
ArcAppContextMenu::ArcAppContextMenu( ArcAppContextMenu::ArcAppContextMenu(app_list::AppContextMenuDelegate* delegate,
app_list::AppContextMenuDelegate* delegate, Profile* profile,
Profile* profile, const std::string& app_id,
const std::string& app_id, AppListControllerDelegate* controller)
AppListControllerDelegate* controller) : app_list::AppContextMenu(delegate, profile, app_id, controller) {}
: app_list::AppContextMenu(delegate, profile, app_id, controller) {
}
ArcAppContextMenu::~ArcAppContextMenu() = default; ArcAppContextMenu::~ArcAppContextMenu() = default;
void ArcAppContextMenu::GetMenuModel(GetMenuModelCallback callback) {
auto menu_model = std::make_unique<ui::SimpleMenuModel>(this);
menu_model->set_histogram_name("Apps.ContextMenuExecuteCommand.FromApp");
BuildMenu(menu_model.get());
if (!features::IsTouchableAppContextMenuEnabled()) {
std::move(callback).Run(std::move(menu_model));
return;
}
BuildAppShortcutsMenu(std::move(menu_model), std::move(callback));
}
void ArcAppContextMenu::BuildMenu(ui::SimpleMenuModel* menu_model) { void ArcAppContextMenu::BuildMenu(ui::SimpleMenuModel* menu_model) {
const ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile()); const ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile());
DCHECK(arc_prefs); DCHECK(arc_prefs);
...@@ -92,6 +103,46 @@ void ArcAppContextMenu::ExecuteCommand(int command_id, int event_flags) { ...@@ -92,6 +103,46 @@ void ArcAppContextMenu::ExecuteCommand(int command_id, int event_flags) {
} }
} }
void ArcAppContextMenu::BuildAppShortcutsMenu(
std::unique_ptr<ui::SimpleMenuModel> menu_model,
GetMenuModelCallback callback) {
const ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile());
DCHECK(arc_prefs);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
arc_prefs->GetApp(app_id());
if (!app_info) {
LOG(ERROR) << "App " << app_id() << " is not available.";
std::move(callback).Run(std::move(menu_model));
arc_app_shortcuts_request_.reset();
return;
}
DCHECK(!arc_app_shortcuts_request_);
// Using base::Unretained(this) here is safe becuase |this| owns
// |arc_app_shortcuts_request_|. When |this| is deleted,
// |arc_app_shortcuts_request_| is also deleted, and once that happens,
// |arc_app_shortcuts_request_| will never run the callback.
arc_app_shortcuts_request_ =
std::make_unique<arc::ArcAppShortcutsRequest>(base::BindOnce(
&ArcAppContextMenu::OnGetAppShortcutItems, base::Unretained(this),
std::move(menu_model), std::move(callback)));
arc_app_shortcuts_request_->StartForPackage(app_info->package_name);
}
void ArcAppContextMenu::OnGetAppShortcutItems(
std::unique_ptr<ui::SimpleMenuModel> menu_model,
GetMenuModelCallback callback,
std::unique_ptr<arc::ArcAppShortcutItems> shortcut_items) {
if (shortcut_items) {
int command_id = LAUNCH_APP_SHORTCUT_FIRST;
DCHECK_LT(command_id + shortcut_items->size(), LAUNCH_APP_SHORTCUT_LAST);
for (const auto& item : *shortcut_items)
menu_model->AddItemWithIcon(command_id++, item.short_label, item.icon);
}
std::move(callback).Run(std::move(menu_model));
arc_app_shortcuts_request_.reset();
}
void ArcAppContextMenu::ShowPackageInfo() { void ArcAppContextMenu::ShowPackageInfo() {
const ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile()); const ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile());
DCHECK(arc_prefs); DCHECK(arc_prefs);
......
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
#ifndef CHROME_BROWSER_UI_APP_LIST_ARC_ARC_APP_CONTEXT_MENU_H_ #ifndef CHROME_BROWSER_UI_APP_LIST_ARC_ARC_APP_CONTEXT_MENU_H_
#define CHROME_BROWSER_UI_APP_LIST_ARC_ARC_APP_CONTEXT_MENU_H_ #define CHROME_BROWSER_UI_APP_LIST_ARC_ARC_APP_CONTEXT_MENU_H_
#include <memory>
#include <string> #include <string>
#include "base/macros.h" #include "base/macros.h"
#include "chrome/browser/chromeos/arc/app_shortcuts/arc_app_shortcuts_request.h"
#include "chrome/browser/ui/app_list/app_context_menu.h" #include "chrome/browser/ui/app_list/app_context_menu.h"
class AppListControllerDelegate; class AppListControllerDelegate;
...@@ -25,7 +27,8 @@ class ArcAppContextMenu : public app_list::AppContextMenu { ...@@ -25,7 +27,8 @@ class ArcAppContextMenu : public app_list::AppContextMenu {
AppListControllerDelegate* controller); AppListControllerDelegate* controller);
~ArcAppContextMenu() override; ~ArcAppContextMenu() override;
// AppListContextMenu overrides: // AppContextMenu overrides:
void GetMenuModel(GetMenuModelCallback callback) override;
void BuildMenu(ui::SimpleMenuModel* menu_model) override; void BuildMenu(ui::SimpleMenuModel* menu_model) override;
// ui::SimpleMenuModel::Delegate overrides: // ui::SimpleMenuModel::Delegate overrides:
...@@ -33,9 +36,22 @@ class ArcAppContextMenu : public app_list::AppContextMenu { ...@@ -33,9 +36,22 @@ class ArcAppContextMenu : public app_list::AppContextMenu {
bool IsCommandIdEnabled(int command_id) const override; bool IsCommandIdEnabled(int command_id) const override;
private: private:
void IsAppOpen(); // Build additional app shortcuts menu items.
// TODO(warx): consider merging into BuildMenu.
void BuildAppShortcutsMenu(std::unique_ptr<ui::SimpleMenuModel> menu_model,
GetMenuModelCallback callback);
// Bound by |arc_app_shortcuts_manager_|'s OnGetAppShortcutItems method.
void OnGetAppShortcutItems(
std::unique_ptr<ui::SimpleMenuModel> menu_model,
GetMenuModelCallback callback,
std::unique_ptr<arc::ArcAppShortcutItems> shortcut_items);
void ShowPackageInfo(); void ShowPackageInfo();
// Handles requesting app shortcuts from Android.
std::unique_ptr<arc::ArcAppShortcutsRequest> arc_app_shortcuts_request_;
DISALLOW_COPY_AND_ASSIGN(ArcAppContextMenu); DISALLOW_COPY_AND_ASSIGN(ArcAppContextMenu);
}; };
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// //
// Next MinVersion: 29 // Next MinVersion: 30
module arc.mojom; module arc.mojom;
...@@ -150,7 +150,8 @@ struct AppDataResult { ...@@ -150,7 +150,8 @@ struct AppDataResult {
string label@1; string label@1;
// Text information for the result. // Text information for the result.
string text@3; string text@3;
// Icon information in png format. It could be null if not provided. // Icon information in png format. It could be null if not provided. Decoded
// in the utility process.
array<uint8>? icon_png_data@2; array<uint8>? icon_png_data@2;
// Type of this app data search result. // Type of this app data search result.
[MinVersion=28] AppDataResultType type@4; [MinVersion=28] AppDataResultType type@4;
...@@ -180,6 +181,19 @@ enum AppDataRequestState { ...@@ -180,6 +181,19 @@ enum AppDataRequestState {
FAILED_TO_CALL_GLOBALQUERY = 8, FAILED_TO_CALL_GLOBALQUERY = 8,
}; };
// Describes app shortcut that is published by Android's ShortcutManager.
struct AppShortcutItem {
// The ID of this shortcut. Unique within each publisher app and stable across
// devices.
string shortcut_id;
// The short description of this shortcut.
string short_label;
// The icon for this shortcut, decoded in the utility process.
array<uint8> icon_png;
};
// Next method ID: 18 // Next method ID: 18
interface AppHost { interface AppHost {
// Sends newly added ARC app to Chrome. This message is sent when ARC receives // Sends newly added ARC app to Chrome. This message is sent when ARC receives
...@@ -267,7 +281,7 @@ interface AppHost { ...@@ -267,7 +281,7 @@ interface AppHost {
}; };
// TODO(lhchavez): Migrate all request/response messages to Mojo. // TODO(lhchavez): Migrate all request/response messages to Mojo.
// Next method ID: 23 // Next method ID: 24
interface AppInstance { interface AppInstance {
// DEPRECATED: Please use Init@21 instead. // DEPRECATED: Please use Init@21 instead.
InitDeprecated@0(AppHost host_ptr); InitDeprecated@0(AppHost host_ptr);
...@@ -351,13 +365,18 @@ interface AppInstance { ...@@ -351,13 +365,18 @@ interface AppInstance {
// happens) is ignored, and uninstall option should appear in the UI. // happens) is ignored, and uninstall option should appear in the UI.
[MinVersion=2] UninstallPackage@5(string package_name); [MinVersion=2] UninstallPackage@5(string package_name);
// Starts a query for Play Store apps. // Sends a request to ARC to get app shortcuts for app with |package_name|.
[MinVersion=20] GetRecentAndSuggestedAppsFromPlayStore@16( [MinVersion=29] GetAppShortcutItems@23(string package_name) =>
string query, int32 max_results) => (array<AppShortcutItem> shortcut_items);
(AppDiscoveryRequestState state@1, array<AppDiscoveryResult> results@0);
// Starts a query for app data, which is querying icing indexable contents. // Starts a query for app data, which is querying icing indexable contents.
[MinVersion=27] GetIcingGlobalQueryResults@22( [MinVersion=27] GetIcingGlobalQueryResults@22(
string query, int32 max_results) => string query, int32 max_results) =>
(AppDataRequestState state, array<AppDataResult> results); (AppDataRequestState state, array<AppDataResult> results);
// Starts a query for Play Store apps.
[MinVersion=20] GetRecentAndSuggestedAppsFromPlayStore@16(
string query, int32 max_results) =>
(AppDiscoveryRequestState state@1,
array<AppDiscoveryResult> results@0);
}; };
...@@ -382,6 +382,27 @@ void FakeAppInstance::GetIcingGlobalQueryResults( ...@@ -382,6 +382,27 @@ void FakeAppInstance::GetIcingGlobalQueryResults(
std::move(fake_app_data_results)); std::move(fake_app_data_results));
} }
void FakeAppInstance::GetAppShortcutItems(
const std::string& pacakge_name,
GetAppShortcutItemsCallback callback) {
// Fake app shortcut items results.
std::vector<mojom::AppShortcutItemPtr> fake_app_shortcut_items;
// Fake icon data.
std::string png_data_as_string;
GetFakeIcon(mojom::ScaleFactor::SCALE_FACTOR_100P, &png_data_as_string);
std::vector<uint8_t> fake_icon_png_data(png_data_as_string.begin(),
png_data_as_string.end());
for (int i = 0; i < 3; ++i) {
fake_app_shortcut_items.push_back(mojom::AppShortcutItem::New(
base::StringPrintf("ShortcutId %d", i),
base::StringPrintf("ShortLabel %d", i), fake_icon_png_data));
}
std::move(callback).Run(std::move(fake_app_shortcut_items));
}
void FakeAppInstance::StartPaiFlow() { void FakeAppInstance::StartPaiFlow() {
++start_pai_request_count_; ++start_pai_request_count_;
} }
......
...@@ -128,6 +128,8 @@ class FakeAppInstance : public mojom::AppInstance { ...@@ -128,6 +128,8 @@ class FakeAppInstance : public mojom::AppInstance {
const std::string& query, const std::string& query,
int32_t max_results, int32_t max_results,
GetIcingGlobalQueryResultsCallback callback) override; GetIcingGlobalQueryResultsCallback callback) override;
void GetAppShortcutItems(const std::string& pacakge_name,
GetAppShortcutItemsCallback callback) override;
void StartPaiFlow() override; void StartPaiFlow() override;
// Methods to reply messages. // Methods to reply messages.
......
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