123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- // 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 "ash/assistant/assistant_interaction_controller_impl.h"
- #include <algorithm>
- #include <map>
- #include "ash/assistant/assistant_suggestions_controller_impl.h"
- #include "ash/assistant/model/assistant_interaction_model.h"
- #include "ash/assistant/model/assistant_interaction_model_observer.h"
- #include "ash/assistant/model/assistant_response.h"
- #include "ash/assistant/model/assistant_response_observer.h"
- #include "ash/assistant/model/ui/assistant_card_element.h"
- #include "ash/assistant/model/ui/assistant_error_element.h"
- #include "ash/assistant/model/ui/assistant_ui_element.h"
- #include "ash/assistant/test/assistant_ash_test_base.h"
- #include "ash/assistant/ui/assistant_view_ids.h"
- #include "ash/assistant/ui/main_stage/assistant_error_element_view.h"
- #include "ash/constants/ash_features.h"
- #include "ash/public/cpp/app_list/app_list_features.h"
- #include "ash/public/cpp/ash_web_view.h"
- #include "ash/public/cpp/assistant/controller/assistant_interaction_controller.h"
- #include "ash/public/cpp/assistant/controller/assistant_suggestions_controller.h"
- #include "ash/test/fake_android_intent_helper.h"
- #include "ash/test/test_ash_web_view.h"
- #include "base/bind.h"
- #include "base/run_loop.h"
- #include "base/test/scoped_feature_list.h"
- #include "base/time/time.h"
- #include "chromeos/ash/services/assistant/public/cpp/assistant_service.h"
- #include "chromeos/ash/services/assistant/public/cpp/features.h"
- #include "chromeos/ash/services/assistant/test_support/mock_assistant_interaction_subscriber.h"
- #include "testing/gmock/include/gmock/gmock.h"
- namespace ash {
- namespace {
- using assistant::Assistant;
- using assistant::AssistantInteractionSubscriber;
- using assistant::MockAssistantInteractionSubscriber;
- using assistant::ScopedAssistantInteractionSubscriber;
- using chromeos::assistant::AndroidAppInfo;
- using chromeos::assistant::AssistantInteractionMetadata;
- using chromeos::assistant::AssistantInteractionType;
- using chromeos::assistant::AssistantQuerySource;
- using chromeos::assistant::AssistantSuggestion;
- using chromeos::assistant::AssistantSuggestionType;
- using ::testing::Invoke;
- using ::testing::Mock;
- using ::testing::Return;
- using ::testing::StrictMock;
- // Mocks -----------------------------------------------------------------------
- class AssistantInteractionSubscriberMock
- : public AssistantInteractionSubscriber {
- public:
- explicit AssistantInteractionSubscriberMock(Assistant* service) {
- scoped_subscriber_.Observe(service);
- }
- ~AssistantInteractionSubscriberMock() override = default;
- MOCK_METHOD(void,
- OnInteractionStarted,
- (const AssistantInteractionMetadata&),
- (override));
- private:
- ScopedAssistantInteractionSubscriber scoped_subscriber_{this};
- };
- // AssistantInteractionControllerImplTest --------------------------------------
- class AssistantInteractionControllerImplTest : public AssistantAshTestBase {
- public:
- AssistantInteractionControllerImplTest() = default;
- AssistantInteractionControllerImpl* interaction_controller() {
- return static_cast<AssistantInteractionControllerImpl*>(
- AssistantInteractionController::Get());
- }
- AssistantSuggestionsControllerImpl* suggestion_controller() {
- return static_cast<AssistantSuggestionsControllerImpl*>(
- AssistantSuggestionsController::Get());
- }
- const AssistantInteractionModel* interaction_model() {
- return interaction_controller()->GetModel();
- }
- void StartInteraction() {
- interaction_controller()->OnInteractionStarted(
- AssistantInteractionMetadata());
- }
- AndroidAppInfo CreateAndroidAppInfo(const std::string& app_name = "unknown") {
- AndroidAppInfo result;
- result.localized_app_name = app_name;
- return result;
- }
- };
- AssistantCardElement* GetAssistantCardElement(
- const std::vector<std::unique_ptr<AssistantUiElement>>& ui_elements) {
- if (ui_elements.size() != 1lu ||
- ui_elements.front()->type() != AssistantUiElementType::kCard) {
- return nullptr;
- }
- return static_cast<AssistantCardElement*>(ui_elements.front().get());
- }
- } // namespace
- TEST_F(AssistantInteractionControllerImplTest,
- ShouldBecomeActiveWhenInteractionStarts) {
- EXPECT_EQ(interaction_model()->interaction_state(),
- InteractionState::kInactive);
- interaction_controller()->OnInteractionStarted(
- AssistantInteractionMetadata());
- EXPECT_EQ(interaction_model()->interaction_state(),
- InteractionState::kActive);
- }
- TEST_F(AssistantInteractionControllerImplTest,
- ShouldBeNoOpWhenOpenAppIsCalledWhileInactive) {
- EXPECT_EQ(interaction_model()->interaction_state(),
- InteractionState::kInactive);
- FakeAndroidIntentHelper fake_helper;
- fake_helper.AddApp("app-name", "app-intent");
- interaction_controller()->OnOpenAppResponse(CreateAndroidAppInfo("app-name"));
- EXPECT_FALSE(fake_helper.last_launched_android_intent().has_value());
- }
- TEST_F(AssistantInteractionControllerImplTest,
- ShouldBeNoOpWhenOpenAppIsCalledForUnknownAndroidApp) {
- StartInteraction();
- FakeAndroidIntentHelper fake_helper;
- interaction_controller()->OnOpenAppResponse(
- CreateAndroidAppInfo("unknown-app-name"));
- EXPECT_FALSE(fake_helper.last_launched_android_intent().has_value());
- }
- TEST_F(AssistantInteractionControllerImplTest,
- ShouldLaunchAppAndReturnSuccessWhenOpenAppIsCalled) {
- const std::string app_name = "AppName";
- const std::string intent = "intent://AppName";
- StartInteraction();
- FakeAndroidIntentHelper fake_helper;
- fake_helper.AddApp(app_name, intent);
- interaction_controller()->OnOpenAppResponse(CreateAndroidAppInfo(app_name));
- EXPECT_EQ(intent, fake_helper.last_launched_android_intent());
- }
- TEST_F(AssistantInteractionControllerImplTest,
- ShouldAddSchemeToIntentWhenLaunchingAndroidApp) {
- const std::string app_name = "AppName";
- const std::string intent = "#Intent-without-a-scheme";
- const std::string intent_with_scheme = "intent://" + intent;
- StartInteraction();
- FakeAndroidIntentHelper fake_helper;
- fake_helper.AddApp(app_name, intent);
- interaction_controller()->OnOpenAppResponse(CreateAndroidAppInfo(app_name));
- EXPECT_EQ(intent_with_scheme, fake_helper.last_launched_android_intent());
- }
- TEST_F(AssistantInteractionControllerImplTest,
- ShouldCorrectlyMapSuggestionTypeToQuerySource) {
- // Mock Assistant interaction subscriber.
- StrictMock<AssistantInteractionSubscriberMock> mock(assistant_service());
- // Configure the expected mappings between suggestion type and query source.
- const std::map<AssistantSuggestionType, AssistantQuerySource>
- types_to_sources = {{AssistantSuggestionType::kConversationStarter,
- AssistantQuerySource::kConversationStarter},
- {AssistantSuggestionType::kBetterOnboarding,
- AssistantQuerySource::kBetterOnboarding},
- {AssistantSuggestionType::kUnspecified,
- AssistantQuerySource::kSuggestionChip}};
- // Iterate over all expected mappings.
- for (const auto& type_to_source : types_to_sources) {
- base::RunLoop run_loop;
- // Confirm subscribers are delivered the expected query source...
- EXPECT_CALL(mock, OnInteractionStarted)
- .WillOnce(Invoke([&](const AssistantInteractionMetadata& metadata) {
- EXPECT_EQ(type_to_source.second, metadata.source);
- run_loop.QuitClosure().Run();
- }));
- AssistantSuggestion suggestion{/*id=*/base::UnguessableToken::Create(),
- /*type=*/type_to_source.first,
- /*text=*/""};
- const_cast<AssistantSuggestionsModel*>(suggestion_controller()->GetModel())
- ->SetConversationStarters({suggestion});
- // ...when an Assistant suggestion of a given type is pressed.
- interaction_controller()->OnSuggestionPressed(suggestion.id);
- run_loop.Run();
- }
- }
- TEST_F(AssistantInteractionControllerImplTest, ShouldDisplayGenericErrorOnce) {
- StartInteraction();
- // Call OnTtsStarted twice to mimic the behavior of libassistant when network
- // is disconnected.
- interaction_controller()->OnTtsStarted(/*due_to_error=*/true);
- interaction_controller()->OnTtsStarted(/*due_to_error=*/true);
- base::RunLoop().RunUntilIdle();
- auto& ui_elements =
- interaction_controller()->GetModel()->response()->GetUiElements();
- EXPECT_EQ(ui_elements.size(), 1ul);
- EXPECT_EQ(ui_elements.front()->type(), AssistantUiElementType::kError);
- base::RunLoop().RunUntilIdle();
- interaction_controller()->OnInteractionFinished(
- assistant::AssistantInteractionResolution::kError);
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(ui_elements.size(), 1ul);
- EXPECT_EQ(ui_elements.front()->type(), AssistantUiElementType::kError);
- }
- TEST_F(AssistantInteractionControllerImplTest,
- ShouldUpdateTimeOfLastInteraction) {
- MockAssistantInteractionSubscriber mock_subscriber;
- ScopedAssistantInteractionSubscriber scoped_subscriber{&mock_subscriber};
- scoped_subscriber.Observe(assistant_service());
- base::RunLoop run_loop;
- base::Time actual_time_of_last_interaction;
- EXPECT_CALL(mock_subscriber, OnInteractionStarted)
- .WillOnce(Invoke([&](const AssistantInteractionMetadata& metadata) {
- actual_time_of_last_interaction = base::Time::Now();
- run_loop.QuitClosure().Run();
- }));
- ShowAssistantUi();
- MockTextInteraction().WithTextResponse("<Any-Text-Response>");
- run_loop.Run();
- auto actual = interaction_controller()->GetTimeDeltaSinceLastInteraction();
- auto expected = base::Time::Now() - actual_time_of_last_interaction;
- EXPECT_NEAR(actual.InSeconds(), expected.InSeconds(), 1);
- }
- TEST_F(AssistantInteractionControllerImplTest, CompactBubbleLauncher) {
- static constexpr int kStandardLayoutAshWebViewWidth = 592;
- static constexpr int kNarrowLayoutAshWebViewWidth = 496;
- base::test::ScopedFeatureList scoped_feature_list;
- scoped_feature_list.InitWithFeatures(
- {app_list_features::kCompactBubbleLauncher,
- features::kProductivityLauncher},
- {});
- UpdateDisplay("1200x800");
- ShowAssistantUi();
- StartInteraction();
- interaction_controller()->OnHtmlResponse("<html></html>", "fallback");
- base::RunLoop().RunUntilIdle();
- AssistantCardElement* card_element = GetAssistantCardElement(
- interaction_controller()->GetModel()->response()->GetUiElements());
- ASSERT_TRUE(card_element);
- EXPECT_EQ(card_element->viewport_width(), 638);
- EXPECT_EQ(
- page_view()->GetViewByID(AssistantViewID::kAshWebView)->size().width(),
- kStandardLayoutAshWebViewWidth);
- ASSERT_TRUE(page_view()->GetViewByID(AssistantViewID::kAshWebView) !=
- nullptr);
- TestAshWebView* ash_web_view = static_cast<TestAshWebView*>(
- page_view()->GetViewByID(AssistantViewID::kAshWebView));
- // max_size and min_size in AshWebView::InitParams are different from the view
- // size. min_size affects to the size of rendered content, i.e. renderer will
- // try to render the content to the size. But View::Size() doesn't.
- ASSERT_TRUE(ash_web_view->init_params_for_testing().max_size);
- ASSERT_TRUE(ash_web_view->init_params_for_testing().min_size);
- EXPECT_EQ(ash_web_view->init_params_for_testing().max_size.value().width(),
- kStandardLayoutAshWebViewWidth);
- EXPECT_EQ(ash_web_view->init_params_for_testing().min_size.value().width(),
- kStandardLayoutAshWebViewWidth);
- CloseAssistantUi();
- // Change work area width < 1200 and confirm that the viewport width gets
- // updated to narrow layout one.
- UpdateDisplay("1199x800");
- ShowAssistantUi();
- StartInteraction();
- interaction_controller()->OnHtmlResponse("<html></html>", "fallback");
- base::RunLoop().RunUntilIdle();
- card_element = GetAssistantCardElement(
- interaction_controller()->GetModel()->response()->GetUiElements());
- ASSERT_TRUE(card_element);
- ASSERT_TRUE(page_view()->GetViewByID(AssistantViewID::kAshWebView) !=
- nullptr);
- EXPECT_EQ(card_element->viewport_width(), 542);
- EXPECT_EQ(
- page_view()->GetViewByID(AssistantViewID::kAshWebView)->size().width(),
- kNarrowLayoutAshWebViewWidth);
- ASSERT_TRUE(page_view()->GetViewByID(AssistantViewID::kAshWebView) !=
- nullptr);
- ash_web_view = static_cast<TestAshWebView*>(
- page_view()->GetViewByID(AssistantViewID::kAshWebView));
- ASSERT_TRUE(ash_web_view->init_params_for_testing().max_size);
- ASSERT_TRUE(ash_web_view->init_params_for_testing().min_size);
- EXPECT_EQ(ash_web_view->init_params_for_testing().max_size.value().width(),
- kNarrowLayoutAshWebViewWidth);
- EXPECT_EQ(ash_web_view->init_params_for_testing().min_size.value().width(),
- kNarrowLayoutAshWebViewWidth);
- }
- TEST_F(AssistantInteractionControllerImplTest, FixedZoomLevel) {
- ShowAssistantUi();
- StartInteraction();
- interaction_controller()->OnHtmlResponse("<html></html>", "fallback");
- base::RunLoop().RunUntilIdle();
- TestAshWebView* ash_web_view = static_cast<TestAshWebView*>(
- page_view()->GetViewByID(AssistantViewID::kAshWebView));
- EXPECT_TRUE(ash_web_view->init_params_for_testing().fix_zoom_level_to_one);
- }
- } // namespace ash
|