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