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.
5 #include "chrome/browser/chromeos/input_method/input_method_syncer.h"
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/common/pref_names.h"
18 #include "components/pref_registry/pref_registry_syncable.h"
19 #include "components/syncable_prefs/pref_service_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"
26 namespace input_method
{
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());
39 // Convert engine IDs to input method extension IDs.
40 std::transform(values
->begin(), values
->end(), values
->begin(),
41 extension_ime_util::GetInputMethodIDByEngineID
);
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()) {
50 it
= values
->erase(it
);
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())
61 std::vector
<std::string
> values
= base::SplitString(
62 languages
, ",", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
64 const std::string app_locale
= g_browser_process
->GetApplicationLocale();
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());
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(),
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(),
87 *value_iter
= resolved_locale
;
92 value_iter
= values
.erase(value_iter
);
95 return base::JoinString(values
, ",");
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());
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
))
108 dest
->push_back(token
);
109 unique_tokens
.insert(token
);
113 } // anonymous namespace
115 InputMethodSyncer::InputMethodSyncer(
116 syncable_prefs::PrefServiceSyncable
* prefs
,
117 scoped_refptr
<input_method::InputMethodManager::State
> ime_state
)
119 ime_state_(ime_state
),
121 weak_factory_(this) {}
123 InputMethodSyncer::~InputMethodSyncer() {
124 prefs_
->RemoveObserver(this);
128 void InputMethodSyncer::RegisterProfilePrefs(
129 user_prefs::PrefRegistrySyncable
* registry
) {
130 registry
->RegisterStringPref(
131 prefs::kLanguagePreferredLanguagesSyncable
,
133 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
134 registry
->RegisterStringPref(
135 prefs::kLanguagePreloadEnginesSyncable
,
137 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
138 registry
->RegisterStringPref(
139 prefs::kLanguageEnabledExtensionImesSyncable
,
141 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
142 registry
->RegisterBooleanPref(prefs::kLanguageShouldMergeInputMethods
, false);
145 void InputMethodSyncer::Initialize() {
146 // This causes OnIsSyncingChanged to be called when the value of
147 // PrefService::IsSyncing() changes.
148 prefs_
->AddObserver(this);
150 preferred_languages_syncable_
.Init(prefs::kLanguagePreferredLanguagesSyncable
,
152 preload_engines_syncable_
.Init(prefs::kLanguagePreloadEnginesSyncable
,
154 enabled_extension_imes_syncable_
.Init(
155 prefs::kLanguageEnabledExtensionImesSyncable
, prefs_
);
157 BooleanPrefMember::NamedChangeCallback callback
=
158 base::Bind(&InputMethodSyncer::OnPreferenceChanged
,
159 base::Unretained(this));
160 preferred_languages_
.Init(prefs::kLanguagePreferredLanguages
,
162 preload_engines_
.Init(prefs::kLanguagePreloadEngines
,
164 enabled_extension_imes_
.Init(
165 prefs::kLanguageEnabledExtensionImes
, prefs_
, callback
);
167 // If we have already synced but haven't merged input methods yet, do so now.
168 if (prefs_
->GetBoolean(prefs::kLanguageShouldMergeInputMethods
) &&
169 !(preferred_languages_syncable_
.GetValue().empty() &&
170 preload_engines_syncable_
.GetValue().empty() &&
171 enabled_extension_imes_syncable_
.GetValue().empty())) {
176 void InputMethodSyncer::MergeSyncedPrefs() {
177 // This should only be done after the first ever sync.
178 DCHECK(prefs_
->GetBoolean(prefs::kLanguageShouldMergeInputMethods
));
179 prefs_
->SetBoolean(prefs::kLanguageShouldMergeInputMethods
, false);
182 std::string preferred_languages_syncable
=
183 preferred_languages_syncable_
.GetValue();
184 std::string preload_engines_syncable
=
185 preload_engines_syncable_
.GetValue();
186 std::string enabled_extension_imes_syncable
=
187 enabled_extension_imes_syncable_
.GetValue();
189 std::vector
<std::string
> synced_tokens
;
190 std::vector
<std::string
> new_tokens
;
192 // First, set the syncable prefs to the union of the local and synced prefs.
194 base::SplitString(preferred_languages_syncable_
.GetValue(), ",",
195 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
196 new_tokens
= base::SplitString(preferred_languages_
.GetValue(), ",",
197 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
199 // Append the synced values to the current values.
200 MergeLists(&new_tokens
, synced_tokens
);
201 preferred_languages_syncable_
.SetValue(base::JoinString(new_tokens
, ","));
204 base::SplitString(enabled_extension_imes_syncable_
.GetValue(), ",",
205 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
206 new_tokens
= base::SplitString(enabled_extension_imes_
.GetValue(), ",",
207 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
209 MergeLists(&new_tokens
, synced_tokens
);
210 enabled_extension_imes_syncable_
.SetValue(base::JoinString(new_tokens
, ","));
212 // Revert preload engines to legacy component IDs.
213 new_tokens
= base::SplitString(preload_engines_
.GetValue(), ",",
214 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
215 std::transform(new_tokens
.begin(), new_tokens
.end(), new_tokens
.begin(),
216 extension_ime_util::GetComponentIDByInputMethodID
);
218 base::SplitString(preload_engines_syncable_
.GetValue(), ",",
219 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
221 MergeLists(&new_tokens
, synced_tokens
);
222 preload_engines_syncable_
.SetValue(base::JoinString(new_tokens
, ","));
224 // Second, set the local prefs, incorporating new values from the sync server.
225 preload_engines_
.SetValue(
226 AddSupportedInputMethodValues(preload_engines_
.GetValue(),
227 preload_engines_syncable
,
228 prefs::kLanguagePreloadEngines
));
229 enabled_extension_imes_
.SetValue(
230 AddSupportedInputMethodValues(enabled_extension_imes_
.GetValue(),
231 enabled_extension_imes_syncable
,
232 prefs::kLanguageEnabledExtensionImes
));
234 // Remove unsupported locales before updating the local languages preference.
235 std::string
languages(
236 AddSupportedInputMethodValues(preferred_languages_
.GetValue(),
237 preferred_languages_syncable
,
238 prefs::kLanguagePreferredLanguages
));
239 base::PostTaskAndReplyWithResult(
240 content::BrowserThread::GetBlockingPool(),
242 base::Bind(&CheckAndResolveLocales
, languages
),
243 base::Bind(&InputMethodSyncer::FinishMerge
,
244 weak_factory_
.GetWeakPtr()));
247 std::string
InputMethodSyncer::AddSupportedInputMethodValues(
248 const std::string
& pref
,
249 const std::string
& synced_pref
,
250 const char* pref_name
) {
251 std::vector
<std::string
> old_tokens
=
252 base::SplitString(pref
, ",", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
253 std::vector
<std::string
> new_tokens
= base::SplitString(
254 synced_pref
, ",", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
256 // Check and convert the new tokens.
257 if (pref_name
== prefs::kLanguagePreloadEngines
||
258 pref_name
== prefs::kLanguageEnabledExtensionImes
) {
259 input_method::InputMethodManager
* manager
=
260 input_method::InputMethodManager::Get();
261 scoped_ptr
<input_method::InputMethodDescriptors
> supported_descriptors
;
263 if (pref_name
== prefs::kLanguagePreloadEngines
) {
264 // Set the known input methods.
265 supported_descriptors
= manager
->GetSupportedInputMethods();
266 // Add the available component extension IMEs.
267 ComponentExtensionIMEManager
* component_extension_manager
=
268 manager
->GetComponentExtensionIMEManager();
269 input_method::InputMethodDescriptors component_descriptors
=
270 component_extension_manager
->GetAllIMEAsInputMethodDescriptor();
271 supported_descriptors
->insert(supported_descriptors
->end(),
272 component_descriptors
.begin(),
273 component_descriptors
.end());
275 supported_descriptors
.reset(new input_method::InputMethodDescriptors
);
276 ime_state_
->GetInputMethodExtensions(supported_descriptors
.get());
278 CheckAndResolveInputMethodIDs(*supported_descriptors
, &new_tokens
);
279 } else if (pref_name
!= prefs::kLanguagePreferredLanguages
) {
280 NOTREACHED() << "Attempting to merge an invalid preference.";
281 // kLanguagePreferredLanguages is checked in CheckAndResolveLocales().
284 // Do the actual merging.
285 MergeLists(&old_tokens
, new_tokens
);
286 return base::JoinString(old_tokens
, ",");
289 void InputMethodSyncer::FinishMerge(const std::string
& languages
) {
290 // Since the merge only removed locales that are unsupported on this system,
291 // we don't need to update the syncable prefs. If the local preference changes
292 // later, the sync server will lose the values we dropped. That's okay since
293 // the values from this device should then become the new defaults anyway.
294 preferred_languages_
.SetValue(languages
);
296 // We've finished merging.
300 void InputMethodSyncer::OnPreferenceChanged(const std::string
& pref_name
) {
301 DCHECK(pref_name
== prefs::kLanguagePreferredLanguages
||
302 pref_name
== prefs::kLanguagePreloadEngines
||
303 pref_name
== prefs::kLanguageEnabledExtensionImes
);
305 if (merging_
|| prefs_
->GetBoolean(prefs::kLanguageShouldMergeInputMethods
))
308 // Set the language and input prefs at the same time. Otherwise we may,
309 // e.g., use a stale languages setting but push a new preload engines setting.
310 preferred_languages_syncable_
.SetValue(preferred_languages_
.GetValue());
311 enabled_extension_imes_syncable_
.SetValue(enabled_extension_imes_
.GetValue());
313 // For preload engines, use legacy xkb IDs so the preference can sync
314 // across Chrome OS and Chromium OS.
315 std::vector
<std::string
> engines
=
316 base::SplitString(preload_engines_
.GetValue(), ",", base::TRIM_WHITESPACE
,
317 base::SPLIT_WANT_ALL
);
318 std::transform(engines
.begin(), engines
.end(), engines
.begin(),
319 extension_ime_util::GetComponentIDByInputMethodID
);
320 preload_engines_syncable_
.SetValue(base::JoinString(engines
, ","));
323 void InputMethodSyncer::OnIsSyncingChanged() {
324 if (prefs_
->GetBoolean(prefs::kLanguageShouldMergeInputMethods
) &&
325 prefs_
->IsSyncing()) {
330 } // namespace input_method
331 } // namespace chromeos