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..9ef0c3cd4530b551284ee96b9d2b3ed8e07a0cd5 |
--- /dev/null |
+++ b/chrome/browser/chromeos/arc/open_with_menu_controller_delegate.cc |
@@ -0,0 +1,313 @@ |
+// 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 "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 kMinInstanceVersion = 2; // see intent_helper.mojom |
+ |
+// 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, |
+ int menu_id_start, |
+ size_t num_menu_items) { |
+ // 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. We assume |
+ // |num_menu_items| is always >=3. |
+ DCHECK_GE(num_menu_items, 3U); |
+ |
+ std::set<int> ids; |
+ |
+ 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) { |
+ ids.insert(menu_id_start + i); |
+ } |
+ } 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) { |
+ ids.insert(menu_id_start + i); |
+ } |
+ // Insert the parent of the sub menu. |
+ ids.insert(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) { |
+ ids.insert(IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_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, |
+ int menu_id_start, |
+ size_t num_menu_items, |
+ const std::set<int>& all_ids) { |
+ DCHECK_GE(num_menu_items, 3U); |
+ |
+ std::set<int> ids; |
+ std::set<int> enabled( |
+ GetCommandIdsToEnable(num_apps, menu_id_start, num_menu_items)); |
+ 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 menu_id_start, |
+ size_t num_menu_items) { |
+ DCHECK_GE(num_menu_items, 3U); |
+ |
+ int index = -1; |
+ if (command_id >= menu_id_start && |
hidehiko
2016/03/14 12:07:05
nit: how about short-circuit return?
if (...) {
Yusuke Sato
2016/03/15 00:00:13
Removed the function. Done.
|
+ // '-1' is needed because the last item in the main menu is the parent of |
+ // the sub menu. |
+ static_cast<size_t>(command_id) < menu_id_start + num_menu_items - 1) { |
+ index = command_id - menu_id_start; |
+ } else if (command_id >= IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 && |
+ command_id <= IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP_LAST) { |
+ // When the sub menu is in use, the main menu only holds |
+ // (num_menu_items - 2) app names. |
+ const int offset = num_menu_items - 2; |
+ index = command_id - IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1 + offset; |
+ } |
+ return index; |
+} |
+ |
+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; |
+ |
+ // |index| will be UINT_MAX when command_id is invalid/unknown. |
+ const size_t index = |
+ GetIndexForCommandId(id, main_menu_id_start_, num_main_menu_items_); |
+ if (index < handlers_.size()) { |
+ intent_helper_instance->HandleUrl(link_url_.spec(), |
+ handlers_[index]->package_name); |
+ } |
+} |
+ |
+std::set<int> OpenWithMenuControllerDelegate::GetCommandIdsToEnableForTesting( |
hidehiko
2016/03/14 12:07:05
Could you make those testing utility just as proxi
Yusuke Sato
2016/03/15 00:00:13
Yeah that's better of course. Done, thanks.
|
+ size_t num_apps) { |
+ return GetCommandIdsToEnable( |
+ num_apps, IDC_CONTENT_CONTEXT_OPEN_WITH1, |
+ IDC_CONTENT_CONTEXT_OPEN_WITH_LAST - IDC_CONTENT_CONTEXT_OPEN_WITH1 + 1); |
+} |
+ |
+std::set<int> OpenWithMenuControllerDelegate::GetCommandIdsToDisableForTesting( |
+ size_t num_apps) { |
+ std::set<int> all_ids; |
+ for (size_t i = IDC_CONTENT_CONTEXT_OPEN_WITH1; |
+ i <= IDC_CONTENT_CONTEXT_OPEN_WITH_LAST; ++i) { |
+ all_ids.insert(i); |
+ } |
+ for (size_t i = IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP1; |
+ i <= IDC_CONTENT_CONTEXT_OPEN_WITH_ARC_APP_LAST; ++i) { |
+ all_ids.insert(i); |
+ } |
+ return GetCommandIdsToDisable( |
+ num_apps, IDC_CONTENT_CONTEXT_OPEN_WITH1, |
+ IDC_CONTENT_CONTEXT_OPEN_WITH_LAST - IDC_CONTENT_CONTEXT_OPEN_WITH1 + 1, |
+ all_ids); |
+} |
+ |
+int OpenWithMenuControllerDelegate::GetIndexForCommandIdForTesting( |
+ int command_id) { |
+ return GetIndexForCommandId( |
+ command_id, IDC_CONTENT_CONTEXT_OPEN_WITH1, |
+ IDC_CONTENT_CONTEXT_OPEN_WITH_LAST - IDC_CONTENT_CONTEXT_OPEN_WITH1 + 1); |
+} |
+ |
+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); |
+ |
+ // |index| will be UINT_MAX when command_id is invalid/unknown. |
+ const size_t index = GetIndexForCommandId(command_id, main_menu_id_start_, |
+ num_main_menu_items_); |
+ 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 OpenWithMenuControllerDelegate::OnUrlHandlerList( |
+ mojo::Array<UrlHandlerInfoPtr> handlers) { |
+ animation_timer_.Stop(); |
+ handlers_ = std::move(handlers); |
+ |
+ const size_t num_apps = handlers_.size(); |
+ std::set<int> disabled(GetCommandIdsToDisable( |
+ num_apps, main_menu_id_start_, num_main_menu_items_, 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, main_menu_id_start_, |
+ num_main_menu_items_)); |
+ 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 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 |