chromium/chrome/browser/chromeos/arc/open_with_menu.cc

// Copyright 2016 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/open_with_menu.h"

#include <algorithm>

#include "base/strings/string_util.h"
#include "build/chromeos_buildflags.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/grit/generated_resources.h"
#include "components/renderer_context_menu/render_view_context_menu_proxy.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/context_menu_params.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/arc/intent_helper/arc_intent_helper_mojo_ash.h"
#else  // BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chrome/browser/lacros/arc/arc_intent_helper_mojo_lacros.h"
#endif

namespace arc {

const int OpenWithMenu::kNumMainMenuCommands = 4;
const int OpenWithMenu::kNumSubMenuCommands = 10;

bool OpenWithMenu::SubMenuDelegate::IsCommandIdChecked(int command_id) const {
  return false;
}

bool OpenWithMenu::SubMenuDelegate::IsCommandIdEnabled(int command_id) const {
  return true;
}

void OpenWithMenu::SubMenuDelegate::ExecuteCommand(int command_id,
                                                   int event_flags) {
  parent_->ExecuteCommand(command_id);
}

OpenWithMenu::OpenWithMenu(content::BrowserContext* context,
                           RenderViewContextMenuProxy* proxy)
    : context_(context),
      proxy_(proxy),
      more_apps_label_(
          l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_MORE_APPS)) {}

OpenWithMenu::~OpenWithMenu() = default;

void OpenWithMenu::InitMenu(const content::ContextMenuParams& params) {
  // Enforcing no items are added to the context menu during incognito mode.
  if (context_->IsOffTheRecord())
    return;

#if BUILDFLAG(IS_CHROMEOS_ASH)
  using ArcIntentHelperMojo = ArcIntentHelperMojoAsh;
#else  // BUILDFLAG(IS_CHROMEOS_LACROS)
  using ArcIntentHelperMojo = ArcIntentHelperMojoLacros;
#endif
  menu_model_ = LinkHandlerModel::Create(
      context_, params.link_url, std::make_unique<ArcIntentHelperMojo>());
  if (!menu_model_)
    return;

  // Add placeholder items.
  std::unique_ptr<ui::SimpleMenuModel> submenu(
      new ui::SimpleMenuModel(&submenu_delegate_));
  AddPlaceholderItems(proxy_, submenu.get());
  submenu_ = std::move(submenu);

  menu_model_->AddObserver(this);
}

bool OpenWithMenu::IsCommandIdSupported(int command_id) {
  return command_id >= IDC_CONTENT_CONTEXT_OPEN_WITH1 &&
         command_id <= IDC_CONTENT_CONTEXT_OPEN_WITH_LAST;
}

bool OpenWithMenu::IsCommandIdChecked(int command_id) {
  return false;
}

bool OpenWithMenu::IsCommandIdEnabled(int command_id) {
  return true;
}

void OpenWithMenu::ExecuteCommand(int command_id) {
  // Note: SubmenuDelegate also calls this method with a command_id for the
  // submenu.
  const auto it = handlers_.find(command_id);
  if (it == handlers_.end())
    return;

  menu_model_->OpenLinkWithHandler(it->second.id);
}

void OpenWithMenu::ModelChanged(const std::vector<LinkHandlerInfo>& handlers) {
  auto result = BuildHandlersMap(handlers);
  handlers_ = std::move(result.first);
  const int submenu_parent_id = result.second;
  for (int command_id = IDC_CONTENT_CONTEXT_OPEN_WITH1;
       command_id <= IDC_CONTENT_CONTEXT_OPEN_WITH_LAST; ++command_id) {
    const auto it = handlers_.find(command_id);
    if (command_id == submenu_parent_id) {
      // Show the submenu parent.
      proxy_->UpdateMenuItem(command_id, /*enabled=*/true, /*hidden=*/false,
                             /*title=*/more_apps_label_);
    } else if (it == handlers_.end()) {
      // Hide the menu or submenu parent.
      proxy_->UpdateMenuItem(command_id, /*enabled=*/false, /*hidden=*/true,
                             /*title=*/std::u16string());
    } else {
      // Update the menu with the new model.
      const std::u16string label = l10n_util::GetStringFUTF16(
          IDS_CONTENT_CONTEXT_OPEN_WITH_APP, it->second.name);
      proxy_->UpdateMenuItem(command_id, /*enabled=*/true, /*hidden=*/false,
                             label);
      if (!it->second.icon.IsEmpty()) {
        proxy_->UpdateMenuIcon(command_id,
                               ui::ImageModel::FromImage(it->second.icon));
      }
    }
  }
}

void OpenWithMenu::AddPlaceholderItemsForTesting(
    RenderViewContextMenuProxy* proxy,
    ui::SimpleMenuModel* submenu) {
  return AddPlaceholderItems(proxy, submenu);
}

std::pair<OpenWithMenu::HandlerMap, int>
OpenWithMenu::BuildHandlersMapForTesting(
    const std::vector<LinkHandlerInfo>& handlers) {
  return BuildHandlersMap(handlers);
}

void OpenWithMenu::AddPlaceholderItems(RenderViewContextMenuProxy* proxy,
                                       ui::SimpleMenuModel* submenu) {
  for (int i = 0; i < kNumSubMenuCommands; ++i) {
    const int command_id =
        IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands + i;
    submenu->AddItem(command_id, /*title=*/std::u16string());
  }
  int command_id;
  for (int i = 0; i < kNumMainMenuCommands - 1; ++i) {
    command_id = IDC_CONTENT_CONTEXT_OPEN_WITH1 + i;
    proxy->AddMenuItem(command_id, /*title=*/std::u16string());
  }
  proxy->AddSubMenu(++command_id, /*label=*/std::u16string(), submenu);
}

std::pair<OpenWithMenu::HandlerMap, int> OpenWithMenu::BuildHandlersMap(
    const std::vector<LinkHandlerInfo>& handlers) {
  const int kInvalidCommandId = -1;
  const int submenu_id_start =
      IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands;

  OpenWithMenu::HandlerMap handler_map;
  int submenu_parent_command_id = kInvalidCommandId;

  const int num_apps = handlers.size();
  size_t handlers_index = 0;
  // We use the last item in the main menu (IDC_CONTENT_CONTEXT_OPEN_WITH1 +
  // kNumMainMenuCommands- 1) as a parent of a submenu, and others as regular
  // menu items.
  if (num_apps < kNumMainMenuCommands) {
    // All apps can be shown with the regular main menu items.
    for (int i = 0; i < num_apps; ++i) {
      handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH1 + i] =
          handlers[handlers_index++];
    }
  } else {
    // Otherwise, use the submenu too. In this case, disable the last item of
    // the regular main menu (hence '-2').
    for (int i = 0; i < kNumMainMenuCommands - 2; ++i) {
      handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH1 + i] =
          handlers[handlers_index++];
    }
    submenu_parent_command_id =
        IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands - 1;
    const int sub_items =
        std::min(num_apps - (kNumMainMenuCommands - 2), kNumSubMenuCommands);
    for (int i = 0; i < sub_items; ++i) {
      handler_map[submenu_id_start + i] = handlers[handlers_index++];
    }
  }

  return std::make_pair(std::move(handler_map), submenu_parent_command_id);
}

}  // namespace arc