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/ui/webui/chromeos/login/l10n_util.h" 6 7 #include <algorithm> 8 #include <iterator> 9 #include <map> 10 #include <set> 11 #include <utility> 12 13 #include "base/basictypes.h" 14 #include "base/bind.h" 15 #include "base/i18n/rtl.h" 16 #include "base/location.h" 17 #include "base/logging.h" 18 #include "base/memory/ref_counted.h" 19 #include "base/sequenced_task_runner.h" 20 #include "base/strings/string16.h" 21 #include "base/strings/stringprintf.h" 22 #include "base/strings/utf_string_conversions.h" 23 #include "base/task_runner_util.h" 24 #include "base/threading/sequenced_worker_pool.h" 25 #include "base/values.h" 26 #include "chrome/browser/browser_process.h" 27 #include "chrome/browser/chromeos/customization_document.h" 28 #include "chrome/browser/chromeos/input_method/input_method_util.h" 29 #include "chrome/grit/generated_resources.h" 30 #include "chromeos/ime/component_extension_ime_manager.h" 31 #include "chromeos/ime/input_method_descriptor.h" 32 #include "chromeos/ime/input_method_manager.h" 33 #include "content/public/browser/browser_thread.h" 34 #include "ui/base/l10n/l10n_util.h" 35 36 namespace chromeos { 37 38 namespace { 39 40 const char kSequenceToken[] = "chromeos_login_l10n_util"; 41 42 scoped_ptr<base::DictionaryValue> CreateInputMethodsEntry( 43 const input_method::InputMethodDescriptor& method, 44 const std::string selected) { 45 input_method::InputMethodUtil* util = 46 input_method::InputMethodManager::Get()->GetInputMethodUtil(); 47 const std::string& ime_id = method.id(); 48 scoped_ptr<base::DictionaryValue> input_method(new base::DictionaryValue); 49 input_method->SetString("value", ime_id); 50 input_method->SetString("title", util->GetInputMethodLongName(method)); 51 input_method->SetBoolean("selected", ime_id == selected); 52 return input_method.Pass(); 53 } 54 55 // Returns true if element was inserted. 56 bool InsertString(const std::string& str, std::set<std::string>* to) { 57 const std::pair<std::set<std::string>::iterator, bool> result = 58 to->insert(str); 59 return result.second; 60 } 61 62 #if !defined(USE_ATHENA) 63 // TODO(dpolukhin): crbug.com/407579 64 void AddOptgroupOtherLayouts(base::ListValue* input_methods_list) { 65 scoped_ptr<base::DictionaryValue> optgroup(new base::DictionaryValue); 66 optgroup->SetString( 67 "optionGroupName", 68 l10n_util::GetStringUTF16(IDS_OOBE_OTHER_KEYBOARD_LAYOUTS)); 69 input_methods_list->Append(optgroup.release()); 70 } 71 #endif 72 73 // Gets the list of languages with |descriptors| based on |base_language_codes|. 74 // The |most_relevant_language_codes| will be first in the list. If 75 // |insert_divider| is true, an entry with its "code" attribute set to 76 // kMostRelevantLanguagesDivider is placed between the most relevant languages 77 // and all others. 78 scoped_ptr<base::ListValue> GetLanguageList( 79 const input_method::InputMethodDescriptors& descriptors, 80 const std::vector<std::string>& base_language_codes, 81 const std::vector<std::string>& most_relevant_language_codes, 82 bool insert_divider) { 83 const std::string app_locale = g_browser_process->GetApplicationLocale(); 84 85 std::set<std::string> language_codes; 86 // Collect the language codes from the supported input methods. 87 for (size_t i = 0; i < descriptors.size(); ++i) { 88 const input_method::InputMethodDescriptor& descriptor = descriptors[i]; 89 const std::vector<std::string>& languages = descriptor.language_codes(); 90 for (size_t i = 0; i < languages.size(); ++i) 91 language_codes.insert(languages[i]); 92 } 93 94 // Language sort order. 95 std::map<std::string, int /* index */> language_index; 96 for (size_t i = 0; i < most_relevant_language_codes.size(); ++i) 97 language_index[most_relevant_language_codes[i]] = i; 98 99 // Map of display name -> {language code, native_display_name}. 100 // In theory, we should be able to create a map that is sorted by 101 // display names using ICU comparator, but doing it is hard, thus we'll 102 // use an auxiliary vector to achieve the same result. 103 typedef std::pair<std::string, base::string16> LanguagePair; 104 typedef std::map<base::string16, LanguagePair> LanguageMap; 105 LanguageMap language_map; 106 107 // The auxiliary vector mentioned above (except the most relevant locales). 108 std::vector<base::string16> display_names; 109 110 // Separate vector of the most relevant locales. 111 std::vector<base::string16> most_relevant_locales_display_names( 112 most_relevant_language_codes.size()); 113 114 size_t most_relevant_locales_count = 0; 115 116 // Build the list of display names, and build the language map. 117 118 // The list of configured locales might have entries not in 119 // base_language_codes. If there are unsupported language variants, 120 // but they resolve to backup locale within base_language_codes, also 121 // add them to the list. 122 for (std::map<std::string, int>::const_iterator it = language_index.begin(); 123 it != language_index.end(); ++it) { 124 const std::string& language_id = it->first; 125 126 const std::string lang = l10n_util::GetLanguage(language_id); 127 128 // Ignore non-specific codes. 129 if (lang.empty() || lang == language_id) 130 continue; 131 132 if (std::find(base_language_codes.begin(), 133 base_language_codes.end(), 134 language_id) != base_language_codes.end()) { 135 // Language is supported. No need to replace 136 continue; 137 } 138 std::string resolved_locale; 139 if (!l10n_util::CheckAndResolveLocale(language_id, &resolved_locale)) 140 continue; 141 142 if (std::find(base_language_codes.begin(), 143 base_language_codes.end(), 144 resolved_locale) == base_language_codes.end()) { 145 // Resolved locale is not supported. 146 continue; 147 } 148 149 const base::string16 display_name = 150 l10n_util::GetDisplayNameForLocale(language_id, app_locale, true); 151 const base::string16 native_display_name = 152 l10n_util::GetDisplayNameForLocale( 153 language_id, language_id, true); 154 155 language_map[display_name] = 156 std::make_pair(language_id, native_display_name); 157 158 most_relevant_locales_display_names[it->second] = display_name; 159 ++most_relevant_locales_count; 160 } 161 162 // Translate language codes, generated from input methods. 163 for (std::set<std::string>::const_iterator it = language_codes.begin(); 164 it != language_codes.end(); ++it) { 165 // Exclude the language which is not in |base_langauge_codes| even it has 166 // input methods. 167 if (std::find(base_language_codes.begin(), 168 base_language_codes.end(), 169 *it) == base_language_codes.end()) { 170 continue; 171 } 172 173 const base::string16 display_name = 174 l10n_util::GetDisplayNameForLocale(*it, app_locale, true); 175 const base::string16 native_display_name = 176 l10n_util::GetDisplayNameForLocale(*it, *it, true); 177 178 language_map[display_name] = 179 std::make_pair(*it, native_display_name); 180 181 const std::map<std::string, int>::const_iterator index_pos = 182 language_index.find(*it); 183 if (index_pos != language_index.end()) { 184 base::string16& stored_display_name = 185 most_relevant_locales_display_names[index_pos->second]; 186 if (stored_display_name.empty()) { 187 stored_display_name = display_name; 188 ++most_relevant_locales_count; 189 } 190 } else { 191 display_names.push_back(display_name); 192 } 193 } 194 DCHECK_EQ(display_names.size() + most_relevant_locales_count, 195 language_map.size()); 196 197 // Build the list of display names, and build the language map. 198 for (size_t i = 0; i < base_language_codes.size(); ++i) { 199 // Skip this language if it was already added. 200 if (language_codes.find(base_language_codes[i]) != language_codes.end()) 201 continue; 202 203 base::string16 display_name = 204 l10n_util::GetDisplayNameForLocale( 205 base_language_codes[i], app_locale, false); 206 base::string16 native_display_name = 207 l10n_util::GetDisplayNameForLocale( 208 base_language_codes[i], base_language_codes[i], false); 209 language_map[display_name] = 210 std::make_pair(base_language_codes[i], native_display_name); 211 212 const std::map<std::string, int>::const_iterator index_pos = 213 language_index.find(base_language_codes[i]); 214 if (index_pos != language_index.end()) { 215 most_relevant_locales_display_names[index_pos->second] = display_name; 216 ++most_relevant_locales_count; 217 } else { 218 display_names.push_back(display_name); 219 } 220 } 221 222 // Sort display names using locale specific sorter. 223 l10n_util::SortStrings16(app_locale, &display_names); 224 // Concatenate most_relevant_locales_display_names and display_names. 225 // Insert special divider in between. 226 std::vector<base::string16> out_display_names; 227 for (size_t i = 0; i < most_relevant_locales_display_names.size(); ++i) { 228 if (most_relevant_locales_display_names[i].size() == 0) 229 continue; 230 out_display_names.push_back(most_relevant_locales_display_names[i]); 231 } 232 233 base::string16 divider16; 234 if (insert_divider && !out_display_names.empty()) { 235 // Insert a divider if requested, but only if 236 // |most_relevant_locales_display_names| is not empty. 237 divider16 = base::ASCIIToUTF16(kMostRelevantLanguagesDivider); 238 out_display_names.push_back(divider16); 239 } 240 241 std::copy(display_names.begin(), 242 display_names.end(), 243 std::back_inserter(out_display_names)); 244 245 // Build the language list from the language map. 246 scoped_ptr<base::ListValue> language_list(new base::ListValue()); 247 for (size_t i = 0; i < out_display_names.size(); ++i) { 248 // Sets the directionality of the display language name. 249 base::string16 display_name(out_display_names[i]); 250 if (insert_divider && display_name == divider16) { 251 // Insert divider. 252 base::DictionaryValue* dictionary = new base::DictionaryValue(); 253 dictionary->SetString("code", kMostRelevantLanguagesDivider); 254 language_list->Append(dictionary); 255 continue; 256 } 257 const bool markup_removal = 258 base::i18n::UnadjustStringForLocaleDirection(&display_name); 259 DCHECK(markup_removal); 260 const bool has_rtl_chars = 261 base::i18n::StringContainsStrongRTLChars(display_name); 262 const std::string directionality = has_rtl_chars ? "rtl" : "ltr"; 263 264 const LanguagePair& pair = language_map[out_display_names[i]]; 265 base::DictionaryValue* dictionary = new base::DictionaryValue(); 266 dictionary->SetString("code", pair.first); 267 dictionary->SetString("displayName", out_display_names[i]); 268 dictionary->SetString("textDirection", directionality); 269 dictionary->SetString("nativeDisplayName", pair.second); 270 language_list->Append(dictionary); 271 } 272 273 return language_list.Pass(); 274 } 275 276 // Invokes |callback| with a list of keyboard layouts that can be used for 277 // |resolved_locale|. 278 void GetKeyboardLayoutsForResolvedLocale( 279 const GetKeyboardLayoutsForLocaleCallback& callback, 280 const std::string& resolved_locale) { 281 input_method::InputMethodUtil* util = 282 input_method::InputMethodManager::Get()->GetInputMethodUtil(); 283 std::vector<std::string> layouts = util->GetHardwareInputMethodIds(); 284 std::vector<std::string> layouts_from_locale; 285 util->GetInputMethodIdsFromLanguageCode( 286 resolved_locale, 287 input_method::kKeyboardLayoutsOnly, 288 &layouts_from_locale); 289 layouts.insert(layouts.end(), layouts_from_locale.begin(), 290 layouts_from_locale.end()); 291 292 std::string selected; 293 if (!layouts_from_locale.empty()) { 294 selected = 295 util->GetInputMethodDescriptorFromId(layouts_from_locale[0])->id(); 296 } 297 298 scoped_ptr<base::ListValue> input_methods_list(new base::ListValue); 299 std::set<std::string> input_methods_added; 300 for (std::vector<std::string>::const_iterator it = layouts.begin(); 301 it != layouts.end(); ++it) { 302 const input_method::InputMethodDescriptor* ime = 303 util->GetInputMethodDescriptorFromId(*it); 304 if (!InsertString(ime->id(), &input_methods_added)) 305 continue; 306 input_methods_list->Append( 307 CreateInputMethodsEntry(*ime, selected).release()); 308 } 309 310 callback.Run(input_methods_list.Pass()); 311 } 312 313 } // namespace 314 315 const char kMostRelevantLanguagesDivider[] = "MOST_RELEVANT_LANGUAGES_DIVIDER"; 316 317 scoped_ptr<base::ListValue> GetUILanguageList( 318 const std::vector<std::string>* most_relevant_language_codes, 319 const std::string& selected) { 320 ComponentExtensionIMEManager* manager = 321 input_method::InputMethodManager::Get()-> 322 GetComponentExtensionIMEManager(); 323 input_method::InputMethodDescriptors descriptors = 324 manager->GetXkbIMEAsInputMethodDescriptor(); 325 scoped_ptr<base::ListValue> languages_list(GetLanguageList( 326 descriptors, 327 l10n_util::GetAvailableLocales(), 328 most_relevant_language_codes 329 ? *most_relevant_language_codes 330 : StartupCustomizationDocument::GetInstance()->configured_locales(), 331 true)); 332 333 for (size_t i = 0; i < languages_list->GetSize(); ++i) { 334 base::DictionaryValue* language_info = NULL; 335 if (!languages_list->GetDictionary(i, &language_info)) 336 NOTREACHED(); 337 338 std::string value; 339 language_info->GetString("code", &value); 340 std::string display_name; 341 language_info->GetString("displayName", &display_name); 342 std::string native_name; 343 language_info->GetString("nativeDisplayName", &native_name); 344 345 // If it's an option group divider, add field name. 346 if (value == kMostRelevantLanguagesDivider) { 347 language_info->SetString( 348 "optionGroupName", 349 l10n_util::GetStringUTF16(IDS_OOBE_OTHER_LANGUAGES)); 350 } 351 if (display_name != native_name) { 352 display_name = base::StringPrintf("%s - %s", 353 display_name.c_str(), 354 native_name.c_str()); 355 } 356 357 language_info->SetString("value", value); 358 language_info->SetString("title", display_name); 359 if (value == selected) 360 language_info->SetBoolean("selected", true); 361 } 362 return languages_list.Pass(); 363 } 364 365 std::string FindMostRelevantLocale( 366 const std::vector<std::string>& most_relevant_language_codes, 367 const base::ListValue& available_locales, 368 const std::string& fallback_locale) { 369 for (std::vector<std::string>::const_iterator most_relevant_it = 370 most_relevant_language_codes.begin(); 371 most_relevant_it != most_relevant_language_codes.end(); 372 ++most_relevant_it) { 373 for (base::ListValue::const_iterator available_it = 374 available_locales.begin(); 375 available_it != available_locales.end(); ++available_it) { 376 base::DictionaryValue* dict; 377 std::string available_locale; 378 if (!(*available_it)->GetAsDictionary(&dict) || 379 !dict->GetString("value", &available_locale)) { 380 NOTREACHED(); 381 continue; 382 } 383 if (available_locale == *most_relevant_it) 384 return *most_relevant_it; 385 } 386 } 387 388 return fallback_locale; 389 } 390 391 scoped_ptr<base::ListValue> GetAcceptLanguageList() { 392 // Collect the language codes from the supported accept-languages. 393 const std::string app_locale = g_browser_process->GetApplicationLocale(); 394 std::vector<std::string> accept_language_codes; 395 l10n_util::GetAcceptLanguagesForLocale(app_locale, &accept_language_codes); 396 return GetLanguageList( 397 *input_method::InputMethodManager::Get()->GetSupportedInputMethods(), 398 accept_language_codes, 399 StartupCustomizationDocument::GetInstance()->configured_locales(), 400 false); 401 } 402 403 scoped_ptr<base::ListValue> GetAndActivateLoginKeyboardLayouts( 404 const std::string& locale, 405 const std::string& selected) { 406 scoped_ptr<base::ListValue> input_methods_list(new base::ListValue); 407 #if !defined(USE_ATHENA) 408 // TODO(dpolukhin): crbug.com/407579 409 input_method::InputMethodManager* manager = 410 input_method::InputMethodManager::Get(); 411 input_method::InputMethodUtil* util = manager->GetInputMethodUtil(); 412 413 const std::vector<std::string>& hardware_login_input_methods = 414 util->GetHardwareLoginInputMethodIds(); 415 416 manager->GetActiveIMEState()->EnableLoginLayouts( 417 locale, hardware_login_input_methods); 418 419 scoped_ptr<input_method::InputMethodDescriptors> input_methods( 420 manager->GetActiveIMEState()->GetActiveInputMethods()); 421 std::set<std::string> input_methods_added; 422 423 for (std::vector<std::string>::const_iterator i = 424 hardware_login_input_methods.begin(); 425 i != hardware_login_input_methods.end(); 426 ++i) { 427 const input_method::InputMethodDescriptor* ime = 428 util->GetInputMethodDescriptorFromId(*i); 429 // Do not crash in case of misconfiguration. 430 if (ime) { 431 input_methods_added.insert(*i); 432 input_methods_list->Append( 433 CreateInputMethodsEntry(*ime, selected).release()); 434 } else { 435 NOTREACHED(); 436 } 437 } 438 439 bool optgroup_added = false; 440 for (size_t i = 0; i < input_methods->size(); ++i) { 441 // Makes sure the id is in legacy xkb id format. 442 const std::string& ime_id = (*input_methods)[i].id(); 443 if (!InsertString(ime_id, &input_methods_added)) 444 continue; 445 if (!optgroup_added) { 446 optgroup_added = true; 447 AddOptgroupOtherLayouts(input_methods_list.get()); 448 } 449 input_methods_list->Append(CreateInputMethodsEntry((*input_methods)[i], 450 selected).release()); 451 } 452 453 // "xkb:us::eng" should always be in the list of available layouts. 454 const std::string us_keyboard_id = 455 util->GetFallbackInputMethodDescriptor().id(); 456 if (input_methods_added.find(us_keyboard_id) == input_methods_added.end()) { 457 const input_method::InputMethodDescriptor* us_eng_descriptor = 458 util->GetInputMethodDescriptorFromId(us_keyboard_id); 459 DCHECK(us_eng_descriptor); 460 if (!optgroup_added) { 461 optgroup_added = true; 462 AddOptgroupOtherLayouts(input_methods_list.get()); 463 } 464 input_methods_list->Append(CreateInputMethodsEntry(*us_eng_descriptor, 465 selected).release()); 466 } 467 #endif 468 return input_methods_list.Pass(); 469 } 470 471 void GetKeyboardLayoutsForLocale( 472 const GetKeyboardLayoutsForLocaleCallback& callback, 473 const std::string& locale) { 474 base::SequencedWorkerPool* worker_pool = 475 content::BrowserThread::GetBlockingPool(); 476 scoped_refptr<base::SequencedTaskRunner> background_task_runner = 477 worker_pool->GetSequencedTaskRunnerWithShutdownBehavior( 478 worker_pool->GetNamedSequenceToken(kSequenceToken), 479 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 480 481 // Resolve |locale| on a background thread, then continue on the current 482 // thread. 483 std::string (*get_application_locale)(const std::string&, bool) = 484 &l10n_util::GetApplicationLocale; 485 base::PostTaskAndReplyWithResult( 486 background_task_runner.get(), 487 FROM_HERE, 488 base::Bind(get_application_locale, locale, false /* set_icu_locale */), 489 base::Bind(&GetKeyboardLayoutsForResolvedLocale, callback)); 490 } 491 492 scoped_ptr<base::DictionaryValue> GetCurrentKeyboardLayout() { 493 const input_method::InputMethodDescriptor current_input_method = 494 input_method::InputMethodManager::Get() 495 ->GetActiveIMEState() 496 ->GetCurrentInputMethod(); 497 return CreateInputMethodsEntry(current_input_method, 498 current_input_method.id()); 499 } 500 501 } // namespace chromeos 502