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