| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/assistant/assistant_ui_controller_impl.h" |
| |
| #include <optional> |
| |
| #include "ash/assistant/assistant_controller_impl.h" |
| #include "ash/assistant/model/assistant_interaction_model.h" |
| #include "ash/assistant/ui/assistant_ui_constants.h" |
| #include "ash/assistant/util/assistant_util.h" |
| #include "ash/assistant/util/deep_link_util.h" |
| #include "ash/assistant/util/histogram_util.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/constants/notifier_catalogs.h" |
| #include "ash/public/cpp/assistant/assistant_setup.h" |
| #include "ash/public/cpp/assistant/assistant_state.h" |
| #include "ash/public/cpp/assistant/controller/assistant_interaction_controller.h" |
| #include "ash/public/cpp/system/toast_data.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/toast/toast_manager_impl.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "chromeos/ash/services/assistant/public/cpp/assistant_browser_delegate.h" |
| #include "chromeos/ash/services/assistant/public/cpp/assistant_prefs.h" |
| #include "chromeos/ash/services/assistant/public/cpp/assistant_service.h" |
| #include "chromeos/ash/services/assistant/public/cpp/features.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // Helpers --------------------------------------------------------------------- |
| |
| PrefService* pref_service() { |
| auto* result = |
| Shell::Get()->session_controller()->GetPrimaryUserPrefService(); |
| DCHECK(result); |
| return result; |
| } |
| |
| // Toast ----------------------------------------------------------------------- |
| |
| constexpr char kUnboundServiceToastId[] = |
| "assistant_controller_unbound_service"; |
| |
| void ShowToast(const std::string& id, |
| ToastCatalogName catalog_name, |
| int message_id) { |
| ToastData toast(id, catalog_name, l10n_util::GetStringUTF16(message_id)); |
| Shell::Get()->toast_manager()->Show(std::move(toast)); |
| } |
| |
| } // namespace |
| |
| // AssistantUiControllerImpl --------------------------------------------------- |
| |
| AssistantUiControllerImpl::AssistantUiControllerImpl( |
| AssistantControllerImpl* assistant_controller) |
| : assistant_controller_(assistant_controller) { |
| model_.AddObserver(this); |
| assistant_controller_observation_.Observe(AssistantController::Get()); |
| overview_controller_observation_.Observe(Shell::Get()->overview_controller()); |
| } |
| |
| AssistantUiControllerImpl::~AssistantUiControllerImpl() { |
| model_.RemoveObserver(this); |
| } |
| |
| void AssistantUiControllerImpl::SetAssistant(assistant::Assistant* assistant) { |
| assistant_ = assistant; |
| } |
| |
| const AssistantUiModel* AssistantUiControllerImpl::GetModel() const { |
| return &model_; |
| } |
| |
| int AssistantUiControllerImpl::GetNumberOfSessionsWhereOnboardingShown() const { |
| return pref_service()->GetInteger( |
| prefs::kAssistantNumSessionsWhereOnboardingShown); |
| } |
| |
| bool AssistantUiControllerImpl::HasShownOnboarding() const { |
| return has_shown_onboarding_; |
| } |
| |
| void AssistantUiControllerImpl::SetKeyboardTraversalMode( |
| bool keyboard_traversal_mode) { |
| model_.SetKeyboardTraversalMode(keyboard_traversal_mode); |
| } |
| |
| void AssistantUiControllerImpl::ShowUi(AssistantEntryPoint entry_point) { |
| // TODO: crbug.com/417538592 - open Gemini PWA via AppList and delete |
| // dependency to AssistantBrowserDelegate. |
| if (ash::assistant::features::IsNewEntryPointEnabled()) { |
| assistant::AssistantBrowserDelegate::Get()->OpenNewEntryPoint(); |
| return; |
| } |
| |
| // Skip if the opt-in window is active. |
| auto* assistant_setup = AssistantSetup::GetInstance(); |
| if (assistant_setup && assistant_setup->BounceOptInWindowIfActive()) |
| return; |
| |
| auto* assistant_state = AssistantState::Get(); |
| |
| if (!assistant_state->settings_enabled().value_or(false) || |
| assistant_state->locked_full_screen_enabled().value_or(false)) { |
| return; |
| } |
| |
| // TODO(dmblack): Show a more helpful message to the user. |
| if (assistant_state->assistant_status() == |
| assistant::AssistantStatus::NOT_READY) { |
| ShowUnboundErrorToast(); |
| return; |
| } |
| |
| if (!assistant_) { |
| ShowUnboundErrorToast(); |
| return; |
| } |
| |
| model_.SetVisible(entry_point); |
| } |
| |
| std::optional<base::ScopedClosureRunner> AssistantUiControllerImpl::CloseUi( |
| AssistantExitPoint exit_point) { |
| if (model_.visibility() != AssistantVisibility::kVisible) |
| return std::nullopt; |
| |
| // Set visibility to `kClosing`. |
| model_.SetClosing(exit_point); |
| |
| // When the return value is destroyed, visibility will be set to `kClosed` |
| // provided the visibility change hasn't been invalidated. |
| return base::ScopedClosureRunner(base::BindOnce( |
| [](const base::WeakPtr<AssistantUiControllerImpl>& weak_ptr, |
| assistant::AssistantExitPoint exit_point) { |
| if (weak_ptr) |
| weak_ptr->model_.SetClosed(exit_point); |
| }, |
| weak_factory_for_delayed_visibility_changes_.GetWeakPtr(), exit_point)); |
| } |
| |
| void AssistantUiControllerImpl::SetAppListBubbleWidth(int width) { |
| model_.SetAppListBubbleWidth(width); |
| } |
| |
| void AssistantUiControllerImpl::ToggleUi( |
| std::optional<AssistantEntryPoint> entry_point, |
| std::optional<AssistantExitPoint> exit_point) { |
| // When not visible, toggling will show the UI. |
| if (model_.visibility() != AssistantVisibility::kVisible) { |
| DCHECK(entry_point.has_value()); |
| ShowUi(entry_point.value()); |
| return; |
| } |
| |
| // Otherwise toggling closes the UI. |
| DCHECK(exit_point.has_value()); |
| CloseUi(exit_point.value()); |
| } |
| |
| void AssistantUiControllerImpl::OnInteractionStateChanged( |
| InteractionState interaction_state) { |
| if (interaction_state != InteractionState::kActive) |
| return; |
| |
| // If there is an active interaction, we need to show Assistant UI if it is |
| // not already showing. We don't have enough information here to know what |
| // the interaction source is. |
| ShowUi(AssistantEntryPoint::kUnspecified); |
| } |
| |
| void AssistantUiControllerImpl::OnAssistantControllerConstructed() { |
| AssistantInteractionController::Get()->GetModel()->AddObserver(this); |
| assistant_controller_->view_delegate()->AddObserver(this); |
| } |
| |
| void AssistantUiControllerImpl::OnAssistantControllerDestroying() { |
| assistant_controller_->view_delegate()->RemoveObserver(this); |
| AssistantInteractionController::Get()->GetModel()->RemoveObserver(this); |
| } |
| |
| void AssistantUiControllerImpl::OnOpeningUrl(const GURL& url, |
| bool in_background, |
| bool from_server) { |
| if (model_.visibility() != AssistantVisibility::kVisible) |
| return; |
| |
| CloseUi(from_server ? AssistantExitPoint::kNewBrowserTabFromServer |
| : AssistantExitPoint::kNewBrowserTabFromUser); |
| } |
| |
| void AssistantUiControllerImpl::OnUiVisibilityChanged( |
| AssistantVisibility new_visibility, |
| AssistantVisibility old_visibility, |
| std::optional<AssistantEntryPoint> entry_point, |
| std::optional<AssistantExitPoint> exit_point) { |
| weak_factory_for_delayed_visibility_changes_.InvalidateWeakPtrs(); |
| |
| if (new_visibility == AssistantVisibility::kVisible) { |
| // Only record the entry point when Assistant UI becomes visible. |
| assistant::util::RecordAssistantEntryPoint(entry_point.value()); |
| } |
| |
| if (old_visibility == AssistantVisibility::kVisible) { |
| // Only record the exit point when Assistant UI becomes invisible to |
| // avoid recording duplicate events (e.g. pressing ESC key). |
| assistant::util::RecordAssistantExitPoint(exit_point.value()); |
| } |
| } |
| |
| void AssistantUiControllerImpl::OnOnboardingShown() { |
| if (has_shown_onboarding_) |
| return; |
| |
| has_shown_onboarding_ = true; |
| |
| // Update the number of user sessions in which Assistant onboarding was shown. |
| pref_service()->SetInteger(prefs::kAssistantNumSessionsWhereOnboardingShown, |
| GetNumberOfSessionsWhereOnboardingShown() + 1); |
| } |
| |
| void AssistantUiControllerImpl::OnOverviewModeWillStart() { |
| // Close Assistant UI before entering overview mode. |
| CloseUi(AssistantExitPoint::kOverviewMode); |
| } |
| |
| void AssistantUiControllerImpl::ShowUnboundErrorToast() { |
| ShowToast(kUnboundServiceToastId, ToastCatalogName::kAssistantUnboundService, |
| IDS_ASH_ASSISTANT_ERROR_GENERIC); |
| } |
| |
| } // namespace ash |