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