Chromium Code Reviews| Index: chrome/browser/chromeos/arc/open_with_menu_controller_delegate.cc |
| diff --git a/chrome/browser/chromeos/arc/open_with_menu_controller_delegate.cc b/chrome/browser/chromeos/arc/open_with_menu_controller_delegate.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..93dca75c1a8ec468a0a9b99cf512d048fdd99660 |
| --- /dev/null |
| +++ b/chrome/browser/chromeos/arc/open_with_menu_controller_delegate.cc |
| @@ -0,0 +1,256 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chrome/browser/chromeos/arc/open_with_menu_controller_delegate.h" |
| + |
| +#include <algorithm> |
| +#include <utility> |
| + |
| +#include "base/bind.h" |
| +#include "base/strings/string_util.h" |
| +#include "base/strings/utf_string_conversions.h" |
| +#include "chrome/app/chrome_command_ids.h" |
| +#include "chrome/browser/renderer_context_menu/render_view_context_menu.h" |
| +#include "chrome/grit/generated_resources.h" |
| +#include "components/arc/arc_bridge_service.h" |
| +#include "content/public/common/context_menu_params.h" |
| +#include "ui/base/l10n/l10n_util.h" |
| +#include "ui/base/models/simple_menu_model.h" |
| + |
| +namespace arc { |
| + |
| +namespace { |
| + |
| +const size_t kMaxArcAppsInSubMenu = IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP_LAST - |
| + IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 + 1; |
| + |
| +const int kInvalidCommandId = -1; |
| +const int kMinInstanceVersion = 2; // see intent_helper.mojom |
| + |
| +// Converts |handlers| into HandlerMap which is a map from a command ID to a |
| +// UrlHandlerInfoPtr and returns the map. Also returns a command id for the |
| +// parent of the sub menu. When the sub menu is not needed, the function |
| +// returns |kInvalidCommandId|. |
| +std::pair<OpenWithMenuControllerDelegate::HandlerMap, int> BuildHandlersMap( |
| + int menu_id_start, |
| + size_t num_menu_items, |
| + mojo::Array<UrlHandlerInfoPtr> handlers) { |
| + // We assume |num_menu_items| is always >=3. |
| + DCHECK_GE(num_menu_items, 3U); |
| + |
| + OpenWithMenuControllerDelegate::HandlerMap handler_map; |
| + int sub_menu_parent_command_id = kInvalidCommandId; |
| + |
| + const size_t num_apps = handlers.size(); |
| + size_t handlers_index = 0; |
| + // We use the last item in the main menu (menu_id_start + num_menu_items - 1) |
| + // as a parent of a sub menu, and others as regular menu items. |
| + if (num_apps < num_menu_items) { |
| + // All apps can be shown with the regular main menu items. |
| + for (size_t i = 0; i < num_apps; ++i) { |
| + handler_map[menu_id_start + i] = std::move(handlers[handlers_index++]); |
| + } |
| + } else { |
| + // Otherwise, use the sub menu too. In this case, disable the last item of |
| + // the regular main menu (hence '-2'). |
| + for (size_t i = 0; i < num_menu_items - 2; ++i) { |
| + handler_map[menu_id_start + i] = std::move(handlers[handlers_index++]); |
| + } |
| + sub_menu_parent_command_id = menu_id_start + num_menu_items - 1; |
| + const size_t sub_items = |
| + std::min(num_apps - (num_menu_items - 2), kMaxArcAppsInSubMenu); |
| + for (size_t i = 0; i < sub_items; ++i) { |
| + handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 + i] = |
| + std::move(handlers[handlers_index++]); |
| + } |
| + } |
| + |
| + return std::make_pair(std::move(handler_map), sub_menu_parent_command_id); |
| +} |
| + |
| +class SubMenuDelegate : public ui::SimpleMenuModel::Delegate { |
| + public: |
| + explicit SubMenuDelegate(OpenWithMenuControllerDelegate* parent_menu) |
| + : parent_menu_(parent_menu) {} |
| + ~SubMenuDelegate() override {} |
| + |
| + // ui::SimpleMenuModel::Delegate overrides: |
| + bool IsCommandIdChecked(int command_id) const override { return false; } |
| + bool IsCommandIdEnabled(int command_id) const override { return true; } |
| + bool GetAcceleratorForCommandId(int command_id, |
| + ui::Accelerator* accelerator) override { |
| + return false; |
| + } |
| + void ExecuteCommand(int command_id, int event_flags) override { |
| + parent_menu_->ExecuteCommand(command_id); |
| + } |
| + |
| + private: |
| + OpenWithMenuControllerDelegate* const parent_menu_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(SubMenuDelegate); |
| +}; |
| + |
| +} // namespace |
| + |
| +OpenWithMenuControllerDelegate::OpenWithMenuControllerDelegate( |
| + ArcBridgeService* bridge_service) |
| + : bridge_service_(bridge_service), |
| + sub_menu_(new SubMenuDelegate(this)), |
| + loading_frame_(0), |
| + weak_ptr_factory_(this) { |
| + DCHECK(bridge_service_); |
| +} |
| + |
| +OpenWithMenuControllerDelegate::~OpenWithMenuControllerDelegate() {} |
| + |
| +IntentHelperInstance* OpenWithMenuControllerDelegate::GetIntentHelper() { |
| + if (!bridge_service_) { |
| + DLOG(WARNING) << "ARC bridge is not ready."; |
| + return nullptr; |
| + } |
| + IntentHelperInstance* intent_helper_instance = |
| + bridge_service_->intent_helper_instance(); |
| + if (!intent_helper_instance) { |
| + DLOG(WARNING) << "ARC intent helper instance is not ready."; |
| + return nullptr; |
| + } |
| + if (bridge_service_->intent_helper_version() < kMinInstanceVersion) { |
| + DLOG(WARNING) << "ARC intent helper instance is too old."; |
| + return nullptr; |
| + } |
| + return intent_helper_instance; |
| +} |
| + |
| +int OpenWithMenuControllerDelegate::Init(RenderViewContextMenuProxy* proxy, |
| + int menu_id_start, |
| + size_t num_menu_items, |
| + const GURL& url) { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + const int kNumMenuItemsToEnableNow = 0; |
| + |
| + IntentHelperInstance* intent_helper_instance = GetIntentHelper(); |
| + if (!intent_helper_instance || !url.is_valid() || num_menu_items < 3) |
| + return kNumMenuItemsToEnableNow; |
| + |
| + proxy_ = proxy; |
| + all_command_ids_.clear(); |
| + main_menu_id_start_ = menu_id_start; |
| + num_main_menu_items_ = num_menu_items; |
| + link_url_ = url; |
| + loading_message_ = |
| + l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_LOADING_ARC_APP_NAMES); |
| + |
| + // Add placeholder items. |
| + for (size_t i = 0; i < num_menu_items; ++i) { |
| + const int id = menu_id_start + i; |
| + if (i == (num_menu_items - 1)) |
| + proxy_->AddSubMenu(id, loading_message_, &sub_menu_); |
| + else |
| + proxy_->AddMenuItem(id, loading_message_); |
| + all_command_ids_.insert(id); |
| + } |
| + |
| + // Add placeholder sub items. |
| + for (size_t i = 0; i < kMaxArcAppsInSubMenu; ++i) { |
| + const int id = IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 + i; |
| + sub_menu_.AddItem(id, loading_message_); |
| + all_command_ids_.insert(id); |
| + } |
| + |
| + // Check if ARC apps can handle the |link_url_|. Since the information is |
| + // held in a different (ARC) process, issue a mojo IPC request. Usually, the |
| + // callback function, OnUrlHandlerList, is called within a few milliseconds |
| + // even on a slowest Chromebook we support. |
| + intent_helper_instance->RequestUrlHandlerList( |
| + link_url_.spec(), |
| + base::Bind(&OpenWithMenuControllerDelegate::OnUrlHandlerList, |
| + weak_ptr_factory_.GetWeakPtr())); |
| + |
| + // Start the animation just in case. Since the IPC above almost always returns |
| + // within a few milliseconds, the user will unlikely see the anination. |
| + animation_timer_.Start( |
| + FROM_HERE, base::TimeDelta::FromSeconds(1), this, |
| + &OpenWithMenuControllerDelegate::OnAnimationTimerExpired); |
| + |
| + return kNumMenuItemsToEnableNow; |
| +} |
| + |
| +void OpenWithMenuControllerDelegate::ExecuteCommand(int id) { |
| + IntentHelperInstance* intent_helper_instance = GetIntentHelper(); |
| + if (!intent_helper_instance) |
| + return; |
| + |
| + const auto iter = handlers_.find(id); |
| + if (iter != handlers_.end()) { |
| + intent_helper_instance->HandleUrl(link_url_.spec(), |
| + iter->second->package_name); |
| + } |
| +} |
| + |
| +std::pair<OpenWithMenuControllerDelegate::HandlerMap, int> |
| +OpenWithMenuControllerDelegate::BuildHandlersMapForTesting( |
| + int menu_id_start, |
| + size_t num_menu_items, |
| + mojo::Array<UrlHandlerInfoPtr> handlers) { |
| + return BuildHandlersMap(menu_id_start, num_menu_items, std::move(handlers)); |
| +} |
| + |
| +base::string16 OpenWithMenuControllerDelegate::GetLabelForCommandId( |
| + int command_id) const { |
| + if (static_cast<size_t>(command_id) == |
| + (main_menu_id_start_ + num_main_menu_items_ - 1)) |
| + return l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_MORE_ARC_APPS); |
| + |
| + const auto iter = handlers_.find(command_id); |
| + if (iter != handlers_.end()) { |
| + return l10n_util::GetStringFUTF16( |
| + IDS_CONTENT_CONTEXT_OPEN_WITH_ARC_APP, |
| + base::UTF8ToUTF16(iter->second->name.get())); |
| + } |
| + return base::EmptyString16(); |
| +} |
| + |
| +void OpenWithMenuControllerDelegate::OnUrlHandlerList( |
| + mojo::Array<UrlHandlerInfoPtr> handlers) { |
| + animation_timer_.Stop(); |
| + |
| + 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
|
| + std::move(handlers)); |
| + handlers_ = std::move(result.first); |
| + int sub_menu_parent = result.second; |
| + |
| + for (const auto& command_id : all_command_ids_) { |
| + if (command_id == sub_menu_parent || handlers_.count(command_id)) { |
| + // TODO(yusukes): Show icon of the app. |
| + proxy_->UpdateMenuItem(command_id, |
| + true, // enabled |
| + false, // hidden |
| + GetLabelForCommandId(command_id)); |
| + } else { |
| + proxy_->UpdateMenuItem(command_id, |
| + false, // enabled |
| + true, // hidden |
| + base::EmptyString16()); |
| + } |
| + } |
| +} |
| + |
| +void OpenWithMenuControllerDelegate::OnAnimationTimerExpired() { |
| + // Append '.' characters to the end of "Checking". |
| + loading_frame_ = (loading_frame_ + 1) & 3; |
| + base::string16 loading_message = |
| + loading_message_ + base::string16(loading_frame_, '.'); |
| + |
| + // Update the menu item with the text. We disable this item to prevent users |
| + // from selecting it. |
| + for (const auto& command_id : all_command_ids_) { |
| + proxy_->UpdateMenuItem(command_id, |
| + false, // enabled |
| + false, // hidden |
| + loading_message); |
| + } |
| +} |
| + |
| +} // namespace arc |