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))); | |
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 |