assistant_interaction_controller_impl_unittest.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/assistant/assistant_interaction_controller_impl.h"
  5. #include <algorithm>
  6. #include <map>
  7. #include "ash/assistant/assistant_suggestions_controller_impl.h"
  8. #include "ash/assistant/model/assistant_interaction_model.h"
  9. #include "ash/assistant/model/assistant_interaction_model_observer.h"
  10. #include "ash/assistant/model/assistant_response.h"
  11. #include "ash/assistant/model/assistant_response_observer.h"
  12. #include "ash/assistant/model/ui/assistant_card_element.h"
  13. #include "ash/assistant/model/ui/assistant_error_element.h"
  14. #include "ash/assistant/model/ui/assistant_ui_element.h"
  15. #include "ash/assistant/test/assistant_ash_test_base.h"
  16. #include "ash/assistant/ui/assistant_view_ids.h"
  17. #include "ash/assistant/ui/main_stage/assistant_error_element_view.h"
  18. #include "ash/constants/ash_features.h"
  19. #include "ash/public/cpp/app_list/app_list_features.h"
  20. #include "ash/public/cpp/ash_web_view.h"
  21. #include "ash/public/cpp/assistant/controller/assistant_interaction_controller.h"
  22. #include "ash/public/cpp/assistant/controller/assistant_suggestions_controller.h"
  23. #include "ash/test/fake_android_intent_helper.h"
  24. #include "ash/test/test_ash_web_view.h"
  25. #include "base/bind.h"
  26. #include "base/run_loop.h"
  27. #include "base/test/scoped_feature_list.h"
  28. #include "base/time/time.h"
  29. #include "chromeos/ash/services/assistant/public/cpp/assistant_service.h"
  30. #include "chromeos/ash/services/assistant/public/cpp/features.h"
  31. #include "chromeos/ash/services/assistant/test_support/mock_assistant_interaction_subscriber.h"
  32. #include "testing/gmock/include/gmock/gmock.h"
  33. namespace ash {
  34. namespace {
  35. using assistant::Assistant;
  36. using assistant::AssistantInteractionSubscriber;
  37. using assistant::MockAssistantInteractionSubscriber;
  38. using assistant::ScopedAssistantInteractionSubscriber;
  39. using chromeos::assistant::AndroidAppInfo;
  40. using chromeos::assistant::AssistantInteractionMetadata;
  41. using chromeos::assistant::AssistantInteractionType;
  42. using chromeos::assistant::AssistantQuerySource;
  43. using chromeos::assistant::AssistantSuggestion;
  44. using chromeos::assistant::AssistantSuggestionType;
  45. using ::testing::Invoke;
  46. using ::testing::Mock;
  47. using ::testing::Return;
  48. using ::testing::StrictMock;
  49. // Mocks -----------------------------------------------------------------------
  50. class AssistantInteractionSubscriberMock
  51. : public AssistantInteractionSubscriber {
  52. public:
  53. explicit AssistantInteractionSubscriberMock(Assistant* service) {
  54. scoped_subscriber_.Observe(service);
  55. }
  56. ~AssistantInteractionSubscriberMock() override = default;
  57. MOCK_METHOD(void,
  58. OnInteractionStarted,
  59. (const AssistantInteractionMetadata&),
  60. (override));
  61. private:
  62. ScopedAssistantInteractionSubscriber scoped_subscriber_{this};
  63. };
  64. // AssistantInteractionControllerImplTest --------------------------------------
  65. class AssistantInteractionControllerImplTest : public AssistantAshTestBase {
  66. public:
  67. AssistantInteractionControllerImplTest() = default;
  68. AssistantInteractionControllerImpl* interaction_controller() {
  69. return static_cast<AssistantInteractionControllerImpl*>(
  70. AssistantInteractionController::Get());
  71. }
  72. AssistantSuggestionsControllerImpl* suggestion_controller() {
  73. return static_cast<AssistantSuggestionsControllerImpl*>(
  74. AssistantSuggestionsController::Get());
  75. }
  76. const AssistantInteractionModel* interaction_model() {
  77. return interaction_controller()->GetModel();
  78. }
  79. void StartInteraction() {
  80. interaction_controller()->OnInteractionStarted(
  81. AssistantInteractionMetadata());
  82. }
  83. AndroidAppInfo CreateAndroidAppInfo(const std::string& app_name = "unknown") {
  84. AndroidAppInfo result;
  85. result.localized_app_name = app_name;
  86. return result;
  87. }
  88. };
  89. AssistantCardElement* GetAssistantCardElement(
  90. const std::vector<std::unique_ptr<AssistantUiElement>>& ui_elements) {
  91. if (ui_elements.size() != 1lu ||
  92. ui_elements.front()->type() != AssistantUiElementType::kCard) {
  93. return nullptr;
  94. }
  95. return static_cast<AssistantCardElement*>(ui_elements.front().get());
  96. }
  97. } // namespace
  98. TEST_F(AssistantInteractionControllerImplTest,
  99. ShouldBecomeActiveWhenInteractionStarts) {
  100. EXPECT_EQ(interaction_model()->interaction_state(),
  101. InteractionState::kInactive);
  102. interaction_controller()->OnInteractionStarted(
  103. AssistantInteractionMetadata());
  104. EXPECT_EQ(interaction_model()->interaction_state(),
  105. InteractionState::kActive);
  106. }
  107. TEST_F(AssistantInteractionControllerImplTest,
  108. ShouldBeNoOpWhenOpenAppIsCalledWhileInactive) {
  109. EXPECT_EQ(interaction_model()->interaction_state(),
  110. InteractionState::kInactive);
  111. FakeAndroidIntentHelper fake_helper;
  112. fake_helper.AddApp("app-name", "app-intent");
  113. interaction_controller()->OnOpenAppResponse(CreateAndroidAppInfo("app-name"));
  114. EXPECT_FALSE(fake_helper.last_launched_android_intent().has_value());
  115. }
  116. TEST_F(AssistantInteractionControllerImplTest,
  117. ShouldBeNoOpWhenOpenAppIsCalledForUnknownAndroidApp) {
  118. StartInteraction();
  119. FakeAndroidIntentHelper fake_helper;
  120. interaction_controller()->OnOpenAppResponse(
  121. CreateAndroidAppInfo("unknown-app-name"));
  122. EXPECT_FALSE(fake_helper.last_launched_android_intent().has_value());
  123. }
  124. TEST_F(AssistantInteractionControllerImplTest,
  125. ShouldLaunchAppAndReturnSuccessWhenOpenAppIsCalled) {
  126. const std::string app_name = "AppName";
  127. const std::string intent = "intent://AppName";
  128. StartInteraction();
  129. FakeAndroidIntentHelper fake_helper;
  130. fake_helper.AddApp(app_name, intent);
  131. interaction_controller()->OnOpenAppResponse(CreateAndroidAppInfo(app_name));
  132. EXPECT_EQ(intent, fake_helper.last_launched_android_intent());
  133. }
  134. TEST_F(AssistantInteractionControllerImplTest,
  135. ShouldAddSchemeToIntentWhenLaunchingAndroidApp) {
  136. const std::string app_name = "AppName";
  137. const std::string intent = "#Intent-without-a-scheme";
  138. const std::string intent_with_scheme = "intent://" + intent;
  139. StartInteraction();
  140. FakeAndroidIntentHelper fake_helper;
  141. fake_helper.AddApp(app_name, intent);
  142. interaction_controller()->OnOpenAppResponse(CreateAndroidAppInfo(app_name));
  143. EXPECT_EQ(intent_with_scheme, fake_helper.last_launched_android_intent());
  144. }
  145. TEST_F(AssistantInteractionControllerImplTest,
  146. ShouldCorrectlyMapSuggestionTypeToQuerySource) {
  147. // Mock Assistant interaction subscriber.
  148. StrictMock<AssistantInteractionSubscriberMock> mock(assistant_service());
  149. // Configure the expected mappings between suggestion type and query source.
  150. const std::map<AssistantSuggestionType, AssistantQuerySource>
  151. types_to_sources = {{AssistantSuggestionType::kConversationStarter,
  152. AssistantQuerySource::kConversationStarter},
  153. {AssistantSuggestionType::kBetterOnboarding,
  154. AssistantQuerySource::kBetterOnboarding},
  155. {AssistantSuggestionType::kUnspecified,
  156. AssistantQuerySource::kSuggestionChip}};
  157. // Iterate over all expected mappings.
  158. for (const auto& type_to_source : types_to_sources) {
  159. base::RunLoop run_loop;
  160. // Confirm subscribers are delivered the expected query source...
  161. EXPECT_CALL(mock, OnInteractionStarted)
  162. .WillOnce(Invoke([&](const AssistantInteractionMetadata& metadata) {
  163. EXPECT_EQ(type_to_source.second, metadata.source);
  164. run_loop.QuitClosure().Run();
  165. }));
  166. AssistantSuggestion suggestion{/*id=*/base::UnguessableToken::Create(),
  167. /*type=*/type_to_source.first,
  168. /*text=*/""};
  169. const_cast<AssistantSuggestionsModel*>(suggestion_controller()->GetModel())
  170. ->SetConversationStarters({suggestion});
  171. // ...when an Assistant suggestion of a given type is pressed.
  172. interaction_controller()->OnSuggestionPressed(suggestion.id);
  173. run_loop.Run();
  174. }
  175. }
  176. TEST_F(AssistantInteractionControllerImplTest, ShouldDisplayGenericErrorOnce) {
  177. StartInteraction();
  178. // Call OnTtsStarted twice to mimic the behavior of libassistant when network
  179. // is disconnected.
  180. interaction_controller()->OnTtsStarted(/*due_to_error=*/true);
  181. interaction_controller()->OnTtsStarted(/*due_to_error=*/true);
  182. base::RunLoop().RunUntilIdle();
  183. auto& ui_elements =
  184. interaction_controller()->GetModel()->response()->GetUiElements();
  185. EXPECT_EQ(ui_elements.size(), 1ul);
  186. EXPECT_EQ(ui_elements.front()->type(), AssistantUiElementType::kError);
  187. base::RunLoop().RunUntilIdle();
  188. interaction_controller()->OnInteractionFinished(
  189. assistant::AssistantInteractionResolution::kError);
  190. base::RunLoop().RunUntilIdle();
  191. EXPECT_EQ(ui_elements.size(), 1ul);
  192. EXPECT_EQ(ui_elements.front()->type(), AssistantUiElementType::kError);
  193. }
  194. TEST_F(AssistantInteractionControllerImplTest,
  195. ShouldUpdateTimeOfLastInteraction) {
  196. MockAssistantInteractionSubscriber mock_subscriber;
  197. ScopedAssistantInteractionSubscriber scoped_subscriber{&mock_subscriber};
  198. scoped_subscriber.Observe(assistant_service());
  199. base::RunLoop run_loop;
  200. base::Time actual_time_of_last_interaction;
  201. EXPECT_CALL(mock_subscriber, OnInteractionStarted)
  202. .WillOnce(Invoke([&](const AssistantInteractionMetadata& metadata) {
  203. actual_time_of_last_interaction = base::Time::Now();
  204. run_loop.QuitClosure().Run();
  205. }));
  206. ShowAssistantUi();
  207. MockTextInteraction().WithTextResponse("<Any-Text-Response>");
  208. run_loop.Run();
  209. auto actual = interaction_controller()->GetTimeDeltaSinceLastInteraction();
  210. auto expected = base::Time::Now() - actual_time_of_last_interaction;
  211. EXPECT_NEAR(actual.InSeconds(), expected.InSeconds(), 1);
  212. }
  213. TEST_F(AssistantInteractionControllerImplTest, CompactBubbleLauncher) {
  214. static constexpr int kStandardLayoutAshWebViewWidth = 592;
  215. static constexpr int kNarrowLayoutAshWebViewWidth = 496;
  216. base::test::ScopedFeatureList scoped_feature_list;
  217. scoped_feature_list.InitWithFeatures(
  218. {app_list_features::kCompactBubbleLauncher,
  219. features::kProductivityLauncher},
  220. {});
  221. UpdateDisplay("1200x800");
  222. ShowAssistantUi();
  223. StartInteraction();
  224. interaction_controller()->OnHtmlResponse("<html></html>", "fallback");
  225. base::RunLoop().RunUntilIdle();
  226. AssistantCardElement* card_element = GetAssistantCardElement(
  227. interaction_controller()->GetModel()->response()->GetUiElements());
  228. ASSERT_TRUE(card_element);
  229. EXPECT_EQ(card_element->viewport_width(), 638);
  230. EXPECT_EQ(
  231. page_view()->GetViewByID(AssistantViewID::kAshWebView)->size().width(),
  232. kStandardLayoutAshWebViewWidth);
  233. ASSERT_TRUE(page_view()->GetViewByID(AssistantViewID::kAshWebView) !=
  234. nullptr);
  235. TestAshWebView* ash_web_view = static_cast<TestAshWebView*>(
  236. page_view()->GetViewByID(AssistantViewID::kAshWebView));
  237. // max_size and min_size in AshWebView::InitParams are different from the view
  238. // size. min_size affects to the size of rendered content, i.e. renderer will
  239. // try to render the content to the size. But View::Size() doesn't.
  240. ASSERT_TRUE(ash_web_view->init_params_for_testing().max_size);
  241. ASSERT_TRUE(ash_web_view->init_params_for_testing().min_size);
  242. EXPECT_EQ(ash_web_view->init_params_for_testing().max_size.value().width(),
  243. kStandardLayoutAshWebViewWidth);
  244. EXPECT_EQ(ash_web_view->init_params_for_testing().min_size.value().width(),
  245. kStandardLayoutAshWebViewWidth);
  246. CloseAssistantUi();
  247. // Change work area width < 1200 and confirm that the viewport width gets
  248. // updated to narrow layout one.
  249. UpdateDisplay("1199x800");
  250. ShowAssistantUi();
  251. StartInteraction();
  252. interaction_controller()->OnHtmlResponse("<html></html>", "fallback");
  253. base::RunLoop().RunUntilIdle();
  254. card_element = GetAssistantCardElement(
  255. interaction_controller()->GetModel()->response()->GetUiElements());
  256. ASSERT_TRUE(card_element);
  257. ASSERT_TRUE(page_view()->GetViewByID(AssistantViewID::kAshWebView) !=
  258. nullptr);
  259. EXPECT_EQ(card_element->viewport_width(), 542);
  260. EXPECT_EQ(
  261. page_view()->GetViewByID(AssistantViewID::kAshWebView)->size().width(),
  262. kNarrowLayoutAshWebViewWidth);
  263. ASSERT_TRUE(page_view()->GetViewByID(AssistantViewID::kAshWebView) !=
  264. nullptr);
  265. ash_web_view = static_cast<TestAshWebView*>(
  266. page_view()->GetViewByID(AssistantViewID::kAshWebView));
  267. ASSERT_TRUE(ash_web_view->init_params_for_testing().max_size);
  268. ASSERT_TRUE(ash_web_view->init_params_for_testing().min_size);
  269. EXPECT_EQ(ash_web_view->init_params_for_testing().max_size.value().width(),
  270. kNarrowLayoutAshWebViewWidth);
  271. EXPECT_EQ(ash_web_view->init_params_for_testing().min_size.value().width(),
  272. kNarrowLayoutAshWebViewWidth);
  273. }
  274. TEST_F(AssistantInteractionControllerImplTest, FixedZoomLevel) {
  275. ShowAssistantUi();
  276. StartInteraction();
  277. interaction_controller()->OnHtmlResponse("<html></html>", "fallback");
  278. base::RunLoop().RunUntilIdle();
  279. TestAshWebView* ash_web_view = static_cast<TestAshWebView*>(
  280. page_view()->GetViewByID(AssistantViewID::kAshWebView));
  281. EXPECT_TRUE(ash_web_view->init_params_for_testing().fix_zoom_level_to_one);
  282. }
  283. } // namespace ash