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