Chromium Code Reviews| 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 #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 "chrome/app/chrome_command_ids.h" | |
| 14 #include "chrome/browser/renderer_context_menu/render_view_context_menu.h" | |
| 15 #include "chrome/grit/generated_resources.h" | |
| 16 #include "components/arc/arc_bridge_service.h" | |
| 17 #include "content/public/common/context_menu_params.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 size_t kMaxArcAppsInSubMenu = IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP_LAST - | |
| 26 IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 + 1; | |
| 27 | |
| 28 const int kInvalidCommandId = -1; | |
| 29 const int kMinInstanceVersion = 2; // see intent_helper.mojom | |
| 30 | |
| 31 // Converts |handlers| into HandlerMap which is a map from a command ID to a | |
| 32 // UrlHandlerInfoPtr and returns the map. Also returns a command id for the | |
| 33 // parent of the sub menu. When the sub menu is not needed, the function | |
| 34 // returns |kInvalidCommandId|. | |
| 35 std::pair<OpenWithMenuControllerDelegate::HandlerMap, int> BuildHandlersMap( | |
| 36 int menu_id_start, | |
| 37 size_t num_menu_items, | |
| 38 mojo::Array<UrlHandlerInfoPtr> handlers) { | |
| 39 // We assume |num_menu_items| is always >=3. | |
| 40 DCHECK_GE(num_menu_items, 3U); | |
| 41 | |
| 42 OpenWithMenuControllerDelegate::HandlerMap handler_map; | |
| 43 int sub_menu_parent_command_id = kInvalidCommandId; | |
| 44 | |
| 45 const size_t num_apps = handlers.size(); | |
| 46 size_t handlers_index = 0; | |
| 47 // We use the last item in the main menu (menu_id_start + num_menu_items - 1) | |
| 48 // as a parent of a sub menu, and others as regular menu items. | |
| 49 if (num_apps < num_menu_items) { | |
| 50 // All apps can be shown with the regular main menu items. | |
| 51 for (size_t i = 0; i < num_apps; ++i) { | |
| 52 handler_map[menu_id_start + i] = std::move(handlers[handlers_index++]); | |
| 53 } | |
| 54 } else { | |
| 55 // Otherwise, use the sub menu too. In this case, disable the last item of | |
| 56 // the regular main menu (hence '-2'). | |
| 57 for (size_t i = 0; i < num_menu_items - 2; ++i) { | |
| 58 handler_map[menu_id_start + i] = std::move(handlers[handlers_index++]); | |
| 59 } | |
| 60 sub_menu_parent_command_id = menu_id_start + num_menu_items - 1; | |
| 61 const size_t sub_items = | |
| 62 std::min(num_apps - (num_menu_items - 2), kMaxArcAppsInSubMenu); | |
| 63 for (size_t i = 0; i < sub_items; ++i) { | |
| 64 handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 + i] = | |
| 65 std::move(handlers[handlers_index++]); | |
| 66 } | |
| 67 } | |
| 68 | |
| 69 return std::make_pair(std::move(handler_map), sub_menu_parent_command_id); | |
| 70 } | |
| 71 | |
| 72 class SubMenuDelegate : public ui::SimpleMenuModel::Delegate { | |
| 73 public: | |
| 74 explicit SubMenuDelegate(OpenWithMenuControllerDelegate* parent_menu) | |
| 75 : parent_menu_(parent_menu) {} | |
| 76 ~SubMenuDelegate() override {} | |
| 77 | |
| 78 // ui::SimpleMenuModel::Delegate overrides: | |
| 79 bool IsCommandIdChecked(int command_id) const override { return false; } | |
| 80 bool IsCommandIdEnabled(int command_id) const override { return true; } | |
| 81 bool GetAcceleratorForCommandId(int command_id, | |
| 82 ui::Accelerator* accelerator) override { | |
| 83 return false; | |
| 84 } | |
| 85 void ExecuteCommand(int command_id, int event_flags) override { | |
| 86 parent_menu_->ExecuteCommand(command_id); | |
| 87 } | |
| 88 | |
| 89 private: | |
| 90 OpenWithMenuControllerDelegate* const parent_menu_; | |
| 91 | |
| 92 DISALLOW_COPY_AND_ASSIGN(SubMenuDelegate); | |
| 93 }; | |
| 94 | |
| 95 } // namespace | |
| 96 | |
| 97 OpenWithMenuControllerDelegate::OpenWithMenuControllerDelegate( | |
| 98 ArcBridgeService* bridge_service) | |
| 99 : bridge_service_(bridge_service), | |
| 100 sub_menu_(new SubMenuDelegate(this)), | |
| 101 loading_frame_(0), | |
| 102 weak_ptr_factory_(this) { | |
| 103 DCHECK(bridge_service_); | |
| 104 } | |
| 105 | |
| 106 OpenWithMenuControllerDelegate::~OpenWithMenuControllerDelegate() {} | |
| 107 | |
| 108 IntentHelperInstance* OpenWithMenuControllerDelegate::GetIntentHelper() { | |
| 109 if (!bridge_service_) { | |
| 110 DLOG(WARNING) << "ARC bridge is not ready."; | |
| 111 return nullptr; | |
| 112 } | |
| 113 IntentHelperInstance* intent_helper_instance = | |
| 114 bridge_service_->intent_helper_instance(); | |
| 115 if (!intent_helper_instance) { | |
| 116 DLOG(WARNING) << "ARC intent helper instance is not ready."; | |
| 117 return nullptr; | |
| 118 } | |
| 119 if (bridge_service_->intent_helper_version() < kMinInstanceVersion) { | |
| 120 DLOG(WARNING) << "ARC intent helper instance is too old."; | |
| 121 return nullptr; | |
| 122 } | |
| 123 return intent_helper_instance; | |
| 124 } | |
| 125 | |
| 126 int OpenWithMenuControllerDelegate::Init(RenderViewContextMenuProxy* proxy, | |
| 127 int menu_id_start, | |
| 128 size_t num_menu_items, | |
| 129 const GURL& url) { | |
| 130 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 131 const int kNumMenuItemsToEnableNow = 0; | |
| 132 | |
| 133 IntentHelperInstance* intent_helper_instance = GetIntentHelper(); | |
| 134 if (!intent_helper_instance || !url.is_valid() || num_menu_items < 3) | |
| 135 return kNumMenuItemsToEnableNow; | |
| 136 | |
| 137 proxy_ = proxy; | |
| 138 all_command_ids_.clear(); | |
| 139 main_menu_id_start_ = menu_id_start; | |
| 140 num_main_menu_items_ = num_menu_items; | |
| 141 link_url_ = url; | |
| 142 loading_message_ = | |
| 143 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_LOADING_ARC_APP_NAMES); | |
| 144 | |
| 145 // Add placeholder items. | |
| 146 for (size_t i = 0; i < num_menu_items; ++i) { | |
| 147 const int id = menu_id_start + i; | |
| 148 if (i == (num_menu_items - 1)) | |
| 149 proxy_->AddSubMenu(id, loading_message_, &sub_menu_); | |
| 150 else | |
| 151 proxy_->AddMenuItem(id, loading_message_); | |
| 152 all_command_ids_.insert(id); | |
| 153 } | |
| 154 | |
| 155 // Add placeholder sub items. | |
| 156 for (size_t i = 0; i < kMaxArcAppsInSubMenu; ++i) { | |
| 157 const int id = IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 + i; | |
| 158 sub_menu_.AddItem(id, loading_message_); | |
| 159 all_command_ids_.insert(id); | |
| 160 } | |
| 161 | |
| 162 // Check if ARC apps can handle the |link_url_|. Since the information is | |
| 163 // held in a different (ARC) process, issue a mojo IPC request. Usually, the | |
| 164 // callback function, OnUrlHandlerList, is called within a few milliseconds | |
| 165 // even on a slowest Chromebook we support. | |
| 166 intent_helper_instance->RequestUrlHandlerList( | |
| 167 link_url_.spec(), | |
| 168 base::Bind(&OpenWithMenuControllerDelegate::OnUrlHandlerList, | |
| 169 weak_ptr_factory_.GetWeakPtr())); | |
| 170 | |
| 171 // Start the animation just in case. Since the IPC above almost always returns | |
| 172 // within a few milliseconds, the user will unlikely see the anination. | |
| 173 animation_timer_.Start( | |
| 174 FROM_HERE, base::TimeDelta::FromSeconds(1), this, | |
| 175 &OpenWithMenuControllerDelegate::OnAnimationTimerExpired); | |
| 176 | |
| 177 return kNumMenuItemsToEnableNow; | |
| 178 } | |
| 179 | |
| 180 void OpenWithMenuControllerDelegate::ExecuteCommand(int id) { | |
| 181 IntentHelperInstance* intent_helper_instance = GetIntentHelper(); | |
| 182 if (!intent_helper_instance) | |
| 183 return; | |
| 184 | |
| 185 const auto iter = handlers_.find(id); | |
| 186 if (iter != handlers_.end()) { | |
| 187 intent_helper_instance->HandleUrl(link_url_.spec(), | |
| 188 iter->second->package_name); | |
| 189 } | |
| 190 } | |
| 191 | |
| 192 std::pair<OpenWithMenuControllerDelegate::HandlerMap, int> | |
| 193 OpenWithMenuControllerDelegate::BuildHandlersMapForTesting( | |
| 194 int menu_id_start, | |
| 195 size_t num_menu_items, | |
| 196 mojo::Array<UrlHandlerInfoPtr> handlers) { | |
| 197 return BuildHandlersMap(menu_id_start, num_menu_items, std::move(handlers)); | |
| 198 } | |
| 199 | |
| 200 base::string16 OpenWithMenuControllerDelegate::GetLabelForCommandId( | |
| 201 int command_id) const { | |
| 202 if (static_cast<size_t>(command_id) == | |
| 203 (main_menu_id_start_ + num_main_menu_items_ - 1)) | |
| 204 return l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_MORE_ARC_APPS); | |
| 205 | |
| 206 const auto iter = handlers_.find(command_id); | |
| 207 if (iter != handlers_.end()) { | |
| 208 return l10n_util::GetStringFUTF16( | |
| 209 IDS_CONTENT_CONTEXT_OPEN_WITH_ARC_APP, | |
| 210 base::UTF8ToUTF16(iter->second->name.get())); | |
| 211 } | |
| 212 return base::EmptyString16(); | |
| 213 } | |
| 214 | |
| 215 void OpenWithMenuControllerDelegate::OnUrlHandlerList( | |
| 216 mojo::Array<UrlHandlerInfoPtr> handlers) { | |
| 217 animation_timer_.Stop(); | |
| 218 | |
| 219 auto result = BuildHandlersMap(main_menu_id_start_, num_main_menu_items_, | |
|
hidehiko
2016/03/16 02:23:53
nit: FYI. You can remove |result|.
int sub_menu_p
Yusuke Sato
2016/03/31 14:46:29
I know std::tie is already used in some directorie
| |
| 220 std::move(handlers)); | |
| 221 handlers_ = std::move(result.first); | |
| 222 int sub_menu_parent = result.second; | |
| 223 | |
| 224 for (const auto& command_id : all_command_ids_) { | |
| 225 if (command_id == sub_menu_parent || handlers_.count(command_id)) { | |
| 226 // TODO(yusukes): Show icon of the app. | |
| 227 proxy_->UpdateMenuItem(command_id, | |
| 228 true, // enabled | |
| 229 false, // hidden | |
| 230 GetLabelForCommandId(command_id)); | |
| 231 } else { | |
| 232 proxy_->UpdateMenuItem(command_id, | |
| 233 false, // enabled | |
| 234 true, // hidden | |
| 235 base::EmptyString16()); | |
| 236 } | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 void OpenWithMenuControllerDelegate::OnAnimationTimerExpired() { | |
| 241 // Append '.' characters to the end of "Checking". | |
| 242 loading_frame_ = (loading_frame_ + 1) & 3; | |
| 243 base::string16 loading_message = | |
| 244 loading_message_ + base::string16(loading_frame_, '.'); | |
| 245 | |
| 246 // Update the menu item with the text. We disable this item to prevent users | |
| 247 // from selecting it. | |
| 248 for (const auto& command_id : all_command_ids_) { | |
| 249 proxy_->UpdateMenuItem(command_id, | |
| 250 false, // enabled | |
| 251 false, // hidden | |
| 252 loading_message); | |
| 253 } | |
| 254 } | |
| 255 | |
| 256 } // namespace arc | |
| OLD | NEW |