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 "base/task_runner_util.h" |
| 16 #include "chrome/browser/browser_process.h" |
| 17 #include "chrome/browser/prefs/pref_service_syncable.h" |
| 18 #include "chrome/common/pref_names.h" |
| 19 #include "components/pref_registry/pref_registry_syncable.h" |
| 20 #include "content/public/browser/browser_thread.h" |
| 21 #include "ui/base/ime/chromeos/component_extension_ime_manager.h" |
| 22 #include "ui/base/ime/chromeos/extension_ime_util.h" |
| 23 #include "ui/base/l10n/l10n_util.h" |
| 24 |
| 25 namespace chromeos { |
| 26 namespace input_method { |
| 27 namespace { |
| 28 |
| 29 // Checks input method IDs, converting engine IDs to input method IDs and |
| 30 // removing unsupported IDs from |values|. |
| 31 void CheckAndResolveInputMethodIDs( |
| 32 const input_method::InputMethodDescriptors& supported_descriptors, |
| 33 std::vector<std::string>* values) { |
| 34 // Extract the supported input method IDs into a set. |
| 35 std::set<std::string> supported_input_method_ids; |
| 36 for (const auto& descriptor : supported_descriptors) |
| 37 supported_input_method_ids.insert(descriptor.id()); |
| 38 |
| 39 // Convert engine IDs to input method extension IDs. |
| 40 std::transform(values->begin(), values->end(), values->begin(), |
| 41 extension_ime_util::GetInputMethodIDByEngineID); |
| 42 |
| 43 // Remove values that aren't found in the set of supported input method IDs. |
| 44 std::vector<std::string>::iterator it = values->begin(); |
| 45 while (it != values->end()) { |
| 46 if (it->size() && supported_input_method_ids.find(*it) != |
| 47 supported_input_method_ids.end()) { |
| 48 ++it; |
| 49 } else { |
| 50 it = values->erase(it); |
| 51 } |
| 52 } |
| 53 } |
| 54 |
| 55 // Checks whether each language is supported, replacing locales with variants |
| 56 // if they are available. Must be called on a thread that allows IO. |
| 57 std::string CheckAndResolveLocales(const std::string& languages) { |
| 58 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
| 59 if (languages.empty()) |
| 60 return languages; |
| 61 std::vector<std::string> values; |
| 62 base::SplitString(languages, ',', &values); |
| 63 |
| 64 const std::string app_locale = g_browser_process->GetApplicationLocale(); |
| 65 |
| 66 std::vector<std::string> accept_language_codes; |
| 67 l10n_util::GetAcceptLanguagesForLocale(app_locale, &accept_language_codes); |
| 68 std::sort(accept_language_codes.begin(), accept_language_codes.end()); |
| 69 |
| 70 // Remove unsupported language values. |
| 71 std::vector<std::string>::iterator value_iter = values.begin(); |
| 72 while (value_iter != values.end()) { |
| 73 if (binary_search(accept_language_codes.begin(), |
| 74 accept_language_codes.end(), |
| 75 *value_iter)) { |
| 76 ++value_iter; |
| 77 continue; |
| 78 } |
| 79 |
| 80 // If a language code resolves to a supported backup locale, replace it |
| 81 // with the resolved locale. |
| 82 std::string resolved_locale; |
| 83 if (l10n_util::CheckAndResolveLocale(*value_iter, &resolved_locale)) { |
| 84 if (binary_search(accept_language_codes.begin(), |
| 85 accept_language_codes.end(), |
| 86 resolved_locale)) { |
| 87 *value_iter = resolved_locale; |
| 88 ++value_iter; |
| 89 continue; |
| 90 } |
| 91 } |
| 92 value_iter = values.erase(value_iter); |
| 93 } |
| 94 |
| 95 return JoinString(values, ','); |
| 96 } |
| 97 |
| 98 // Appends tokens from |src| that are not in |dest| to |dest|. |
| 99 void MergeLists(std::vector<std::string>* dest, |
| 100 const std::vector<std::string>& src) { |
| 101 // Keep track of already-added tokens. |
| 102 std::set<std::string> unique_tokens(dest->begin(), dest->end()); |
| 103 |
| 104 for (const std::string& token : src) { |
| 105 // Skip token if it's already in |dest|. |
| 106 if (binary_search(unique_tokens.begin(), unique_tokens.end(), token)) |
| 107 continue; |
| 108 dest->push_back(token); |
| 109 unique_tokens.insert(token); |
| 110 } |
| 111 } |
| 112 |
| 113 } // anonymous namespace |
| 114 |
| 115 InputMethodSyncer::InputMethodSyncer( |
| 116 PrefServiceSyncable* prefs, |
| 117 scoped_refptr<input_method::InputMethodManager::State> ime_state) |
| 118 : prefs_(prefs), |
| 119 ime_state_(ime_state), |
| 120 merging_(false), |
| 121 weak_factory_(this) { |
| 122 } |
| 123 |
| 124 InputMethodSyncer::~InputMethodSyncer() { |
| 125 prefs_->RemoveObserver(this); |
| 126 } |
| 127 |
| 128 // static |
| 129 void InputMethodSyncer::RegisterProfilePrefs( |
| 130 user_prefs::PrefRegistrySyncable* registry) { |
| 131 registry->RegisterStringPref( |
| 132 prefs::kLanguagePreferredLanguagesSyncable, |
| 133 "", |
| 134 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| 135 registry->RegisterStringPref( |
| 136 prefs::kLanguagePreloadEnginesSyncable, |
| 137 "", |
| 138 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| 139 registry->RegisterStringPref( |
| 140 prefs::kLanguageEnabledExtensionImesSyncable, |
| 141 "", |
| 142 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| 143 registry->RegisterBooleanPref( |
| 144 prefs::kLanguageShouldMergeInputMethods, |
| 145 false, |
| 146 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| 147 } |
| 148 |
| 149 void InputMethodSyncer::Initialize() { |
| 150 // This causes OnIsSyncingChanged to be called when the value of |
| 151 // PrefService::IsSyncing() changes. |
| 152 prefs_->AddObserver(this); |
| 153 |
| 154 preferred_languages_syncable_.Init(prefs::kLanguagePreferredLanguagesSyncable, |
| 155 prefs_); |
| 156 preload_engines_syncable_.Init(prefs::kLanguagePreloadEnginesSyncable, |
| 157 prefs_); |
| 158 enabled_extension_imes_syncable_.Init( |
| 159 prefs::kLanguageEnabledExtensionImesSyncable, prefs_); |
| 160 |
| 161 BooleanPrefMember::NamedChangeCallback callback = |
| 162 base::Bind(&InputMethodSyncer::OnPreferenceChanged, |
| 163 base::Unretained(this)); |
| 164 preferred_languages_.Init(prefs::kLanguagePreferredLanguages, |
| 165 prefs_, callback); |
| 166 preload_engines_.Init(prefs::kLanguagePreloadEngines, |
| 167 prefs_, callback); |
| 168 enabled_extension_imes_.Init( |
| 169 prefs::kLanguageEnabledExtensionImes, prefs_, callback); |
| 170 |
| 171 // If we have already synced but haven't merged input methods yet, do so now. |
| 172 if (prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods) && |
| 173 !(preferred_languages_syncable_.GetValue().empty() && |
| 174 preload_engines_syncable_.GetValue().empty() && |
| 175 enabled_extension_imes_syncable_.GetValue().empty())) { |
| 176 MergeSyncedPrefs(); |
| 177 } |
| 178 } |
| 179 |
| 180 void InputMethodSyncer::MergeSyncedPrefs() { |
| 181 // This should only be done after the first ever sync. |
| 182 DCHECK(prefs_->GetBoolean(prefs::kLanguageShouldMergeInputMethods)); |
| 183 prefs_->SetBoolean(prefs::kLanguageShouldMergeInputMethods, false); |
| 184 merging_ = true; |
| 185 |
| 186 std::string preferred_languages_syncable = |
| 187 preferred_languages_syncable_.GetValue(); |
| 188 std::string preload_engines_syncable = |
| 189 preload_engines_syncable_.GetValue(); |
| 190 std::string enabled_extension_imes_syncable = |
| 191 enabled_extension_imes_syncable_.GetValue(); |
| 192 |
| 193 std::vector<std::string> synced_tokens; |
| 194 std::vector<std::string> new_tokens; |
| 195 |
| 196 // First, set the syncable prefs to the union of the local and synced prefs. |
| 197 base::SplitString( |
| 198 preferred_languages_syncable_.GetValue(), ',', &synced_tokens); |
| 199 base::SplitString(preferred_languages_.GetValue(), ',', &new_tokens); |
| 200 |
| 201 // Append the synced values to the current values. |
| 202 MergeLists(&new_tokens, synced_tokens); |
| 203 preferred_languages_syncable_.SetValue(JoinString(new_tokens, ',')); |
| 204 |
| 205 base::SplitString( |
| 206 enabled_extension_imes_syncable_.GetValue(), ',', &synced_tokens); |
| 207 base::SplitString(enabled_extension_imes_.GetValue(), ',', &new_tokens); |
| 208 |
| 209 MergeLists(&new_tokens, synced_tokens); |
| 210 enabled_extension_imes_syncable_.SetValue(JoinString(new_tokens, ',')); |
| 211 |
| 212 // Revert preload engines to legacy component IDs. |
| 213 base::SplitString(preload_engines_.GetValue(), ',', &new_tokens); |
| 214 std::transform(new_tokens.begin(), new_tokens.end(), new_tokens.begin(), |
| 215 extension_ime_util::GetComponentIDByInputMethodID); |
| 216 base::SplitString( |
| 217 preload_engines_syncable_.GetValue(), ',', &synced_tokens); |
| 218 |
| 219 MergeLists(&new_tokens, synced_tokens); |
| 220 preload_engines_syncable_.SetValue(JoinString(new_tokens, ',')); |
| 221 |
| 222 // Second, set the local prefs, incorporating new values from the sync server. |
| 223 preload_engines_.SetValue( |
| 224 AddSupportedInputMethodValues(preload_engines_.GetValue(), |
| 225 preload_engines_syncable, |
| 226 prefs::kLanguagePreloadEngines)); |
| 227 enabled_extension_imes_.SetValue( |
| 228 AddSupportedInputMethodValues(enabled_extension_imes_.GetValue(), |
| 229 enabled_extension_imes_syncable, |
| 230 prefs::kLanguageEnabledExtensionImes)); |
| 231 |
| 232 // Remove unsupported locales before updating the local languages preference. |
| 233 std::string languages( |
| 234 AddSupportedInputMethodValues(preferred_languages_.GetValue(), |
| 235 preferred_languages_syncable, |
| 236 prefs::kLanguagePreferredLanguages)); |
| 237 base::PostTaskAndReplyWithResult( |
| 238 content::BrowserThread::GetBlockingPool(), |
| 239 FROM_HERE, |
| 240 base::Bind(&CheckAndResolveLocales, languages), |
| 241 base::Bind(&InputMethodSyncer::FinishMerge, |
| 242 weak_factory_.GetWeakPtr())); |
| 243 } |
| 244 |
| 245 std::string InputMethodSyncer::AddSupportedInputMethodValues( |
| 246 const std::string& pref, |
| 247 const std::string& synced_pref, |
| 248 const char* pref_name) { |
| 249 std::vector<std::string> old_tokens; |
| 250 std::vector<std::string> new_tokens; |
| 251 base::SplitString(pref, ',', &old_tokens); |
| 252 base::SplitString(synced_pref, ',', &new_tokens); |
| 253 |
| 254 // Check and convert the new tokens. |
| 255 if (pref_name == prefs::kLanguagePreloadEngines || |
| 256 pref_name == prefs::kLanguageEnabledExtensionImes) { |
| 257 input_method::InputMethodManager* manager = |
| 258 input_method::InputMethodManager::Get(); |
| 259 scoped_ptr<input_method::InputMethodDescriptors> supported_descriptors; |
| 260 |
| 261 if (pref_name == prefs::kLanguagePreloadEngines) { |
| 262 // Set the known input methods. |
| 263 supported_descriptors = manager->GetSupportedInputMethods(); |
| 264 // Add the available component extension IMEs. |
| 265 ComponentExtensionIMEManager* component_extension_manager = |
| 266 manager->GetComponentExtensionIMEManager(); |
| 267 input_method::InputMethodDescriptors component_descriptors = |
| 268 component_extension_manager->GetAllIMEAsInputMethodDescriptor(); |
| 269 supported_descriptors->insert(supported_descriptors->end(), |
| 270 component_descriptors.begin(), |
| 271 component_descriptors.end()); |
| 272 } else { |
| 273 supported_descriptors.reset(new input_method::InputMethodDescriptors); |
| 274 ime_state_->GetInputMethodExtensions(supported_descriptors.get()); |
| 275 } |
| 276 CheckAndResolveInputMethodIDs(*supported_descriptors, &new_tokens); |
| 277 } else if (pref_name != prefs::kLanguagePreferredLanguages) { |
| 278 NOTREACHED() << "Attempting to merge an invalid preference."; |
| 279 // kLanguagePreferredLanguages is checked in CheckAndResolveLocales(). |
| 280 } |
| 281 |
| 282 // Do the actual merging. |
| 283 MergeLists(&old_tokens, new_tokens); |
| 284 return JoinString(old_tokens, ','); |
| 285 } |
| 286 |
| 287 void InputMethodSyncer::FinishMerge(const 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 |