chromium/ash/assistant/assistant_ui_controller_impl.cc

// 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_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);
}

// static
void AssistantUiControllerImpl::RegisterProfilePrefs(
    PrefRegistrySimple* registry) {
  registry->RegisterIntegerPref(
      prefs::kAssistantNumSessionsWhereOnboardingShown, 0);
}

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) {
  // 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