Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Side by Side Diff: components/arc/intent_helper/open_with_menu_observer.cc

Issue 1760773004: Add "Open with <ARC-app-name>" items to the context menu (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: add a missing override; Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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 "components/arc/intent_helper/open_with_menu_observer.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "components/arc/arc_bridge_service.h"
14 #include "components/renderer_context_menu/render_view_context_menu_proxy.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/common/context_menu_params.h"
17 #include "grit/components_strings.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/models/simple_menu_model.h"
20
21 namespace arc {
22
23 namespace {
24
25 const int kInvalidCommandId = -1;
26 const int kMinInstanceVersion = 2; // see intent_helper.mojom
27
28 // Converts |handlers| into HandlerMap which is a map from a command ID to a
29 // UrlHandlerInfoPtr and returns the map. Also returns a command id for the
30 // parent of the sub menu. When the sub menu is not needed, the function
31 // returns |kInvalidCommandId|.
32 std::pair<OpenWithMenuObserver::HandlerMap, int> BuildHandlersMap(
33 int menu_id_start,
34 size_t num_menu_items,
35 int sub_menu_id_start,
36 size_t num_sub_menu_items,
37 mojo::Array<UrlHandlerInfoPtr> handlers) {
38 // We assume |num_menu_items| is always >=3.
39 DCHECK_GE(num_menu_items, 3U);
40
41 OpenWithMenuObserver::HandlerMap handler_map;
42 int sub_menu_parent_command_id = kInvalidCommandId;
43
44 const size_t num_apps = handlers.size();
45 size_t handlers_index = 0;
46 // We use the last item in the main menu (menu_id_start + num_menu_items - 1)
47 // as a parent of a sub menu, and others as regular menu items.
48 if (num_apps < num_menu_items) {
49 // All apps can be shown with the regular main menu items.
50 for (size_t i = 0; i < num_apps; ++i) {
51 handler_map[menu_id_start + i] = std::move(handlers[handlers_index++]);
52 }
53 } else {
54 // Otherwise, use the sub menu too. In this case, disable the last item of
55 // the regular main menu (hence '-2').
56 for (size_t i = 0; i < num_menu_items - 2; ++i) {
57 handler_map[menu_id_start + i] = std::move(handlers[handlers_index++]);
58 }
59 sub_menu_parent_command_id = menu_id_start + num_menu_items - 1;
60 const size_t sub_items =
61 std::min(num_apps - (num_menu_items - 2), num_sub_menu_items);
62 for (size_t i = 0; i < sub_items; ++i) {
63 handler_map[sub_menu_id_start + i] =
64 std::move(handlers[handlers_index++]);
65 }
66 }
67
68 return std::make_pair(std::move(handler_map), sub_menu_parent_command_id);
69 }
70
71 class SubMenuDelegate : public ui::SimpleMenuModel::Delegate {
72 public:
73 explicit SubMenuDelegate(OpenWithMenuObserver* parent_menu)
74 : parent_menu_(parent_menu) {}
75 ~SubMenuDelegate() override {}
76
77 // ui::SimpleMenuModel::Delegate overrides:
78 bool IsCommandIdChecked(int command_id) const override { return false; }
79 bool IsCommandIdEnabled(int command_id) const override { return true; }
80 bool GetAcceleratorForCommandId(int command_id,
81 ui::Accelerator* accelerator) override {
82 return false;
83 }
84 void ExecuteCommand(int command_id, int event_flags) override {
85 parent_menu_->ExecuteCommand(command_id);
86 }
87
88 private:
89 OpenWithMenuObserver* const parent_menu_;
90
91 DISALLOW_COPY_AND_ASSIGN(SubMenuDelegate);
92 };
93
94 } // namespace
95
96 OpenWithMenuObserver::OpenWithMenuObserver(ArcBridgeService* bridge_service,
97 RenderViewContextMenuProxy* proxy,
98 int main_menu_id_start,
99 size_t num_main_menu_items,
100 int sub_menu_id_start,
101 size_t num_sub_menu_items)
102 : bridge_service_(bridge_service),
103 proxy_(proxy),
104 main_menu_id_start_(main_menu_id_start),
105 num_main_menu_items_(num_main_menu_items),
106 sub_menu_id_start_(sub_menu_id_start),
107 num_sub_menu_items_(num_sub_menu_items),
108 sub_menu_(new SubMenuDelegate(this)),
109 loading_frame_(0),
110 weak_ptr_factory_(this) {
111 DCHECK(bridge_service_);
112 }
113
114 OpenWithMenuObserver::~OpenWithMenuObserver() {}
115
116 void OpenWithMenuObserver::InitMenu(const content::ContextMenuParams& params) {
117 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
118
119 link_url_ = params.link_url;
120 if (link_url_.is_empty() || !link_url_.is_valid())
121 return;
122
123 IntentHelperInstance* intent_helper_instance = GetIntentHelper();
124 if (!intent_helper_instance || num_main_menu_items_ < 3)
125 return;
126
127 loading_message_ =
128 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_LOADING_APP_NAMES);
129
130 // Add placeholder items.
131 for (size_t i = 0; i < num_main_menu_items_; ++i) {
132 const int id = main_menu_id_start_ + i;
133 if (i == (num_main_menu_items_ - 1))
134 proxy_->AddSubMenu(id, loading_message_, &sub_menu_);
135 else
136 proxy_->AddMenuItem(id, loading_message_);
137 all_command_ids_.insert(id);
138 }
139
140 // Add placeholder sub items.
141 for (size_t i = 0; i < num_sub_menu_items_; ++i) {
142 const int id = sub_menu_id_start_ + i;
143 sub_menu_.AddItem(id, loading_message_);
144 all_command_ids_.insert(id);
145 }
146
147 // Check if ARC apps can handle the |link_url_|. Since the information is
148 // held in a different (ARC) process, issue a mojo IPC request. Usually, the
149 // callback function, OnUrlHandlerList, is called within a few milliseconds
150 // even on a slowest Chromebook we support.
151 intent_helper_instance->RequestUrlHandlerList(
152 link_url_.spec(), base::Bind(&OpenWithMenuObserver::OnUrlHandlerList,
153 weak_ptr_factory_.GetWeakPtr()));
154
155 // Start the animation just in case. Since the IPC above almost always returns
156 // within a few milliseconds, the user will unlikely see the anination.
157 animation_timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(1), this,
158 &OpenWithMenuObserver::OnAnimationTimerExpired);
159 }
160
161 bool OpenWithMenuObserver::IsCommandIdSupported(int command_id) {
162 return all_command_ids_.count(command_id);
163 }
164
165 bool OpenWithMenuObserver::IsCommandIdChecked(int command_id) {
166 return false;
167 }
168
169 bool OpenWithMenuObserver::IsCommandIdEnabled(int command_id) {
170 return false;
171 }
172
173 void OpenWithMenuObserver::OnMenuCancel() {}
174
175 IntentHelperInstance* OpenWithMenuObserver::GetIntentHelper() {
176 if (!bridge_service_) {
177 DLOG(WARNING) << "ARC bridge is not ready.";
178 return nullptr;
179 }
180 IntentHelperInstance* intent_helper_instance =
181 bridge_service_->intent_helper_instance();
182 if (!intent_helper_instance) {
183 DLOG(WARNING) << "ARC intent helper instance is not ready.";
184 return nullptr;
185 }
186 if (bridge_service_->intent_helper_version() < kMinInstanceVersion) {
187 DLOG(WARNING) << "ARC intent helper instance is too old.";
188 return nullptr;
189 }
190 return intent_helper_instance;
191 }
192
193 void OpenWithMenuObserver::ExecuteCommand(int command_id) {
194 IntentHelperInstance* intent_helper_instance = GetIntentHelper();
195 if (!intent_helper_instance)
196 return;
197
198 const auto iter = handlers_.find(command_id);
199 if (iter != handlers_.end()) {
200 intent_helper_instance->HandleUrl(link_url_.spec(),
201 iter->second->package_name);
202 }
203 }
204
205 std::pair<OpenWithMenuObserver::HandlerMap, int>
206 OpenWithMenuObserver::BuildHandlersMapForTesting(
207 int menu_id_start,
208 size_t num_menu_items,
209 int sub_menu_id_start,
210 size_t num_sub_menu_items,
211 mojo::Array<UrlHandlerInfoPtr> handlers) {
212 return BuildHandlersMap(menu_id_start, num_menu_items, sub_menu_id_start,
213 num_sub_menu_items, std::move(handlers));
214 }
215
216 base::string16 OpenWithMenuObserver::GetLabelForCommandId(
217 int command_id) const {
218 if (static_cast<size_t>(command_id) ==
219 (main_menu_id_start_ + num_main_menu_items_ - 1)) {
220 return l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_MORE_APPS);
221 }
222
223 const auto iter = handlers_.find(command_id);
224 if (iter != handlers_.end()) {
225 return l10n_util::GetStringFUTF16(
226 IDS_CONTENT_CONTEXT_OPEN_WITH_APP,
227 base::UTF8ToUTF16(iter->second->name.get()));
228 }
229 return base::EmptyString16();
230 }
231
232 void OpenWithMenuObserver::OnUrlHandlerList(
233 mojo::Array<UrlHandlerInfoPtr> handlers) {
234 animation_timer_.Stop();
235
236 auto result = BuildHandlersMap(main_menu_id_start_, num_main_menu_items_,
237 sub_menu_id_start_, num_sub_menu_items_,
238 std::move(handlers));
239 handlers_ = std::move(result.first);
240 int sub_menu_parent = result.second;
241
242 for (const auto& command_id : all_command_ids_) {
243 if (command_id == sub_menu_parent || handlers_.count(command_id)) {
244 // TODO(yusukes): Show icon of the app.
245 proxy_->UpdateMenuItem(command_id,
246 true, // enabled
247 false, // hidden
248 GetLabelForCommandId(command_id));
249 } else {
250 proxy_->UpdateMenuItem(command_id,
251 false, // enabled
252 true, // hidden
253 base::EmptyString16());
254 }
255 }
256 }
257
258 void OpenWithMenuObserver::OnAnimationTimerExpired() {
259 // Append '.' characters to the end of "Checking".
260 loading_frame_ = (loading_frame_ + 1) & 3;
261 base::string16 loading_message =
262 loading_message_ + base::string16(loading_frame_, '.');
263
264 // Update the menu item with the text. We disable this item to prevent users
265 // from selecting it.
266 for (const auto& command_id : all_command_ids_) {
267 proxy_->UpdateMenuItem(command_id,
268 false, // enabled
269 false, // hidden
270 loading_message);
271 }
272 }
273
274 } // namespace arc
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698