Chromium Code Reviews| Index: chrome/browser/chromeos/preferences.cc |
| diff --git a/chrome/browser/chromeos/preferences.cc b/chrome/browser/chromeos/preferences.cc |
| index b75d42e9515949a5e8d9c898f18eb43c028f012c..c934761ac0523a4f639a5e58a7adfeee08b31543 100644 |
| --- a/chrome/browser/chromeos/preferences.cc |
| +++ b/chrome/browser/chromeos/preferences.cc |
| @@ -4,6 +4,7 @@ |
| #include "chrome/browser/chromeos/preferences.h" |
| +#include <algorithm> |
| #include <vector> |
| #include "ash/autoclick/autoclick_controller.h" |
| @@ -32,6 +33,7 @@ |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/chromeos_switches.h" |
| +#include "chromeos/ime/component_extension_ime_manager.h" |
| #include "chromeos/ime/extension_ime_util.h" |
| #include "chromeos/ime/ime_keyboard.h" |
| #include "chromeos/ime/input_method_manager.h" |
| @@ -39,15 +41,370 @@ |
| #include "components/feedback/tracing_manager.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/user_manager/user.h" |
| +#include "content/public/browser/browser_thread.h" |
| #include "third_party/icu/source/i18n/unicode/timezone.h" |
| +#include "ui/base/l10n/l10n_util.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/event_utils.h" |
| #include "url/gurl.h" |
| namespace chromeos { |
| +namespace { |
| static const char kFallbackInputMethodLocale[] = "en-US"; |
| +// Checks input method IDs, converting engine IDs to input method IDs and |
| +// removing unsupported IDs from |values|. |
| +void CheckAndResolveInputMethodIDs( |
| + std::vector<std::string>* values, |
| + const input_method::InputMethodDescriptors& supported_descriptors) { |
|
Alexander Alekseev
2014/10/28 15:11:00
Input arguments should go first.
michaelpg
2014/11/18 03:47:39
Done.
|
| + // Extract the supported input method IDs into a set. |
| + std::set<std::string> supported_input_method_ids; |
| + for (size_t i = 0; i < supported_descriptors.size(); i++) |
| + supported_input_method_ids.insert(supported_descriptors[i].id()); |
| + |
| + // Convert engine IDs to input method extension IDs. |
| + std::transform(values->begin(), values->end(), values->begin(), |
| + extension_ime_util::GetInputMethodIDByEngineID); |
| + |
| + // Remove values that aren't found in the set of supported input method IDs. |
| + std::vector<std::string>::iterator it = values->begin(); |
| + while (it != values->end()) { |
| + if (it->size() && std::find(supported_input_method_ids.begin(), |
| + supported_input_method_ids.end(), |
| + *it) != supported_input_method_ids.end()) { |
|
Alexander Alekseev
2014/10/28 15:11:00
Maybe a bit shorter:
supported_input_method_ids.f
michaelpg
2014/11/18 03:47:39
Done.
|
| + ++it; |
| + } else { |
| + it = values->erase(it); |
| + } |
| + } |
| +} |
| + |
| +// Checks whether each language is supported, replacing locales with variants |
| +// if they are available. Must be called on a thread that allows IO. |
| +void CheckAndResolveLocales(std::string* languages) { |
|
Alexander Alekseev
2014/10/28 15:11:00
DCHECK(BrowserThread::GetBlockingPool()->RunsTasks
michaelpg
2014/11/18 03:47:39
Done.
|
| + if (languages->empty()) |
| + return; |
| + std::vector<std::string> values; |
| + base::SplitString(*languages, ',', &values); |
| + |
| + const std::string app_locale = g_browser_process->GetApplicationLocale(); |
| + |
| + std::vector<std::string> accept_language_codes; |
| + l10n_util::GetAcceptLanguagesForLocale(app_locale, &accept_language_codes); |
| + |
| + // Remove unsupported language values. |
| + std::vector<std::string>::iterator value_iter = values.begin(); |
| + while (value_iter != values.end()) { |
| + if (std::find(accept_language_codes.begin(), accept_language_codes.end(), |
|
Alexander Alekseev
2014/10/28 15:11:00
You traverse accept_language_codes linearly twice
michaelpg
2014/11/18 03:47:39
Done.
|
| + *value_iter) != accept_language_codes.end()) { |
| + ++value_iter; |
| + continue; |
| + } |
| + |
| + // If a language code resolves to a supported backup locale, replace it |
| + // with the resolved locale. |
| + std::string resolved_locale; |
| + if (l10n_util::CheckAndResolveLocale(*value_iter, &resolved_locale)) { |
| + if (std::find(accept_language_codes.begin(), accept_language_codes.end(), |
| + resolved_locale) != accept_language_codes.end()) { |
| + *value_iter = resolved_locale; |
| + ++value_iter; |
| + continue; |
| + } |
| + } |
| + value_iter = values.erase(value_iter); |
| + } |
| + |
| + *languages = JoinString(values, ','); |
| +} |
| + |
| +// Appends tokens from |src| that are not in |dest| to |dest|. Quadratic |
| +// runtime; use only for small lists. |
| +void MergeLists(std::vector<std::string>* dest, |
| + const std::vector<std::string>& src) { |
| + for (size_t i = 0; i < src.size(); i++) { |
|
dzhioev (left Google)
2014/11/06 16:29:07
nit: for (const std::string& token: src)
michaelpg
2014/11/18 03:47:39
Done.
|
| + // Skip token if it's already in |dest|. |
| + if (std::find(dest->begin(), dest->end(), src[i]) == dest->end()) |
| + dest->push_back(src[i]); |
| + } |
| +} |
| + |
| +// Helper class to handle syncing of language and input method preferences. |
| +// Changes to local preferences are handed up to the sync server. But Chrome OS |
| +// should not locally apply the corresponding preferences from the sync server, |
| +// except once: when the user first logs into the device. |
| +// Thus, the user's most recent changes to language and input method preferences |
| +// will be brought down when signing in to a new device but not in future syncs. |
| +class InputMethodSyncer : public PrefServiceSyncableObserver { |
|
Nikita (slow)
2014/10/21 11:45:56
I wonder if this class should be extracted into a
Alexander Alekseev
2014/10/28 15:11:00
+1
michaelpg
2014/11/18 03:47:39
Done.
|
| + public: |
| + InputMethodSyncer( |
| + PrefServiceSyncable* prefs, |
| + scoped_refptr<input_method::InputMethodManager::State> ime_state); |
| + |
| + // Registers the syncable input method prefs. |
| + static void RegisterProfilePrefs( |
| + user_prefs::PrefRegistrySyncable* registry); |
| + |
| + // Must be called after InputMethodSyncer is created. |
| + void Initialize(); |
| + |
| + private: |
| + // Adds the input methods from the syncable prefs to the device-local prefs. |
| + // This should only be called once (after user's first sync) and only adds |
| + // to, not removes from, the user's input method prefs. |
| + void MergeSyncedPrefs(); |
| + |
| + // For the given input method pref, adds unique values from |synced_pref| to |
| + // values in |pref|. The new values are converted from legacy engine IDs to |
| + // input method IDs if necessary. |
| + std::string AddSupportedInputMethodValues( |
| + const std::string& pref, |
| + const std::string& synced_pref, |
| + const char* pref_name); |
| + |
| + // Sets prefs::kLanguagePreferredLanguages and sets |merging_| to false. |
| + void FinishMerge(scoped_ptr<std::string> languages); |
| + |
| + // Callback method for preference changes. Updates the syncable prefs using |
| + // the local pref values. |
| + void OnPreferenceChanged(const std::string& pref_name); |
| + |
| + // PrefServiceSyncableObserver implementation. |
| + virtual void OnIsSyncingChanged() override; |
| + |
| + StringPrefMember preferred_languages_; |
| + StringPrefMember preload_engines_; |
| + StringPrefMember enabled_extension_imes_; |
| + // These are syncable variants which don't change the device settings. We can |
| + // set these to keep track of the user's most recent choices. That way, after |
| + // the initial sync, we can add the user's synced choices to the values that |
| + // have already been chosen at OOBE. |
| + StringPrefMember preferred_languages_syncable_; |
| + StringPrefMember preload_engines_syncable_; |
| + StringPrefMember enabled_extension_imes_syncable_; |
| + |
| + PrefServiceSyncable* prefs_; |
| + scoped_refptr<input_method::InputMethodManager::State> ime_state_; |
| + |
| + // Used to ignore PrefChanged events while InputMethodManager is merging. |
| + bool merging_; |
| + |
| + base::WeakPtrFactory<InputMethodSyncer> weak_factory_; |
| +}; |
| + |
| +InputMethodSyncer::InputMethodSyncer( |
| + PrefServiceSyncable* prefs, |
| + scoped_refptr<input_method::InputMethodManager::State> ime_state) |
| + : prefs_(prefs), |
| + ime_state_(ime_state), |
| + merging_(false), |
| + weak_factory_(this) { |
| +} |
| + |
| +// static |
| +void InputMethodSyncer::RegisterProfilePrefs( |
| + user_prefs::PrefRegistrySyncable* registry) { |
| + registry->RegisterStringPref( |
| + prefs::kLanguagePreferredLanguagesSyncable, |
| + "", |
| + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| + registry->RegisterStringPref( |
| + prefs::kLanguagePreloadEnginesSyncable, |
| + "", |
| + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| + registry->RegisterStringPref( |
| + prefs::kLanguageEnabledExtensionImesSyncable, |
| + "", |
| + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| + registry->RegisterBooleanPref( |
| + prefs::kLanguageShouldMergeInputMethods, |
| + false, |
| + user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| +} |
| + |
| +void InputMethodSyncer::Initialize() { |
| + // This causes OnIsSyncingChanged to be called when the value of |
| + // PrefService::IsSyncing() changes. |
| + prefs_->AddObserver(this); |
| + |
| + preferred_languages_syncable_.Init(prefs::kLanguagePreferredLanguagesSyncable, |
| + prefs_); |
| + preload_engines_syncable_.Init(prefs::kLanguagePreloadEnginesSyncable, |
| + prefs_); |
| + enabled_extension_imes_syncable_.Init( |
| + prefs::kLanguageEnabledExtensionImesSyncable, prefs_); |
| + |
| + BooleanPrefMember::NamedChangeCallback callback = |
| + base::Bind(&InputMethodSyncer::OnPreferenceChanged, |
| + base::Unretained(this)); |
| + preferred_languages_.Init(prefs::kLanguagePreferredLanguages, |
| + prefs_, callback); |
|
dzhioev (left Google)
2014/11/06 16:11:06
nit: fix indentation.
michaelpg
2014/11/18 03:47:39
Done.
|
| + preload_engines_.Init(prefs::kLanguagePreloadEngines, |
| + prefs_, callback); |
|
dzhioev (left Google)
2014/11/06 16:11:06
nit: fix indentation.
michaelpg
2014/11/18 03:47:39
Done.
|
| + enabled_extension_imes_.Init( |
| + prefs::kLanguageEnabledExtensionImes, prefs_, callback); |
| + |
| + // If we have already synced but haven't merged input methods yet, do so now. |
| + if (prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods) && |
| + !(preferred_languages_syncable_.GetValue().empty() && |
| + preload_engines_syncable_.GetValue().empty() && |
| + enabled_extension_imes_syncable_.GetValue().empty())) { |
| + MergeSyncedPrefs(); |
| + } |
| +} |
| + |
| +void InputMethodSyncer::MergeSyncedPrefs() { |
| + // This should only be done after the first ever sync. |
| + DCHECK(prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods)); |
| + prefs_->SetBoolean(prefs::kLanguageShouldMergeInputMethods, false); |
| + merging_ = true; |
| + |
| + std::string preferred_languages_syncable = |
| + preferred_languages_syncable_.GetValue(); |
| + std::string preload_engines_syncable = |
| + preload_engines_syncable_.GetValue(); |
| + std::string enabled_extension_imes_syncable = |
| + enabled_extension_imes_syncable_.GetValue(); |
| + |
| + std::vector<std::string> synced_tokens; |
| + std::vector<std::string> new_tokens; |
| + |
| + // First, set the syncable prefs to the union of the local and synced prefs. |
| + base::SplitString( |
| + preferred_languages_syncable_.GetValue(), ',', &synced_tokens); |
| + base::SplitString(preferred_languages_.GetValue(), ',', &new_tokens); |
| + |
| + // Append the synced values to the current values. |
| + MergeLists(&new_tokens, synced_tokens); |
| + preferred_languages_syncable_.SetValue(JoinString(new_tokens, ',')); |
| + |
| + base::SplitString( |
| + enabled_extension_imes_syncable_.GetValue(), ',', &synced_tokens); |
| + base::SplitString(enabled_extension_imes_.GetValue(), ',', &new_tokens); |
| + |
| + MergeLists(&new_tokens, synced_tokens); |
| + enabled_extension_imes_syncable_.SetValue(JoinString(new_tokens, ',')); |
| + |
| + // Revert preload engines to legacy component IDs. |
| + base::SplitString(preload_engines_.GetValue(), ',', &new_tokens); |
| + std::transform(new_tokens.begin(), new_tokens.end(), new_tokens.begin(), |
| + extension_ime_util::GetComponentIDByInputMethodID); |
| + base::SplitString( |
| + preload_engines_syncable_.GetValue(), ',', &synced_tokens); |
| + |
| + MergeLists(&new_tokens, synced_tokens); |
| + preload_engines_syncable_.SetValue(JoinString(new_tokens, ',')); |
| + |
| + // Second, set the local prefs, incorporating new values from the sync server. |
| + preload_engines_.SetValue( |
| + AddSupportedInputMethodValues(preload_engines_.GetValue(), |
| + preload_engines_syncable, |
| + prefs::kLanguagePreloadEngines)); |
| + enabled_extension_imes_.SetValue( |
| + AddSupportedInputMethodValues(enabled_extension_imes_.GetValue(), |
| + enabled_extension_imes_syncable, |
| + prefs::kLanguageEnabledExtensionImes)); |
| + |
| + // Remove unsupported locales before updating the local languages preference. |
| + scoped_ptr<std::string> languages(new std::string( |
| + AddSupportedInputMethodValues(preferred_languages_.GetValue(), |
| + preferred_languages_syncable, |
| + prefs::kLanguagePreferredLanguages))); |
| + content::BrowserThread::PostTaskAndReply( |
| + content::BrowserThread::FILE, |
|
Alexander Alekseev
2014/10/28 15:11:00
AFAIK, FILE thread is deprecated.
BrowserThread::
michaelpg
2014/11/18 03:47:39
Done.
|
| + FROM_HERE, |
| + base::Bind(&CheckAndResolveLocales, languages.get()), |
| + base::Bind(&InputMethodSyncer::FinishMerge, |
| + weak_factory_.GetWeakPtr(), |
| + base::Passed(&languages))); |
| +} |
| + |
| +std::string InputMethodSyncer::AddSupportedInputMethodValues( |
| + const std::string& pref, |
| + const std::string& synced_pref, |
| + const char* pref_name) { |
| + std::vector<std::string> old_tokens; |
| + std::vector<std::string> new_tokens; |
| + base::SplitString(pref, ',', &old_tokens); |
| + base::SplitString(synced_pref, ',', &new_tokens); |
| + |
| + // Check and convert the new tokens. |
| + if (pref_name == prefs::kLanguagePreloadEngines || |
| + pref_name == prefs::kLanguageEnabledExtensionImes) { |
| + input_method::InputMethodManager* manager = |
| + input_method::InputMethodManager::Get(); |
| + scoped_ptr<input_method::InputMethodDescriptors> supported_descriptors; |
| + |
| + if (pref_name == prefs::kLanguagePreloadEngines) { |
| + // Set the known input methods. |
| + supported_descriptors = manager->GetSupportedInputMethods(); |
| + // Add the available component extension IMEs. |
| + ComponentExtensionIMEManager* component_extension_manager = |
| + manager->GetComponentExtensionIMEManager(); |
| + input_method::InputMethodDescriptors component_descriptors = |
| + component_extension_manager->GetAllIMEAsInputMethodDescriptor(); |
| + supported_descriptors->insert(supported_descriptors->end(), |
| + component_descriptors.begin(), |
| + component_descriptors.end()); |
| + } else { |
| + supported_descriptors.reset(new input_method::InputMethodDescriptors); |
| + ime_state_->GetInputMethodExtensions(supported_descriptors.get()); |
| + } |
| + CheckAndResolveInputMethodIDs(&new_tokens, *supported_descriptors); |
| + } else if (pref_name != prefs::kLanguagePreferredLanguages) { |
| + NOTREACHED() << "Attempting to merge an invalid preference."; |
| + // kLanguagePreferredLanguages is checked in CheckAndResolveLocales(). |
| + } |
| + |
| + // Do the actual merging. |
| + MergeLists(&old_tokens, new_tokens); |
| + return JoinString(old_tokens, ','); |
| +} |
| + |
| +void InputMethodSyncer::FinishMerge( |
| + scoped_ptr<std::string> languages) { |
| + // Since the merge only removed locales that are unsupported on this system, |
| + // we don't need to update the syncable prefs. If the local preference changes |
| + // later, the sync server will lose the values we dropped. That's okay since |
| + // the values from this device should then become the new defaults anyway. |
| + preferred_languages_.SetValue(*languages); |
| + |
| + // We've finished merging. |
| + merging_ = false; |
| +} |
| + |
| +void InputMethodSyncer::OnPreferenceChanged(const std::string& pref_name) { |
| + DCHECK(pref_name == prefs::kLanguagePreferredLanguages || |
| + pref_name == prefs::kLanguagePreloadEngines || |
| + pref_name == prefs::kLanguageEnabledExtensionImes); |
| + |
| + if (merging_ || prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods)) |
| + return; |
| + |
| + // Set the language and input prefs at the same time. Otherwise we may, |
| + // e.g., use a stale languages setting but push a new preload engines setting. |
| + preferred_languages_syncable_.SetValue(preferred_languages_.GetValue()); |
| + enabled_extension_imes_syncable_.SetValue(enabled_extension_imes_.GetValue()); |
| + |
| + // For preload engines, use legacy xkb IDs so the preference can sync |
| + // across Chrome OS and Chromium OS. |
| + std::vector<std::string> engines; |
| + base::SplitString(preload_engines_.GetValue(), ',', &engines); |
| + std::transform(engines.begin(), engines.end(), engines.begin(), |
| + extension_ime_util::GetComponentIDByInputMethodID); |
| + preload_engines_syncable_.SetValue(JoinString(engines, ',')); |
| +} |
| + |
| +void InputMethodSyncer::OnIsSyncingChanged() { |
| + if (prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods) && |
| + prefs_->IsSyncing()) { |
| + MergeSyncedPrefs(); |
| + } |
| +} |
| + |
| +} // anonymous namespace |
| + |
| Preferences::Preferences() |
| : prefs_(NULL), |
| input_method_manager_(input_method::InputMethodManager::Get()), |
| @@ -215,9 +572,6 @@ void Preferences::RegisterProfilePrefs( |
| prefs::kLanguagePreviousInputMethod, |
| "", |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| - // We don't sync the list of input methods and preferred languages since a |
| - // user might use two or more devices with different hardware keyboards. |
| - // crosbug.com/15181 |
| registry->RegisterStringPref( |
| prefs::kLanguagePreferredLanguages, |
| kFallbackInputMethodLocale, |
| @@ -311,6 +665,8 @@ void Preferences::RegisterProfilePrefs( |
| prefs::kTouchVirtualKeyboardEnabled, |
| false, |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| + |
| + InputMethodSyncer::RegisterProfilePrefs(registry); |
| } |
| void Preferences::InitUserPrefs(PrefServiceSyncable* prefs) { |
| @@ -354,6 +710,9 @@ void Preferences::Init(Profile* profile, const user_manager::User* user) { |
| DCHECK(profile); |
| DCHECK(user); |
| PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile); |
| + // This causes OnIsSyncingChanged to be called when the value of |
| + // PrefService::IsSyncing() changes. |
| + prefs->AddObserver(this); |
| user_ = user; |
| user_is_primary_ = |
| user_manager::UserManager::Get()->GetPrimaryUser() == user_; |
| @@ -361,10 +720,6 @@ void Preferences::Init(Profile* profile, const user_manager::User* user) { |
| user_manager::UserManager::Get()->AddSessionStateObserver(this); |
| - // This causes OnIsSyncingChanged to be called when the value of |
| - // PrefService::IsSyncing() changes. |
| - prefs->AddObserver(this); |
| - |
| UserSessionManager* session_manager = UserSessionManager::GetInstance(); |
| DCHECK(session_manager); |
| ime_state_ = session_manager->GetDefaultIMEState(profile); |
| @@ -372,6 +727,9 @@ void Preferences::Init(Profile* profile, const user_manager::User* user) { |
| // Initialize preferences to currently saved state. |
| ApplyPreferences(REASON_INITIALIZATION, ""); |
| + input_method_syncer_.reset( |
| + new InputMethodSyncer(prefs, ime_state_)); |
| + input_method_syncer_->Initialize(); |
| // If a guest is logged in, initialize the prefs as if this is the first |
| // login. For a regular user this is done in |
| @@ -391,6 +749,10 @@ void Preferences::InitUserPrefsForTesting( |
| input_method_manager_->SetState(ime_state); |
| InitUserPrefs(prefs); |
| + |
| + input_method_syncer_.reset( |
| + new InputMethodSyncer(prefs, ime_state_)); |
| + input_method_syncer_->Initialize(); |
| } |
| void Preferences::SetInputMethodListForTesting() { |
| @@ -532,7 +894,9 @@ void Preferences::ApplyPreferences(ApplyReason reason, |
| #if !defined(USE_ATHENA) |
| if (user_is_active) { |
| const bool enabled = touch_hud_projection_enabled_.GetValue(); |
| - ash::Shell::GetInstance()->SetTouchHudProjectionEnabled(enabled); |
| + // There may not be a shell, e.g., in some unit tests. |
| + if (ash::Shell::HasInstance()) |
| + ash::Shell::GetInstance()->SetTouchHudProjectionEnabled(enabled); |
| } |
| #endif |
| } |