|  | // Copyright 2017 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "chrome/browser/language/language_model_manager_factory.h" | 
|  |  | 
|  | #include <memory> | 
|  |  | 
|  | #include "base/feature_list.h" | 
|  | #include "base/task/thread_pool.h" | 
|  | #include "build/build_config.h" | 
|  | #include "chrome/browser/browser_process.h" | 
|  | #include "chrome/browser/language/url_language_histogram_factory.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "components/keyed_service/core/keyed_service.h" | 
|  | #include "components/language/content/browser/geo_language_model.h" | 
|  | #include "components/language/content/browser/geo_language_provider.h" | 
|  | #include "components/language/core/browser/language_model_manager.h" | 
|  | #include "components/language/core/browser/language_prefs.h" | 
|  | #include "components/language/core/browser/locale_util.h" | 
|  | #include "components/language/core/browser/pref_names.h" | 
|  | #include "components/language/core/browser/ulp_metrics_logger.h" | 
|  | #include "components/language/core/browser/url_language_histogram.h" | 
|  | #include "components/language/core/common/language_util.h" | 
|  | #include "components/language/core/language_model/fluent_language_model.h" | 
|  | #include "components/language/core/language_model/ulp_language_model.h" | 
|  | #include "components/pref_registry/pref_registry_syncable.h" | 
|  | #include "components/prefs/pref_service.h" | 
|  | #include "components/translate/core/browser/translate_prefs.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | #include "base/android/jni_string.h" | 
|  | #include "base/android/scoped_java_ref.h" | 
|  | #include "chrome/browser/language/android/language_bridge.h" | 
|  |  | 
|  | using language::ULPMetricsLogger; | 
|  | #endif | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | // Records per-initialization ULP-related metrics. | 
|  | void RecordULPInitMetrics( | 
|  | PrefService* pref_service, | 
|  | const language::UrlLanguageHistogram& page_language_histogram, | 
|  | const std::vector<std::string>& ulp_languages) { | 
|  | language::ULPMetricsLogger logger; | 
|  |  | 
|  | logger.RecordInitiationLanguageCount(ulp_languages.size()); | 
|  |  | 
|  | const std::string app_locale = g_browser_process->GetApplicationLocale(); | 
|  | logger.RecordInitiationUILanguageInULP( | 
|  | ULPMetricsLogger::DetermineLanguageStatus(app_locale, ulp_languages)); | 
|  |  | 
|  | const std::string target_language = | 
|  | translate::TranslatePrefs(pref_service).GetRecentTargetLanguage(); | 
|  | logger.RecordInitiationTranslateTargetInULP( | 
|  | ULPMetricsLogger::DetermineLanguageStatus(target_language, | 
|  | ulp_languages)); | 
|  |  | 
|  | std::vector<std::string> accept_languages; | 
|  | language::LanguagePrefs(pref_service) | 
|  | .GetAcceptLanguagesList(&accept_languages); | 
|  |  | 
|  | language::ULPLanguageStatus accept_language_status = | 
|  | language::ULPLanguageStatus::kLanguageEmpty; | 
|  | if (accept_languages.size() > 0) { | 
|  | accept_language_status = ULPMetricsLogger::DetermineLanguageStatus( | 
|  | accept_languages[0], ulp_languages); | 
|  | } | 
|  | logger.RecordInitiationTopAcceptLanguageInULP(accept_language_status); | 
|  |  | 
|  | logger.RecordInitiationAcceptLanguagesULPOverlap( | 
|  | ULPMetricsLogger::LanguagesOverlapRatio(accept_languages, ulp_languages)); | 
|  |  | 
|  | std::vector<std::string> never_languages_not_in_ulp = | 
|  | ULPMetricsLogger::RemoveULPLanguages( | 
|  | translate::TranslatePrefs(pref_service).GetNeverTranslateLanguages(), | 
|  | ulp_languages); | 
|  | logger.RecordInitiationNeverLanguagesMissingFromULP( | 
|  | never_languages_not_in_ulp); | 
|  | logger.RecordInitiationNeverLanguagesMissingFromULPCount( | 
|  | never_languages_not_in_ulp.size()); | 
|  |  | 
|  | std::vector<std::string> page_languages; | 
|  | for (const language::UrlLanguageHistogram::LanguageInfo& language_info : | 
|  | page_language_histogram.GetTopLanguages()) { | 
|  | page_languages.emplace_back(language_info.language_code); | 
|  | } | 
|  | logger.RecordInitiationAcceptLanguagesPageLanguageOverlap( | 
|  | ULPMetricsLogger::LanguagesOverlapRatio(page_languages, ulp_languages)); | 
|  | std::vector<std::string> page_languages_not_in_ulp = | 
|  | ULPMetricsLogger::RemoveULPLanguages(page_languages, ulp_languages); | 
|  | logger.RecordInitiationPageLanguagesMissingFromULP(page_languages_not_in_ulp); | 
|  | logger.RecordInitiationPageLanguagesMissingFromULPCount( | 
|  | page_languages_not_in_ulp.size()); | 
|  | } | 
|  |  | 
|  | void CreateAndAddULPLanguageModel(Profile* profile, | 
|  | std::vector<std::string> languages) { | 
|  | PrefService* pref_service = profile->GetPrefs(); | 
|  | language::UrlLanguageHistogram* page_languages = | 
|  | UrlLanguageHistogramFactory::GetForBrowserContext(profile); | 
|  | RecordULPInitMetrics(pref_service, *page_languages, languages); | 
|  | language::LanguagePrefs(pref_service).SetULPLanguages(languages); | 
|  |  | 
|  | std::unique_ptr<language::ULPLanguageModel> ulp_model = | 
|  | std::make_unique<language::ULPLanguageModel>(); | 
|  |  | 
|  | int score_divisor = 1; | 
|  | for (std::string lang : languages) { | 
|  | // List of languages is already ordered by preference, generate scores | 
|  | // accordingly. | 
|  | ulp_model->AddULPLanguage(lang, 1.0f / score_divisor); | 
|  | score_divisor++; | 
|  | } | 
|  |  | 
|  | language::LanguageModelManager* manager = | 
|  | LanguageModelManagerFactory::GetForBrowserContext(profile); | 
|  | manager->AddModel(language::LanguageModelManager::ModelType::ULP, | 
|  | std::move(ulp_model)); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | void PrepareLanguageModels(Profile* const profile, | 
|  | language::LanguageModelManager* const manager) { | 
|  | // Use the GeoLanguageModel as the primary Language Model if its experiment is | 
|  | // enabled, and the FluentLanguageModel otherwise. | 
|  | if (language::GetOverrideLanguageModel() == | 
|  | language::OverrideLanguageModel::GEO) { | 
|  | manager->AddModel(language::LanguageModelManager::ModelType::GEO, | 
|  | std::make_unique<language::GeoLanguageModel>( | 
|  | language::GeoLanguageProvider::GetInstance())); | 
|  | manager->SetPrimaryModel(language::LanguageModelManager::ModelType::GEO); | 
|  | } else { | 
|  | manager->AddModel( | 
|  | language::LanguageModelManager::ModelType::FLUENT, | 
|  | std::make_unique<language::FluentLanguageModel>(profile->GetPrefs())); | 
|  | manager->SetPrimaryModel(language::LanguageModelManager::ModelType::FLUENT); | 
|  | } | 
|  |  | 
|  | // On Android, additionally create a ULPLanguageModel and populate it with | 
|  | // ULP data. | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | base::ThreadPool::PostTaskAndReplyWithResult( | 
|  | FROM_HERE, {base::MayBlock()}, | 
|  | base::BindOnce(&language::LanguageBridge::GetULPLanguagesFromDevice, | 
|  | profile->GetProfileUserName()), | 
|  | base::BindOnce(&CreateAndAddULPLanguageModel, profile)); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // static | 
|  | LanguageModelManagerFactory* LanguageModelManagerFactory::GetInstance() { | 
|  | static base::NoDestructor<LanguageModelManagerFactory> instance; | 
|  | return instance.get(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | language::LanguageModelManager* | 
|  | LanguageModelManagerFactory::GetForBrowserContext( | 
|  | content::BrowserContext* const browser_context) { | 
|  | return static_cast<language::LanguageModelManager*>( | 
|  | GetInstance()->GetServiceForBrowserContext(browser_context, true)); | 
|  | } | 
|  |  | 
|  | LanguageModelManagerFactory::LanguageModelManagerFactory() | 
|  | : ProfileKeyedServiceFactory( | 
|  | "LanguageModelManager", | 
|  | // Use the original profile's language model even in Incognito mode. | 
|  | ProfileSelections::Builder() | 
|  | .WithRegular(ProfileSelection::kRedirectedToOriginal) | 
|  | // TODO(crbug.com/40257657): Check if this service is needed in | 
|  | // Guest mode. | 
|  | .WithGuest(ProfileSelection::kRedirectedToOriginal) | 
|  | // TODO(crbug.com/41488885): Check if this service is needed for | 
|  | // Ash Internals. | 
|  | .WithAshInternals(ProfileSelection::kRedirectedToOriginal) | 
|  | .Build()) {} | 
|  |  | 
|  | LanguageModelManagerFactory::~LanguageModelManagerFactory() = default; | 
|  |  | 
|  | std::unique_ptr<KeyedService> | 
|  | LanguageModelManagerFactory::BuildServiceInstanceForBrowserContext( | 
|  | content::BrowserContext* const browser_context) const { | 
|  | Profile* const profile = Profile::FromBrowserContext(browser_context); | 
|  | std::unique_ptr<language::LanguageModelManager> manager = | 
|  | std::make_unique<language::LanguageModelManager>( | 
|  | profile->GetPrefs(), g_browser_process->GetApplicationLocale()); | 
|  | PrepareLanguageModels(profile, manager.get()); | 
|  | return manager; | 
|  | } |