Index: chrome/browser/chromeos/preferences.cc |
diff --git a/chrome/browser/chromeos/preferences.cc b/chrome/browser/chromeos/preferences.cc |
index fd7b1940c28d75f0d88f82b096e378850df4ab22..1de6d9e19193091f393b15590a058d75ee3bf23c 100644 |
--- a/chrome/browser/chromeos/preferences.cc |
+++ b/chrome/browser/chromeos/preferences.cc |
@@ -4,7 +4,7 @@ |
#include "chrome/browser/chromeos/preferences.h" |
-#include <vector> |
+#include <algorithm> |
#include "ash/autoclick/autoclick_controller.h" |
#include "ash/magnifier/magnifier_constants.h" |
@@ -31,6 +31,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" |
@@ -38,20 +39,132 @@ |
#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) { |
+ // 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()) { |
+ ++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) { |
+ if (languages->empty()) |
+ return; |
+ std::vector<std::string> values; |
+ base::SplitString(*languages, ',', &values); |
+ |
+ // Remove unsupported language values and set the resolved locales. |
+ std::vector<std::string>::iterator value_iter = values.begin(); |
+ while (value_iter != values.end()) { |
+ std::string resolved_locale; |
+ if (l10n_util::CheckAndResolveLocale(*value_iter, &resolved_locale)) { |
+ *value_iter = resolved_locale; |
+ ++value_iter; |
+ } else { |
+ 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++) { |
+ // Skip token if it's already in |dest|. |
+ if (std::find(dest->begin(), dest->end(), src[i]) == dest->end()) |
+ dest->push_back(src[i]); |
+ } |
+} |
+ |
+// 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) { |
+ 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(); |
+ if (component_extension_manager->IsInitialized()) { |
+ 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); |
+ manager->GetInputMethodExtensions(supported_descriptors.get()); |
+ } |
+ CheckAndResolveInputMethodIDs(&new_tokens, *supported_descriptors); |
+ } else if (pref_name != prefs::kLanguagePreferredLanguages) { |
+ NOTREACHED() << "Attempting to merge an invalid preference."; |
+ } |
+ |
+ // Do the actual merging. |
+ MergeLists(&old_tokens, new_tokens); |
+ return JoinString(old_tokens, ','); |
+} |
+ |
+} // anonymous namespace |
+ |
Preferences::Preferences() |
: prefs_(NULL), |
input_method_manager_(input_method::InputMethodManager::Get()), |
user_(NULL), |
- user_is_primary_(false) { |
+ user_is_primary_(false), |
+ weak_factory_(this) { |
// Do not observe shell, if there is no shell instance; e.g., in some unit |
// tests. |
if (ash::Shell::HasInstance()) |
@@ -62,7 +175,8 @@ Preferences::Preferences(input_method::InputMethodManager* input_method_manager) |
: prefs_(NULL), |
input_method_manager_(input_method_manager), |
user_(NULL), |
- user_is_primary_(false) { |
+ user_is_primary_(false), |
+ weak_factory_(this) { |
// Do not observe shell, if there is no shell instance; e.g., in some unit |
// tests. |
if (ash::Shell::HasInstance()) |
@@ -214,21 +328,34 @@ 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, |
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
registry->RegisterStringPref( |
+ prefs::kLanguagePreferredLanguagesSyncable, |
+ "", |
+ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
+ registry->RegisterStringPref( |
prefs::kLanguagePreloadEngines, |
hardware_keyboard_id, |
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
registry->RegisterStringPref( |
+ prefs::kLanguagePreloadEnginesSyncable, |
+ "", |
+ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
+ registry->RegisterStringPref( |
prefs::kLanguageEnabledExtensionImes, |
"", |
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
+ registry->RegisterStringPref( |
+ prefs::kLanguageEnabledExtensionImesSyncable, |
+ "", |
+ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
+ registry->RegisterBooleanPref( |
+ prefs::kLanguageShouldMergeInputMethods, |
+ false, |
+ user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
registry->RegisterIntegerPref( |
prefs::kLanguageRemapSearchKeyTo, |
@@ -333,9 +460,17 @@ void Preferences::InitUserPrefs(PrefServiceSyncable* prefs) { |
prefs, callback); |
touch_hud_projection_enabled_.Init(prefs::kTouchHudProjectionEnabled, |
prefs, callback); |
+ preferred_languages_.Init(prefs::kLanguagePreferredLanguages, |
+ prefs, callback); |
preload_engines_.Init(prefs::kLanguagePreloadEngines, prefs, callback); |
enabled_extension_imes_.Init(prefs::kLanguageEnabledExtensionImes, |
prefs, callback); |
+ preferred_languages_syncable_.Init(prefs::kLanguagePreferredLanguagesSyncable, |
+ prefs, callback); |
+ preload_engines_syncable_.Init(prefs::kLanguagePreloadEnginesSyncable, |
+ prefs, callback); |
+ enabled_extension_imes_syncable_.Init( |
+ prefs::kLanguageEnabledExtensionImesSyncable, prefs, callback); |
current_input_method_.Init(prefs::kLanguageCurrentInputMethod, |
prefs, callback); |
previous_input_method_.Init(prefs::kLanguagePreviousInputMethod, |
@@ -516,7 +651,9 @@ void Preferences::ApplyPreferences(ApplyReason reason, |
pref_name == prefs::kTouchHudProjectionEnabled) { |
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); |
} |
} |
@@ -536,25 +673,28 @@ void Preferences::ApplyPreferences(ApplyReason reason, |
UpdateAutoRepeatRate(); |
} |
+ if (reason == REASON_PREF_CHANGED && |
+ pref_name == prefs::kLanguagePreferredLanguages) |
+ SetSyncableInputMethodPrefs(); |
+ |
if (reason != REASON_PREF_CHANGED && user_is_active) { |
SetInputMethodList(); |
- } else if (pref_name == prefs::kLanguagePreloadEngines && user_is_active) { |
- SetLanguageConfigStringListAsCSV(language_prefs::kGeneralSectionName, |
- language_prefs::kPreloadEnginesConfigName, |
- preload_engines_.GetValue()); |
+ } else if (pref_name == prefs::kLanguagePreloadEngines) { |
+ if (user_is_active) { |
+ SetLanguageConfigStringListAsCSV( |
+ language_prefs::kGeneralSectionName, |
+ language_prefs::kPreloadEnginesConfigName, |
+ preload_engines_.GetValue()); |
+ } |
+ SetSyncableInputMethodPrefs(); |
} |
if (reason != REASON_PREF_CHANGED || |
pref_name == prefs::kLanguageEnabledExtensionImes) { |
- if (user_is_active) { |
- std::string value(enabled_extension_imes_.GetValue()); |
- |
- std::vector<std::string> split_values; |
- if (!value.empty()) |
- base::SplitString(value, ',', &split_values); |
- |
- input_method_manager_->SetEnabledExtensionImes(&split_values); |
- } |
+ if (user_is_active) |
+ SetEnabledExtensionImes(); |
+ if (reason == REASON_PREF_CHANGED) |
+ SetSyncableInputMethodPrefs(); |
} |
if (user_is_active) { |
@@ -567,6 +707,10 @@ void Preferences::ApplyPreferences(ApplyReason reason, |
void Preferences::OnIsSyncingChanged() { |
DVLOG(1) << "OnIsSyncingChanged"; |
ForceNaturalScrollDefault(); |
+ if (prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods) && |
+ prefs_->IsSyncing()) { |
+ MergeSyncedInputMethods(); |
+ } |
} |
void Preferences::ForceNaturalScrollDefault() { |
@@ -601,6 +745,15 @@ void Preferences::SetLanguageConfigStringListAsCSV(const char* section, |
} |
} |
+void Preferences::SetPreferredLanguages(scoped_ptr<std::string> languages) { |
+ // Since this only removes 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, but that's okay |
+ // since the values from this device would become the new defaults anyway. |
+ if (*languages != preferred_languages_.GetValue()) |
+ preferred_languages_.SetValue(*languages); |
+} |
+ |
void Preferences::SetInputMethodList() { |
// When |preload_engines_| are set, InputMethodManager::ChangeInputMethod() |
// might be called to change the current input method to the first one in the |
@@ -626,6 +779,70 @@ void Preferences::SetInputMethodList() { |
input_method_manager_->ChangeInputMethod(current_input_method_id); |
} |
+void Preferences::SetEnabledExtensionImes() { |
+ std::string value(enabled_extension_imes_.GetValue()); |
+ std::vector<std::string> split_values; |
+ if (!value.empty()) |
+ base::SplitString(value, ',', &split_values); |
+ input_method_manager_->SetEnabledExtensionImes(&split_values); |
+} |
+ |
+void Preferences::SetSyncableInputMethodPrefs() { |
+ // 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::GetEngineIDByInputMethodID); |
+ preload_engines_syncable_.SetValue(JoinString(engines, ',')); |
+} |
+ |
+void Preferences::MergeSyncedInputMethods() { |
+ // This should only be done on the first ever sync. |
+ DCHECK(prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods)); |
+ prefs_->SetBoolean(prefs::kLanguageShouldMergeInputMethods, false); |
+ |
+ // Get the syncable values, because they will be overwritten once the local |
+ // preferences are set. |
+ 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(); |
+ |
+ // Merge the values from the sync server into the local values. |
+ prefs_->SetString(prefs::kLanguagePreferredLanguages, |
+ AddSupportedInputMethodValues(preferred_languages_.GetValue(), |
+ preferred_languages_syncable, |
+ prefs::kLanguagePreferredLanguages)); |
+ prefs_->SetString(prefs::kLanguagePreloadEngines, |
+ AddSupportedInputMethodValues(preload_engines_.GetValue(), |
+ preload_engines_syncable, |
+ prefs::kLanguagePreloadEngines)); |
+ prefs_->SetString(prefs::kLanguageEnabledExtensionImes, |
+ AddSupportedInputMethodValues(enabled_extension_imes_.GetValue(), |
+ enabled_extension_imes_syncable, |
+ prefs::kLanguageEnabledExtensionImes)); |
+ |
+ // Check the new list of preferred languages to remove unsupported locales. |
+ scoped_ptr<std::string> languages( |
+ new std::string(preferred_languages_.GetValue())); |
+ content::BrowserThread::PostTaskAndReply( |
+ content::BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(&CheckAndResolveLocales, languages.get()), |
+ base::Bind(&Preferences::SetPreferredLanguages, |
+ weak_factory_.GetWeakPtr(), |
+ base::Passed(&languages))); |
+} |
+ |
void Preferences::UpdateAutoRepeatRate() { |
input_method::AutoRepeatRate rate; |
rate.initial_delay_in_ms = xkb_auto_repeat_delay_pref_.GetValue(); |