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