text: // Copyright 2019 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 "chromeos/components/quick_answers/quick_answers_client.h"
  
  #include <utility>
  
  #include "base/strings/stringprintf.h"
  #include "chromeos/components/quick_answers/quick_answers_model.h"
  #include "chromeos/components/quick_answers/utils/quick_answers_metrics.h"
  #include "chromeos/constants/chromeos_features.h"
  #include "third_party/icu/source/common/unicode/locid.h"
  
  namespace chromeos {
  namespace quick_answers {
  namespace {
  
  using network::mojom::URLLoaderFactory;
  
  constexpr char kUnitConversionQueryRewriteTemplate[] = "Convert:%s";
  constexpr char kDictionaryQueryRewriteTemplate[] = "Define:%s";
  constexpr char kTranslationQueryRewriteTemplate[] = "Translate:%s";
  
  QuickAnswersClient::ResultLoaderFactoryCallback*
      g_testing_result_factory_callback = nullptr;
  
  QuickAnswersClient::IntentGeneratorFactoryCallback*
      g_testing_intent_generator_factory_callback = nullptr;
  
  const PreprocessedOutput PreprocessRequest(const QuickAnswersRequest& request,
                                             const std::string& intent_text,
                                             IntentType intent_type) {
    PreprocessedOutput processed_output;
    processed_output.intent_text = intent_text;
    processed_output.query = intent_text;
    processed_output.intent_type = intent_type;
  
    switch (intent_type) {
      case IntentType::kUnit:
        processed_output.query = base::StringPrintf(
            kUnitConversionQueryRewriteTemplate, intent_text.c_str());
        break;
      case IntentType::kDictionary:
        processed_output.query = base::StringPrintf(
            kDictionaryQueryRewriteTemplate, intent_text.c_str());
        break;
      case IntentType::kTranslation:
        processed_output.query = base::StringPrintf(
            kTranslationQueryRewriteTemplate, intent_text.c_str());
        break;
      case IntentType::kUnknown:
        // TODO(llin): Update to NOTREACHED after integrating with TCLib.
        break;
    }
    return processed_output;
  }
  
  }  // namespace
  
  // static
  void QuickAnswersClient::SetResultLoaderFactoryForTesting(
      ResultLoaderFactoryCallback* factory) {
    g_testing_result_factory_callback = factory;
  }
  
  void QuickAnswersClient::SetIntentGeneratorFactoryForTesting(
      IntentGeneratorFactoryCallback* factory) {
    g_testing_intent_generator_factory_callback = factory;
  }
  
  bool QuickAnswersClient::IsQuickAnswersAllowedForLocale(
      const std::string& locale,
      const std::string& runtime_locale) {
    // String literals used in some cases in the array because their
    // constant equivalents don't exist in:
    // third_party/icu/source/common/unicode/uloc.h
    const std::string kAllowedLocales[] = {ULOC_CANADA, ULOC_UK, ULOC_US,
                                           "en_AU",     "en_IN", "en_NZ"};
    return base::Contains(kAllowedLocales, locale) ||
           base::Contains(kAllowedLocales, runtime_locale);
  }
  
  QuickAnswersClient::QuickAnswersClient(URLLoaderFactory* url_loader_factory,
                                         ash::AssistantState* assistant_state,
                                         QuickAnswersDelegate* delegate)
      : url_loader_factory_(url_loader_factory),
        assistant_state_(assistant_state),
        delegate_(delegate) {
    if (assistant_state_) {
      // We observe Assistant state to detect enabling/disabling of Assistant in
      // settings as well as enabling/disabling of screen context.
      assistant_state_->AddObserver(this);
    }
  }
  
  QuickAnswersClient::~QuickAnswersClient() {
    if (assistant_state_)
      assistant_state_->RemoveObserver(this);
  }
  
  void QuickAnswersClient::OnAssistantFeatureAllowedChanged(
      chromeos::assistant::AssistantAllowedState state) {
    assistant_allowed_state_ = state;
    NotifyEligibilityChanged();
  }
  
  void QuickAnswersClient::OnAssistantSettingsEnabled(bool enabled) {
    assistant_enabled_ = enabled;
    NotifyEligibilityChanged();
  }
  
  void QuickAnswersClient::OnAssistantContextEnabled(bool enabled) {
    assistant_context_enabled_ = enabled;
    NotifyEligibilityChanged();
  }
  
  void QuickAnswersClient::OnAssistantQuickAnswersEnabled(bool enabled) {
    quick_answers_settings_enabled_ = enabled;
    NotifyEligibilityChanged();
  }
  
  void QuickAnswersClient::OnLocaleChanged(const std::string& locale) {
    locale_supported_ = IsQuickAnswersAllowedForLocale(
        locale, icu::Locale::getDefault().getName());
    NotifyEligibilityChanged();
  }
  
  void QuickAnswersClient::OnAssistantStateDestroyed() {
    assistant_state_ = nullptr;
  }
  
  void QuickAnswersClient::SendRequestForPreprocessing(
      const QuickAnswersRequest& quick_answers_request) {
    SendRequestInternal(quick_answers_request, /*skip_fetch=*/true);
  }
  
  void QuickAnswersClient::FetchQuickAnswers(
      const QuickAnswersRequest& preprocessed_request) {
    DCHECK(!preprocessed_request.preprocessed_output.query.empty());
  
    result_loader_ =
        CreateResultLoader(preprocessed_request.preprocessed_output.intent_type);
    // Load and parse search result.
    result_loader_->Fetch(preprocessed_request.preprocessed_output.query);
  }
  
  void QuickAnswersClient::SendRequest(
      const QuickAnswersRequest& quick_answers_request) {
    SendRequestInternal(quick_answers_request, /*skip_fetch=*/false);
  }
  
  void QuickAnswersClient::OnQuickAnswerClick(ResultType result_type) {
    RecordClick(result_type, GetImpressionDuration());
  }
  
  void QuickAnswersClient::OnQuickAnswersDismissed(ResultType result_type,
                                                   bool is_active) {
    if (is_active)
      RecordActiveImpression(result_type, GetImpressionDuration());
  }
  
  void QuickAnswersClient::NotifyEligibilityChanged() {
    DCHECK(delegate_);
  
    bool is_eligible =
        (chromeos::features::IsQuickAnswersEnabled() && assistant_state_ &&
         assistant_enabled_ && locale_supported_ && assistant_context_enabled_ &&
         (!chromeos::features::IsQuickAnswersSettingToggleEnabled() ||
          quick_answers_settings_enabled_) &&
         assistant_allowed_state_ ==
             chromeos::assistant::AssistantAllowedState::ALLOWED);
  
    if (is_eligible_ != is_eligible) {
      is_eligible_ = is_eligible;
      delegate_->OnEligibilityChanged(is_eligible);
    }
  }
  
  std::unique_ptr<ResultLoader> QuickAnswersClient::CreateResultLoader(
      IntentType intent_type) {
    if (g_testing_result_factory_callback)
      return g_testing_result_factory_callback->Run();
    return ResultLoader::Create(intent_type, url_loader_factory_, this);
  }
  
  std::unique_ptr<IntentGenerator> QuickAnswersClient::CreateIntentGenerator(
      const QuickAnswersRequest& request,
      bool skip_fetch) {
    if (g_testing_intent_generator_factory_callback)
      return g_testing_intent_generator_factory_callback->Run();
    return std::make_unique<IntentGenerator>(
        base::BindOnce(&QuickAnswersClient::IntentGeneratorCallback,
                       weak_factory_.GetWeakPtr(), request, skip_fetch));
  }
  
  void QuickAnswersClient::OnNetworkError() {
    DCHECK(delegate_);
    delegate_->OnNetworkError();
  }
  
  void QuickAnswersClient::OnQuickAnswerReceived(
      std::unique_ptr<QuickAnswer> quick_answer) {
    DCHECK(delegate_);
    quick_answer_received_time_ = base::TimeTicks::Now();
    delegate_->OnQuickAnswerReceived(std::move(quick_answer));
  }
  
  void QuickAnswersClient::SendRequestInternal(
      const QuickAnswersRequest& quick_answers_request,
      bool skip_fetch) {
    RecordSelectedTextLength(quick_answers_request.selected_text.length());
  
    // Generate intent from |quick_answers_request|.
    intent_generator_ = CreateIntentGenerator(quick_answers_request, skip_fetch);
    intent_generator_->GenerateIntent(quick_answers_request);
  }
  
  void QuickAnswersClient::IntentGeneratorCallback(
      const QuickAnswersRequest& quick_answers_request,
      bool skip_fetch,
      const std::string& intent_text,
      IntentType intent_type) {
    DCHECK(delegate_);
  
    // Preprocess the request.
    QuickAnswersRequest processed_request = quick_answers_request;
    processed_request.preprocessed_output =
        PreprocessRequest(quick_answers_request, intent_text, intent_type);
  
    delegate_->OnRequestPreprocessFinished(processed_request);
  
    if (features::IsQuickAnswersTextAnnotatorEnabled()) {
      RecordIntentType(processed_request.preprocessed_output.intent_type);
      if (processed_request.preprocessed_output.intent_type ==
          IntentType::kUnknown) {
        // Don't fetch answer if no intent is generated.
        return;
      }
    }
  
    if (!skip_fetch)
      FetchQuickAnswers(processed_request);
  }
  
  base::TimeDelta QuickAnswersClient::GetImpressionDuration() const {
    // Use default 0 duration.
    base::TimeDelta duration;
    if (!quick_answer_received_time_.is_null()) {
      // Fetch finish, set the duration to be between fetch finish and now.
      duration = base::TimeTicks::Now() - quick_answer_received_time_;
    }
    return duration;
  }
  
  }  // namespace quick_answers
  }  // namespace chromeos
  