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/assistant_controller_impl.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "ash/accessibility/accessibility_controller_impl.h"
11 #include "ash/assistant/util/deep_link_util.h"
12 #include "ash/public/cpp/android_intent_helper.h"
13 #include "ash/public/cpp/ash_pref_names.h"
14 #include "ash/public/cpp/new_window_delegate.h"
15 #include "ash/public/mojom/assistant_volume_control.mojom.h"
16 #include "ash/session/session_controller_impl.h"
17 #include "ash/shell.h"
18 #include "ash/utility/screenshot_controller.h"
19 #include "base/bind.h"
20 #include "base/memory/scoped_refptr.h"
21 #include "chromeos/services/assistant/public/cpp/assistant_prefs.h"
22 #include "chromeos/services/assistant/public/cpp/assistant_service.h"
23 #include "chromeos/services/assistant/public/cpp/features.h"
24 #include "components/prefs/pref_registry_simple.h"
25 #include "net/traffic_annotation/network_traffic_annotation.h"
26 #include "url/gurl.h"
27 
28 namespace ash {
29 
AssistantControllerImpl()30 AssistantControllerImpl::AssistantControllerImpl() {
31   assistant_state_controller_.AddObserver(this);
32   chromeos::CrasAudioHandler::Get()->AddAudioObserver(this);
33   AddObserver(this);
34 
35   // The Assistant service needs to have accessibility state synced with ash
36   // and be notified of any accessibility status changes in the future to
37   // provide an opportunity to turn on/off A11Y features.
38   Shell::Get()->accessibility_controller()->AddObserver(this);
39 
40   NotifyConstructed();
41 }
42 
~AssistantControllerImpl()43 AssistantControllerImpl::~AssistantControllerImpl() {
44   NotifyDestroying();
45 
46   chromeos::CrasAudioHandler::Get()->RemoveAudioObserver(this);
47   Shell::Get()->accessibility_controller()->RemoveObserver(this);
48   assistant_state_controller_.RemoveObserver(this);
49   RemoveObserver(this);
50 }
51 
52 // static
RegisterProfilePrefs(PrefRegistrySimple * registry)53 void AssistantControllerImpl::RegisterProfilePrefs(
54     PrefRegistrySimple* registry) {
55   AssistantInteractionControllerImpl::RegisterProfilePrefs(registry);
56   AssistantUiControllerImpl::RegisterProfilePrefs(registry);
57 }
58 
BindReceiver(mojo::PendingReceiver<mojom::AssistantVolumeControl> receiver)59 void AssistantControllerImpl::BindReceiver(
60     mojo::PendingReceiver<mojom::AssistantVolumeControl> receiver) {
61   assistant_volume_control_receiver_.Bind(std::move(receiver));
62 }
63 
SetAssistant(chromeos::assistant::Assistant * assistant)64 void AssistantControllerImpl::SetAssistant(
65     chromeos::assistant::Assistant* assistant) {
66   assistant_ = assistant;
67 
68   // Provide reference to sub-controllers.
69   assistant_alarm_timer_controller_.SetAssistant(assistant);
70   assistant_interaction_controller_.SetAssistant(assistant);
71   assistant_notification_controller_.SetAssistant(assistant);
72   assistant_screen_context_controller_.SetAssistant(assistant);
73   assistant_ui_controller_.SetAssistant(assistant);
74 
75   OnAccessibilityStatusChanged();
76 
77   if (assistant) {
78     for (AssistantControllerObserver& observer : observers_)
79       observer.OnAssistantReady();
80   }
81 }
82 
SendAssistantFeedback(bool assistant_debug_info_allowed,const std::string & feedback_description,const std::string & screenshot_png)83 void AssistantControllerImpl::SendAssistantFeedback(
84     bool assistant_debug_info_allowed,
85     const std::string& feedback_description,
86     const std::string& screenshot_png) {
87   chromeos::assistant::AssistantFeedback assistant_feedback;
88   assistant_feedback.assistant_debug_info_allowed =
89       assistant_debug_info_allowed;
90   assistant_feedback.description = feedback_description;
91   assistant_feedback.screenshot_png = screenshot_png;
92   assistant_->SendAssistantFeedback(std::move(assistant_feedback));
93 }
94 
StartSpeakerIdEnrollmentFlow()95 void AssistantControllerImpl::StartSpeakerIdEnrollmentFlow() {
96   setup_controller()->StartOnboarding(false /* relaunch */,
97                                       FlowType::kSpeakerIdEnrollment);
98 }
99 
DownloadImage(const GURL & url,ImageDownloader::DownloadCallback callback)100 void AssistantControllerImpl::DownloadImage(
101     const GURL& url,
102     ImageDownloader::DownloadCallback callback) {
103   constexpr net::NetworkTrafficAnnotationTag kNetworkTrafficAnnotationTag =
104       net::DefineNetworkTrafficAnnotation("image_downloader", R"(
105             "semantics: {
106               sender: "Google Assistant"
107               description:
108                 "The Google Assistant requires dynamic loading of images to "
109                 "provide a media rich user experience. Images are downloaded "
110                 "on an as needed basis."
111               trigger:
112                 "Generally triggered in direct response to a user issued "
113                 "query. A single query may necessitate the downloading of "
114                 "multiple images."
115               destination: GOOGLE_OWNED_SERVICE
116             }
117             "policy": {
118               cookies_allowed: NO
119               setting:
120                 "The Google Assistant can be enabled/disabled in Chrome "
121                 "Settings and is subject to eligibility requirements."
122             })");
123 
124   ImageDownloader::Get()->Download(url, kNetworkTrafficAnnotationTag,
125                                    std::move(callback));
126 }
127 
128 void AssistantControllerImpl::AddObserver(
129     AssistantControllerObserver* observer) {
130   observers_.AddObserver(observer);
131 }
132 
133 void AssistantControllerImpl::RemoveObserver(
134     AssistantControllerObserver* observer) {
135   observers_.RemoveObserver(observer);
136 }
137 
138 void AssistantControllerImpl::OpenUrl(const GURL& url,
139                                       bool in_background,
140                                       bool from_server) {
141   if (assistant::util::IsDeepLinkUrl(url)) {
142     NotifyDeepLinkReceived(url);
143     return;
144   }
145 
146   auto* android_helper = AndroidIntentHelper::GetInstance();
147   if (IsAndroidIntent(url) && !android_helper) {
148     NOTREACHED();
149     return;
150   }
151 
152   // Give observers an opportunity to perform any necessary handling before we
153   // open the specified |url| in a new browser tab.
154   NotifyOpeningUrl(url, in_background, from_server);
155 
156   if (IsAndroidIntent(url)) {
157     android_helper->LaunchAndroidIntent(url.spec());
158   } else {
159     // The new tab should be opened with a user activation since the user
160     // interacted with the Assistant to open the url. |in_background| describes
161     // the relationship between |url| and Assistant UI, not the browser. As
162     // such, the browser will always be instructed to open |url| in a new
163     // browser tab and Assistant UI state will be updated downstream to respect
164     // |in_background|.
165     NewWindowDelegate::GetInstance()->NewTabWithUrl(
166         url, /*from_user_interaction=*/true);
167   }
168   NotifyUrlOpened(url, from_server);
169 }
170 
171 void AssistantControllerImpl::OpenAssistantSettings() {
172   // Launch Assistant settings via deeplink.
173   OpenUrl(assistant::util::CreateAssistantSettingsDeepLink(),
174           /*in_background=*/false, /*from_server=*/false);
175 }
176 
177 base::WeakPtr<ash::AssistantController> AssistantControllerImpl::GetWeakPtr() {
178   return weak_factory_.GetWeakPtr();
179 }
180 
181 void AssistantControllerImpl::OnDeepLinkReceived(
182     assistant::util::DeepLinkType type,
183     const std::map<std::string, std::string>& params) {
184   using assistant::util::DeepLinkParam;
185   using assistant::util::DeepLinkType;
186 
187   switch (type) {
188     case DeepLinkType::kChromeSettings: {
189       // Chrome Settings deep links are opened in a new browser tab.
190       OpenUrl(
191           assistant::util::GetChromeSettingsUrl(
192               assistant::util::GetDeepLinkParam(params, DeepLinkParam::kPage)),
193           /*in_background=*/false, /*from_server=*/false);
194       break;
195     }
196     case DeepLinkType::kFeedback:
197       NewWindowDelegate::GetInstance()->OpenFeedbackPage(
198           /*from_assistant=*/true);
199 
200       // Close the assistant UI so that the feedback page is visible.
201       assistant_ui_controller_.CloseUi(
202           chromeos::assistant::AssistantExitPoint::kUnspecified);
203       break;
204     case DeepLinkType::kScreenshot:
205       // We close the UI before taking the screenshot as it's probably not the
206       // user's intention to include the Assistant in the picture.
207       assistant_ui_controller_.CloseUi(
208           chromeos::assistant::AssistantExitPoint::kScreenshot);
209       Shell::Get()->screenshot_controller()->TakeScreenshotForAllRootWindows();
210       break;
211     case DeepLinkType::kTaskManager:
212       // Open task manager window.
213       NewWindowDelegate::GetInstance()->ShowTaskManager();
214       break;
215     case DeepLinkType::kUnsupported:
216     case DeepLinkType::kAlarmTimer:
217     case DeepLinkType::kLists:
218     case DeepLinkType::kNotes:
219     case DeepLinkType::kOnboarding:
220     case DeepLinkType::kProactiveSuggestions:
221     case DeepLinkType::kQuery:
222     case DeepLinkType::kReminders:
223     case DeepLinkType::kSettings:
224     case DeepLinkType::kWhatsOnMyScreen:
225       // No action needed.
226       break;
227   }
228 }
229 
230 void AssistantControllerImpl::SetVolume(int volume, bool user_initiated) {
231   volume = std::min(100, volume);
232   volume = std::max(volume, 0);
233   chromeos::CrasAudioHandler::Get()->SetOutputVolumePercent(volume);
234 }
235 
236 void AssistantControllerImpl::SetMuted(bool muted) {
237   chromeos::CrasAudioHandler::Get()->SetOutputMute(muted);
238 }
239 
240 void AssistantControllerImpl::AddVolumeObserver(
241     mojo::PendingRemote<mojom::VolumeObserver> observer) {
242   volume_observers_.Add(std::move(observer));
243 
244   int output_volume =
245       chromeos::CrasAudioHandler::Get()->GetOutputVolumePercent();
246   bool mute = chromeos::CrasAudioHandler::Get()->IsOutputMuted();
247   OnOutputMuteChanged(mute);
248   OnOutputNodeVolumeChanged(0 /* node */, output_volume);
249 }
250 
251 void AssistantControllerImpl::OnOutputMuteChanged(bool mute_on) {
252   for (auto& observer : volume_observers_)
253     observer->OnMuteStateChanged(mute_on);
254 }
255 
256 void AssistantControllerImpl::OnOutputNodeVolumeChanged(uint64_t node,
257                                                         int volume) {
258   // |node| refers to the active volume device, which we don't care here.
259   for (auto& observer : volume_observers_)
260     observer->OnVolumeChanged(volume);
261 }
262 
263 void AssistantControllerImpl::OnAccessibilityStatusChanged() {
264   if (!assistant_)
265     return;
266 
267   // The Assistant service needs to be informed of changes to accessibility
268   // state so that it can turn on/off A11Y features appropriately.
269   assistant_->OnAccessibilityStatusChanged(
270       Shell::Get()->accessibility_controller()->spoken_feedback().enabled());
271 }
272 
273 bool AssistantControllerImpl::IsAssistantReady() const {
274   return !!assistant_;
275 }
276 
277 void AssistantControllerImpl::NotifyConstructed() {
278   for (AssistantControllerObserver& observer : observers_)
279     observer.OnAssistantControllerConstructed();
280 }
281 
282 void AssistantControllerImpl::NotifyDestroying() {
283   for (AssistantControllerObserver& observer : observers_)
284     observer.OnAssistantControllerDestroying();
285 }
286 
287 void AssistantControllerImpl::NotifyDeepLinkReceived(const GURL& deep_link) {
288   using assistant::util::DeepLinkType;
289 
290   // Retrieve deep link type and parsed parameters.
291   DeepLinkType type = assistant::util::GetDeepLinkType(deep_link);
292   const std::map<std::string, std::string> params =
293       assistant::util::GetDeepLinkParams(deep_link);
294 
295   for (AssistantControllerObserver& observer : observers_)
296     observer.OnDeepLinkReceived(type, params);
297 }
298 
299 void AssistantControllerImpl::NotifyOpeningUrl(const GURL& url,
300                                                bool in_background,
301                                                bool from_server) {
302   for (AssistantControllerObserver& observer : observers_)
303     observer.OnOpeningUrl(url, in_background, from_server);
304 }
305 
306 void AssistantControllerImpl::NotifyUrlOpened(const GURL& url,
307                                               bool from_server) {
308   for (AssistantControllerObserver& observer : observers_)
309     observer.OnUrlOpened(url, from_server);
310 }
311 
312 void AssistantControllerImpl::OnAssistantStatusChanged(
313     chromeos::assistant::AssistantStatus status) {
314   if (status == chromeos::assistant::AssistantStatus::NOT_READY)
315     assistant_ui_controller_.CloseUi(
316         chromeos::assistant::AssistantExitPoint::kUnspecified);
317 }
318 
319 void AssistantControllerImpl::OnLockedFullScreenStateChanged(bool enabled) {
320   if (enabled)
321     assistant_ui_controller_.CloseUi(
322         chromeos::assistant::AssistantExitPoint::kUnspecified);
323 }
324 
325 void AssistantControllerImpl::BindVolumeControl(
326     mojo::PendingReceiver<mojom::AssistantVolumeControl> receiver) {
327   Shell::Get()->assistant_controller()->BindReceiver(std::move(receiver));
328 }
329 
330 }  // namespace ash
331