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