Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(155)

Unified Diff: chrome/browser/chromeos/preferences.cc

Issue 312023002: Sync starting language and input method preferences (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Refactored for xkb refactor Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/chromeos/preferences.h ('k') | chrome/browser/chromeos/preferences_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
}
« no previous file with comments | « chrome/browser/chromeos/preferences.h ('k') | chrome/browser/chromeos/preferences_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698