| // Copyright 2018 The Chromium Authors |
| // 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/start_smart_selection_action_menu.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" |
| #include "chrome/browser/apps/app_service/intent_util.h" |
| #include "chrome/browser/apps/app_service/launch_utils.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chromeos/ash/experiences/arc/intent_helper/arc_intent_helper_package.h" |
| #include "chromeos/ash/experiences/arc/metrics/arc_metrics_constants.h" |
| #include "components/renderer_context_menu/render_view_context_menu_proxy.h" |
| #include "components/services/app_service/public/cpp/app_launch_util.h" |
| #include "components/services/app_service/public/cpp/intent.h" |
| #include "content/public/browser/context_menu_params.h" |
| #include "ui/base/models/image_model.h" |
| #include "ui/base/resource/resource_scale_factor.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| |
| namespace arc { |
| |
| namespace { |
| |
| apps::IntentPtr CreateIntent( |
| arc::ArcIntentHelperMojoDelegate::IntentInfo arc_intent, |
| arc::ArcIntentHelperMojoDelegate::ActivityName activity) { |
| auto intent = std::make_unique<apps::Intent>(arc_intent.action); |
| intent->data = std::move(arc_intent.data); |
| intent->mime_type = std::move(arc_intent.type); |
| if (arc_intent.categories.has_value()) |
| intent->categories = std::move(arc_intent.categories.value()); |
| intent->ui_bypassed = arc_intent.ui_bypassed; |
| if (arc_intent.extras.has_value()) |
| intent->extras = std::move(arc_intent.extras.value()); |
| |
| intent->activity_name = std::move(activity.activity_name); |
| |
| return intent; |
| } |
| |
| } // namespace |
| |
| // The maximum number of smart actions to show. |
| constexpr size_t kMaxMainMenuCommands = 5; |
| |
| StartSmartSelectionActionMenu::StartSmartSelectionActionMenu( |
| content::BrowserContext* context, |
| RenderViewContextMenuProxy* proxy, |
| std::unique_ptr<ArcIntentHelperMojoDelegate> delegate) |
| : context_(context), proxy_(proxy), delegate_(std::move(delegate)) {} |
| |
| StartSmartSelectionActionMenu::~StartSmartSelectionActionMenu() = default; |
| |
| void StartSmartSelectionActionMenu::InitMenu( |
| const content::ContextMenuParams& params) { |
| const std::string converted_text = base::UTF16ToUTF8(params.selection_text); |
| if (converted_text.empty()) |
| return; |
| |
| DCHECK(delegate_); |
| if (!delegate_->IsRequestTextSelectionActionsAvailable()) { |
| // RequestTextSelectionActions is either not supported or not yet ready. |
| // In this case, immediately stop menu initialization instead of calling |
| // callback HandleTextSelectionActions with empty result. |
| // |
| // This conditions is required to avoid accessing to null menu due to the |
| // timing issue. |
| // |
| // In Lacros, RequestTextSelectionActions API will return false |
| // synchronously when mojo API is not supported in Lacros-side. In this |
| // case, the context menu is not initialized yet, so Lacros tries to access |
| // to null menu in HandleTextSelectionActions. To avoid this, we skip the |
| // following operation when RequestTextSelectionActions API is not supported |
| // in Lacros-side. Note that we can ignore the case where |
| // RequestTextSelectionActions API fails remotely since it runs |
| // asynchronously anyway. |
| // |
| // In Ash, it will always return false synchronously when mojo API is not |
| // supported in Ash-side or ARC-side. In both cases, we need to skip the |
| // following operation with the same reason above. |
| return; |
| } |
| |
| if (!delegate_->RequestTextSelectionActions( |
| converted_text, ui::GetMaxSupportedResourceScaleFactor(), |
| base::BindOnce( |
| &StartSmartSelectionActionMenu::HandleTextSelectionActions, |
| weak_ptr_factory_.GetWeakPtr()))) { |
| return; |
| } |
| |
| base::RecordAction(base::UserMetricsAction("Arc.SmartTextSelection.Request")); |
| |
| // Add placeholder items. |
| for (size_t i = 0; i < kMaxMainMenuCommands; ++i) { |
| proxy_->AddMenuItem(IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1 + i, |
| /*title=*/std::u16string()); |
| } |
| } |
| |
| bool StartSmartSelectionActionMenu::IsCommandIdSupported(int command_id) { |
| return command_id >= IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1 && |
| command_id <= IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION_LAST; |
| } |
| |
| bool StartSmartSelectionActionMenu::IsCommandIdChecked(int command_id) { |
| return false; |
| } |
| |
| bool StartSmartSelectionActionMenu::IsCommandIdEnabled(int command_id) { |
| return true; |
| } |
| |
| void StartSmartSelectionActionMenu::ExecuteCommand(int command_id) { |
| if (!IsCommandIdSupported(command_id)) |
| return; |
| |
| size_t index = command_id - IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1; |
| if (actions_.size() <= index) |
| return; |
| |
| gfx::Point point = display::Screen::Get()->GetCursorScreenPoint(); |
| display::Display display = |
| display::Screen::Get()->GetDisplayNearestPoint(point); |
| |
| Profile* profile = Profile::FromBrowserContext(context_); |
| if (actions_[index].activity.package_name == |
| arc::kArcIntentHelperPackageName) { |
| // The intent_helper app can't be launched as a regular app that then |
| // handles this smart action intent because it is not a launcher app. |
| // Instead, directly request that the intent be handled by it without |
| // really launching the app. |
| // This is necessary for "generic" smart selection actions that aren't |
| // created for specific apps such as opening a URL or a street address. |
| delegate_->HandleIntent(std::move(actions_[index].action_intent), |
| internal::ActivityIconLoader::ActivityName( |
| actions_[index].activity.package_name, |
| actions_[index].activity.activity_name)); |
| return; |
| } |
| // The app that this intent points to is able to handle it, launch it. |
| apps::AppServiceProxyFactory::GetForProfile(profile)->LaunchAppWithIntent( |
| actions_[index].app_id, ui::EF_NONE, |
| CreateIntent(std::move(actions_[index].action_intent), |
| std::move(actions_[index].activity)), |
| apps::LaunchSource::kFromSmartTextContextMenu, |
| std::make_unique<apps::WindowInfo>(display.id()), base::DoNothing()); |
| } |
| |
| void StartSmartSelectionActionMenu::OnContextMenuShown( |
| const content::ContextMenuParams& params, |
| const gfx::Rect& rect) { |
| // Since entries are kept as place holders, make them non editable and hidden. |
| for (size_t i = 0; i < kMaxMainMenuCommands; i++) { |
| proxy_->UpdateMenuItem( |
| IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1 + i, |
| /*enabled=*/false, |
| /*hidden=*/true, |
| /*title=*/std::u16string()); |
| } |
| } |
| |
| void StartSmartSelectionActionMenu::HandleTextSelectionActions( |
| std::vector<ArcIntentHelperMojoDelegate::TextSelectionAction> actions) { |
| actions_ = std::move(actions); |
| |
| for (size_t i = 0; i < actions_.size(); ++i) { |
| proxy_->UpdateMenuItem( |
| IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1 + i, |
| /*enabled=*/true, |
| /*hidden=*/false, |
| /*title=*/base::UTF8ToUTF16(actions_[i].title)); |
| |
| proxy_->UpdateMenuIcon( |
| IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1 + i, |
| ui::ImageModel::FromImageSkia(std::move(actions_[i].icon))); |
| } |
| |
| for (size_t i = actions_.size(); i < kMaxMainMenuCommands; ++i) { |
| // There were fewer actions returned than placeholder slots, remove the |
| // empty menu item. |
| proxy_->RemoveMenuItem(IDC_CONTENT_CONTEXT_START_SMART_SELECTION_ACTION1 + |
| i); |
| } |
| |
| // The asynchronous nature of adding smart actions means that sometimes, |
| // depending on whether actions were found and if extensions menu items were |
| // added synchronously, there could be extra (adjacent) separators in the |
| // context menu that must be removed once we've finished loading everything. |
| proxy_->RemoveAdjacentSeparators(); |
| } |
| |
| } // namespace arc |