Commit e03c42ac authored by Li Lin's avatar Li Lin Committed by Commit Bot

Add Quick Answers context menu observer.

The feature is guarded by a feature flag that is currently disabled by default.

This CL implements a temporarily UI for getting early feedback on product and
data quality while we are figuring out the final UI.

More detail at: go/quick-answers-cros and go/quick-answers

Bug: 1020004
Test: Unit tests
Change-Id: I17dac669c230f5fbd2052cd5683344fa30c41b85
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1961210Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarEvan Stade <estade@chromium.org>
Commit-Queue: Li Lin <llin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#732713}
parent d5c59d14
......@@ -395,6 +395,12 @@
#define IDC_CONTENT_CONTEXT_ACCESSIBILITY_LABELS 52411
#define IDC_CONTENT_CONTEXT_ACCESSIBILITY_LABELS_TOGGLE_ONCE 52412
#if defined(OS_CHROMEOS)
// Quick Answers context menu items.
#define IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER 52413
#define IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY 52414
#endif
// NOTE: The last valid command value is 57343 (0xDFFF)
// See http://msdn.microsoft.com/en-us/library/t2zechd4(VS.71).aspx
......
......@@ -224,6 +224,10 @@ aggregate_vector_icons("chrome_vector_icons") {
"google_chrome/google_pay_logo.icon",
]
}
if (is_chrome_branded && is_chromeos) {
icons += [ "google_chrome/assistant.icon" ]
}
}
source_set("vector_icons") {
......
......@@ -3812,6 +3812,8 @@ jumbo_static_library("browser") {
"notifications/web_page_notifier_controller.h",
"policy/default_geolocation_policy_handler.cc",
"policy/default_geolocation_policy_handler.h",
"renderer_context_menu/quick_answers_menu_observer.cc",
"renderer_context_menu/quick_answers_menu_observer.h",
"signin/signin_error_notifier_ash.cc",
"signin/signin_error_notifier_ash.h",
"signin/signin_error_notifier_factory_ash.cc",
......@@ -3846,6 +3848,7 @@ jumbo_static_library("browser") {
"//chrome/browser/chromeos",
"//chrome/services/app_service/public/cpp:instance_update",
"//chromeos/components/account_manager",
"//chromeos/components/quick_answers",
"//chromeos/components/sync_wifi",
"//chromeos/services/assistant/public:feature_flags",
"//chromeos/services/assistant/public/cpp:prefs",
......
// Copyright 2020 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/quick_answers_menu_observer.h"
#include <utility>
#include "base/strings/utf_string_conversions.h"
#include "build/branding_buildflags.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/components/quick_answers/quick_answers_model.h"
#include "components/renderer_context_menu/render_view_context_menu_proxy.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/context_menu_params.h"
namespace {
using chromeos::quick_answers::QuickAnswer;
using chromeos::quick_answers::QuickAnswersClient;
using chromeos::quick_answers::QuickAnswersRequest;
// TODO(llin): Update the placeholder after finalizing on the design.
constexpr char kLoadingPlaceholder[] = "Loading...";
constexpr char kNoResult[] = "See result in Assistant";
} // namespace
QuickAnswersMenuObserver::QuickAnswersMenuObserver(
RenderViewContextMenuProxy* proxy)
: proxy_(proxy) {
auto* assistant_state = ash::AssistantState::Get();
if (assistant_state && proxy_ && proxy_->GetBrowserContext()) {
auto* browser_context = proxy_->GetBrowserContext();
if (browser_context->IsOffTheRecord())
return;
quick_answers_client_ = std::make_unique<QuickAnswersClient>(
content::BrowserContext::GetDefaultStoragePartition(browser_context)
->GetURLLoaderFactoryForBrowserProcess()
.get(),
assistant_state, this);
}
}
QuickAnswersMenuObserver::~QuickAnswersMenuObserver() = default;
void QuickAnswersMenuObserver::InitMenu(
const content::ContextMenuParams& params) {
if (!is_eligible_ || !proxy_ || !quick_answers_client_)
return;
if (params.input_field_type ==
blink::ContextMenuDataInputFieldType::kPassword)
return;
auto selected_text = base::UTF16ToUTF8(params.selection_text);
if (selected_text.empty())
return;
// Add Quick Answer Menu item.
// TODO(llin): Update the menu item after finalizing on the design.
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
proxy_->AddMenuItemWithIcon(IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
params.selection_text, kAssistantIcon);
#else
proxy_->AddMenuItem(IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
params.selection_text);
#endif
proxy_->AddMenuItem(IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
base::UTF8ToUTF16(kLoadingPlaceholder));
proxy_->AddSeparator();
// Fetch Quick Answer.
QuickAnswersRequest request;
request.selected_text = selected_text;
quick_answers_client_->SendRequest(request);
}
bool QuickAnswersMenuObserver::IsCommandIdSupported(int command_id) {
return (command_id == IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY ||
command_id == IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER);
}
bool QuickAnswersMenuObserver::IsCommandIdChecked(int command_id) {
return false;
}
bool QuickAnswersMenuObserver::IsCommandIdEnabled(int command_id) {
return command_id == IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY;
}
void QuickAnswersMenuObserver::ExecuteCommand(int command_id) {
// TODO(llin): Implements Quick Answers click action.
}
void QuickAnswersMenuObserver::OnQuickAnswerReceived(
std::unique_ptr<QuickAnswer> quick_answer) {
if (quick_answer) {
proxy_->UpdateMenuItem(
IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
/*enabled=*/false,
/*hidden=*/false,
/*title=*/
base::UTF8ToUTF16(quick_answer->primary_answer.empty()
? kNoResult
: quick_answer->primary_answer));
if (!quick_answer->secondary_answer.empty()) {
proxy_->UpdateMenuItem(
IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
/*enabled=*/true,
/*hidden=*/false,
/*title=*/base::UTF8ToUTF16(quick_answer->secondary_answer));
}
} else {
proxy_->UpdateMenuItem(IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
/*enabled=*/false,
/*hidden=*/false,
/*title=*/base::UTF8ToUTF16(kNoResult));
}
}
void QuickAnswersMenuObserver::OnEligibilityChanged(bool eligible) {
is_eligible_ = eligible;
}
void QuickAnswersMenuObserver::SetQuickAnswerClientForTesting(
std::unique_ptr<chromeos::quick_answers::QuickAnswersClient>
quick_answers_client) {
quick_answers_client_ = std::move(quick_answers_client);
}
// Copyright 2020 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.
#ifndef CHROME_BROWSER_RENDERER_CONTEXT_MENU_QUICK_ANSWERS_MENU_OBSERVER_H_
#define CHROME_BROWSER_RENDERER_CONTEXT_MENU_QUICK_ANSWERS_MENU_OBSERVER_H_
#include <memory>
#include <string>
#include "chromeos/components/quick_answers/quick_answers_client.h"
#include "components/renderer_context_menu/render_view_context_menu_observer.h"
class RenderViewContextMenuProxy;
// A class that implements the quick answers menu.
class QuickAnswersMenuObserver
: public RenderViewContextMenuObserver,
public chromeos::quick_answers::QuickAnswersClient::QuickAnswersDelegate {
public:
QuickAnswersMenuObserver(const QuickAnswersMenuObserver&) = delete;
QuickAnswersMenuObserver& operator=(const QuickAnswersMenuObserver&) = delete;
explicit QuickAnswersMenuObserver(RenderViewContextMenuProxy* proxy);
~QuickAnswersMenuObserver() override;
// RenderViewContextMenuObserver implementation.
void InitMenu(const content::ContextMenuParams& params) override;
bool IsCommandIdSupported(int command_id) override;
bool IsCommandIdChecked(int command_id) override;
bool IsCommandIdEnabled(int command_id) override;
void ExecuteCommand(int command_id) override;
// QuickAnswersDelegate implementation.
void OnQuickAnswerReceived(
std::unique_ptr<chromeos::quick_answers::QuickAnswer> answer) override;
void OnEligibilityChanged(bool eligible) override;
void SetQuickAnswerClientForTesting(
std::unique_ptr<chromeos::quick_answers::QuickAnswersClient>
quick_answers_client);
private:
// The interface to add a context-menu item and update it.
RenderViewContextMenuProxy* proxy_;
std::unique_ptr<chromeos::quick_answers::QuickAnswersClient>
quick_answers_client_;
// Whether the feature is enabled and all eligibility criterias are met (
// locale, consents, etc).
bool is_eligible_ = false;
};
#endif // CHROME_BROWSER_RENDERER_CONTEXT_MENU_QUICK_ANSWERS_MENU_OBSERVER_H_
// Copyright 2020 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/quick_answers_menu_observer.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/renderer_context_menu/mock_render_view_context_menu.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/components/quick_answers/quick_answers_client.h"
#include "chromeos/components/quick_answers/quick_answers_model.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
using chromeos::quick_answers::QuickAnswer;
using chromeos::quick_answers::QuickAnswersClient;
using chromeos::quick_answers::QuickAnswersRequest;
using testing::_;
class MockQuickAnswersClient : public QuickAnswersClient {
public:
MockQuickAnswersClient(network::mojom::URLLoaderFactory* url_loader_factory,
ash::AssistantState* assistant_state,
QuickAnswersMenuObserver* delegate)
: QuickAnswersClient(url_loader_factory, assistant_state, delegate) {}
MockQuickAnswersClient(const MockQuickAnswersClient&) = delete;
MockQuickAnswersClient& operator=(const MockQuickAnswersClient&) = delete;
// QuickAnswersClient::QuickAnswersClient:
MOCK_METHOD1(SendRequest, void(const QuickAnswersRequest&));
};
MATCHER_P(QuickAnswersRequestEqual, quick_answers_request, "") {
return (arg.selected_text == quick_answers_request.selected_text);
}
// A test class for Quick Answers. This test should be a browser test because it
//// accesses resources.
class QuickAnswersMenuObserverTest : public InProcessBrowserTest {
public:
QuickAnswersMenuObserverTest() = default;
QuickAnswersMenuObserverTest(const QuickAnswersMenuObserverTest&) = delete;
QuickAnswersMenuObserverTest& operator=(const QuickAnswersMenuObserverTest&) =
delete;
// InProcessBrowserTest overrides:
void SetUpOnMainThread() override {
Reset(false);
mock_quick_answers_cient_ = std::make_unique<MockQuickAnswersClient>(
/*url_loader_factory=*/nullptr,
/*assistant_state=*/ash::AssistantState::Get(),
/*delegate=*/observer_.get());
observer_->OnEligibilityChanged(true);
}
void TearDownOnMainThread() override {
observer_.reset();
menu_.reset();
}
void Reset(bool incognito) {
observer_.reset();
menu_ = std::make_unique<MockRenderViewContextMenu>(incognito);
observer_ = std::make_unique<QuickAnswersMenuObserver>(menu_.get());
menu_->SetObserver(observer_.get());
}
void InitMenu() {
content::ContextMenuParams params;
static const base::string16 selected_text = base::ASCIIToUTF16("sel");
params.selection_text = selected_text;
observer_->InitMenu(params);
}
MockRenderViewContextMenu* menu() { return menu_.get(); }
QuickAnswersMenuObserver* observer() { return observer_.get(); }
protected:
void VerifyMenuItems(int index,
int expected_command_id,
const std::string& expected_title,
bool enabled) {
MockRenderViewContextMenu::MockMenuItem item;
menu()->GetMenuItem(index, &item);
EXPECT_EQ(expected_command_id, item.command_id);
EXPECT_EQ(base::UTF8ToUTF16(expected_title), item.title);
EXPECT_EQ(enabled, item.enabled);
EXPECT_FALSE(item.hidden);
}
void MockQuickAnswerClient() {
std::unique_ptr<QuickAnswersRequest> expected_quick_answers_request =
std::make_unique<QuickAnswersRequest>();
expected_quick_answers_request->selected_text = "sel";
EXPECT_CALL(
*mock_quick_answers_cient_,
SendRequest(QuickAnswersRequestEqual(*expected_quick_answers_request)))
.Times(1);
observer_->SetQuickAnswerClientForTesting(
std::move(mock_quick_answers_cient_));
}
std::unique_ptr<QuickAnswersMenuObserver> observer_;
std::unique_ptr<MockQuickAnswersClient> mock_quick_answers_cient_;
std::unique_ptr<MockRenderViewContextMenu> menu_;
};
} // namespace
IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, PlaceHolderMenuItems) {
MockQuickAnswerClient();
InitMenu();
// Shows quick answers loading state.
ASSERT_EQ(3u, menu()->GetMenuSize());
// Verify the query menu item.
VerifyMenuItems(
/*index=*/0,
/*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
/*expected_title=*/"sel",
/*enabled=*/true);
// Verify the answer menu item.
VerifyMenuItems(
/*index=*/1,
/*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
/*expected_title=*/"Loading...",
/*enabled=*/false);
}
IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, PrimaryAnswerOnly) {
MockQuickAnswerClient();
InitMenu();
std::unique_ptr<QuickAnswer> quick_answer = std::make_unique<QuickAnswer>();
quick_answer->primary_answer = "primary answer";
observer_->OnQuickAnswerReceived(std::move(quick_answer));
// Verify that quick answer menu items is showing.
ASSERT_EQ(3u, menu()->GetMenuSize());
// Verify the query menu item.
VerifyMenuItems(
/*index=*/0,
/*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
/*expected_title=*/"sel",
/*enabled=*/true);
// Verify the answer menu item.
VerifyMenuItems(
/*index=*/1,
/*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
/*expected_title=*/"primary answer",
/*enabled=*/false);
}
IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, SecondaryAnswerOnly) {
MockQuickAnswerClient();
InitMenu();
std::unique_ptr<QuickAnswer> quick_answer = std::make_unique<QuickAnswer>();
quick_answer->secondary_answer = "secondary answer";
observer_->OnQuickAnswerReceived(std::move(quick_answer));
// Verify that quick answer menu items is showing.
ASSERT_EQ(3u, menu()->GetMenuSize());
// Verify the query menu item.
VerifyMenuItems(
/*index=*/0,
/*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
/*expected_title=*/"secondary answer",
/*enabled=*/true);
// Verify the answer menu item.
VerifyMenuItems(
/*index=*/1,
/*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
/*expected_title=*/"See result in Assistant",
/*enabled=*/false);
}
IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest,
PrimaryAndSecondaryAnswer) {
MockQuickAnswerClient();
InitMenu();
std::unique_ptr<QuickAnswer> quick_answer = std::make_unique<QuickAnswer>();
quick_answer->primary_answer = "primary answer";
quick_answer->secondary_answer = "secondary answer";
observer_->OnQuickAnswerReceived(std::move(quick_answer));
// Verify that quick answer menu items is showing.
ASSERT_EQ(3u, menu()->GetMenuSize());
// Verify the query menu item.
VerifyMenuItems(
/*index=*/0,
/*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
/*expected_title=*/"secondary answer",
/*enabled=*/true);
// Verify the answer menu item.
VerifyMenuItems(
/*index=*/1,
/*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
/*expected_title=*/"primary answer",
/*enabled=*/false);
}
IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, NoAnswer) {
MockQuickAnswerClient();
InitMenu();
observer_->OnQuickAnswerReceived(nullptr);
// Verify that quick answer menu items is showing.
ASSERT_EQ(3u, menu()->GetMenuSize());
// Verify the query menu item.
VerifyMenuItems(
/*index=*/0,
/*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
/*expected_title=*/"sel",
/*enabled=*/true);
// Verify the answer menu item.
VerifyMenuItems(
/*index=*/1,
/*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
/*expected_title=*/"See result in Assistant",
/*enabled=*/false);
}
IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, FeatureIneligible) {
observer_->OnEligibilityChanged(false);
// Verify that quick answer client is not called to fetch result.
EXPECT_CALL(*mock_quick_answers_cient_, SendRequest(testing::_)).Times(0);
observer_->SetQuickAnswerClientForTesting(
std::move(mock_quick_answers_cient_));
InitMenu();
// Verify that no Quick Answer menu items shown.
ASSERT_EQ(0u, menu()->GetMenuSize());
}
......@@ -198,6 +198,7 @@
#include "chrome/browser/chromeos/arc/arc_util.h"
#include "chrome/browser/chromeos/arc/intent_helper/open_with_menu.h"
#include "chrome/browser/chromeos/arc/intent_helper/start_smart_selection_action_menu.h"
#include "chrome/browser/renderer_context_menu/quick_answers_menu_observer.h"
#endif
using base::UserMetricsAction;
......@@ -804,6 +805,8 @@ void RenderViewContextMenu::WriteURLToClipboard(const GURL& url) {
void RenderViewContextMenu::InitMenu() {
RenderViewContextMenuBase::InitMenu();
AppendQuickAnswersItems();
if (content_type_->SupportsGroup(
ContextMenuContentType::ITEM_GROUP_PASSWORD)) {
AppendPasswordItems();
......@@ -1331,6 +1334,18 @@ void RenderViewContextMenu::AppendOpenWithLinkItems() {
#endif
}
void RenderViewContextMenu::AppendQuickAnswersItems() {
#if defined(OS_CHROMEOS)
if (!quick_answers_menu_observer_) {
quick_answers_menu_observer_ =
std::make_unique<QuickAnswersMenuObserver>(this);
}
observers_.AddObserver(quick_answers_menu_observer_.get());
quick_answers_menu_observer_->InitMenu(params_);
#endif
}
void RenderViewContextMenu::AppendSmartSelectionActionItems() {
#if defined(OS_CHROMEOS)
start_smart_selection_action_menu_observer_ =
......
......@@ -37,6 +37,7 @@ class AccessibilityLabelsMenuObserver;
class ClickToCallContextMenuObserver;
class PrintPreviewContextMenuObserver;
class Profile;
class QuickAnswersMenuObserver;
class SharedClipboardContextMenuObserver;
class SpellingMenuObserver;
class SpellingOptionsSubMenuObserver;
......@@ -159,6 +160,7 @@ class RenderViewContextMenu : public RenderViewContextMenuBase {
void AppendOpenWithLinkItems();
void AppendSmartSelectionActionItems();
void AppendOpenInBookmarkAppLinkItems();
void AppendQuickAnswersItems();
void AppendImageItems();
void AppendAudioItems();
void AppendCanvasItems();
......@@ -275,6 +277,7 @@ class RenderViewContextMenu : public RenderViewContextMenuBase {
// An observer that handles smart text selection action items.
std::unique_ptr<RenderViewContextMenuObserver>
start_smart_selection_action_menu_observer_;
std::unique_ptr<QuickAnswersMenuObserver> quick_answers_menu_observer_;
#endif
#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
......
......@@ -1495,6 +1495,7 @@ if (!is_android) {
"../browser/policy/accessibility_policy_browsertest.cc",
"../browser/policy/arc_policy_browsertest.cc",
"../browser/policy/assistant_policy_browsertest.cc",
"../browser/renderer_context_menu/quick_answers_menu_observer_browsertest.cc",
]
}
......
......@@ -65,8 +65,8 @@ class QuickAnswersClient : public ash::AssistantStateObserver {
void OnAssistantContextEnabled(bool enabled) override;
void OnLocaleChanged(const std::string& locale) override;
// Send a quick answer request.
void SendRequest(const QuickAnswersRequest& quick_answers_request);
// Send a quick answer request. Virtual for testing.
virtual void SendRequest(const QuickAnswersRequest& quick_answers_request);
private:
void OnQuickAnswerReceived(std::unique_ptr<QuickAnswer> quick_answer);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment