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

Side by Side Diff: chrome/browser/chromeos/arc/open_with_menu_controller_delegate.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: fix unit_tests Created 4 years, 9 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 "chrome/browser/chromeos/arc/open_with_menu_controller_delegate.h"
6
7 #include <algorithm>
8
9 #include "base/bind.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/app/chrome_command_ids.h"
13 #include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
14 #include "chrome/grit/generated_resources.h"
15 #include "components/arc/arc_bridge_service.h"
16 #include "content/public/common/context_menu_params.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/base/models/simple_menu_model.h"
19
20 namespace arc {
21
22 namespace {
23
24 const size_t kMaxArcAppsInSubMenu = IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP_LAST -
25 IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 + 1;
26
27 const int kMinInstanceVersion = 2; // see intent_helper.mojom
28
29 // Returns a set of command IDs to enable when the total number of ARC apps to
30 // show in the main and sub menus is |num_apps|.
31 std::set<int> GetCommandIdsToEnable(size_t num_apps,
32 int menu_id_start,
33 size_t num_menu_items) {
34 // We use the last item in the main menu (menu_id_start + num_menu_items - 1)
35 // as a parent of a sub menu, and others as regular menu items. We assume
36 // |num_menu_items| is always >=3.
37 DCHECK_GE(num_menu_items, 3U);
38
39 std::set<int> ids;
40
41 if (num_apps < num_menu_items) {
42 // All apps can be shown with the regular main menu items.
43 for (size_t i = 0; i < num_apps; ++i) {
44 ids.insert(menu_id_start + i);
45 }
46 } else {
47 // Otherwise, use the sub menu too. In this case, disable the last item of
48 // the regular main menu (hence '-2').
49 for (size_t i = 0; i < num_menu_items - 2; ++i) {
50 ids.insert(menu_id_start + i);
51 }
52 // Insert the parent of the sub menu.
53 ids.insert(menu_id_start + num_menu_items - 1);
54 const size_t sub_items =
55 std::min(num_apps - (num_menu_items - 2), kMaxArcAppsInSubMenu);
56 for (size_t i = 0; i < sub_items; ++i) {
57 ids.insert(IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 + i);
58 }
59 }
60
61 return ids;
62 }
63
64 // The same as GetCommandIdsToEnable except that the function returns a set of
65 // IDs to disable.
66 std::set<int> GetCommandIdsToDisable(size_t num_apps,
67 int menu_id_start,
68 size_t num_menu_items,
69 const std::set<int>& all_ids) {
70 DCHECK_GE(num_menu_items, 3U);
71
72 std::set<int> ids;
73 std::set<int> enabled(
74 GetCommandIdsToEnable(num_apps, menu_id_start, num_menu_items));
75 std::set_difference(all_ids.begin(), all_ids.end(), enabled.begin(),
76 enabled.end(), std::inserter(ids, ids.begin()));
77 return ids;
78 }
79
80 int GetIndexForCommandId(int command_id,
81 int menu_id_start,
82 size_t num_menu_items) {
83 DCHECK_GE(num_menu_items, 3U);
84
85 int index = -1;
86 if (command_id >= menu_id_start &&
hidehiko 2016/03/14 12:07:05 nit: how about short-circuit return? if (...) {
Yusuke Sato 2016/03/15 00:00:13 Removed the function. Done.
87 // '-1' is needed because the last item in the main menu is the parent of
88 // the sub menu.
89 static_cast<size_t>(command_id) < menu_id_start + num_menu_items - 1) {
90 index = command_id - menu_id_start;
91 } else if (command_id >= IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 &&
92 command_id <= IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP_LAST) {
93 // When the sub menu is in use, the main menu only holds
94 // (num_menu_items - 2) app names.
95 const int offset = num_menu_items - 2;
96 index = command_id - IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 + offset;
97 }
98 return index;
99 }
100
101 class SubMenuDelegate : public ui::SimpleMenuModel::Delegate {
102 public:
103 explicit SubMenuDelegate(OpenWithMenuControllerDelegate* parent_menu)
104 : parent_menu_(parent_menu) {}
105 ~SubMenuDelegate() override {}
106
107 // ui::SimpleMenuModel::Delegate overrides:
108 bool IsCommandIdChecked(int command_id) const override { return false; }
109 bool IsCommandIdEnabled(int command_id) const override { return true; }
110 bool GetAcceleratorForCommandId(int command_id,
111 ui::Accelerator* accelerator) override {
112 return false;
113 }
114 void ExecuteCommand(int command_id, int event_flags) override {
115 parent_menu_->ExecuteCommand(command_id);
116 }
117
118 private:
119 OpenWithMenuControllerDelegate* const parent_menu_;
120
121 DISALLOW_COPY_AND_ASSIGN(SubMenuDelegate);
122 };
123
124 } // namespace
125
126 OpenWithMenuControllerDelegate::OpenWithMenuControllerDelegate(
127 ArcBridgeService* bridge_service)
128 : bridge_service_(bridge_service),
129 sub_menu_(new SubMenuDelegate(this)),
130 loading_frame_(0),
131 weak_ptr_factory_(this) {
132 DCHECK(bridge_service_);
133 }
134
135 OpenWithMenuControllerDelegate::~OpenWithMenuControllerDelegate() {}
136
137 IntentHelperInstance* OpenWithMenuControllerDelegate::GetIntentHelper() {
138 if (!bridge_service_) {
139 DLOG(WARNING) << "ARC bridge is not ready.";
140 return nullptr;
141 }
142 IntentHelperInstance* intent_helper_instance =
143 bridge_service_->intent_helper_instance();
144 if (!intent_helper_instance) {
145 DLOG(WARNING) << "ARC intent helper instance is not ready.";
146 return nullptr;
147 }
148 if (bridge_service_->intent_helper_version() < kMinInstanceVersion) {
149 DLOG(WARNING) << "ARC intent helper instance is too old.";
150 return nullptr;
151 }
152 return intent_helper_instance;
153 }
154
155 int OpenWithMenuControllerDelegate::Init(RenderViewContextMenuProxy* proxy,
156 int menu_id_start,
157 size_t num_menu_items,
158 const GURL& url) {
159 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
160 const int kNumMenuItemsToEnableNow = 0;
161
162 IntentHelperInstance* intent_helper_instance = GetIntentHelper();
163 if (!intent_helper_instance || !url.is_valid() || num_menu_items < 3)
164 return kNumMenuItemsToEnableNow;
165
166 proxy_ = proxy;
167 all_command_ids_.clear();
168 main_menu_id_start_ = menu_id_start;
169 num_main_menu_items_ = num_menu_items;
170 link_url_ = url;
171 loading_message_ =
172 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_LOADING_ARC_APP_NAMES);
173
174 // Add placeholder items.
175 for (size_t i = 0; i < num_menu_items; ++i) {
176 const int id = menu_id_start + i;
177 if (i == (num_menu_items - 1))
178 proxy_->AddSubMenu(id, loading_message_, &sub_menu_);
179 else
180 proxy_->AddMenuItem(id, loading_message_);
181 all_command_ids_.insert(id);
182 }
183
184 // Add placeholder sub items.
185 for (size_t i = 0; i < kMaxArcAppsInSubMenu; ++i) {
186 const int id = IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 + i;
187 sub_menu_.AddItem(id, loading_message_);
188 all_command_ids_.insert(id);
189 }
190
191 // Check if ARC apps can handle the |link_url_|. Since the information is
192 // held in a different (ARC) process, issue a mojo IPC request. Usually, the
193 // callback function, OnUrlHandlerList, is called within a few milliseconds
194 // even on a slowest Chromebook we support.
195 intent_helper_instance->RequestUrlHandlerList(
196 link_url_.spec(),
197 base::Bind(&OpenWithMenuControllerDelegate::OnUrlHandlerList,
198 weak_ptr_factory_.GetWeakPtr()));
199
200 // Start the animation just in case. Since the IPC above almost always returns
201 // within a few milliseconds, the user will unlikely see the anination.
202 animation_timer_.Start(
203 FROM_HERE, base::TimeDelta::FromSeconds(1), this,
204 &OpenWithMenuControllerDelegate::OnAnimationTimerExpired);
205
206 return kNumMenuItemsToEnableNow;
207 }
208
209 void OpenWithMenuControllerDelegate::ExecuteCommand(int id) {
210 IntentHelperInstance* intent_helper_instance = GetIntentHelper();
211 if (!intent_helper_instance)
212 return;
213
214 // |index| will be UINT_MAX when command_id is invalid/unknown.
215 const size_t index =
216 GetIndexForCommandId(id, main_menu_id_start_, num_main_menu_items_);
217 if (index < handlers_.size()) {
218 intent_helper_instance->HandleUrl(link_url_.spec(),
219 handlers_[index]->package_name);
220 }
221 }
222
223 std::set<int> OpenWithMenuControllerDelegate::GetCommandIdsToEnableForTesting(
hidehiko 2016/03/14 12:07:05 Could you make those testing utility just as proxi
Yusuke Sato 2016/03/15 00:00:13 Yeah that's better of course. Done, thanks.
224 size_t num_apps) {
225 return GetCommandIdsToEnable(
226 num_apps, IDC_CONTENT_CONTEXT_OPEN_WITH1,
227 IDC_CONTENT_CONTEXT_OPEN_WITH_LAST - IDC_CONTENT_CONTEXT_OPEN_WITH1 + 1);
228 }
229
230 std::set<int> OpenWithMenuControllerDelegate::GetCommandIdsToDisableForTesting(
231 size_t num_apps) {
232 std::set<int> all_ids;
233 for (size_t i = IDC_CONTENT_CONTEXT_OPEN_WITH1;
234 i <= IDC_CONTENT_CONTEXT_OPEN_WITH_LAST; ++i) {
235 all_ids.insert(i);
236 }
237 for (size_t i = IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1;
238 i <= IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP_LAST; ++i) {
239 all_ids.insert(i);
240 }
241 return GetCommandIdsToDisable(
242 num_apps, IDC_CONTENT_CONTEXT_OPEN_WITH1,
243 IDC_CONTENT_CONTEXT_OPEN_WITH_LAST - IDC_CONTENT_CONTEXT_OPEN_WITH1 + 1,
244 all_ids);
245 }
246
247 int OpenWithMenuControllerDelegate::GetIndexForCommandIdForTesting(
248 int command_id) {
249 return GetIndexForCommandId(
250 command_id, IDC_CONTENT_CONTEXT_OPEN_WITH1,
251 IDC_CONTENT_CONTEXT_OPEN_WITH_LAST - IDC_CONTENT_CONTEXT_OPEN_WITH1 + 1);
252 }
253
254 base::string16 OpenWithMenuControllerDelegate::GetLabelForCommandId(
255 int command_id) const {
256 if (static_cast<size_t>(command_id) ==
257 (main_menu_id_start_ + num_main_menu_items_ - 1))
258 return l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_MORE_ARC_APPS);
259
260 // |index| will be UINT_MAX when command_id is invalid/unknown.
261 const size_t index = GetIndexForCommandId(command_id, main_menu_id_start_,
262 num_main_menu_items_);
263 if (index < handlers_.size()) {
264 return l10n_util::GetStringFUTF16(
265 IDS_CONTENT_CONTEXT_OPEN_WITH_ARC_APP,
266 base::UTF8ToUTF16(handlers_[index]->name.get()));
267 }
268 return base::EmptyString16();
269 }
270
271 void OpenWithMenuControllerDelegate::OnUrlHandlerList(
272 mojo::Array<UrlHandlerInfoPtr> handlers) {
273 animation_timer_.Stop();
274 handlers_ = std::move(handlers);
275
276 const size_t num_apps = handlers_.size();
277 std::set<int> disabled(GetCommandIdsToDisable(
278 num_apps, main_menu_id_start_, num_main_menu_items_, all_command_ids_));
279 for (const auto& command_id : disabled) {
280 proxy_->UpdateMenuItem(command_id,
281 false, // enabled
282 true, // hidden
283 base::EmptyString16());
284 }
285
286 std::set<int> enabled(GetCommandIdsToEnable(num_apps, main_menu_id_start_,
287 num_main_menu_items_));
288 for (const auto& command_id : enabled) {
289 // TODO(yusukes): Show icon of the app.
290 proxy_->UpdateMenuItem(command_id,
291 true, // enabled
292 false, // hidden
293 GetLabelForCommandId(command_id));
294 }
295 }
296
297 void OpenWithMenuControllerDelegate::OnAnimationTimerExpired() {
298 // Append '.' characters to the end of "Checking".
299 loading_frame_ = (loading_frame_ + 1) & 3;
300 base::string16 loading_message =
301 loading_message_ + base::string16(loading_frame_, '.');
302
303 // Update the menu item with the text. We disable this item to prevent users
304 // from selecting it.
305 for (const auto& command_id : all_command_ids_) {
306 proxy_->UpdateMenuItem(command_id,
307 false, // enabled
308 false, // hidden
309 loading_message);
310 }
311 }
312
313 } // namespace arc
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698