| Index: components/arc/intent_helper/open_with_menu_observer.cc
|
| diff --git a/components/arc/intent_helper/open_with_menu_observer.cc b/components/arc/intent_helper/open_with_menu_observer.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4905baf6aae5207860c1fbd264e0c2e8fe92e20c
|
| --- /dev/null
|
| +++ b/components/arc/intent_helper/open_with_menu_observer.cc
|
| @@ -0,0 +1,274 @@
|
| +// 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 "components/arc/intent_helper/open_with_menu_observer.h"
|
| +
|
| +#include <algorithm>
|
| +#include <utility>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/strings/string_util.h"
|
| +#include "base/strings/utf_string_conversions.h"
|
| +#include "components/arc/arc_bridge_service.h"
|
| +#include "components/renderer_context_menu/render_view_context_menu_proxy.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +#include "content/public/common/context_menu_params.h"
|
| +#include "grit/components_strings.h"
|
| +#include "ui/base/l10n/l10n_util.h"
|
| +#include "ui/base/models/simple_menu_model.h"
|
| +
|
| +namespace arc {
|
| +
|
| +namespace {
|
| +
|
| +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<OpenWithMenuObserver::HandlerMap, int> BuildHandlersMap(
|
| + int menu_id_start,
|
| + size_t num_menu_items,
|
| + int sub_menu_id_start,
|
| + size_t num_sub_menu_items,
|
| + mojo::Array<UrlHandlerInfoPtr> handlers) {
|
| + // We assume |num_menu_items| is always >=3.
|
| + DCHECK_GE(num_menu_items, 3U);
|
| +
|
| + OpenWithMenuObserver::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), num_sub_menu_items);
|
| + for (size_t i = 0; i < sub_items; ++i) {
|
| + handler_map[sub_menu_id_start + 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(OpenWithMenuObserver* 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:
|
| + OpenWithMenuObserver* const parent_menu_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(SubMenuDelegate);
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +OpenWithMenuObserver::OpenWithMenuObserver(ArcBridgeService* bridge_service,
|
| + RenderViewContextMenuProxy* proxy,
|
| + int main_menu_id_start,
|
| + size_t num_main_menu_items,
|
| + int sub_menu_id_start,
|
| + size_t num_sub_menu_items)
|
| + : bridge_service_(bridge_service),
|
| + proxy_(proxy),
|
| + main_menu_id_start_(main_menu_id_start),
|
| + num_main_menu_items_(num_main_menu_items),
|
| + sub_menu_id_start_(sub_menu_id_start),
|
| + num_sub_menu_items_(num_sub_menu_items),
|
| + sub_menu_(new SubMenuDelegate(this)),
|
| + loading_frame_(0),
|
| + weak_ptr_factory_(this) {
|
| + DCHECK(bridge_service_);
|
| +}
|
| +
|
| +OpenWithMenuObserver::~OpenWithMenuObserver() {}
|
| +
|
| +void OpenWithMenuObserver::InitMenu(const content::ContextMenuParams& params) {
|
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
| +
|
| + link_url_ = params.link_url;
|
| + if (link_url_.is_empty() || !link_url_.is_valid())
|
| + return;
|
| +
|
| + IntentHelperInstance* intent_helper_instance = GetIntentHelper();
|
| + if (!intent_helper_instance || num_main_menu_items_ < 3)
|
| + return;
|
| +
|
| + loading_message_ =
|
| + l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_LOADING_APP_NAMES);
|
| +
|
| + // Add placeholder items.
|
| + for (size_t i = 0; i < num_main_menu_items_; ++i) {
|
| + const int id = main_menu_id_start_ + i;
|
| + if (i == (num_main_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 < num_sub_menu_items_; ++i) {
|
| + const int id = sub_menu_id_start_ + 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(&OpenWithMenuObserver::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,
|
| + &OpenWithMenuObserver::OnAnimationTimerExpired);
|
| +}
|
| +
|
| +bool OpenWithMenuObserver::IsCommandIdSupported(int command_id) {
|
| + return all_command_ids_.count(command_id);
|
| +}
|
| +
|
| +bool OpenWithMenuObserver::IsCommandIdChecked(int command_id) {
|
| + return false;
|
| +}
|
| +
|
| +bool OpenWithMenuObserver::IsCommandIdEnabled(int command_id) {
|
| + return false;
|
| +}
|
| +
|
| +void OpenWithMenuObserver::OnMenuCancel() {}
|
| +
|
| +IntentHelperInstance* OpenWithMenuObserver::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;
|
| +}
|
| +
|
| +void OpenWithMenuObserver::ExecuteCommand(int command_id) {
|
| + IntentHelperInstance* intent_helper_instance = GetIntentHelper();
|
| + if (!intent_helper_instance)
|
| + return;
|
| +
|
| + const auto iter = handlers_.find(command_id);
|
| + if (iter != handlers_.end()) {
|
| + intent_helper_instance->HandleUrl(link_url_.spec(),
|
| + iter->second->package_name);
|
| + }
|
| +}
|
| +
|
| +std::pair<OpenWithMenuObserver::HandlerMap, int>
|
| +OpenWithMenuObserver::BuildHandlersMapForTesting(
|
| + int menu_id_start,
|
| + size_t num_menu_items,
|
| + int sub_menu_id_start,
|
| + size_t num_sub_menu_items,
|
| + mojo::Array<UrlHandlerInfoPtr> handlers) {
|
| + return BuildHandlersMap(menu_id_start, num_menu_items, sub_menu_id_start,
|
| + num_sub_menu_items, std::move(handlers));
|
| +}
|
| +
|
| +base::string16 OpenWithMenuObserver::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_APPS);
|
| + }
|
| +
|
| + const auto iter = handlers_.find(command_id);
|
| + if (iter != handlers_.end()) {
|
| + return l10n_util::GetStringFUTF16(
|
| + IDS_CONTENT_CONTEXT_OPEN_WITH_APP,
|
| + base::UTF8ToUTF16(iter->second->name.get()));
|
| + }
|
| + return base::EmptyString16();
|
| +}
|
| +
|
| +void OpenWithMenuObserver::OnUrlHandlerList(
|
| + mojo::Array<UrlHandlerInfoPtr> handlers) {
|
| + animation_timer_.Stop();
|
| +
|
| + auto result = BuildHandlersMap(main_menu_id_start_, num_main_menu_items_,
|
| + sub_menu_id_start_, num_sub_menu_items_,
|
| + 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 OpenWithMenuObserver::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
|
|
|