OLD | NEW |
---|---|
(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 | |
OLD | NEW |