1 // Copyright (c) 2012 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_util.h" 6 7 #include <algorithm> 8 #include <functional> 9 #include <map> 10 #include <utility> 11 12 #include "base/basictypes.h" 13 #include "base/memory/scoped_ptr.h" 14 #include "base/prefs/pref_service.h" 15 #include "base/strings/string_split.h" 16 #include "base/strings/string_util.h" 17 #include "base/strings/utf_string_conversions.h" 18 #include "chrome/common/extensions/extension_constants.h" 19 #include "chromeos/ime/component_extension_ime_manager.h" 20 #include "chromeos/ime/extension_ime_util.h" 21 // For SetHardwareKeyboardLayoutForTesting. 22 #include "chromeos/ime/fake_input_method_delegate.h" 23 #include "chromeos/ime/input_method_delegate.h" 24 #include "chromeos/ime/input_method_whitelist.h" 25 // TODO(nona): move this header from this file. 26 #include "grit/generated_resources.h" 27 28 namespace { 29 30 // A mapping from an input method id to a string for the language indicator. The 31 // mapping is necessary since some input methods belong to the same language. 32 // For example, both "xkb:us::eng" and "xkb:us:dvorak:eng" are for US English. 33 const struct { 34 const char* engine_id; 35 const char* indicator_text; 36 } kMappingFromIdToIndicatorText[] = { 37 // To distinguish from "xkb:jp::jpn" 38 // TODO(nona): Make following variables configurable. http://crbug.com/232260. 39 { "nacl_mozc_us", "\xe3\x81\x82" }, 40 { "nacl_mozc_jp", "\xe3\x81\x82" }, 41 // For simplified Chinese input methods 42 { "zh-t-i0-pinyin", "\xe6\x8b\xbc" }, // U+62FC 43 { "zh-t-i0-wubi-1986", "\xe4\xba\x94" }, // U+4E94 44 // For traditional Chinese input methods 45 { "zh-hant-t-i0-und", "\xE6\xB3\xA8" }, // U+9177 46 { "zh-hant-t-i0-cangjie-1987", "\xe5\x80\x89" }, // U+5009 47 { "zh-hant-t-i0-cangjie-1987-x-m0-simplified", "\xe9\x80\x9f" }, // U+901F 48 // For Hangul input method. 49 { "hangul_2set", "\xed\x95\x9c" }, // U+D55C 50 { "hangul_3set390", "\xed\x95\x9c" }, // U+D55C 51 { "hangul_3setfinal", "\xed\x95\x9c" }, // U+D55C 52 { "hangul_3setnoshift", "\xed\x95\x9c" }, // U+D55C 53 { "hangul_romaja", "\xed\x95\x9c" }, // U+D55C 54 { extension_misc::kBrailleImeEngineId, 55 // U+2803 U+2817 U+2807 (Unicode braille patterns for the letters 'brl' in 56 // English (and many other) braille codes. 57 "\xe2\xa0\x83\xe2\xa0\x97\xe2\xa0\x87" }, 58 }; 59 60 const size_t kMappingFromIdToIndicatorTextLen = 61 ARRAYSIZE_UNSAFE(kMappingFromIdToIndicatorText); 62 63 // A mapping from an input method id to a resource id for a 64 // medium length language indicator. 65 // For those languages that want to display a slightly longer text in the 66 // "Your input method has changed to..." bubble than in the status tray. 67 // If an entry is not found in this table the short name is used. 68 const struct { 69 const char* engine_id; 70 const int resource_id; 71 } kMappingImeIdToMediumLenNameResourceId[] = { 72 { "hangul_2set", IDS_LANGUAGES_MEDIUM_LEN_NAME_KOREAN }, 73 { "hangul_3set390", IDS_LANGUAGES_MEDIUM_LEN_NAME_KOREAN }, 74 { "hangul_3setfinal", IDS_LANGUAGES_MEDIUM_LEN_NAME_KOREAN }, 75 { "hangul_3setnoshift", IDS_LANGUAGES_MEDIUM_LEN_NAME_KOREAN }, 76 { "hangul_3setromaja", IDS_LANGUAGES_MEDIUM_LEN_NAME_KOREAN }, 77 { "zh-t-i0-pinyin", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED}, 78 { "zh-t-i0-wubi-1986", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED }, 79 { "zh-hant-t-i0-und", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, 80 { "zh-hant-t-i0-cangjie-1987", 81 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, 82 { "zh-hant-t-i0-cangjie-1987-x-m0-simplified", 83 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, 84 { extension_misc::kBrailleImeEngineId, 85 IDS_LANGUAGES_MEDIUM_LEN_NAME_BRAILLE }, 86 }; 87 const size_t kMappingImeIdToMediumLenNameResourceIdLen = 88 ARRAYSIZE_UNSAFE(kMappingImeIdToMediumLenNameResourceId); 89 90 // Due to asynchronous initialization of component extension manager, 91 // GetFirstLogingInputMethodIds may miss component extension IMEs. To enable 92 // component extension IME as the first loging input method, we have to prepare 93 // component extension IME IDs. 94 const struct { 95 const char* locale; 96 const char* layout; 97 const char* engine_id; 98 } kDefaultInputMethodRecommendation[] = { 99 { "ja", "us", "nacl_mozc_us" }, 100 { "ja", "jp", "nacl_mozc_jp" }, 101 { "zh-CN", "us", "zh-t-i0-pinyin" }, 102 { "zh-TW", "us", "zh-hant-t-i0-und" }, 103 { "th", "us", "vkd_th" }, 104 { "vi", "us", "vkd_vi_tcvn" }, 105 }; 106 107 // The map from xkb layout to the indicator text. 108 // Refer to crbug.com/349829. 109 const char* const kXkbIndicators[][2] = {{"am", "AM"}, 110 {"be", "BE"}, 111 {"bg", "BG"}, 112 {"bg(phonetic)", "BG"}, 113 {"br", "BR"}, 114 {"by", "BY"}, 115 {"ca", "CA"}, 116 {"ca(eng)", "CA"}, 117 {"ca(multix)", "CA"}, 118 {"ch", "CH"}, 119 {"ch(fr)", "CH"}, 120 {"cz", "CZ"}, 121 {"cz(qwerty)", "CS"}, 122 {"de", "DE"}, 123 {"de(neo)", "NEO"}, 124 {"dk", "DK"}, 125 {"ee", "EE"}, 126 {"es", "ES"}, 127 {"es(cat)", "CAS"}, 128 {"fi", "FI"}, 129 {"fr", "FR"}, 130 {"gb(dvorak)", "DV"}, 131 {"gb(extd)", "GB"}, 132 {"ge", "GE"}, 133 {"gr", "GR"}, 134 {"hr", "HR"}, 135 {"hu", "HU"}, 136 {"il", "IL"}, 137 {"is", "IS"}, 138 {"it", "IT"}, 139 {"jp", "JA"}, 140 {"latam", "LA"}, 141 {"lt", "LT"}, 142 {"lv(apostrophe)", "LV"}, 143 {"mn", "MN"}, 144 {"no", "NO"}, 145 {"pl", "PL"}, 146 {"pt", "PT"}, 147 {"ro", "RO"}, 148 {"rs", "RS"}, 149 {"ru", "RU"}, 150 {"ru(phonetic)", "RU"}, 151 {"se", "SE"}, 152 {"si", "SI"}, 153 {"sk", "SK"}, 154 {"tr", "TR"}, 155 {"ua", "UA"}, 156 {"us", "US"}, 157 {"us(altgr-intl)", "EXTD"}, 158 {"us(colemak)", "CO"}, 159 {"us(dvorak)", "DV"}, 160 {"us(intl)", "INTL"}, }; 161 162 // The extension ID map for migration. 163 const char* const kExtensionIdMigrationMap[][2] = { 164 // Official Japanese IME extension ID. 165 {"fpfbhcjppmaeaijcidgiibchfbnhbelj", "gjaehgfemfahhmlgpdfknkhdnemmolop"}, 166 // Official M17n keyboard extension ID. 167 {"habcdindjejkmepknlhkkloncjcpcnbf", "gjaehgfemfahhmlgpdfknkhdnemmolop"}, 168 }; 169 170 const size_t kExtensionIdLen = 32; 171 172 const struct EnglishToResouceId { 173 const char* english_string_from_ibus; 174 int resource_id; 175 } kEnglishToResourceIdArray[] = { 176 // For xkb-layouts. 177 { "xkb:am:phonetic:arm", IDS_STATUSBAR_LAYOUT_ARMENIAN_PHONETIC }, 178 { "xkb:be::fra", IDS_STATUSBAR_LAYOUT_BELGIUM }, 179 { "xkb:be::ger", IDS_STATUSBAR_LAYOUT_BELGIUM }, 180 { "xkb:be::nld", IDS_STATUSBAR_LAYOUT_BELGIUM }, 181 { "xkb:bg::bul", IDS_STATUSBAR_LAYOUT_BULGARIA }, 182 { "xkb:bg:phonetic:bul", IDS_STATUSBAR_LAYOUT_BULGARIA_PHONETIC }, 183 { "xkb:br::por", IDS_STATUSBAR_LAYOUT_BRAZIL }, 184 { "xkb:by::bel", IDS_STATUSBAR_LAYOUT_BELARUSIAN }, 185 { "xkb:ca::fra", IDS_STATUSBAR_LAYOUT_CANADA }, 186 { "xkb:ca:eng:eng", IDS_STATUSBAR_LAYOUT_CANADA_ENGLISH }, 187 { "xkb:ca:multix:fra", IDS_STATUSBAR_LAYOUT_CANADIAN_MULTILINGUAL }, 188 { "xkb:ch::ger", IDS_STATUSBAR_LAYOUT_SWITZERLAND }, 189 { "xkb:ch:fr:fra", IDS_STATUSBAR_LAYOUT_SWITZERLAND_FRENCH }, 190 { "xkb:cz::cze", IDS_STATUSBAR_LAYOUT_CZECHIA }, 191 { "xkb:cz:qwerty:cze", IDS_STATUSBAR_LAYOUT_CZECHIA_QWERTY }, 192 { "xkb:de::ger", IDS_STATUSBAR_LAYOUT_GERMANY }, 193 { "xkb:de:neo:ger", IDS_STATUSBAR_LAYOUT_GERMANY_NEO2 }, 194 { "xkb:dk::dan", IDS_STATUSBAR_LAYOUT_DENMARK }, 195 { "xkb:ee::est", IDS_STATUSBAR_LAYOUT_ESTONIA }, 196 { "xkb:es::spa", IDS_STATUSBAR_LAYOUT_SPAIN }, 197 { "xkb:es:cat:cat", IDS_STATUSBAR_LAYOUT_SPAIN_CATALAN }, 198 { "xkb:fi::fin", IDS_STATUSBAR_LAYOUT_FINLAND }, 199 { "xkb:fr::fra", IDS_STATUSBAR_LAYOUT_FRANCE }, 200 { "xkb:gb:dvorak:eng", IDS_STATUSBAR_LAYOUT_UNITED_KINGDOM_DVORAK }, 201 { "xkb:gb:extd:eng", IDS_STATUSBAR_LAYOUT_UNITED_KINGDOM }, 202 { "xkb:ge::geo", IDS_STATUSBAR_LAYOUT_GEORGIAN }, 203 { "xkb:gr::gre", IDS_STATUSBAR_LAYOUT_GREECE }, 204 { "xkb:hr::scr", IDS_STATUSBAR_LAYOUT_CROATIA }, 205 { "xkb:hu::hun", IDS_STATUSBAR_LAYOUT_HUNGARY }, 206 { "xkb:ie::ga", IDS_STATUSBAR_LAYOUT_IRISH }, 207 { "xkb:il::heb", IDS_STATUSBAR_LAYOUT_ISRAEL }, 208 { "xkb:is::ice", IDS_STATUSBAR_LAYOUT_ICELANDIC }, 209 { "xkb:it::ita", IDS_STATUSBAR_LAYOUT_ITALY }, 210 { "xkb:jp::jpn", IDS_STATUSBAR_LAYOUT_JAPAN }, 211 { "xkb:latam::spa", IDS_STATUSBAR_LAYOUT_LATIN_AMERICAN }, 212 { "xkb:lt::lit", IDS_STATUSBAR_LAYOUT_LITHUANIA }, 213 { "xkb:lv:apostrophe:lav", IDS_STATUSBAR_LAYOUT_LATVIA }, 214 { "xkb:mn::mon", IDS_STATUSBAR_LAYOUT_MONGOLIAN }, 215 { "xkb:nl::nld", IDS_STATUSBAR_LAYOUT_NETHERLANDS }, 216 { "xkb:no::nob", IDS_STATUSBAR_LAYOUT_NORWAY }, 217 { "xkb:pl::pol", IDS_STATUSBAR_LAYOUT_POLAND }, 218 { "xkb:pt::por", IDS_STATUSBAR_LAYOUT_PORTUGAL }, 219 { "xkb:ro::rum", IDS_STATUSBAR_LAYOUT_ROMANIA }, 220 { "xkb:rs::srp", IDS_STATUSBAR_LAYOUT_SERBIA }, 221 { "xkb:ru::rus", IDS_STATUSBAR_LAYOUT_RUSSIA }, 222 { "xkb:ru:phonetic:rus", IDS_STATUSBAR_LAYOUT_RUSSIA_PHONETIC }, 223 { "xkb:se::swe", IDS_STATUSBAR_LAYOUT_SWEDEN }, 224 { "xkb:si::slv", IDS_STATUSBAR_LAYOUT_SLOVENIA }, 225 { "xkb:sk::slo", IDS_STATUSBAR_LAYOUT_SLOVAKIA }, 226 { "xkb:tr::tur", IDS_STATUSBAR_LAYOUT_TURKEY }, 227 { "xkb:ua::ukr", IDS_STATUSBAR_LAYOUT_UKRAINE }, 228 { "xkb:us::eng", IDS_STATUSBAR_LAYOUT_USA }, 229 { "xkb:us::fil", IDS_STATUSBAR_LAYOUT_USA }, 230 { "xkb:us::ind", IDS_STATUSBAR_LAYOUT_USA }, 231 { "xkb:us::msa", IDS_STATUSBAR_LAYOUT_USA }, 232 { "xkb:us:altgr-intl:eng", IDS_STATUSBAR_LAYOUT_USA_EXTENDED }, 233 { "xkb:us:colemak:eng", IDS_STATUSBAR_LAYOUT_USA_COLEMAK }, 234 { "xkb:us:dvorak:eng", IDS_STATUSBAR_LAYOUT_USA_DVORAK }, 235 { "xkb:us:intl:eng", IDS_STATUSBAR_LAYOUT_USA_INTERNATIONAL }, 236 { "xkb:us:intl:nld", IDS_STATUSBAR_LAYOUT_USA_INTERNATIONAL }, 237 { "xkb:us:intl:por", IDS_STATUSBAR_LAYOUT_USA_INTERNATIONAL }, 238 }; 239 const size_t kEnglishToResourceIdArraySize = 240 arraysize(kEnglishToResourceIdArray); 241 242 } // namespace 243 244 namespace chromeos { 245 246 namespace input_method { 247 248 InputMethodUtil::InputMethodUtil( 249 InputMethodDelegate* delegate, 250 scoped_ptr<InputMethodDescriptors> supported_input_methods) 251 : delegate_(delegate) { 252 // Makes sure the supported input methods at least have the fallback ime. 253 // So that it won't cause massive test failures. 254 if (supported_input_methods->empty()) 255 supported_input_methods->push_back(GetFallbackInputMethodDescriptor()); 256 257 ResetInputMethods(*supported_input_methods); 258 259 // Initialize a map from English string to Chrome string resource ID as well. 260 for (size_t i = 0; i < kEnglishToResourceIdArraySize; ++i) { 261 const EnglishToResouceId& map_entry = kEnglishToResourceIdArray[i]; 262 const bool result = english_to_resource_id_.insert(std::make_pair( 263 map_entry.english_string_from_ibus, map_entry.resource_id)).second; 264 DCHECK(result) << "Duplicated string is found: " 265 << map_entry.english_string_from_ibus; 266 } 267 268 // Initialize the map from xkb layout to indicator text. 269 for (size_t i = 0; i < arraysize(kXkbIndicators); ++i) { 270 xkb_layout_to_indicator_[kXkbIndicators[i][0]] = kXkbIndicators[i][1]; 271 } 272 } 273 274 InputMethodUtil::~InputMethodUtil() { 275 } 276 277 bool InputMethodUtil::TranslateStringInternal( 278 const std::string& english_string, base::string16 *out_string) const { 279 DCHECK(out_string); 280 // |english_string| could be an input method id. So legacy xkb id is required 281 // to get the translated string. 282 std::string key_string = extension_ime_util::MaybeGetLegacyXkbId( 283 english_string); 284 HashType::const_iterator iter = english_to_resource_id_.find(key_string); 285 286 if (iter == english_to_resource_id_.end()) { 287 // TODO(yusukes): Write Autotest which checks if all display names and all 288 // property names for supported input methods are listed in the resource 289 // ID array (crosbug.com/4572). 290 LOG(ERROR) << "Resource ID is not found for: " << english_string 291 << ", " << key_string; 292 return false; 293 } 294 295 *out_string = delegate_->GetLocalizedString(iter->second); 296 return true; 297 } 298 299 base::string16 InputMethodUtil::TranslateString( 300 const std::string& english_string) const { 301 base::string16 localized_string; 302 if (TranslateStringInternal(english_string, &localized_string)) { 303 return localized_string; 304 } 305 return base::UTF8ToUTF16(english_string); 306 } 307 308 bool InputMethodUtil::IsValidInputMethodId( 309 const std::string& input_method_id) const { 310 // We can't check the component extension is whilelisted or not here because 311 // it might not be initialized. 312 return GetInputMethodDescriptorFromId(input_method_id) != NULL || 313 extension_ime_util::IsComponentExtensionIME(input_method_id); 314 } 315 316 // static 317 bool InputMethodUtil::IsKeyboardLayout(const std::string& input_method_id) { 318 return StartsWithASCII(input_method_id, "xkb:", false) || 319 extension_ime_util::IsKeyboardLayoutExtension(input_method_id); 320 } 321 322 std::string InputMethodUtil::GetKeyboardLayoutName( 323 const std::string& input_method_id) const { 324 InputMethodIdToDescriptorMap::const_iterator iter 325 = id_to_descriptor_.find(input_method_id); 326 return (iter == id_to_descriptor_.end()) ? 327 "" : iter->second.GetPreferredKeyboardLayout(); 328 } 329 330 std::string InputMethodUtil::GetInputMethodDisplayNameFromId( 331 const std::string& input_method_id) const { 332 base::string16 display_name; 333 if (!extension_ime_util::IsExtensionIME(input_method_id) && 334 TranslateStringInternal(input_method_id, &display_name)) { 335 return base::UTF16ToUTF8(display_name); 336 } 337 // Return an empty string if the display name is not found. 338 return ""; 339 } 340 341 base::string16 InputMethodUtil::GetInputMethodShortName( 342 const InputMethodDescriptor& input_method) const { 343 // For the status area, we use two-letter, upper-case language code like 344 // "US" and "JP". 345 346 // Use the indicator string if set. 347 if (!input_method.indicator().empty()) { 348 return base::UTF8ToUTF16(input_method.indicator()); 349 } 350 351 base::string16 text; 352 // Check special cases first. 353 for (size_t i = 0; i < kMappingFromIdToIndicatorTextLen; ++i) { 354 if (extension_ime_util::GetInputMethodIDByEngineID( 355 kMappingFromIdToIndicatorText[i].engine_id) == input_method.id()) { 356 text = base::UTF8ToUTF16(kMappingFromIdToIndicatorText[i].indicator_text); 357 break; 358 } 359 } 360 361 // Display the keyboard layout name when using a keyboard layout. 362 if (text.empty() && IsKeyboardLayout(input_method.id())) { 363 std::map<std::string, std::string>::const_iterator it = 364 xkb_layout_to_indicator_.find(GetKeyboardLayoutName(input_method.id())); 365 if (it != xkb_layout_to_indicator_.end()) 366 text = base::UTF8ToUTF16(it->second); 367 } 368 369 // TODO(yusukes): Some languages have two or more input methods. For example, 370 // Thai has 3, Vietnamese has 4. If these input methods could be activated at 371 // the same time, we should do either of the following: 372 // (1) Add mappings to |kMappingFromIdToIndicatorText| 373 // (2) Add suffix (1, 2, ...) to |text| when ambiguous. 374 375 if (text.empty()) { 376 const size_t kMaxLanguageNameLen = 2; 377 DCHECK(!input_method.language_codes().empty()); 378 const std::string language_code = input_method.language_codes().at(0); 379 text = StringToUpperASCII(base::UTF8ToUTF16(language_code)).substr( 380 0, kMaxLanguageNameLen); 381 } 382 DCHECK(!text.empty()) << input_method.id(); 383 return text; 384 } 385 386 base::string16 InputMethodUtil::GetInputMethodMediumName( 387 const InputMethodDescriptor& input_method) const { 388 // For the "Your input method has changed to..." bubble. In most cases 389 // it uses the same name as the short name, unless found in a table 390 // for medium length names. 391 for (size_t i = 0; i < kMappingImeIdToMediumLenNameResourceIdLen; ++i) { 392 if (extension_ime_util::GetInputMethodIDByEngineID( 393 kMappingImeIdToMediumLenNameResourceId[i].engine_id) == 394 input_method.id()) { 395 return delegate_->GetLocalizedString( 396 kMappingImeIdToMediumLenNameResourceId[i].resource_id); 397 } 398 } 399 return GetInputMethodShortName(input_method); 400 } 401 402 base::string16 InputMethodUtil::GetInputMethodLongName( 403 const InputMethodDescriptor& input_method) const { 404 if (!input_method.name().empty() && !IsKeyboardLayout(input_method.id())) { 405 // If the descriptor has a name, use it. 406 return base::UTF8ToUTF16(input_method.name()); 407 } 408 409 // We don't show language here. Name of keyboard layout or input method 410 // usually imply (or explicitly include) its language. 411 412 // Special case for German, French and Dutch: these languages have multiple 413 // keyboard layouts and share the same layout of keyboard (Belgian). We need 414 // to show explicitly the language for the layout. For Arabic, Amharic, and 415 // Indic languages: they share "Standard Input Method". 416 const base::string16 standard_input_method_text = 417 delegate_->GetLocalizedString( 418 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD); 419 DCHECK(!input_method.language_codes().empty()); 420 const std::string language_code = input_method.language_codes().at(0); 421 422 base::string16 text = TranslateString(input_method.id()); 423 if (text == standard_input_method_text || 424 language_code == "de" || 425 language_code == "fr" || 426 language_code == "nl") { 427 const base::string16 language_name = delegate_->GetDisplayLanguageName( 428 language_code); 429 430 text = language_name + base::UTF8ToUTF16(" - ") + text; 431 } 432 433 DCHECK(!text.empty()); 434 return text; 435 } 436 437 const InputMethodDescriptor* InputMethodUtil::GetInputMethodDescriptorFromId( 438 const std::string& input_method_id) const { 439 InputMethodIdToDescriptorMap::const_iterator iter = 440 id_to_descriptor_.find(input_method_id); 441 if (iter == id_to_descriptor_.end()) 442 return NULL; 443 return &(iter->second); 444 } 445 446 bool InputMethodUtil::GetInputMethodIdsFromLanguageCode( 447 const std::string& normalized_language_code, 448 InputMethodType type, 449 std::vector<std::string>* out_input_method_ids) const { 450 return GetInputMethodIdsFromLanguageCodeInternal( 451 language_code_to_ids_, 452 normalized_language_code, type, out_input_method_ids); 453 } 454 455 bool InputMethodUtil::GetInputMethodIdsFromLanguageCodeInternal( 456 const std::multimap<std::string, std::string>& language_code_to_ids, 457 const std::string& normalized_language_code, 458 InputMethodType type, 459 std::vector<std::string>* out_input_method_ids) const { 460 DCHECK(out_input_method_ids); 461 out_input_method_ids->clear(); 462 463 bool result = false; 464 std::pair<LanguageCodeToIdsMap::const_iterator, 465 LanguageCodeToIdsMap::const_iterator> range = 466 language_code_to_ids.equal_range(normalized_language_code); 467 for (LanguageCodeToIdsMap::const_iterator iter = range.first; 468 iter != range.second; ++iter) { 469 const std::string& input_method_id = iter->second; 470 if ((type == kAllInputMethods) || IsKeyboardLayout(input_method_id)) { 471 out_input_method_ids->push_back(input_method_id); 472 result = true; 473 } 474 } 475 if ((type == kAllInputMethods) && !result) { 476 DVLOG(1) << "Unknown language code: " << normalized_language_code; 477 } 478 return result; 479 } 480 481 void InputMethodUtil::GetFirstLoginInputMethodIds( 482 const std::string& language_code, 483 const InputMethodDescriptor& current_input_method, 484 std::vector<std::string>* out_input_method_ids) const { 485 out_input_method_ids->clear(); 486 487 // First, add the current keyboard layout (one used on the login screen). 488 out_input_method_ids->push_back(current_input_method.id()); 489 490 const std::string current_layout 491 = current_input_method.GetPreferredKeyboardLayout(); 492 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kDefaultInputMethodRecommendation); 493 ++i) { 494 if (kDefaultInputMethodRecommendation[i].locale == language_code && 495 kDefaultInputMethodRecommendation[i].layout == current_layout) { 496 out_input_method_ids->push_back( 497 extension_ime_util::GetInputMethodIDByEngineID( 498 kDefaultInputMethodRecommendation[i].engine_id)); 499 return; 500 } 501 } 502 503 // Second, find the most popular input method associated with the 504 // current UI language. The input method IDs returned from 505 // GetInputMethodIdsFromLanguageCode() are sorted by popularity, hence 506 // our basic strategy is to pick the first one, but it's a bit more 507 // complicated as shown below. 508 std::string most_popular_id; 509 std::vector<std::string> input_method_ids; 510 // This returns the input methods sorted by popularity. 511 GetInputMethodIdsFromLanguageCode( 512 language_code, kAllInputMethods, &input_method_ids); 513 for (size_t i = 0; i < input_method_ids.size(); ++i) { 514 const std::string& input_method_id = input_method_ids[i]; 515 // Pick the first one. 516 if (most_popular_id.empty()) 517 most_popular_id = input_method_id; 518 519 // Check if there is one that matches the current keyboard layout, but 520 // not the current keyboard itself. This is useful if there are 521 // multiple keyboard layout choices for one input method. For 522 // instance, Mozc provides three choices: mozc (US keyboard), mozc-jp 523 // (JP keyboard), mozc-dv (Dvorak). 524 const InputMethodDescriptor* descriptor = 525 GetInputMethodDescriptorFromId(input_method_id); 526 if (descriptor && 527 descriptor->id() != current_input_method.id() && 528 descriptor->GetPreferredKeyboardLayout() == 529 current_input_method.GetPreferredKeyboardLayout()) { 530 most_popular_id = input_method_id; 531 break; 532 } 533 } 534 // Add the most popular input method ID, if it's different from the 535 // current input method. 536 if (most_popular_id != current_input_method.id()) { 537 out_input_method_ids->push_back(most_popular_id); 538 } 539 } 540 541 void InputMethodUtil::GetLanguageCodesFromInputMethodIds( 542 const std::vector<std::string>& input_method_ids, 543 std::vector<std::string>* out_language_codes) const { 544 out_language_codes->clear(); 545 546 for (size_t i = 0; i < input_method_ids.size(); ++i) { 547 const std::string& input_method_id = input_method_ids[i]; 548 const InputMethodDescriptor* input_method = 549 GetInputMethodDescriptorFromId(input_method_id); 550 if (!input_method) { 551 DVLOG(1) << "Unknown input method ID: " << input_method_ids[i]; 552 continue; 553 } 554 DCHECK(!input_method->language_codes().empty()); 555 const std::string language_code = input_method->language_codes().at(0); 556 // Add it if it's not already present. 557 if (std::count(out_language_codes->begin(), out_language_codes->end(), 558 language_code) == 0) { 559 out_language_codes->push_back(language_code); 560 } 561 } 562 } 563 564 std::string InputMethodUtil::GetLanguageDefaultInputMethodId( 565 const std::string& language_code) { 566 std::vector<std::string> candidates; 567 GetInputMethodIdsFromLanguageCode( 568 language_code, input_method::kKeyboardLayoutsOnly, &candidates); 569 if (candidates.size()) 570 return candidates.front(); 571 572 return std::string(); 573 } 574 575 bool InputMethodUtil::MigrateInputMethods( 576 std::vector<std::string>* input_method_ids) { 577 bool rewritten = false; 578 std::vector<std::string>& ids = *input_method_ids; 579 for (size_t i = 0; i < ids.size(); ++i) { 580 std::string id = 581 extension_ime_util::GetInputMethodIDByEngineID(ids[i]); 582 // Migrates old ime id's to new ones. 583 for (size_t j = 0; j < arraysize(kExtensionIdMigrationMap); ++j) { 584 size_t pos = id.find(kExtensionIdMigrationMap[j][0]); 585 if (pos != std::string::npos) 586 id.replace(pos, kExtensionIdLen, kExtensionIdMigrationMap[j][1]); 587 if (id != ids[i]) { 588 ids[i] = id; 589 rewritten = true; 590 } 591 } 592 } 593 if (rewritten) { 594 // Removes the duplicates. 595 std::vector<std::string> new_ids; 596 for (size_t i = 0; i < ids.size(); ++i) { 597 if (std::find(new_ids.begin(), new_ids.end(), ids[i]) == new_ids.end()) 598 new_ids.push_back(ids[i]); 599 } 600 ids.swap(new_ids); 601 } 602 return rewritten; 603 } 604 605 void InputMethodUtil::UpdateHardwareLayoutCache() { 606 DCHECK(thread_checker_.CalledOnValidThread()); 607 hardware_layouts_.clear(); 608 hardware_login_layouts_.clear(); 609 if (cached_hardware_layouts_.empty()) 610 Tokenize(delegate_->GetHardwareKeyboardLayouts(), ",", 611 &cached_hardware_layouts_); 612 hardware_layouts_ = cached_hardware_layouts_; 613 MigrateInputMethods(&hardware_layouts_); 614 615 for (size_t i = 0; i < hardware_layouts_.size(); ++i) { 616 if (IsLoginKeyboard(hardware_layouts_[i])) 617 hardware_login_layouts_.push_back(hardware_layouts_[i]); 618 } 619 if (hardware_layouts_.empty()) { 620 // This is totally fine if it's empty. The hardware keyboard layout is 621 // not stored if startup_manifest.json (OEM customization data) is not 622 // present (ex. Cr48 doen't have that file). 623 hardware_layouts_.push_back(GetFallbackInputMethodDescriptor().id()); 624 } 625 626 if (hardware_login_layouts_.empty()) 627 hardware_login_layouts_.push_back(GetFallbackInputMethodDescriptor().id()); 628 } 629 630 void InputMethodUtil::SetHardwareKeyboardLayoutForTesting( 631 const std::string& layout) { 632 delegate_->SetHardwareKeyboardLayoutForTesting(layout); 633 cached_hardware_layouts_.clear(); 634 UpdateHardwareLayoutCache(); 635 } 636 637 const std::vector<std::string>& 638 InputMethodUtil::GetHardwareInputMethodIds() { 639 DCHECK(thread_checker_.CalledOnValidThread()); 640 UpdateHardwareLayoutCache(); 641 return hardware_layouts_; 642 } 643 644 const std::vector<std::string>& 645 InputMethodUtil::GetHardwareLoginInputMethodIds() { 646 DCHECK(thread_checker_.CalledOnValidThread()); 647 UpdateHardwareLayoutCache(); 648 return hardware_login_layouts_; 649 } 650 651 bool InputMethodUtil::IsLoginKeyboard(const std::string& input_method_id) 652 const { 653 const InputMethodDescriptor* ime = 654 GetInputMethodDescriptorFromId(input_method_id); 655 return ime ? ime->is_login_keyboard() : false; 656 } 657 658 void InputMethodUtil::AppendInputMethods(const InputMethodDescriptors& imes) { 659 for (size_t i = 0; i < imes.size(); ++i) { 660 const InputMethodDescriptor& input_method = imes[i]; 661 DCHECK(!input_method.language_codes().empty()); 662 const std::vector<std::string>& language_codes = 663 input_method.language_codes(); 664 id_to_descriptor_[input_method.id()] = input_method; 665 666 typedef LanguageCodeToIdsMap::const_iterator It; 667 for (size_t j = 0; j < language_codes.size(); ++j) { 668 std::pair<It, It> range = 669 language_code_to_ids_.equal_range(language_codes[j]); 670 It it = range.first; 671 for (; it != range.second; ++it) { 672 if (it->second == input_method.id()) 673 break; 674 } 675 if (it == range.second) 676 language_code_to_ids_.insert( 677 std::make_pair(language_codes[j], input_method.id())); 678 } 679 } 680 } 681 682 void InputMethodUtil::ResetInputMethods(const InputMethodDescriptors& imes) { 683 // Clear the existing maps. 684 language_code_to_ids_.clear(); 685 id_to_descriptor_.clear(); 686 687 AppendInputMethods(imes); 688 } 689 690 void InputMethodUtil::InitXkbInputMethodsForTesting() { 691 cached_hardware_layouts_.clear(); 692 ResetInputMethods(*(InputMethodWhitelist().GetSupportedInputMethods())); 693 } 694 695 const InputMethodUtil::InputMethodIdToDescriptorMap& 696 InputMethodUtil::GetIdToDesciptorMapForTesting() { 697 return id_to_descriptor_; 698 } 699 700 InputMethodDescriptor InputMethodUtil::GetFallbackInputMethodDescriptor() { 701 std::vector<std::string> layouts; 702 layouts.push_back("us"); 703 std::vector<std::string> languages; 704 languages.push_back("en-US"); 705 return InputMethodDescriptor( 706 extension_ime_util::GetInputMethodIDByEngineID("xkb:us::eng"), 707 "", 708 "US", 709 layouts, 710 languages, 711 true, // login keyboard. 712 GURL(), // options page, not available. 713 GURL()); // input view page, not available. 714 } 715 716 } // namespace input_method 717 } // namespace chromeos 718