chromium/ash/assistant/assistant_suggestions_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_suggestions_controller_impl.h"

#include <algorithm>
#include <string>
#include <utility>
#include <vector>

#include "ash/assistant/model/assistant_ui_model.h"
#include "ash/assistant/util/assistant_util.h"
#include "ash/assistant/util/deep_link_util.h"
#include "ash/assistant/util/resource_util.h"
#include "ash/public/cpp/assistant/controller/assistant_ui_controller.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/functional/bind.h"
#include "base/rand_util.h"
#include "base/unguessable_token.h"
#include "chromeos/ash/services/assistant/public/cpp/assistant_prefs.h"
#include "chromeos/ash/services/assistant/public/cpp/features.h"
#include "chromeos/ash/services/libassistant/public/cpp/assistant_suggestion.h"
#include "ui/base/l10n/l10n_util.h"

namespace ash {

namespace {

using assistant::AssistantSuggestion;
using assistant::AssistantSuggestionType;
using assistant::prefs::AssistantOnboardingMode;

// Conversation starters -------------------------------------------------------

constexpr int kMaxNumOfConversationStarters = 3;

}  // namespace

// AssistantSuggestionsControllerImpl ------------------------------------------

AssistantSuggestionsControllerImpl::AssistantSuggestionsControllerImpl() {
  UpdateConversationStarters();
  assistant_controller_observation_.Observe(AssistantController::Get());
}

AssistantSuggestionsControllerImpl::~AssistantSuggestionsControllerImpl() =
    default;

const AssistantSuggestionsModel* AssistantSuggestionsControllerImpl::GetModel()
    const {
  return &model_;
}

void AssistantSuggestionsControllerImpl::OnAssistantControllerConstructed() {
  AssistantUiController::Get()->GetModel()->AddObserver(this);
  AssistantState::Get()->AddObserver(this);
}

void AssistantSuggestionsControllerImpl::OnAssistantControllerDestroying() {
  AssistantState::Get()->RemoveObserver(this);
  AssistantUiController::Get()->GetModel()->RemoveObserver(this);
}

void AssistantSuggestionsControllerImpl::OnUiVisibilityChanged(
    AssistantVisibility new_visibility,
    AssistantVisibility old_visibility,
    std::optional<AssistantEntryPoint> entry_point,
    std::optional<AssistantExitPoint> exit_point) {
  // When Assistant is finishing a session, we update our cache of conversation
  // starters so that they're fresh for the next launch.
  if (assistant::util::IsFinishingSession(new_visibility))
    UpdateConversationStarters();
}

void AssistantSuggestionsControllerImpl::OnAssistantContextEnabled(
    bool enabled) {
  // We currently assume that the context setting is not being modified while
  // Assistant UI is visible.
  DCHECK_NE(AssistantVisibility::kVisible,
            AssistantUiController::Get()->GetModel()->visibility());
  UpdateConversationStarters();
}

void AssistantSuggestionsControllerImpl::OnAssistantOnboardingModeChanged(
    AssistantOnboardingMode onboarding_mode) {
  UpdateOnboardingSuggestions();
}

void AssistantSuggestionsControllerImpl::UpdateConversationStarters() {
  std::vector<AssistantSuggestion> conversation_starters;

  // Adds a conversation starter for the given |message_id| and |action_url|.
  auto AddConversationStarter = [&conversation_starters](
                                    int message_id, GURL action_url = GURL()) {
    AssistantSuggestion starter;
    starter.id = base::UnguessableToken::Create();
    starter.type = AssistantSuggestionType::kConversationStarter;
    starter.text = l10n_util::GetStringUTF8(message_id);
    starter.action_url = action_url;
    conversation_starters.push_back(std::move(starter));
  };

  // Always show the "What can you do?" conversation starter.
  AddConversationStarter(IDS_ASH_ASSISTANT_CHIP_WHAT_CAN_YOU_DO);

  // The rest of the conversation starters will be shuffled...
  std::vector<int> shuffled_message_ids;

  shuffled_message_ids.push_back(IDS_ASH_ASSISTANT_CHIP_IM_BORED);
  shuffled_message_ids.push_back(IDS_ASH_ASSISTANT_CHIP_OPEN_FILES);
  shuffled_message_ids.push_back(IDS_ASH_ASSISTANT_CHIP_PLAY_MUSIC);
  shuffled_message_ids.push_back(IDS_ASH_ASSISTANT_CHIP_SEND_AN_EMAIL);
  shuffled_message_ids.push_back(IDS_ASH_ASSISTANT_CHIP_SET_A_REMINDER);
  shuffled_message_ids.push_back(IDS_ASH_ASSISTANT_CHIP_TELL_ME_A_JOKE);
  shuffled_message_ids.push_back(IDS_ASH_ASSISTANT_CHIP_WHATS_ON_MY_CALENDAR);
  shuffled_message_ids.push_back(IDS_ASH_ASSISTANT_CHIP_WHATS_THE_WEATHER);

  base::RandomShuffle(shuffled_message_ids.begin(), shuffled_message_ids.end());

  // ...and added until we have no more than |kMaxNumOfConversationStarters|.
  for (int i = 0;
       conversation_starters.size() < kMaxNumOfConversationStarters &&
       i < static_cast<int>(shuffled_message_ids.size());
       ++i) {
    AddConversationStarter(shuffled_message_ids[i]);
  }

  model_.SetConversationStarters(std::move(conversation_starters));
}

void AssistantSuggestionsControllerImpl::UpdateOnboardingSuggestions() {
  auto CreateIconResourceLink = [](int message_id) {
    switch (message_id) {
      case IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_CONVERSION:
        return assistant::util::CreateIconResourceLink(
            assistant::util::IconName::kConversionPath);
      case IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_KNOWLEDGE:
        return assistant::util::CreateIconResourceLink(
            assistant::util::IconName::kPersonPinCircle);
      case IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_KNOWLEDGE_EDU:
        return assistant::util::CreateIconResourceLink(
            assistant::util::IconName::kStraighten);
      case IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_LANGUAGE:
        return assistant::util::CreateIconResourceLink(
            assistant::util::IconName::kTranslate);
      case IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_MATH:
        return assistant::util::CreateIconResourceLink(
            assistant::util::IconName::kCalculate);
      case IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_PERSONALITY:
        return assistant::util::CreateIconResourceLink(
            assistant::util::IconName::kSentimentVerySatisfied);
      case IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_PRODUCTIVITY:
        return assistant::util::CreateIconResourceLink(
            assistant::util::IconName::kTimer);
      case IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_TECHNICAL:
        return assistant::util::CreateIconResourceLink(
            assistant::util::IconName::kScreenshot);
      default:
        NOTREACHED();
    }
  };

  std::vector<AssistantSuggestion> onboarding_suggestions;

  using assistant::AssistantBetterOnboardingType;
  auto AddSuggestion = [&CreateIconResourceLink, &onboarding_suggestions](
                           int message_id, AssistantBetterOnboardingType type) {
    onboarding_suggestions.emplace_back();
    auto& suggestion = onboarding_suggestions.back();
    suggestion.id = base::UnguessableToken::Create();
    suggestion.type = AssistantSuggestionType::kBetterOnboarding;
    suggestion.better_onboarding_type = type;
    suggestion.text = l10n_util::GetStringUTF8(message_id);
    suggestion.icon_url = CreateIconResourceLink(message_id);
    suggestion.action_url = GURL();
  };

  switch (AssistantState::Get()->onboarding_mode().value_or(
      AssistantOnboardingMode::kDefault)) {
    case AssistantOnboardingMode::kEducation:
      AddSuggestion(IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_MATH,
                    AssistantBetterOnboardingType::kMath);
      AddSuggestion(IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_KNOWLEDGE_EDU,
                    AssistantBetterOnboardingType::kKnowledgeEdu);
      break;
    case AssistantOnboardingMode::kDefault:
      AddSuggestion(IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_CONVERSION,
                    AssistantBetterOnboardingType::kConversion);
      AddSuggestion(IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_KNOWLEDGE,
                    AssistantBetterOnboardingType::kKnowledge);
      break;
  }

  AddSuggestion(IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_PRODUCTIVITY,
                AssistantBetterOnboardingType::kProductivity);
  AddSuggestion(IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_PERSONALITY,
                AssistantBetterOnboardingType::kPersonality);
  AddSuggestion(IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_LANGUAGE,
                AssistantBetterOnboardingType::kLanguage);
  AddSuggestion(IDS_ASH_ASSISTANT_ONBOARDING_SUGGESTION_TECHNICAL,
                AssistantBetterOnboardingType::kTechnical);

  model_.SetOnboardingSuggestions(std::move(onboarding_suggestions));
}

}  // namespace ash