1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/arc/intent_helper/start_smart_selection_action_menu.h"
6
7 #include <algorithm>
8 #include <string>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/metrics/user_metrics.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/app/chrome_command_ids.h"
17 #include "chrome/browser/apps/app_service/app_icon_factory.h"
18 #include "chrome/common/chrome_features.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "components/arc/arc_service_manager.h"
21 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
22 #include "components/arc/metrics/arc_metrics_constants.h"
23 #include "components/arc/mojom/intent_helper.mojom.h"
24 #include "components/arc/session/arc_bridge_service.h"
25 #include "components/renderer_context_menu/render_view_context_menu_proxy.h"
26 #include "content/public/browser/context_menu_params.h"
27 #include "ui/base/models/image_model.h"
28 #include "ui/gfx/image/image_skia_operations.h"
29
30 namespace arc {
31
32 // The maximum number of smart actions to show.
33 constexpr size_t kMaxMainMenuCommands = 5;
34
35 constexpr size_t kSmallIconSizeInDip = 16;
36 constexpr size_t kMaxIconSizeInPx = 200;
37
StartSmartSelectionActionMenu(RenderViewContextMenuProxy * proxy)38 StartSmartSelectionActionMenu::StartSmartSelectionActionMenu(
39 RenderViewContextMenuProxy* proxy)
40 : proxy_(proxy) {}
41
42 StartSmartSelectionActionMenu::~StartSmartSelectionActionMenu() = default;
43
InitMenu(const content::ContextMenuParams & params)44 void StartSmartSelectionActionMenu::InitMenu(
45 const content::ContextMenuParams& params) {
46 const std::string converted_text = base::UTF16ToUTF8(params.selection_text);
47 if (converted_text.empty())
48 return;
49
50 auto* arc_service_manager = ArcServiceManager::Get();
51 if (!arc_service_manager)
52 return;
53 auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
54 arc_service_manager->arc_bridge_service()->intent_helper(),
55 RequestTextSelectionActions);
56 if (!instance)
57 return;
58
59 base::RecordAction(base::UserMetricsAction("Arc.SmartTextSelection.Request"));
60 instance->RequestTextSelectionActions(
61 converted_text, mojom::ScaleFactor(ui::GetSupportedScaleFactors().back()),
62 base::BindOnce(&StartSmartSelectionActionMenu::HandleTextSelectionActions,
63 weak_ptr_factory_.GetWeakPtr()));
64
65 // Add placeholder items.
66 for (size_t i = 0; i < kMaxMainMenuCommands; ++i) {
67 proxy_->AddMenuItem(IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1 + i,
68 /*title=*/base::string16());
69 }
70 }
71
IsCommandIdSupported(int command_id)72 bool StartSmartSelectionActionMenu::IsCommandIdSupported(int command_id) {
73 return command_id >= IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1 &&
74 command_id <= IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION_LAST;
75 }
76
IsCommandIdChecked(int command_id)77 bool StartSmartSelectionActionMenu::IsCommandIdChecked(int command_id) {
78 return false;
79 }
80
IsCommandIdEnabled(int command_id)81 bool StartSmartSelectionActionMenu::IsCommandIdEnabled(int command_id) {
82 return true;
83 }
84
ExecuteCommand(int command_id)85 void StartSmartSelectionActionMenu::ExecuteCommand(int command_id) {
86 if (!IsCommandIdSupported(command_id))
87 return;
88
89 size_t index = command_id - IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1;
90 if (actions_.size() <= index)
91 return;
92
93 auto* arc_service_manager = ArcServiceManager::Get();
94 if (!arc_service_manager)
95 return;
96 auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
97 arc_service_manager->arc_bridge_service()->intent_helper(), HandleIntent);
98 if (!instance)
99 return;
100
101 instance->HandleIntent(std::move(actions_[index]->action_intent),
102 std::move(actions_[index]->activity));
103
104 UMA_HISTOGRAM_ENUMERATION(
105 "Arc.UserInteraction",
106 arc::UserInteractionType::
107 APP_STARTED_FROM_SMART_TEXT_SELECTION_CONTEXT_MENU);
108 }
109
HandleTextSelectionActions(std::vector<mojom::TextSelectionActionPtr> actions)110 void StartSmartSelectionActionMenu::HandleTextSelectionActions(
111 std::vector<mojom::TextSelectionActionPtr> actions) {
112 actions_ = std::move(actions);
113
114 for (size_t i = 0; i < actions_.size(); ++i) {
115 proxy_->UpdateMenuItem(
116 IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1 + i,
117 /*enabled=*/true,
118 /*hidden=*/false,
119 /*title=*/base::UTF8ToUTF16(actions_[i]->title));
120
121 if (actions_[i]->icon) {
122 UpdateMenuIcon(IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1 + i,
123 std::move(actions_[i]->icon));
124 }
125 }
126
127 for (size_t i = actions_.size(); i < kMaxMainMenuCommands; ++i) {
128 // There were fewer actions returned than placeholder slots, remove the
129 // empty menu item.
130 proxy_->RemoveMenuItem(IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1 +
131 i);
132 }
133
134 // The asynchronous nature of adding smart actions means that sometimes,
135 // depending on whether actions were found and if extensions menu items were
136 // added synchronously, there could be extra (adjacent) separators in the
137 // context menu that must be removed once we've finished loading everything.
138 proxy_->RemoveAdjacentSeparators();
139 }
140
UpdateMenuIcon(int command_id,mojom::ActivityIconPtr icon)141 void StartSmartSelectionActionMenu::UpdateMenuIcon(
142 int command_id,
143 mojom::ActivityIconPtr icon) {
144 constexpr size_t kBytesPerPixel = 4; // BGRA
145 if (icon->width > kMaxIconSizeInPx || icon->height > kMaxIconSizeInPx ||
146 icon->width == 0 || icon->height == 0 ||
147 icon->icon.size() != (icon->width * icon->height * kBytesPerPixel)) {
148 return;
149 }
150
151 if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) {
152 DCHECK(icon->icon_png_data);
153 apps::ArcRawIconPngDataToImageSkia(
154 std::move(icon->icon_png_data), kSmallIconSizeInDip,
155 base::BindOnce(&StartSmartSelectionActionMenu::SetMenuIcon,
156 weak_ptr_factory_.GetWeakPtr(), command_id));
157 return;
158 }
159
160 SkBitmap bitmap;
161 bitmap.allocPixels(SkImageInfo::MakeN32Premul(icon->width, icon->height));
162 if (!bitmap.getPixels())
163 return;
164
165 DCHECK_GE(bitmap.computeByteSize(), icon->icon.size());
166 memcpy(bitmap.getPixels(), &icon->icon.front(), icon->icon.size());
167
168 gfx::ImageSkia original(gfx::ImageSkia::CreateFrom1xBitmap(bitmap));
169
170 gfx::ImageSkia icon_small(gfx::ImageSkiaOperations::CreateResizedImage(
171 original, skia::ImageOperations::RESIZE_BEST,
172 gfx::Size(kSmallIconSizeInDip, kSmallIconSizeInDip)));
173
174 SetMenuIcon(command_id, icon_small);
175 }
176
SetMenuIcon(int command_id,const gfx::ImageSkia & image)177 void StartSmartSelectionActionMenu::SetMenuIcon(int command_id,
178 const gfx::ImageSkia& image) {
179 proxy_->UpdateMenuIcon(command_id, ui::ImageModel::FromImageSkia(image));
180 }
181
182 } // namespace arc
183