Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/chromeos/input_method/input_method_syncer.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <set> | |
| 9 #include <vector> | |
| 10 | |
| 11 #include "base/bind.h" | |
| 12 #include "base/strings/string_split.h" | |
| 13 #include "base/strings/string_util.h" | |
| 14 #include "base/task_runner.h" | |
| 15 #include "chrome/browser/browser_process.h" | |
| 16 #include "chrome/browser/prefs/pref_service_syncable.h" | |
| 17 #include "chrome/common/pref_names.h" | |
| 18 #include "components/pref_registry/pref_registry_syncable.h" | |
| 19 #include "content/public/browser/browser_thread.h" | |
| 20 #include "ui/base/ime/chromeos/component_extension_ime_manager.h" | |
| 21 #include "ui/base/ime/chromeos/extension_ime_util.h" | |
| 22 #include "ui/base/l10n/l10n_util.h" | |
| 23 | |
| 24 namespace chromeos { | |
| 25 namespace input_method { | |
| 26 namespace { | |
| 27 | |
| 28 // Checks input method IDs, converting engine IDs to input method IDs and | |
| 29 // removing unsupported IDs from |values|. | |
| 30 void CheckAndResolveInputMethodIDs( | |
| 31 const input_method::InputMethodDescriptors& supported_descriptors, | |
| 32 std::vector<std::string>* values) { | |
| 33 // Extract the supported input method IDs into a set. | |
| 34 std::set<std::string> supported_input_method_ids; | |
| 35 for (const auto& descriptor : supported_descriptors) | |
| 36 supported_input_method_ids.insert(descriptor.id()); | |
| 37 | |
| 38 // Convert engine IDs to input method extension IDs. | |
| 39 std::transform(values->begin(), values->end(), values->begin(), | |
| 40 extension_ime_util::GetInputMethodIDByEngineID); | |
| 41 | |
| 42 // Remove values that aren't found in the set of supported input method IDs. | |
| 43 std::vector<std::string>::iterator it = values->begin(); | |
| 44 while (it != values->end()) { | |
| 45 if (it->size() && supported_input_method_ids.find(*it) != | |
| 46 supported_input_method_ids.end()) { | |
| 47 ++it; | |
| 48 } else { | |
| 49 it = values->erase(it); | |
| 50 } | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 // Checks whether each language is supported, replacing locales with variants | |
| 55 // if they are available. Must be called on a thread that allows IO. | |
| 56 void CheckAndResolveLocales(std::string* languages) { | |
| 57 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 58 if (languages->empty()) | |
| 59 return; | |
| 60 std::vector<std::string> values; | |
| 61 base::SplitString(*languages, ',', &values); | |
| 62 | |
| 63 const std::string app_locale = g_browser_process->GetApplicationLocale(); | |
| 64 | |
| 65 std::vector<std::string> accept_language_codes; | |
| 66 l10n_util::GetAcceptLanguagesForLocale(app_locale, &accept_language_codes); | |
| 67 std::sort(accept_language_codes.begin(), accept_language_codes.end()); | |
| 68 | |
| 69 // Remove unsupported language values. | |
| 70 std::vector<std::string>::iterator value_iter = values.begin(); | |
| 71 while (value_iter != values.end()) { | |
| 72 if (binary_search(accept_language_codes.begin(), | |
| 73 accept_language_codes.end(), | |
| 74 *value_iter)) { | |
| 75 ++value_iter; | |
| 76 continue; | |
| 77 } | |
| 78 | |
| 79 // If a language code resolves to a supported backup locale, replace it | |
| 80 // with the resolved locale. | |
| 81 std::string resolved_locale; | |
| 82 if (l10n_util::CheckAndResolveLocale(*value_iter, &resolved_locale)) { | |
| 83 if (binary_search(accept_language_codes.begin(), | |
| 84 accept_language_codes.end(), | |
| 85 resolved_locale)) { | |
| 86 *value_iter = resolved_locale; | |
| 87 ++value_iter; | |
| 88 continue; | |
| 89 } | |
| 90 } | |
| 91 value_iter = values.erase(value_iter); | |
| 92 } | |
| 93 | |
| 94 *languages = JoinString(values, ','); | |
| 95 } | |
| 96 | |
| 97 // Appends tokens from |src| that are not in |dest| to |dest|. | |
| 98 void MergeLists(std::vector<std::string>* dest, | |
| 99 const std::vector<std::string>& src) { | |
| 100 // Keep track of already-added tokens. | |
| 101 std::set<std::string> unique_tokens(dest->begin(), dest->end()); | |
| 102 | |
| 103 for (const std::string& token : src) { | |
| 104 // Skip token if it's already in |dest|. | |
| 105 if (binary_search(unique_tokens.begin(), unique_tokens.end(), token)) | |
| 106 continue; | |
| 107 dest->push_back(token); | |
| 108 unique_tokens.insert(token); | |
| 109 } | |
| 110 } | |
| 111 | |
| 112 } // anonymous namespace | |
| 113 | |
| 114 InputMethodSyncer::InputMethodSyncer( | |
| 115 PrefServiceSyncable* prefs, | |
| 116 scoped_refptr<input_method::InputMethodManager::State> ime_state) | |
| 117 : prefs_(prefs), | |
| 118 ime_state_(ime_state), | |
| 119 merging_(false), | |
| 120 weak_factory_(this) { | |
| 121 } | |
| 122 | |
| 123 InputMethodSyncer::~InputMethodSyncer() { | |
| 124 prefs_->RemoveObserver(this); | |
| 125 } | |
| 126 | |
| 127 // static | |
| 128 void InputMethodSyncer::RegisterProfilePrefs( | |
| 129 user_prefs::PrefRegistrySyncable* registry) { | |
| 130 registry->RegisterStringPref( | |
| 131 prefs::kLanguagePreferredLanguagesSyncable, | |
| 132 "", | |
| 133 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); | |
| 134 registry->RegisterStringPref( | |
| 135 prefs::kLanguagePreloadEnginesSyncable, | |
| 136 "", | |
| 137 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); | |
| 138 registry->RegisterStringPref( | |
| 139 prefs::kLanguageEnabledExtensionImesSyncable, | |
| 140 "", | |
| 141 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); | |
| 142 registry->RegisterBooleanPref( | |
| 143 prefs::kLanguageShouldMergeInputMethods, | |
| 144 false, | |
| 145 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); | |
| 146 } | |
| 147 | |
| 148 void InputMethodSyncer::Initialize() { | |
| 149 // This causes OnIsSyncingChanged to be called when the value of | |
| 150 // PrefService::IsSyncing() changes. | |
| 151 prefs_->AddObserver(this); | |
| 152 | |
| 153 preferred_languages_syncable_.Init(prefs::kLanguagePreferredLanguagesSyncable, | |
| 154 prefs_); | |
| 155 preload_engines_syncable_.Init(prefs::kLanguagePreloadEnginesSyncable, | |
| 156 prefs_); | |
| 157 enabled_extension_imes_syncable_.Init( | |
| 158 prefs::kLanguageEnabledExtensionImesSyncable, prefs_); | |
| 159 | |
| 160 BooleanPrefMember::NamedChangeCallback callback = | |
| 161 base::Bind(&InputMethodSyncer::OnPreferenceChanged, | |
| 162 base::Unretained(this)); | |
| 163 preferred_languages_.Init(prefs::kLanguagePreferredLanguages, | |
| 164 prefs_, callback); | |
| 165 preload_engines_.Init(prefs::kLanguagePreloadEngines, | |
| 166 prefs_, callback); | |
| 167 enabled_extension_imes_.Init( | |
| 168 prefs::kLanguageEnabledExtensionImes, prefs_, callback); | |
| 169 | |
| 170 // If we have already synced but haven't merged input methods yet, do so now. | |
| 171 if (prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods) && | |
| 172 !(preferred_languages_syncable_.GetValue().empty() && | |
| 173 preload_engines_syncable_.GetValue().empty() && | |
| 174 enabled_extension_imes_syncable_.GetValue().empty())) { | |
| 175 MergeSyncedPrefs(); | |
| 176 } | |
| 177 } | |
| 178 | |
| 179 void InputMethodSyncer::MergeSyncedPrefs() { | |
| 180 // This should only be done after the first ever sync. | |
| 181 DCHECK(prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods)); | |
| 182 prefs_->SetBoolean(prefs::kLanguageShouldMergeInputMethods, false); | |
| 183 merging_ = true; | |
| 184 | |
| 185 std::string preferred_languages_syncable = | |
| 186 preferred_languages_syncable_.GetValue(); | |
| 187 std::string preload_engines_syncable = | |
| 188 preload_engines_syncable_.GetValue(); | |
| 189 std::string enabled_extension_imes_syncable = | |
| 190 enabled_extension_imes_syncable_.GetValue(); | |
| 191 | |
| 192 std::vector<std::string> synced_tokens; | |
| 193 std::vector<std::string> new_tokens; | |
| 194 | |
| 195 // First, set the syncable prefs to the union of the local and synced prefs. | |
| 196 base::SplitString( | |
| 197 preferred_languages_syncable_.GetValue(), ',', &synced_tokens); | |
| 198 base::SplitString(preferred_languages_.GetValue(), ',', &new_tokens); | |
| 199 | |
| 200 // Append the synced values to the current values. | |
| 201 MergeLists(&new_tokens, synced_tokens); | |
| 202 preferred_languages_syncable_.SetValue(JoinString(new_tokens, ',')); | |
| 203 | |
| 204 base::SplitString( | |
| 205 enabled_extension_imes_syncable_.GetValue(), ',', &synced_tokens); | |
| 206 base::SplitString(enabled_extension_imes_.GetValue(), ',', &new_tokens); | |
| 207 | |
| 208 MergeLists(&new_tokens, synced_tokens); | |
| 209 enabled_extension_imes_syncable_.SetValue(JoinString(new_tokens, ',')); | |
| 210 | |
| 211 // Revert preload engines to legacy component IDs. | |
| 212 base::SplitString(preload_engines_.GetValue(), ',', &new_tokens); | |
| 213 std::transform(new_tokens.begin(), new_tokens.end(), new_tokens.begin(), | |
| 214 extension_ime_util::GetComponentIDByInputMethodID); | |
| 215 base::SplitString( | |
| 216 preload_engines_syncable_.GetValue(), ',', &synced_tokens); | |
| 217 | |
| 218 MergeLists(&new_tokens, synced_tokens); | |
| 219 preload_engines_syncable_.SetValue(JoinString(new_tokens, ',')); | |
| 220 | |
| 221 // Second, set the local prefs, incorporating new values from the sync server. | |
| 222 preload_engines_.SetValue( | |
| 223 AddSupportedInputMethodValues(preload_engines_.GetValue(), | |
| 224 preload_engines_syncable, | |
| 225 prefs::kLanguagePreloadEngines)); | |
| 226 enabled_extension_imes_.SetValue( | |
| 227 AddSupportedInputMethodValues(enabled_extension_imes_.GetValue(), | |
| 228 enabled_extension_imes_syncable, | |
| 229 prefs::kLanguageEnabledExtensionImes)); | |
| 230 | |
| 231 // Remove unsupported locales before updating the local languages preference. | |
| 232 scoped_ptr<std::string> languages(new std::string( | |
| 233 AddSupportedInputMethodValues(preferred_languages_.GetValue(), | |
| 234 preferred_languages_syncable, | |
| 235 prefs::kLanguagePreferredLanguages))); | |
| 236 content::BrowserThread::GetBlockingPool()->PostTaskAndReply( | |
| 237 FROM_HERE, | |
| 238 base::Bind(&CheckAndResolveLocales, languages.get()), | |
| 239 base::Bind(&InputMethodSyncer::FinishMerge, | |
| 240 weak_factory_.GetWeakPtr(), | |
| 241 base::Passed(&languages))); | |
|
spang
2014/12/01 19:52:04
It is not safe to .Pass() and .get() in different
michaelpg
2014/12/02 00:53:48
Sorry about this. It never came up in my testing b
| |
| 242 } | |
| 243 | |
| 244 std::string InputMethodSyncer::AddSupportedInputMethodValues( | |
| 245 const std::string& pref, | |
| 246 const std::string& synced_pref, | |
| 247 const char* pref_name) { | |
| 248 std::vector<std::string> old_tokens; | |
| 249 std::vector<std::string> new_tokens; | |
| 250 base::SplitString(pref, ',', &old_tokens); | |
| 251 base::SplitString(synced_pref, ',', &new_tokens); | |
| 252 | |
| 253 // Check and convert the new tokens. | |
| 254 if (pref_name == prefs::kLanguagePreloadEngines || | |
| 255 pref_name == prefs::kLanguageEnabledExtensionImes) { | |
| 256 input_method::InputMethodManager* manager = | |
| 257 input_method::InputMethodManager::Get(); | |
| 258 scoped_ptr<input_method::InputMethodDescriptors> supported_descriptors; | |
| 259 | |
| 260 if (pref_name == prefs::kLanguagePreloadEngines) { | |
| 261 // Set the known input methods. | |
| 262 supported_descriptors = manager->GetSupportedInputMethods(); | |
| 263 // Add the available component extension IMEs. | |
| 264 ComponentExtensionIMEManager* component_extension_manager = | |
| 265 manager->GetComponentExtensionIMEManager(); | |
| 266 input_method::InputMethodDescriptors component_descriptors = | |
| 267 component_extension_manager->GetAllIMEAsInputMethodDescriptor(); | |
| 268 supported_descriptors->insert(supported_descriptors->end(), | |
| 269 component_descriptors.begin(), | |
| 270 component_descriptors.end()); | |
| 271 } else { | |
| 272 supported_descriptors.reset(new input_method::InputMethodDescriptors); | |
| 273 ime_state_->GetInputMethodExtensions(supported_descriptors.get()); | |
| 274 } | |
| 275 CheckAndResolveInputMethodIDs(*supported_descriptors, &new_tokens); | |
| 276 } else if (pref_name != prefs::kLanguagePreferredLanguages) { | |
| 277 NOTREACHED() << "Attempting to merge an invalid preference."; | |
| 278 // kLanguagePreferredLanguages is checked in CheckAndResolveLocales(). | |
| 279 } | |
| 280 | |
| 281 // Do the actual merging. | |
| 282 MergeLists(&old_tokens, new_tokens); | |
| 283 return JoinString(old_tokens, ','); | |
| 284 } | |
| 285 | |
| 286 void InputMethodSyncer::FinishMerge( | |
| 287 scoped_ptr<std::string> languages) { | |
| 288 // Since the merge only removed locales that are unsupported on this system, | |
| 289 // we don't need to update the syncable prefs. If the local preference changes | |
| 290 // later, the sync server will lose the values we dropped. That's okay since | |
| 291 // the values from this device should then become the new defaults anyway. | |
| 292 preferred_languages_.SetValue(*languages); | |
| 293 | |
| 294 // We've finished merging. | |
| 295 merging_ = false; | |
| 296 } | |
| 297 | |
| 298 void InputMethodSyncer::OnPreferenceChanged(const std::string& pref_name) { | |
| 299 DCHECK(pref_name == prefs::kLanguagePreferredLanguages || | |
| 300 pref_name == prefs::kLanguagePreloadEngines || | |
| 301 pref_name == prefs::kLanguageEnabledExtensionImes); | |
| 302 | |
| 303 if (merging_ || prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods)) | |
| 304 return; | |
| 305 | |
| 306 // Set the language and input prefs at the same time. Otherwise we may, | |
| 307 // e.g., use a stale languages setting but push a new preload engines setting. | |
| 308 preferred_languages_syncable_.SetValue(preferred_languages_.GetValue()); | |
| 309 enabled_extension_imes_syncable_.SetValue(enabled_extension_imes_.GetValue()); | |
| 310 | |
| 311 // For preload engines, use legacy xkb IDs so the preference can sync | |
| 312 // across Chrome OS and Chromium OS. | |
| 313 std::vector<std::string> engines; | |
| 314 base::SplitString(preload_engines_.GetValue(), ',', &engines); | |
| 315 std::transform(engines.begin(), engines.end(), engines.begin(), | |
| 316 extension_ime_util::GetComponentIDByInputMethodID); | |
| 317 preload_engines_syncable_.SetValue(JoinString(engines, ',')); | |
| 318 } | |
| 319 | |
| 320 void InputMethodSyncer::OnIsSyncingChanged() { | |
| 321 if (prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods) && | |
| 322 prefs_->IsSyncing()) { | |
| 323 MergeSyncedPrefs(); | |
| 324 } | |
| 325 } | |
| 326 | |
| 327 } // namespace input_method | |
| 328 } // namespace chromeos | |
| OLD | NEW |