1 // Copyright 2018 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
5 #include "ash/assistant/test/test_assistant_service.h"
6
7 #include <memory>
8 #include <utility>
9 #include <vector>
10
11 #include "base/unguessable_token.h"
12 #include "chromeos/services/assistant/public/cpp/assistant_service.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14
15 namespace ash {
16
17 using chromeos::assistant::AssistantInteractionMetadata;
18 using chromeos::assistant::AssistantInteractionResolution;
19 using chromeos::assistant::AssistantInteractionSubscriber;
20 using chromeos::assistant::AssistantInteractionType;
21 using chromeos::assistant::AssistantSuggestion;
22
23 // Subscriber that will ensure the LibAssistant contract is enforced.
24 // More specifically, it will ensure that:
25 // - A conversation is finished before starting a new one.
26 // - No responses (text, card, ...) are sent before starting or after
27 // finishing an interaction.
28 class LibassistantContractChecker : public AssistantInteractionSubscriber {
29 public:
30 LibassistantContractChecker() = default;
31 ~LibassistantContractChecker() override = default;
32
33 // DefaultAssistantInteractionSubscriber implementation:
OnInteractionStarted(const AssistantInteractionMetadata & metadata)34 void OnInteractionStarted(
35 const AssistantInteractionMetadata& metadata) override {
36 if (current_state_ == ConversationState::kInProgress) {
37 ADD_FAILURE()
38 << "Cannot start a new Assistant interaction without finishing the "
39 "previous interaction first.";
40 }
41 current_state_ = ConversationState::kInProgress;
42 }
43
OnInteractionFinished(AssistantInteractionResolution resolution)44 void OnInteractionFinished(
45 AssistantInteractionResolution resolution) override {
46 // Note: We do not check |current_state_| here as this method can be called
47 // even if no interaction is in progress.
48 current_state_ = ConversationState::kFinished;
49 }
50
OnHtmlResponse(const std::string & response,const std::string & fallback)51 void OnHtmlResponse(const std::string& response,
52 const std::string& fallback) override {
53 CheckResponse();
54 }
55
OnSuggestionsResponse(const std::vector<chromeos::assistant::AssistantSuggestion> & response)56 void OnSuggestionsResponse(
57 const std::vector<chromeos::assistant::AssistantSuggestion>& response)
58 override {
59 CheckResponse();
60 }
61
OnTextResponse(const std::string & response)62 void OnTextResponse(const std::string& response) override { CheckResponse(); }
63
OnOpenUrlResponse(const::GURL & url,bool in_background)64 void OnOpenUrlResponse(const ::GURL& url, bool in_background) override {
65 CheckResponse();
66 }
67
OnOpenAppResponse(const chromeos::assistant::AndroidAppInfo & app_info)68 bool OnOpenAppResponse(
69 const chromeos::assistant::AndroidAppInfo& app_info) override {
70 CheckResponse();
71 return false;
72 }
73
74 private:
CheckResponse()75 void CheckResponse() {
76 if (current_state_ == ConversationState::kNotStarted)
77 ADD_FAILURE() << "Cannot send a response before starting an interaction.";
78 if (current_state_ == ConversationState::kFinished) {
79 ADD_FAILURE()
80 << "Cannot send a response after finishing the interaction.";
81 }
82 }
83
84 enum class ConversationState {
85 kNotStarted,
86 kInProgress,
87 kFinished,
88 };
89
90 ConversationState current_state_ = ConversationState::kNotStarted;
91
92 DISALLOW_COPY_AND_ASSIGN(LibassistantContractChecker);
93 };
94
95 // Subscriber that tracks the current interaction.
96 class CurrentInteractionSubscriber : public AssistantInteractionSubscriber {
97 public:
98 CurrentInteractionSubscriber() = default;
99 CurrentInteractionSubscriber(CurrentInteractionSubscriber&) = delete;
100 CurrentInteractionSubscriber& operator=(CurrentInteractionSubscriber&) =
101 delete;
102 ~CurrentInteractionSubscriber() override = default;
103
104 // AssistantInteractionSubscriber implementation:
OnInteractionStarted(const AssistantInteractionMetadata & metadata)105 void OnInteractionStarted(
106 const AssistantInteractionMetadata& metadata) override {
107 current_interaction_ = metadata;
108 }
109
OnInteractionFinished(AssistantInteractionResolution resolution)110 void OnInteractionFinished(
111 AssistantInteractionResolution resolution) override {
112 current_interaction_ = base::nullopt;
113 }
114
current_interaction()115 base::Optional<AssistantInteractionMetadata> current_interaction() {
116 return current_interaction_;
117 }
118
119 private:
120 base::Optional<AssistantInteractionMetadata> current_interaction_ =
121 base::nullopt;
122 };
123
124 class InteractionResponse::Response {
125 public:
126 Response() = default;
127 virtual ~Response() = default;
128
129 virtual void SendTo(
130 chromeos::assistant::AssistantInteractionSubscriber* receiver) = 0;
131 };
132
133 class TextResponse : public InteractionResponse::Response {
134 public:
TextResponse(const std::string & text)135 explicit TextResponse(const std::string& text) : text_(text) {}
136 ~TextResponse() override = default;
137
SendTo(chromeos::assistant::AssistantInteractionSubscriber * receiver)138 void SendTo(
139 chromeos::assistant::AssistantInteractionSubscriber* receiver) override {
140 receiver->OnTextResponse(text_);
141 }
142
143 private:
144 std::string text_;
145
146 DISALLOW_COPY_AND_ASSIGN(TextResponse);
147 };
148
149 class SuggestionsResponse : public InteractionResponse::Response {
150 public:
SuggestionsResponse(const std::string & text)151 explicit SuggestionsResponse(const std::string& text) : text_(text) {}
152 SuggestionsResponse(const SuggestionsResponse&) = delete;
153 SuggestionsResponse& operator=(const SuggestionsResponse&) = delete;
154 ~SuggestionsResponse() override = default;
155
SendTo(chromeos::assistant::AssistantInteractionSubscriber * receiver)156 void SendTo(
157 chromeos::assistant::AssistantInteractionSubscriber* receiver) override {
158 std::vector<AssistantSuggestion> suggestions;
159 suggestions.emplace_back();
160 auto& suggestion = suggestions.back();
161 suggestion.text = text_;
162 suggestion.id = base::UnguessableToken::Create();
163 receiver->OnSuggestionsResponse(suggestions);
164 }
165
166 private:
167 std::string text_;
168 };
169
170 class ResolutionResponse : public InteractionResponse::Response {
171 public:
172 using Resolution = InteractionResponse::Resolution;
173
ResolutionResponse(Resolution resolution)174 explicit ResolutionResponse(Resolution resolution)
175 : resolution_(resolution) {}
176 ~ResolutionResponse() override = default;
177
SendTo(chromeos::assistant::AssistantInteractionSubscriber * receiver)178 void SendTo(
179 chromeos::assistant::AssistantInteractionSubscriber* receiver) override {
180 receiver->OnInteractionFinished(resolution_);
181 }
182
183 private:
184 Resolution resolution_;
185
186 DISALLOW_COPY_AND_ASSIGN(ResolutionResponse);
187 };
188
TestAssistantService()189 TestAssistantService::TestAssistantService()
190 : libassistant_contract_checker_(
191 std::make_unique<LibassistantContractChecker>()),
192 current_interaction_subscriber_(
193 std::make_unique<CurrentInteractionSubscriber>()) {
194 AddAssistantInteractionSubscriber(libassistant_contract_checker_.get());
195 AddAssistantInteractionSubscriber(current_interaction_subscriber_.get());
196 }
197
198 TestAssistantService::~TestAssistantService() = default;
199
SetInteractionResponse(std::unique_ptr<InteractionResponse> response)200 void TestAssistantService::SetInteractionResponse(
201 std::unique_ptr<InteractionResponse> response) {
202 interaction_response_ = std::move(response);
203 }
204
205 base::Optional<AssistantInteractionMetadata>
current_interaction()206 TestAssistantService::current_interaction() {
207 return current_interaction_subscriber_->current_interaction();
208 }
209
StartEditReminderInteraction(const std::string & client_id)210 void TestAssistantService::StartEditReminderInteraction(
211 const std::string& client_id) {}
212
StartScreenContextInteraction(ax::mojom::AssistantStructurePtr assistant_structure,const std::vector<uint8_t> & assistant_screenshot)213 void TestAssistantService::StartScreenContextInteraction(
214 ax::mojom::AssistantStructurePtr assistant_structure,
215 const std::vector<uint8_t>& assistant_screenshot) {}
216
StartTextInteraction(const std::string & query,chromeos::assistant::AssistantQuerySource source,bool allow_tts)217 void TestAssistantService::StartTextInteraction(
218 const std::string& query,
219 chromeos::assistant::AssistantQuerySource source,
220 bool allow_tts) {
221 StartInteraction(AssistantInteractionType::kText, source, query);
222 if (interaction_response_)
223 SendInteractionResponse();
224 }
225
StartVoiceInteraction()226 void TestAssistantService::StartVoiceInteraction() {
227 StartInteraction(AssistantInteractionType::kVoice);
228 if (interaction_response_)
229 SendInteractionResponse();
230 }
231
StopActiveInteraction(bool cancel_conversation)232 void TestAssistantService::StopActiveInteraction(bool cancel_conversation) {
233 if (!running_active_interaction_)
234 return;
235
236 running_active_interaction_ = false;
237 for (auto& subscriber : interaction_subscribers_) {
238 subscriber.OnInteractionFinished(
239 AssistantInteractionResolution::kInterruption);
240 }
241 }
242
AddAssistantInteractionSubscriber(AssistantInteractionSubscriber * subscriber)243 void TestAssistantService::AddAssistantInteractionSubscriber(
244 AssistantInteractionSubscriber* subscriber) {
245 interaction_subscribers_.AddObserver(subscriber);
246 }
247
RemoveAssistantInteractionSubscriber(AssistantInteractionSubscriber * subscriber)248 void TestAssistantService::RemoveAssistantInteractionSubscriber(
249 AssistantInteractionSubscriber* subscriber) {
250 interaction_subscribers_.RemoveObserver(subscriber);
251 }
252
RetrieveNotification(const chromeos::assistant::AssistantNotification & notification,int action_index)253 void TestAssistantService::RetrieveNotification(
254 const chromeos::assistant::AssistantNotification& notification,
255 int action_index) {}
256
DismissNotification(const chromeos::assistant::AssistantNotification & notification)257 void TestAssistantService::DismissNotification(
258 const chromeos::assistant::AssistantNotification& notification) {}
259
OnAccessibilityStatusChanged(bool spoken_feedback_enabled)260 void TestAssistantService::OnAccessibilityStatusChanged(
261 bool spoken_feedback_enabled) {}
262
SendAssistantFeedback(const chromeos::assistant::AssistantFeedback & feedback)263 void TestAssistantService::SendAssistantFeedback(
264 const chromeos::assistant::AssistantFeedback& feedback) {}
265
NotifyEntryIntoAssistantUi(chromeos::assistant::AssistantEntryPoint entry_point)266 void TestAssistantService::NotifyEntryIntoAssistantUi(
267 chromeos::assistant::AssistantEntryPoint entry_point) {}
268
AddTimeToTimer(const std::string & id,base::TimeDelta duration)269 void TestAssistantService::AddTimeToTimer(const std::string& id,
270 base::TimeDelta duration) {}
271
PauseTimer(const std::string & id)272 void TestAssistantService::PauseTimer(const std::string& id) {}
273
RemoveAlarmOrTimer(const std::string & id)274 void TestAssistantService::RemoveAlarmOrTimer(const std::string& id) {}
275
ResumeTimer(const std::string & id)276 void TestAssistantService::ResumeTimer(const std::string& id) {}
277
StartInteraction(chromeos::assistant::AssistantInteractionType type,chromeos::assistant::AssistantQuerySource source,const std::string & query)278 void TestAssistantService::StartInteraction(
279 chromeos::assistant::AssistantInteractionType type,
280 chromeos::assistant::AssistantQuerySource source,
281 const std::string& query) {
282 DCHECK(!running_active_interaction_);
283 AssistantInteractionMetadata metadata{type, source, query};
284 for (auto& subscriber : interaction_subscribers_) {
285 subscriber.OnInteractionStarted(metadata);
286 }
287 running_active_interaction_ = true;
288 }
289
SendInteractionResponse()290 void TestAssistantService::SendInteractionResponse() {
291 DCHECK(interaction_response_);
292 DCHECK(running_active_interaction_);
293 for (auto& subscriber : interaction_subscribers_)
294 interaction_response_->SendTo(&subscriber);
295 DCHECK(!current_interaction());
296 interaction_response_.reset();
297 running_active_interaction_ = false;
298 }
299
300 InteractionResponse::InteractionResponse() = default;
301 InteractionResponse::~InteractionResponse() = default;
302
AddTextResponse(const std::string & text)303 InteractionResponse* InteractionResponse::AddTextResponse(
304 const std::string& text) {
305 AddResponse(std::make_unique<TextResponse>(text));
306 return this;
307 }
308
AddSuggestionChip(const std::string & text)309 InteractionResponse* InteractionResponse::AddSuggestionChip(
310 const std::string& text) {
311 AddResponse(std::make_unique<SuggestionsResponse>(text));
312 return this;
313 }
314
AddResolution(Resolution resolution)315 InteractionResponse* InteractionResponse::AddResolution(Resolution resolution) {
316 AddResponse(std::make_unique<ResolutionResponse>(resolution));
317 return this;
318 }
319
AddResponse(std::unique_ptr<Response> response)320 void InteractionResponse::AddResponse(std::unique_ptr<Response> response) {
321 responses_.push_back(std::move(response));
322 }
323
SendTo(chromeos::assistant::AssistantInteractionSubscriber * receiver)324 void InteractionResponse::SendTo(
325 chromeos::assistant::AssistantInteractionSubscriber* receiver) {
326 for (auto& response : responses_)
327 response->SendTo(receiver);
328 }
329
330 } // namespace ash
331