| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/assistant/assistant_controller.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "ash/accessibility/accessibility_controller_impl.h" |
| #include "ash/assistant/util/deep_link_util.h" |
| #include "ash/public/cpp/android_intent_helper.h" |
| #include "ash/public/cpp/ash_pref_names.h" |
| #include "ash/public/cpp/new_window_delegate.h" |
| #include "ash/public/mojom/assistant_volume_control.mojom.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/utility/screenshot_controller.h" |
| #include "base/bind.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "chromeos/services/assistant/public/cpp/assistant_prefs.h" |
| #include "chromeos/services/assistant/public/features.h" |
| #include "chromeos/services/assistant/public/mojom/assistant.mojom.h" |
| #include "components/prefs/pref_registry_simple.h" |
| |
| namespace ash { |
| |
| AssistantController::AssistantController() { |
| assistant_state_controller_.AddObserver(this); |
| chromeos::CrasAudioHandler::Get()->AddAudioObserver(this); |
| AddObserver(this); |
| |
| NotifyConstructed(); |
| } |
| |
| AssistantController::~AssistantController() { |
| NotifyDestroying(); |
| |
| chromeos::CrasAudioHandler::Get()->RemoveAudioObserver(this); |
| Shell::Get()->accessibility_controller()->RemoveObserver(this); |
| assistant_state_controller_.RemoveObserver(this); |
| RemoveObserver(this); |
| } |
| |
| // static |
| void AssistantController::RegisterProfilePrefs(PrefRegistrySimple* registry) { |
| registry->RegisterIntegerPref(prefs::kAssistantNumWarmerWelcomeTriggered, 0); |
| } |
| |
| void AssistantController::BindReceiver( |
| mojo::PendingReceiver<chromeos::assistant::mojom::AssistantController> |
| receiver) { |
| assistant_controller_receivers_.Add(this, std::move(receiver)); |
| } |
| |
| void AssistantController::BindReceiver( |
| mojo::PendingReceiver<mojom::AssistantVolumeControl> receiver) { |
| assistant_volume_control_receiver_.Bind(std::move(receiver)); |
| } |
| |
| void AssistantController::AddObserver(AssistantControllerObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void AssistantController::RemoveObserver( |
| AssistantControllerObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void AssistantController::SetAssistant( |
| mojo::PendingRemote<chromeos::assistant::mojom::Assistant> assistant) { |
| assistant_.Bind(std::move(assistant)); |
| |
| // Provide reference to sub-controllers. |
| assistant_alarm_timer_controller_.SetAssistant(assistant_.get()); |
| assistant_interaction_controller_.SetAssistant(assistant_.get()); |
| assistant_notification_controller_.SetAssistant(assistant_.get()); |
| assistant_screen_context_controller_.SetAssistant(assistant_.get()); |
| assistant_ui_controller_.SetAssistant(assistant_.get()); |
| |
| // The Assistant service needs to have accessibility state synced with ash |
| // and be notified of any accessibility status changes in the future to |
| // provide an opportunity to turn on/off A11Y features. |
| Shell::Get()->accessibility_controller()->AddObserver(this); |
| OnAccessibilityStatusChanged(); |
| |
| for (AssistantControllerObserver& observer : observers_) |
| observer.OnAssistantReady(); |
| } |
| |
| void AssistantController::SendAssistantFeedback( |
| bool assistant_debug_info_allowed, |
| const std::string& feedback_description, |
| const std::string& screenshot_png) { |
| chromeos::assistant::mojom::AssistantFeedbackPtr assistant_feedback = |
| chromeos::assistant::mojom::AssistantFeedback::New(); |
| assistant_feedback->assistant_debug_info_allowed = |
| assistant_debug_info_allowed; |
| assistant_feedback->description = feedback_description; |
| assistant_feedback->screenshot_png = screenshot_png; |
| assistant_->SendAssistantFeedback(std::move(assistant_feedback)); |
| } |
| |
| void AssistantController::StartTextInteraction( |
| const std::string& query, |
| bool allow_tts, |
| chromeos::assistant::mojom::AssistantQuerySource source) { |
| assistant_interaction_controller_.StartTextInteraction(query, allow_tts, |
| source); |
| } |
| |
| void AssistantController::StartSpeakerIdEnrollmentFlow() { |
| if (assistant_state_controller_.consent_status().value_or( |
| chromeos::assistant::prefs::ConsentStatus::kUnknown) == |
| chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted) { |
| // If activity control has been accepted, launch the enrollment flow. |
| setup_controller()->StartOnboarding(false /* relaunch */, |
| FlowType::kSpeakerIdEnrollment); |
| } else { |
| // If activity control has not been accepted, launch the opt-in flow. |
| setup_controller()->StartOnboarding(false /* relaunch */, |
| FlowType::kConsentFlow); |
| } |
| } |
| |
| void AssistantController::DownloadImage( |
| const GURL& url, |
| AssistantImageDownloader::DownloadCallback callback) { |
| const UserSession* user_session = |
| Shell::Get()->session_controller()->GetUserSession(0); |
| |
| if (!user_session) { |
| LOG(WARNING) << "Unable to retrieve active user session."; |
| std::move(callback).Run(gfx::ImageSkia()); |
| return; |
| } |
| |
| AccountId account_id = user_session->user_info.account_id; |
| AssistantImageDownloader::GetInstance()->Download(account_id, url, |
| std::move(callback)); |
| } |
| |
| void AssistantController::OnDeepLinkReceived( |
| assistant::util::DeepLinkType type, |
| const std::map<std::string, std::string>& params) { |
| using assistant::util::DeepLinkParam; |
| using assistant::util::DeepLinkType; |
| |
| switch (type) { |
| case DeepLinkType::kChromeSettings: { |
| // Chrome Settings deep links are opened in a new browser tab. |
| OpenUrl(assistant::util::GetChromeSettingsUrl( |
| assistant::util::GetDeepLinkParam(params, DeepLinkParam::kPage))); |
| break; |
| } |
| case DeepLinkType::kFeedback: |
| NewWindowDelegate::GetInstance()->OpenFeedbackPage( |
| /*from_assistant=*/true); |
| break; |
| case DeepLinkType::kScreenshot: |
| // We close the UI before taking the screenshot as it's probably not the |
| // user's intention to include the Assistant in the picture. |
| assistant_ui_controller_.CloseUi( |
| chromeos::assistant::mojom::AssistantExitPoint::kScreenshot); |
| Shell::Get()->screenshot_controller()->TakeScreenshotForAllRootWindows(); |
| break; |
| case DeepLinkType::kTaskManager: |
| // Open task manager window. |
| NewWindowDelegate::GetInstance()->ShowTaskManager(); |
| break; |
| case DeepLinkType::kUnsupported: |
| case DeepLinkType::kAlarmTimer: |
| case DeepLinkType::kLists: |
| case DeepLinkType::kNotes: |
| case DeepLinkType::kOnboarding: |
| case DeepLinkType::kProactiveSuggestions: |
| case DeepLinkType::kQuery: |
| case DeepLinkType::kReminders: |
| case DeepLinkType::kSettings: |
| case DeepLinkType::kWhatsOnMyScreen: |
| // No action needed. |
| break; |
| } |
| } |
| |
| void AssistantController::SetVolume(int volume, bool user_initiated) { |
| volume = std::min(100, volume); |
| volume = std::max(volume, 0); |
| chromeos::CrasAudioHandler::Get()->SetOutputVolumePercent(volume); |
| } |
| |
| void AssistantController::SetMuted(bool muted) { |
| chromeos::CrasAudioHandler::Get()->SetOutputMute(muted); |
| } |
| |
| void AssistantController::AddVolumeObserver( |
| mojo::PendingRemote<mojom::VolumeObserver> observer) { |
| volume_observers_.Add(std::move(observer)); |
| |
| int output_volume = |
| chromeos::CrasAudioHandler::Get()->GetOutputVolumePercent(); |
| bool mute = chromeos::CrasAudioHandler::Get()->IsOutputMuted(); |
| OnOutputMuteChanged(mute); |
| OnOutputNodeVolumeChanged(0 /* node */, output_volume); |
| } |
| |
| void AssistantController::OnOutputMuteChanged(bool mute_on) { |
| for (auto& observer : volume_observers_) |
| observer->OnMuteStateChanged(mute_on); |
| } |
| |
| void AssistantController::OnOutputNodeVolumeChanged(uint64_t node, int volume) { |
| // |node| refers to the active volume device, which we don't care here. |
| for (auto& observer : volume_observers_) |
| observer->OnVolumeChanged(volume); |
| } |
| |
| void AssistantController::OnAccessibilityStatusChanged() { |
| // The Assistant service needs to be informed of changes to accessibility |
| // state so that it can turn on/off A11Y features appropriately. |
| assistant_->OnAccessibilityStatusChanged( |
| Shell::Get()->accessibility_controller()->spoken_feedback_enabled()); |
| } |
| |
| void AssistantController::OpenUrl(const GURL& url, |
| bool in_background, |
| bool from_server) { |
| if (assistant::util::IsDeepLinkUrl(url)) { |
| NotifyDeepLinkReceived(url); |
| return; |
| } |
| |
| auto* android_helper = AndroidIntentHelper::GetInstance(); |
| if (IsAndroidIntent(url) && !android_helper) { |
| NOTREACHED(); |
| return; |
| } |
| |
| // Give observers an opportunity to perform any necessary handling before we |
| // open the specified |url| in a new browser tab. |
| NotifyOpeningUrl(url, in_background, from_server); |
| |
| if (IsAndroidIntent(url)) { |
| android_helper->LaunchAndroidIntent(url.spec()); |
| } else { |
| // The new tab should be opened with a user activation since the user |
| // interacted with the Assistant to open the url. |in_background| describes |
| // the relationship between |url| and Assistant UI, not the browser. As |
| // such, the browser will always be instructed to open |url| in a new |
| // browser tab and Assistant UI state will be updated downstream to respect |
| // |in_background|. |
| NewWindowDelegate::GetInstance()->NewTabWithUrl( |
| url, /*from_user_interaction=*/true); |
| } |
| NotifyUrlOpened(url, from_server); |
| } |
| |
| bool AssistantController::IsAssistantReady() const { |
| return !!assistant_; |
| } |
| |
| void AssistantController::NotifyConstructed() { |
| for (AssistantControllerObserver& observer : observers_) |
| observer.OnAssistantControllerConstructed(); |
| } |
| |
| void AssistantController::NotifyDestroying() { |
| for (AssistantControllerObserver& observer : observers_) |
| observer.OnAssistantControllerDestroying(); |
| } |
| |
| void AssistantController::NotifyDeepLinkReceived(const GURL& deep_link) { |
| using assistant::util::DeepLinkType; |
| |
| // Retrieve deep link type and parsed parameters. |
| DeepLinkType type = assistant::util::GetDeepLinkType(deep_link); |
| const std::map<std::string, std::string> params = |
| assistant::util::GetDeepLinkParams(deep_link); |
| |
| for (AssistantControllerObserver& observer : observers_) |
| observer.OnDeepLinkReceived(type, params); |
| } |
| |
| void AssistantController::NotifyOpeningUrl(const GURL& url, |
| bool in_background, |
| bool from_server) { |
| for (AssistantControllerObserver& observer : observers_) |
| observer.OnOpeningUrl(url, in_background, from_server); |
| } |
| |
| void AssistantController::NotifyUrlOpened(const GURL& url, bool from_server) { |
| for (AssistantControllerObserver& observer : observers_) |
| observer.OnUrlOpened(url, from_server); |
| } |
| |
| void AssistantController::OnAssistantStatusChanged( |
| mojom::AssistantState state) { |
| if (state == mojom::AssistantState::NOT_READY) |
| assistant_ui_controller_.CloseUi( |
| chromeos::assistant::mojom::AssistantExitPoint::kUnspecified); |
| } |
| |
| void AssistantController::OnLockedFullScreenStateChanged(bool enabled) { |
| if (enabled) |
| assistant_ui_controller_.CloseUi( |
| chromeos::assistant::mojom::AssistantExitPoint::kUnspecified); |
| } |
| |
| void AssistantController::BindController( |
| mojo::PendingReceiver<chromeos::assistant::mojom::AssistantController> |
| receiver) { |
| BindReceiver(std::move(receiver)); |
| } |
| |
| void AssistantController::BindAlarmTimerController( |
| mojo::PendingReceiver<mojom::AssistantAlarmTimerController> receiver) { |
| Shell::Get()->assistant_controller()->alarm_timer_controller()->BindReceiver( |
| std::move(receiver)); |
| } |
| |
| void AssistantController::BindNotificationController( |
| mojo::PendingReceiver<mojom::AssistantNotificationController> receiver) { |
| Shell::Get()->assistant_controller()->notification_controller()->BindReceiver( |
| std::move(receiver)); |
| } |
| |
| void AssistantController::BindScreenContextController( |
| mojo::PendingReceiver<mojom::AssistantScreenContextController> receiver) { |
| Shell::Get() |
| ->assistant_controller() |
| ->screen_context_controller() |
| ->BindReceiver(std::move(receiver)); |
| } |
| |
| void AssistantController::BindStateController( |
| mojo::PendingReceiver<mojom::AssistantStateController> receiver) { |
| assistant_state_controller_.BindReceiver(std::move(receiver)); |
| } |
| |
| void AssistantController::BindVolumeControl( |
| mojo::PendingReceiver<mojom::AssistantVolumeControl> receiver) { |
| Shell::Get()->assistant_controller()->BindReceiver(std::move(receiver)); |
| } |
| |
| base::WeakPtr<AssistantController> AssistantController::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| } // namespace ash |