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