blob: 9b81437fc789d48ba24550c2b8d87804fd3a5ca1 [file] [log] [blame]
David Black71ab74d2018-04-12 06:52:311// Copyright 2018 The Chromium Authors. All rights reserved.
David Blacke8f815042018-03-28 18:59:372// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
David Black00fdce32018-05-10 20:59:145#include "ash/assistant/assistant_controller.h"
David Blacke8f815042018-03-28 18:59:376
David Blackd72bae462018-04-28 00:47:147#include "ash/assistant/model/assistant_interaction_model_observer.h"
David Black7a1b66ff2018-05-11 21:48:258#include "ash/assistant/model/assistant_query.h"
David Blackd72bae462018-04-28 00:47:149#include "ash/assistant/model/assistant_ui_element.h"
David Blackf7f8d2012018-04-25 19:48:0210#include "ash/assistant/ui/assistant_bubble.h"
David Blacke1a4b9ed2018-04-19 19:55:0811#include "ash/session/session_controller.h"
David Blacke8f815042018-03-28 18:59:3712#include "ash/shell.h"
13#include "ash/shell_delegate.h"
David Black46b999902018-05-16 19:59:0214#include "ash/system/toast/toast_data.h"
15#include "ash/system/toast/toast_manager.h"
Muyuan Li0070f44d2018-05-16 02:57:2316#include "base/bind.h"
17#include "base/memory/scoped_refptr.h"
David Black46b999902018-05-16 19:59:0218#include "base/strings/utf_string_conversions.h"
David Blacke1a4b9ed2018-04-19 19:55:0819#include "base/unguessable_token.h"
Muyuan Li0070f44d2018-05-16 02:57:2320#include "ui/snapshot/snapshot.h"
David Blacke8f815042018-03-28 18:59:3721
22namespace ash {
23
David Black46b999902018-05-16 19:59:0224namespace {
25
26// Toast -----------------------------------------------------------------------
27
28constexpr int kToastDurationMs = 2500;
29constexpr char kUnboundServiceToastId[] =
30 "assistant_controller_unbound_service";
31
32// TODO(b/77638210): Localize string.
33constexpr char kSomethingWentWrong[] =
34 "Something went wrong. Try again in a few seconds.";
35
36void ShowToast(const std::string& id, const std::string& text) {
37 ToastData toast(id, base::UTF8ToUTF16(text), kToastDurationMs, base::nullopt);
38 Shell::Get()->toast_manager()->Show(toast);
39}
40
41} // namespace
42
43// AssistantController ---------------------------------------------------------
44
David Black00fdce32018-05-10 20:59:1445AssistantController::AssistantController()
David Black75f9fec2018-05-17 22:10:3946 : assistant_event_subscriber_binding_(this),
David Blackf7f8d2012018-04-25 19:48:0247 assistant_bubble_(std::make_unique<AssistantBubble>(this)) {
David Black5318ec12018-05-02 01:54:3248 AddInteractionModelObserver(this);
Qiang Xubd6d84e72018-05-09 01:31:5149 Shell::Get()->highlighter_controller()->AddObserver(this);
David Black71ab74d2018-04-12 06:52:3150}
David Blacke8f815042018-03-28 18:59:3751
David Black00fdce32018-05-10 20:59:1452AssistantController::~AssistantController() {
Qiang Xubd6d84e72018-05-09 01:31:5153 Shell::Get()->highlighter_controller()->RemoveObserver(this);
David Black5318ec12018-05-02 01:54:3254 RemoveInteractionModelObserver(this);
55
David Black75f9fec2018-05-17 22:10:3956 assistant_controller_bindings_.CloseAllBindings();
David Blacke8f815042018-03-28 18:59:3757 assistant_event_subscriber_binding_.Close();
David Blacke8f815042018-03-28 18:59:3758}
59
David Black00fdce32018-05-10 20:59:1460void AssistantController::BindRequest(
61 mojom::AssistantControllerRequest request) {
David Black75f9fec2018-05-17 22:10:3962 assistant_controller_bindings_.AddBinding(this, std::move(request));
David Blacke8f815042018-03-28 18:59:3763}
64
David Black00fdce32018-05-10 20:59:1465void AssistantController::SetAssistant(
David Blacke8f815042018-03-28 18:59:3766 chromeos::assistant::mojom::AssistantPtr assistant) {
David Black4c3656c2018-04-19 17:31:4767 assistant_ = std::move(assistant);
68
David Blacke8f815042018-03-28 18:59:3769 // Subscribe to Assistant events.
70 chromeos::assistant::mojom::AssistantEventSubscriberPtr ptr;
71 assistant_event_subscriber_binding_.Bind(mojo::MakeRequest(&ptr));
David Black4c3656c2018-04-19 17:31:4772 assistant_->AddAssistantEventSubscriber(std::move(ptr));
73}
74
David Black00fdce32018-05-10 20:59:1475void AssistantController::SetAssistantCardRenderer(
David Blacke1a4b9ed2018-04-19 19:55:0876 mojom::AssistantCardRendererPtr assistant_card_renderer) {
77 assistant_card_renderer_ = std::move(assistant_card_renderer);
78}
79
David Black7ffd2842018-05-25 01:25:5380void AssistantController::SetAssistantImageDownloader(
81 mojom::AssistantImageDownloaderPtr assistant_image_downloader) {
82 assistant_image_downloader_ = std::move(assistant_image_downloader);
83}
84
Muyuan Li0070f44d2018-05-16 02:57:2385void AssistantController::RequestScreenshot(
86 const gfx::Rect& rect,
87 RequestScreenshotCallback callback) {
88 // TODO(muyuanli): handle multi-display when assistant's behavior is defined.
89 auto* root_window = Shell::GetPrimaryRootWindow();
90 gfx::Rect source_rect =
91 rect.IsEmpty() ? gfx::Rect(root_window->bounds().size()) : rect;
92 ui::GrabWindowSnapshotAsyncJPEG(
93 root_window, source_rect,
94 base::BindRepeating(
95 [](RequestScreenshotCallback callback,
96 scoped_refptr<base::RefCountedMemory> data) {
97 std::move(callback).Run(std::vector<uint8_t>(
98 data->front(), data->front() + data->size()));
99 },
100 base::Passed(&callback)));
101}
102
David Black00fdce32018-05-10 20:59:14103void AssistantController::RenderCard(
David Blacke1a4b9ed2018-04-19 19:55:08104 const base::UnguessableToken& id_token,
105 mojom::AssistantCardParamsPtr params,
106 mojom::AssistantCardRenderer::RenderCallback callback) {
107 DCHECK(assistant_card_renderer_);
108
109 const mojom::UserSession* user_session =
110 Shell::Get()->session_controller()->GetUserSession(0);
111
112 if (!user_session) {
113 LOG(WARNING) << "Unable to retrieve active user session.";
114 return;
115 }
116
117 AccountId account_id = user_session->user_info->account_id;
David Blacke1a4b9ed2018-04-19 19:55:08118 assistant_card_renderer_->Render(account_id, id_token, std::move(params),
119 std::move(callback));
120}
121
David Black00fdce32018-05-10 20:59:14122void AssistantController::ReleaseCard(const base::UnguessableToken& id_token) {
David Blacke1a4b9ed2018-04-19 19:55:08123 DCHECK(assistant_card_renderer_);
124 assistant_card_renderer_->Release(id_token);
125}
126
David Black00fdce32018-05-10 20:59:14127void AssistantController::ReleaseCards(
David Black3bfd968602018-04-24 22:07:46128 const std::vector<base::UnguessableToken>& id_tokens) {
129 DCHECK(assistant_card_renderer_);
130 assistant_card_renderer_->ReleaseAll(id_tokens);
131}
132
David Black7ffd2842018-05-25 01:25:53133void AssistantController::DownloadImage(
134 const GURL& url,
135 mojom::AssistantImageDownloader::DownloadCallback callback) {
136 DCHECK(assistant_image_downloader_);
137
138 const mojom::UserSession* user_session =
139 Shell::Get()->session_controller()->GetUserSession(0);
140
141 if (!user_session) {
142 LOG(WARNING) << "Unable to retrieve active user session.";
143 return;
144 }
145
146 AccountId account_id = user_session->user_info->account_id;
147 assistant_image_downloader_->Download(account_id, url, std::move(callback));
148}
149
David Black00fdce32018-05-10 20:59:14150void AssistantController::AddInteractionModelObserver(
David Blackd72bae462018-04-28 00:47:14151 AssistantInteractionModelObserver* observer) {
David Black4c3656c2018-04-19 17:31:47152 assistant_interaction_model_.AddObserver(observer);
153}
154
David Black00fdce32018-05-10 20:59:14155void AssistantController::RemoveInteractionModelObserver(
David Blackd72bae462018-04-28 00:47:14156 AssistantInteractionModelObserver* observer) {
David Black4c3656c2018-04-19 17:31:47157 assistant_interaction_model_.RemoveObserver(observer);
David Blacke8f815042018-03-28 18:59:37158}
159
David Black00fdce32018-05-10 20:59:14160void AssistantController::StartInteraction() {
David Black46b999902018-05-16 19:59:02161 if (!assistant_) {
162 ShowToast(kUnboundServiceToastId, kSomethingWentWrong);
163 return;
164 }
David Black418376ae2018-05-15 18:20:10165 OnInteractionStarted();
David Black990b0ac2018-05-01 18:08:08166}
167
David Black00fdce32018-05-10 20:59:14168void AssistantController::StopInteraction() {
David Black418376ae2018-05-15 18:20:10169 assistant_interaction_model_.SetInteractionState(InteractionState::kInactive);
David Black990b0ac2018-05-01 18:08:08170}
171
David Black00fdce32018-05-10 20:59:14172void AssistantController::ToggleInteraction() {
David Black5318ec12018-05-02 01:54:32173 if (assistant_interaction_model_.interaction_state() ==
174 InteractionState::kInactive) {
David Black990b0ac2018-05-01 18:08:08175 StartInteraction();
David Black5318ec12018-05-02 01:54:32176 } else {
David Black990b0ac2018-05-01 18:08:08177 StopInteraction();
David Black5318ec12018-05-02 01:54:32178 }
179}
180
David Black00fdce32018-05-10 20:59:14181void AssistantController::OnInteractionStateChanged(
David Black5318ec12018-05-02 01:54:32182 InteractionState interaction_state) {
David Black418376ae2018-05-15 18:20:10183 if (interaction_state == InteractionState::kActive)
184 return;
David Black873ec7552018-05-05 00:16:27185
David Black418376ae2018-05-15 18:20:10186 // When the user-facing interaction is dismissed, we instruct the service to
187 // terminate any listening, speaking, or query in flight.
188 DCHECK(assistant_);
189 assistant_->StopActiveInteraction();
190
191 assistant_interaction_model_.ClearInteraction();
192 assistant_interaction_model_.SetInputModality(InputModality::kVoice);
David Black990b0ac2018-05-01 18:08:08193}
194
Qiang Xubd3dfbc72018-05-14 23:26:40195void AssistantController::OnHighlighterEnabledChanged(
196 HighlighterEnabledState state) {
Qiang Xubd6d84e72018-05-09 01:31:51197 assistant_interaction_model_.SetInputModality(InputModality::kStylus);
Qiang Xubd3dfbc72018-05-14 23:26:40198 if (state == HighlighterEnabledState::kEnabled) {
199 assistant_interaction_model_.SetInteractionState(InteractionState::kActive);
200 } else if (state == HighlighterEnabledState::kDisabledByUser) {
201 assistant_interaction_model_.SetInteractionState(
202 InteractionState::kInactive);
203 }
Qiang Xubd6d84e72018-05-09 01:31:51204}
205
David Black418376ae2018-05-15 18:20:10206void AssistantController::OnInputModalityChanged(InputModality input_modality) {
207 if (input_modality == InputModality::kVoice)
208 return;
209
210 // When switching to a non-voice input modality we instruct the underlying
David Blackd192d24c2018-05-15 22:44:50211 // service to terminate any listening, speaking, or in flight voice query. We
212 // do not do this when switching to voice input modality because initiation of
213 // a voice interaction will automatically interrupt any pre-existing activity.
David Black418376ae2018-05-15 18:20:10214 // Stopping the active interaction here for voice input modality would
215 // actually have the undesired effect of stopping the voice interaction.
David Blackd192d24c2018-05-15 22:44:50216 if (assistant_interaction_model_.query().type() ==
217 AssistantQueryType::kVoice) {
218 DCHECK(assistant_);
219 assistant_->StopActiveInteraction();
220 }
David Black418376ae2018-05-15 18:20:10221}
222
David Black00fdce32018-05-10 20:59:14223void AssistantController::OnInteractionStarted() {
David Black5318ec12018-05-02 01:54:32224 assistant_interaction_model_.SetInteractionState(InteractionState::kActive);
David Black4d28a352018-04-15 22:00:14225}
226
David Black00fdce32018-05-10 20:59:14227void AssistantController::OnInteractionFinished(
David Blackced6a612018-05-15 20:52:32228 AssistantInteractionResolution resolution) {
229 // When a voice query is interrupted we do not receive any follow up speech
230 // recognition events but the mic is closed.
231 if (resolution == AssistantInteractionResolution::kInterruption) {
232 assistant_interaction_model_.SetMicState(MicState::kClosed);
233 }
234}
235
David Black3d527592018-05-18 18:12:29236void AssistantController::OnCardPressed(const GURL& url) {
237 OnOpenUrlResponse(url);
238}
239
David Blackced6a612018-05-15 20:52:32240void AssistantController::OnDialogPlateActionPressed(const std::string& text) {
241 InputModality input_modality = assistant_interaction_model_.input_modality();
242
243 // When using keyboard input modality, pressing the dialog plate action is
244 // equivalent to a commit.
245 if (input_modality == InputModality::kKeyboard) {
246 OnDialogPlateContentsCommitted(text);
247 return;
248 }
249
250 DCHECK(assistant_);
251
252 // It should not be possible to press the dialog plate action when not using
253 // keyboard or voice input modality.
254 DCHECK(input_modality == InputModality::kVoice);
255
256 // When using voice input modality, pressing the dialog plate action will
257 // toggle the voice interaction state.
258 switch (assistant_interaction_model_.mic_state()) {
259 case MicState::kClosed:
260 assistant_->StartVoiceInteraction();
261 break;
262 case MicState::kOpen:
263 assistant_->StopActiveInteraction();
264 break;
265 }
266}
David Black4d28a352018-04-15 22:00:14267
David Black00fdce32018-05-10 20:59:14268void AssistantController::OnDialogPlateContentsChanged(
David Blackb2df49c2018-05-03 23:52:36269 const std::string& text) {
David Black873ec7552018-05-05 00:16:27270 if (text.empty()) {
271 // Note: This does not open the mic. It only updates the input modality to
272 // voice so that we will show the mic icon in the UI.
273 assistant_interaction_model_.SetInputModality(InputModality::kVoice);
274 } else {
David Black873ec7552018-05-05 00:16:27275 assistant_interaction_model_.SetInputModality(InputModality::kKeyboard);
276 assistant_interaction_model_.SetMicState(MicState::kClosed);
277 }
David Blackb2df49c2018-05-03 23:52:36278}
279
David Black00fdce32018-05-10 20:59:14280void AssistantController::OnDialogPlateContentsCommitted(
David Blackb2df49c2018-05-03 23:52:36281 const std::string& text) {
David Blackced6a612018-05-15 20:52:32282 // TODO(dmblack): Handle an empty text query more gracefully by showing a
283 // helpful message to the user. Currently we just reset state and pretend as
284 // if nothing happened.
285 if (text.empty()) {
286 assistant_interaction_model_.ClearInteraction();
287 assistant_interaction_model_.SetInputModality(InputModality::kVoice);
288 return;
289 }
290
David Blackb2df49c2018-05-03 23:52:36291 assistant_interaction_model_.ClearInteraction();
David Black7a1b66ff2018-05-11 21:48:25292 assistant_interaction_model_.SetQuery(
293 std::make_unique<AssistantTextQuery>(text));
David Blackb2df49c2018-05-03 23:52:36294
David Black873ec7552018-05-05 00:16:27295 // Note: This does not open the mic. It only updates the input modality to
296 // voice so that we will show the mic icon in the UI.
297 assistant_interaction_model_.SetInputModality(InputModality::kVoice);
298
David Blackb2df49c2018-05-03 23:52:36299 DCHECK(assistant_);
300 assistant_->SendTextQuery(text);
301}
302
David Black00fdce32018-05-10 20:59:14303void AssistantController::OnHtmlResponse(const std::string& response) {
David Black3bfd968602018-04-24 22:07:46304 assistant_interaction_model_.AddUiElement(
David Blackd72bae462018-04-28 00:47:14305 std::make_unique<AssistantCardElement>(response));
David Blacke8f815042018-03-28 18:59:37306}
307
David Black59d2b8a2018-05-14 21:02:18308void AssistantController::OnSuggestionChipPressed(int id) {
309 const AssistantSuggestion* suggestion =
310 assistant_interaction_model_.GetSuggestionById(id);
311
312 DCHECK(suggestion);
313
314 // If the suggestion contains a non-empty action url, we will handle the
315 // suggestion chip pressed event by launching the action url in the browser.
316 if (!suggestion->action_url.is_empty()) {
317 OnOpenUrlResponse(suggestion->action_url);
318 return;
319 }
320
321 // Otherwise, we will submit a simple text query using the suggestion text.
322 const std::string text = suggestion->text;
323
David Black4c3656c2018-04-19 17:31:47324 assistant_interaction_model_.ClearInteraction();
David Black7a1b66ff2018-05-11 21:48:25325 assistant_interaction_model_.SetQuery(
326 std::make_unique<AssistantTextQuery>(text));
David Black4c3656c2018-04-19 17:31:47327
328 DCHECK(assistant_);
329 assistant_->SendTextQuery(text);
330}
331
David Black00fdce32018-05-10 20:59:14332void AssistantController::OnSuggestionsResponse(
David Blackdc1996d32018-05-11 01:30:05333 std::vector<AssistantSuggestionPtr> response) {
334 assistant_interaction_model_.AddSuggestions(std::move(response));
David Black46ad4b72018-04-03 00:34:02335}
336
David Black00fdce32018-05-10 20:59:14337void AssistantController::OnTextResponse(const std::string& response) {
David Black3bfd968602018-04-24 22:07:46338 assistant_interaction_model_.AddUiElement(
David Blackd72bae462018-04-28 00:47:14339 std::make_unique<AssistantTextElement>(response));
David Blacke8f815042018-03-28 18:59:37340}
341
David Black00fdce32018-05-10 20:59:14342void AssistantController::OnSpeechRecognitionStarted() {
David Black71ab74d2018-04-12 06:52:31343 assistant_interaction_model_.ClearInteraction();
David Black873ec7552018-05-05 00:16:27344 assistant_interaction_model_.SetInputModality(InputModality::kVoice);
345 assistant_interaction_model_.SetMicState(MicState::kOpen);
David Blackd192d24c2018-05-15 22:44:50346 assistant_interaction_model_.SetQuery(
347 std::make_unique<AssistantVoiceQuery>());
David Black89ac6f82018-04-03 17:38:12348}
349
David Black00fdce32018-05-10 20:59:14350void AssistantController::OnSpeechRecognitionIntermediateResult(
David Black89ac6f82018-04-03 17:38:12351 const std::string& high_confidence_text,
352 const std::string& low_confidence_text) {
David Black7a1b66ff2018-05-11 21:48:25353 assistant_interaction_model_.SetQuery(std::make_unique<AssistantVoiceQuery>(
354 high_confidence_text, low_confidence_text));
David Black89ac6f82018-04-03 17:38:12355}
356
David Black00fdce32018-05-10 20:59:14357void AssistantController::OnSpeechRecognitionEndOfUtterance() {
David Black873ec7552018-05-05 00:16:27358 assistant_interaction_model_.SetMicState(MicState::kClosed);
David Black89ac6f82018-04-03 17:38:12359}
360
David Black00fdce32018-05-10 20:59:14361void AssistantController::OnSpeechRecognitionFinalResult(
David Black89ac6f82018-04-03 17:38:12362 const std::string& final_result) {
David Black7a1b66ff2018-05-11 21:48:25363 assistant_interaction_model_.SetQuery(
364 std::make_unique<AssistantVoiceQuery>(final_result));
David Black89ac6f82018-04-03 17:38:12365}
366
David Black00fdce32018-05-10 20:59:14367void AssistantController::OnSpeechLevelUpdated(float speech_level) {
Alan Lau7bd29ced2018-03-29 18:39:51368 // TODO(dmblack): Handle.
369 NOTIMPLEMENTED();
370}
371
David Black00fdce32018-05-10 20:59:14372void AssistantController::OnOpenUrlResponse(const GURL& url) {
David Blacke8f815042018-03-28 18:59:37373 Shell::Get()->shell_delegate()->OpenUrlFromArc(url);
David Black990b0ac2018-05-01 18:08:08374 StopInteraction();
David Blacke8f815042018-03-28 18:59:37375}
376
David Blacke8f815042018-03-28 18:59:37377} // namespace ash