Chromium Code Reviews| Index: chrome/browser/renderer_context_menu/arc_app_menu_observer_chromeos.cc |
| diff --git a/chrome/browser/renderer_context_menu/arc_app_menu_observer_chromeos.cc b/chrome/browser/renderer_context_menu/arc_app_menu_observer_chromeos.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..ebfa63bef0afaa15a342953322b5467a28bf38ea |
| --- /dev/null |
| +++ b/chrome/browser/renderer_context_menu/arc_app_menu_observer_chromeos.cc |
| @@ -0,0 +1,288 @@ |
| +// 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/renderer_context_menu/arc_app_menu_observer_chromeos.h" |
| + |
| +#include <algorithm> |
| + |
| +#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 { |
| + |
| +const size_t kMaxArcAppsInMainMenu = |
| + IDC_CONTENT_CONTEXT_ARC_APP_LAST - IDC_CONTENT_CONTEXT_ARC_APP1 + 1; |
| +const size_t kMaxArcAppsInSubMenu = |
| + IDC_CONTENT_CONTEXT_ARC_SUB_APP_LAST - IDC_CONTENT_CONTEXT_ARC_SUB_APP1 + 1; |
| + |
| +const int kMinInstanceVersion = 2; // see intent_helper.mojom |
| + |
| +std::set<int> GetAllCommandIds() { |
| + std::set<int> ids; |
| + |
| + for (size_t i = 0; i < kMaxArcAppsInMainMenu; ++i) { |
| + ids.insert(IDC_CONTENT_CONTEXT_ARC_APP1 + i); |
| + } |
| + ids.insert(IDC_CONTENT_CONTEXT_ARC_SUB_MENU); |
| + for (size_t i = 0; i < kMaxArcAppsInSubMenu; ++i) { |
| + ids.insert(IDC_CONTENT_CONTEXT_ARC_SUB_APP1 + i); |
| + } |
| + |
| + return ids; |
| +} |
| + |
| +// Returns a set of command IDs to enable when the total number of ARC apps to |
| +// show in the main and sub menus is |num_apps|. |
| +std::set<int> GetCommandIdsToEnable(size_t num_apps) { |
| + std::set<int> ids; |
| + |
| + if (num_apps <= kMaxArcAppsInMainMenu) { |
| + // When |num_apps| fits in the main menu, do not enable |
| + // IDC_CONTENT_CONTEXT_ARC_SUB_MENU and IDC_CONTENT_CONTEXT_ARC_SUB_APP*. |
| + for (size_t i = 0; i < num_apps; ++i) { |
| + ids.insert(IDC_CONTENT_CONTEXT_ARC_APP1 + i); |
| + } |
| + } else { |
| + // When |num_apps| does not fit in the main menu, enable |
| + // IDC_CONTENT_CONTEXT_ARC_SUB_MENU and some of |
| + // IDC_CONTENT_CONTEXT_ARC_SUB_APP* IDs. In this case, do NOT enable |
| + // IDC_CONTENT_CONTEXT_ARC_APP_LAST, hence the '-1'. |
| + for (size_t i = 0; i < kMaxArcAppsInMainMenu - 1; ++i) { |
| + ids.insert(IDC_CONTENT_CONTEXT_ARC_APP1 + i); |
| + } |
| + |
| + ids.insert(IDC_CONTENT_CONTEXT_ARC_SUB_MENU); |
| + size_t sub_items = std::min(num_apps - (kMaxArcAppsInMainMenu - 1), |
| + kMaxArcAppsInSubMenu); |
| + for (size_t i = 0; i < sub_items; ++i) { |
| + ids.insert(IDC_CONTENT_CONTEXT_ARC_SUB_APP1 + i); |
| + } |
| + } |
| + |
| + return ids; |
| +} |
| + |
| +// The same as GetCommandIdsToEnable except that the function returns a set of |
| +// IDs to disable. |
| +std::set<int> GetCommandIdsToDisable(size_t num_apps, |
| + const std::set<int>& all_ids) { |
| + std::set<int> ids; |
| + std::set<int> enabled(GetCommandIdsToEnable(num_apps)); |
| + std::set_difference(all_ids.begin(), all_ids.end(), enabled.begin(), |
| + enabled.end(), std::inserter(ids, ids.begin())); |
| + return ids; |
| +} |
| + |
| +int GetIndexForCommandId(int command_id) { |
| + int index = -1; |
| + if (command_id >= IDC_CONTENT_CONTEXT_ARC_APP1 && |
| + command_id <= IDC_CONTENT_CONTEXT_ARC_APP_LAST) { |
| + index = command_id - IDC_CONTENT_CONTEXT_ARC_APP1; |
| + } else if (command_id >= IDC_CONTENT_CONTEXT_ARC_SUB_APP1 && |
| + command_id <= IDC_CONTENT_CONTEXT_ARC_SUB_APP_LAST) { |
| + const int offset = kMaxArcAppsInMainMenu - 1; |
| + index = command_id - IDC_CONTENT_CONTEXT_ARC_SUB_APP1 + offset; |
| + } |
| + return index; |
| +} |
| + |
| +class SubMenuDelegate : public ui::SimpleMenuModel::Delegate { |
| + public: |
| + explicit SubMenuDelegate(ArcAppMenuObserver* 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: |
| + ArcAppMenuObserver* parent_menu_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(SubMenuDelegate); |
| +}; |
| + |
| +} // namespace |
| + |
| +ArcAppMenuObserver::ArcAppMenuObserver(RenderViewContextMenuProxy* proxy) |
| + : proxy_(proxy), |
| + all_command_ids_(GetAllCommandIds()), |
| + sub_menu_(new SubMenuDelegate(this)), |
| + loading_frame_(0), |
| + weak_ptr_factory_(this) {} |
| + |
| +ArcAppMenuObserver::~ArcAppMenuObserver() {} |
| + |
| +arc::IntentHelperInstance* ArcAppMenuObserver::GetIntentHelper() { |
| + arc::ArcBridgeService* bridge = arc::ArcBridgeService::Get(); |
| + if (!bridge) { |
| + DLOG(WARNING) << "ARC bridge is not ready."; |
| + return nullptr; |
| + } |
| + arc::IntentHelperInstance* intent_helper_instance = |
| + bridge->intent_helper_instance(); |
| + if (!intent_helper_instance) { |
| + DLOG(WARNING) << "ARC intent helper instance is not ready."; |
| + return nullptr; |
| + } |
| + if (bridge->intent_helper_version() < kMinInstanceVersion) { |
| + DLOG(WARNING) << "ARC intent helper instance is too old."; |
| + return nullptr; |
| + } |
| + return intent_helper_instance; |
| +} |
| + |
| +void ArcAppMenuObserver::InitMenu(const content::ContextMenuParams& params) { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + |
| + arc::IntentHelperInstance* intent_helper_instance = GetIntentHelper(); |
| + if (!intent_helper_instance) { |
| + return; |
| + } |
| + |
| + if (params.link_url.is_empty()) { |
| + return; |
| + } |
| + link_url_ = params.link_url; |
| + |
| + loading_message_ = |
| + l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_LOADING_ARC_APP_NAMES); |
| + |
| + // Add placeholder items. |
| + for (size_t i = 0; i < kMaxArcAppsInMainMenu; ++i) { |
| + proxy_->AddMenuItem(IDC_CONTENT_CONTEXT_ARC_APP1 + i, |
| + loading_message_); |
| + } |
| + // Add a placeholder sub menu. |
| + proxy_->AddSubMenu(IDC_CONTENT_CONTEXT_ARC_SUB_MENU, loading_message_, |
| + &sub_menu_); |
| + // Add placeholder sub items. |
| + for (size_t i = 0; i < kMaxArcAppsInSubMenu; ++i) { |
| + sub_menu_.AddItem(IDC_CONTENT_CONTEXT_ARC_SUB_APP1 + i, |
| + loading_message_); |
| + } |
| + |
| + // Check if ARC apps can handle the |link_url_|. Sicne the information is |
|
hidehiko
2016/03/07 14:40:07
nit: s/Sicne/Since/
Yusuke Sato
2016/03/07 22:25:16
Done.
|
| + // 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(&ArcAppMenuObserver::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, &ArcAppMenuObserver::OnAnimationTimerExpired); |
| +} |
| + |
| +bool ArcAppMenuObserver::IsCommandIdSupported(int command_id) { |
| + return all_command_ids_.count(command_id); |
| +} |
| + |
| +bool ArcAppMenuObserver::IsCommandIdChecked(int command_id) { |
| + return false; |
| +} |
| + |
| +bool ArcAppMenuObserver::IsCommandIdEnabled(int command_id) { |
| + // Items will be enabled later in OnUrlHandlerList() as needed. |
| + return false; |
| +} |
| + |
| +void ArcAppMenuObserver::ExecuteCommand(int command_id) { |
| + arc::IntentHelperInstance* intent_helper_instance = GetIntentHelper(); |
| + if (!intent_helper_instance) { |
| + return; |
| + } |
| + |
| + const size_t index = GetIndexForCommandId(command_id); |
| + if (index < handlers_.size()) { |
| + intent_helper_instance->HandleUrl(link_url_.spec(), |
| + handlers_[index]->package_name); |
| + } |
| +} |
| + |
| +void ArcAppMenuObserver::OnMenuCancel() {} |
| + |
| +std::set<int> ArcAppMenuObserver::GetCommandIdsToEnableForTesting( |
| + size_t num_apps) { |
| + return GetCommandIdsToEnable(num_apps); |
| +} |
| + |
| +std::set<int> ArcAppMenuObserver::GetCommandIdsToDisableForTesting( |
| + size_t num_apps) { |
| + return GetCommandIdsToDisable(num_apps, GetAllCommandIds()); |
| +} |
| + |
| +int ArcAppMenuObserver::GetIndexForCommandIdForTesting(int command_id) { |
| + return GetIndexForCommandId(command_id); |
| +} |
| + |
| +base::string16 ArcAppMenuObserver::GetLabelForCommandId(int command_id) const { |
| + if (command_id == IDC_CONTENT_CONTEXT_ARC_SUB_MENU) |
| + return l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_MORE_ARC_APPS); |
| + |
| + const size_t index = GetIndexForCommandId(command_id); |
|
hidehiko
2016/03/07 14:40:07
nit: GetIndexForCommandId can return a negative va
Yusuke Sato
2016/03/07 22:25:16
Done.
|
| + if (index < handlers_.size()) { |
| + return l10n_util::GetStringFUTF16( |
| + IDS_CONTENT_CONTEXT_OPEN_WITH_ARC_APP, |
| + base::UTF8ToUTF16(handlers_[index]->name.get())); |
| + } |
| + return base::EmptyString16(); |
| +} |
| + |
| +void ArcAppMenuObserver::OnUrlHandlerList( |
| + mojo::Array<arc::UrlHandlerInfoPtr> handlers) { |
| + animation_timer_.Stop(); |
| + handlers_ = std::move(handlers); |
|
hidehiko
2016/03/07 14:40:07
nit: I'm ok with the current code, but how about h
Yusuke Sato
2016/03/07 22:25:16
That allows us to remove GetIndexForCommandId() as
hidehiko
2016/03/14 12:07:05
FYI: what I imagined was;
unordered_map<int /* co
Yusuke Sato
2016/03/15 00:00:13
Ah okay, I guess I misunderstood your comment. Tha
|
| + |
| + const size_t num_apps = handlers_.size(); |
| + std::set<int> disabled(GetCommandIdsToDisable(num_apps, all_command_ids_)); |
| + for (const auto& command_id : disabled) { |
| + proxy_->UpdateMenuItem(command_id, |
| + false, // enabled |
| + true, // hidden |
| + base::EmptyString16()); |
| + } |
| + |
| + std::set<int> enabled(GetCommandIdsToEnable(num_apps)); |
| + for (const auto& command_id : enabled) { |
| + // TODO(yusukes): Show icon of the app. |
| + proxy_->UpdateMenuItem(command_id, |
| + true, // enabled |
| + false, // hidden |
| + GetLabelForCommandId(command_id)); |
| + } |
| +} |
| + |
| +void ArcAppMenuObserver::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); |
| + } |
| +} |