chromium/chromeos/components/quick_answers/utils/spell_checker.cc

// Copyright 2022 The Chromium Authors
// 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/utils/spell_checker.h"

#include "base/logging.h"
#include "base/strings/string_split.h"
#include "base/types/expected.h"
#include "chromeos/components/quick_answers/public/cpp/quick_answers_prefs.h"
#include "chromeos/constants/chromeos_features.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "ui/base/l10n/l10n_util.h"

namespace quick_answers {

namespace {

bool ShouldDownloadDictionaries() {
  if (QuickAnswersState::IsEnabled()) {
    return true;
  }

  // Spell checker dictionaries are required to show a consent UI by Quick
  // Answers code. Note that `IsEligibleAs` always returns false if current
  // feature type does not match specified feature type, e.g., this
  // `IsEligibleAs` always returns false for `kHmr` case.
  return QuickAnswersState::IsEligibleAs(
             QuickAnswersState::FeatureType::kQuickAnswers) &&
         QuickAnswersState::GetConsentStatusAs(
             QuickAnswersState::FeatureType::kQuickAnswers) ==
             quick_answers::prefs::ConsentStatus::kUnknown;
}

}  // namespace

SpellChecker::SpellChecker(
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
    : url_loader_factory_(url_loader_factory) {
  quick_answers_state_observation_.Observe(QuickAnswersState::Get());
}

SpellChecker::~SpellChecker() {
  spellcheck_languages_.clear();
}

void SpellChecker::CheckSpelling(const std::string& word,
                                 CheckSpellingCallback callback) {
  auto iterator = spellcheck_languages_.begin();
  if (iterator == spellcheck_languages_.end()) {
    std::move(callback).Run(false, std::string());
    return;
  }

  iterator->get()->CheckSpelling(
      word,
      base::BindOnce(&SpellChecker::CollectResults, base::Unretained(this),
                     word, std::move(callback), iterator,
                     iterator->get()->language(), languages_list_version_));
}

void SpellChecker::OnSettingsEnabled(bool enabled) {
  CheckEligibilityAndUpdateLanguages(/*should_recreate_languages_list=*/false);
}

void SpellChecker::OnConsentStatusUpdated(prefs::ConsentStatus status) {
  CheckEligibilityAndUpdateLanguages(/*should_recreate_languages_list=*/false);
}

void SpellChecker::OnApplicationLocaleReady(const std::string& locale) {
  CheckEligibilityAndUpdateLanguages(/*should_recreate_languages_list=*/true);
}

void SpellChecker::OnPreferredLanguagesChanged(
    const std::string& preferred_languages) {
  CheckEligibilityAndUpdateLanguages(/*should_recreate_languages_list=*/true);
}

void SpellChecker::OnEligibilityChanged(bool eligible) {
  CheckEligibilityAndUpdateLanguages(/*should_recreate_languages_list=*/false);
}

void SpellChecker::OnPrefsInitialized() {
  CheckEligibilityAndUpdateLanguages(/*should_recreate_languages_list=*/false);
}

void SpellChecker::CheckEligibilityAndUpdateLanguages(
    bool should_recreate_languages_list) {
  // Still waiting for all of the states to be ready.
  // TODO(b/340628526): remove this once all `QuickAnswersState` field become
  // able to handle uinitialized cases. Callers should not need to care
  // `QuickAnswersState` instance level initialization state.
  if (!QuickAnswersState::Get()->prefs_initialized()) {
    return;
  }

  if (!ShouldDownloadDictionaries()) {
    spellcheck_languages_.clear();
    languages_list_version_++;
    return;
  }

  // Return if languages list is already initialized and no need to recreate.
  if (!spellcheck_languages_.empty() && !should_recreate_languages_list) {
    return;
  }

  // Add application language.
  std::set<std::string> languages;
  languages.insert(
      l10n_util::GetLanguage(QuickAnswersState::Get()->application_locale()));

  // Add preferred languages if supported.
  auto preferred_languages_list =
      base::SplitString(QuickAnswersState::Get()->preferred_languages(), ",",
                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  for (const std::string& locale : preferred_languages_list) {
    auto language = l10n_util::GetLanguage(locale);
    if (QuickAnswersState::Get()->IsSupportedLanguage(language))
      languages.insert(language);
  }

  spellcheck_languages_.clear();
  languages_list_version_++;
  for (auto language : languages) {
    spellcheck_languages_.push_back(
        std::make_unique<SpellCheckLanguage>(url_loader_factory_));
    spellcheck_languages_.back()->Initialize(language);
  }
}

void SpellChecker::CollectResults(const std::string& word,
                                  CheckSpellingCallback callback,
                                  SpellCheckLanguageIterator iterator,
                                  const std::string& language,
                                  int languages_list_version,
                                  bool is_correct) {
  if (is_correct) {
    std::move(callback).Run(true, language);
    return;
  }

  // The languages list has been updated, return false for the current call.
  if (languages_list_version != languages_list_version_) {
    std::move(callback).Run(false, std::string());
    return;
  }

  iterator++;
  if (iterator == spellcheck_languages_.end()) {
    std::move(callback).Run(false, std::string());
    return;
  }

  iterator->get()->CheckSpelling(
      word,
      base::BindOnce(&SpellChecker::CollectResults, base::Unretained(this),
                     word, std::move(callback), iterator,
                     iterator->get()->language(), languages_list_version));
}

}  // namespace quick_answers