| // Copyright 2020 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 "components/soda/soda_installer.h" |
| |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "base/observer_list.h" |
| #include "base/values.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/live_caption/pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "components/soda/constants.h" |
| #include "components/soda/pref_names.h" |
| #include "media/base/media_switches.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| namespace { |
| |
| constexpr int kSodaCleanUpDelayInDays = 30; |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| inline std::string GetProjectorLanguageCode(PrefService* pref_service) { |
| return pref_service->GetString(ash::prefs::kProjectorCreationFlowLanguage); |
| } |
| |
| #endif // IS_CHROMEOS_ASH |
| |
| } // namespace |
| |
| namespace speech { |
| |
| SodaInstaller* g_instance = nullptr; |
| |
| // static |
| SodaInstaller* SodaInstaller::GetInstance() { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| DCHECK( |
| base::FeatureList::IsEnabled(ash::features::kOnDeviceSpeechRecognition)); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| return g_instance; |
| } |
| |
| SodaInstaller::SodaInstaller() { |
| DCHECK_EQ(nullptr, g_instance); |
| g_instance = this; |
| } |
| |
| SodaInstaller::~SodaInstaller() { |
| DCHECK_EQ(this, g_instance); |
| g_instance = nullptr; |
| } |
| |
| // static |
| void SodaInstaller::RegisterLocalStatePrefs(PrefRegistrySimple* registry) { |
| registry->RegisterTimePref(prefs::kSodaScheduledDeletionTime, base::Time()); |
| SodaInstaller::RegisterRegisteredLanguagePackPref(registry); |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| // Handle non-Chrome-OS logic here. We need to keep the implementation of this |
| // method in the parent class to avoid duplicate declaration build errors |
| // (specifically on Windows). |
| registry->RegisterFilePathPref(prefs::kSodaBinaryPath, base::FilePath()); |
| |
| // Register language pack config path preferences. |
| for (const SodaLanguagePackComponentConfig& config : |
| kLanguageComponentConfigs) { |
| registry->RegisterFilePathPref(config.config_path_pref, base::FilePath()); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| void SodaInstaller::Init(PrefService* profile_prefs, |
| PrefService* global_prefs) { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| if (!base::FeatureList::IsEnabled( |
| ash::features::kOnDeviceSpeechRecognition) || |
| soda_installer_initialized_) { |
| #else // !BUILDFLAG(IS_CHROMEOS_ASH) |
| if (soda_installer_initialized_) { |
| #endif |
| return; |
| } |
| |
| if (IsAnyFeatureUsingSodaEnabled(profile_prefs)) { |
| soda_installer_initialized_ = true; |
| // Set the SODA uninstaller time to NULL time so that it doesn't get |
| // uninstalled when features are using it. |
| global_prefs->SetTime(prefs::kSodaScheduledDeletionTime, base::Time()); |
| SodaInstaller::GetInstance()->InstallSoda(global_prefs); |
| |
| if (global_prefs->GetValueList(prefs::kSodaRegisteredLanguagePacks) |
| .empty()) { |
| // TODO(crbug.com/1200667): Register the default language used by |
| // Dictation on ChromeOS. |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| RegisterLanguage(GetProjectorLanguageCode(profile_prefs), global_prefs); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| RegisterLanguage(prefs::GetLiveCaptionLanguageCode(profile_prefs), |
| global_prefs); |
| } |
| |
| for (const auto& language : |
| global_prefs->GetValueList(prefs::kSodaRegisteredLanguagePacks)) { |
| SodaInstaller::GetInstance()->InstallLanguage(language.GetString(), |
| global_prefs); |
| } |
| } else { |
| base::Time deletion_time = |
| global_prefs->GetTime(prefs::kSodaScheduledDeletionTime); |
| if (!deletion_time.is_null() && deletion_time <= base::Time::Now()) { |
| UninstallSoda(global_prefs); |
| soda_installer_initialized_ = false; |
| } |
| } |
| } |
| |
| void SodaInstaller::SetUninstallTimer(PrefService* profile_prefs, |
| PrefService* global_prefs) { |
| // Do not schedule uninstallation if any SODA client features are still |
| // enabled. |
| if (IsAnyFeatureUsingSodaEnabled(profile_prefs)) |
| return; |
| |
| // Schedule deletion. |
| global_prefs->SetTime( |
| prefs::kSodaScheduledDeletionTime, |
| base::Time::Now() + base::Days(kSodaCleanUpDelayInDays)); |
| } |
| |
| bool SodaInstaller::IsSodaInstalled(LanguageCode language_code) const { |
| return (soda_binary_installed_ && IsLanguageInstalled(language_code)); |
| } |
| |
| bool SodaInstaller::IsLanguageInstalled(LanguageCode language_code) const { |
| return base::Contains(installed_languages_, language_code); |
| } |
| |
| void SodaInstaller::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void SodaInstaller::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void SodaInstaller::NotifySodaInstalledForTesting(LanguageCode language_code) { |
| // TODO: Call the actual functions in SodaInstallerImpl and |
| // SodaInstallerImpleChromeOS that do this logic |
| // (e.g. SodaInstallerImpl::OnSodaBinaryInstalled) rather than faking it. |
| |
| // If language code is none, this signifies that the SODA binary installed. |
| if (language_code == LanguageCode::kNone) { |
| soda_binary_installed_ = true; |
| is_soda_downloading_ = false; |
| for (LanguageCode installed_language : installed_languages_) { |
| NotifyOnSodaInstalled(installed_language); |
| } |
| return; |
| } |
| |
| // Otherwise, this means a language pack installed. |
| installed_languages_.insert(language_code); |
| if (base::Contains(language_pack_progress_, language_code)) |
| language_pack_progress_.erase(language_code); |
| if (soda_binary_installed_) |
| NotifyOnSodaInstalled(language_code); |
| } |
| |
| void SodaInstaller::NotifySodaErrorForTesting(LanguageCode language_code) { |
| // TODO: Call the actual functions in SodaInstallerImpl and |
| // SodaInstallerImpleChromeOS that do this logic rather than faking it. |
| if (language_code == LanguageCode::kNone) { |
| // Error with the SODA binary download. |
| soda_binary_installed_ = false; |
| is_soda_downloading_ = false; |
| language_pack_progress_.clear(); |
| } else { |
| // Error with the language pack download. |
| if (base::Contains(language_pack_progress_, language_code)) |
| language_pack_progress_.erase(language_code); |
| } |
| NotifyOnSodaError(language_code); |
| } |
| |
| void SodaInstaller::UninstallSodaForTesting() { |
| soda_binary_installed_ = false; |
| is_soda_downloading_ = false; |
| soda_installer_initialized_ = false; |
| installed_languages_.clear(); |
| language_pack_progress_.clear(); |
| } |
| |
| void SodaInstaller::NotifySodaProgressForTesting(int progress, |
| LanguageCode language_code) { |
| // TODO: Call the actual functions in SodaInstallerImpl and |
| // SodaInstallerImpleChromeOS that do this logic rather than faking it. |
| if (language_code == LanguageCode::kNone) { |
| // SODA binary download progress. |
| soda_binary_installed_ = false; |
| is_soda_downloading_ = true; |
| } else { |
| // Language pack download progress. |
| if (base::Contains(language_pack_progress_, language_code)) |
| language_pack_progress_.insert({language_code, progress}); |
| else |
| language_pack_progress_[language_code] = progress; |
| } |
| NotifyOnSodaProgress(language_code, progress); |
| } |
| |
| bool SodaInstaller::IsAnyLanguagePackInstalledForTesting() const { |
| return !installed_languages_.empty(); |
| } |
| |
| void SodaInstaller::RegisterRegisteredLanguagePackPref( |
| PrefRegistrySimple* registry) { |
| // TODO: Default to one of the user's languages. |
| base::Value::List default_languages; |
| default_languages.Append(base::Value(kUsEnglishLocale)); |
| registry->RegisterListPref(prefs::kSodaRegisteredLanguagePacks, |
| std::move(default_languages)); |
| } |
| |
| void SodaInstaller::NotifyOnSodaInstalled(LanguageCode language_code) { |
| for (Observer& observer : observers_) |
| observer.OnSodaInstalled(language_code); |
| } |
| |
| void SodaInstaller::NotifyOnSodaError(LanguageCode language_code) { |
| for (Observer& observer : observers_) |
| observer.OnSodaError(language_code); |
| } |
| |
| void SodaInstaller::NotifyOnSodaProgress(LanguageCode language_code, |
| int progress) { |
| for (Observer& observer : observers_) |
| observer.OnSodaProgress(language_code, progress); |
| } |
| |
| void SodaInstaller::RegisterLanguage(const std::string& language, |
| PrefService* global_prefs) { |
| ListPrefUpdate update(global_prefs, prefs::kSodaRegisteredLanguagePacks); |
| if (!base::Contains(update->GetList(), base::Value(language))) { |
| update->GetList().Append(language); |
| } |
| } |
| |
| void SodaInstaller::UnregisterLanguages(PrefService* global_prefs) { |
| ListPrefUpdate update(global_prefs, prefs::kSodaRegisteredLanguagePacks); |
| update->GetList().clear(); |
| } |
| |
| bool SodaInstaller::IsSodaDownloading(LanguageCode language_code) const { |
| return is_soda_downloading_ || |
| base::Contains(language_pack_progress_, language_code); |
| } |
| |
| bool SodaInstaller::IsAnyFeatureUsingSodaEnabled(PrefService* prefs) { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| return prefs->GetBoolean(prefs::kLiveCaptionEnabled) || |
| prefs->GetBoolean(ash::prefs::kAccessibilityDictationEnabled) || |
| prefs->GetBoolean(ash::prefs::kProjectorCreationFlowEnabled); |
| #else // !BUILDFLAG(IS_CHROMEOS_ASH) |
| return prefs->GetBoolean(prefs::kLiveCaptionEnabled); |
| #endif |
| } |
| |
| } // namespace speech |