cros_language_options_handler.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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/ui/webui/options/chromeos/cros_language_options_handler.h"
6
7#include <map>
8#include <set>
9#include <vector>
10
11#include "base/bind.h"
12#include "base/bind_helpers.h"
13#include "base/i18n/rtl.h"
14#include "base/strings/stringprintf.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/values.h"
17#include "chrome/app/chrome_command_ids.h"
18#include "chrome/browser/browser_process.h"
19#include "chrome/browser/chromeos/input_method/input_method_util.h"
20#include "chrome/browser/lifetime/application_lifetime.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/ui/browser.h"
23#include "chrome/browser/ui/browser_finder.h"
24#include "chromeos/ime/component_extension_ime_manager.h"
25#include "chromeos/ime/input_method_manager.h"
26#include "content/public/browser/navigation_controller.h"
27#include "content/public/browser/user_metrics.h"
28#include "content/public/browser/web_contents.h"
29#include "grit/chromium_strings.h"
30#include "grit/generated_resources.h"
31#include "ui/base/l10n/l10n_util.h"
32
33using content::UserMetricsAction;
34
35namespace {
36// TODO(zork): Remove this blacklist when fonts are added to Chrome OS.
37// see: crbug.com/240586
38
39const char* kLanguageBlacklist[] = {
40  "km", // Khmer language
41  "si", // Sinhala language
42};
43
44bool IsBlacklisted(const std::string& language_code) {
45  for (size_t i = 0; i < arraysize(kLanguageBlacklist); ++i) {
46    if (language_code == kLanguageBlacklist[i])
47      return true;
48  }
49  return false;
50}
51
52} // namespace
53
54namespace chromeos {
55namespace options {
56
57CrosLanguageOptionsHandler::CrosLanguageOptionsHandler()
58    : composition_extension_appended_(false),
59      is_page_initialized_(false) {
60  input_method::InputMethodManager::Get()->GetComponentExtensionIMEManager()->
61      AddObserver(this);
62}
63
64CrosLanguageOptionsHandler::~CrosLanguageOptionsHandler() {
65  input_method::InputMethodManager::Get()->GetComponentExtensionIMEManager()->
66      RemoveObserver(this);
67}
68
69void CrosLanguageOptionsHandler::GetLocalizedValues(
70    DictionaryValue* localized_strings) {
71  ::options::LanguageOptionsHandlerCommon::GetLocalizedValues(
72      localized_strings);
73
74  RegisterTitle(localized_strings, "languagePage",
75                IDS_OPTIONS_SETTINGS_LANGUAGES_AND_INPUT_DIALOG_TITLE);
76  localized_strings->SetString("okButton", l10n_util::GetStringUTF16(IDS_OK));
77  localized_strings->SetString("configure",
78      l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_CONFIGURE));
79  localized_strings->SetString("inputMethod",
80      l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD));
81  localized_strings->SetString("pleaseAddAnotherInputMethod",
82      l10n_util::GetStringUTF16(
83          IDS_OPTIONS_SETTINGS_LANGUAGES_PLEASE_ADD_ANOTHER_INPUT_METHOD));
84  localized_strings->SetString("inputMethodInstructions",
85      l10n_util::GetStringUTF16(
86          IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD_INSTRUCTIONS));
87  localized_strings->SetString("switchInputMethodsHint",
88      l10n_util::GetStringUTF16(
89          IDS_OPTIONS_SETTINGS_LANGUAGES_SWITCH_INPUT_METHODS_HINT));
90  localized_strings->SetString("selectPreviousInputMethodHint",
91      l10n_util::GetStringUTF16(
92          IDS_OPTIONS_SETTINGS_LANGUAGES_SELECT_PREVIOUS_INPUT_METHOD_HINT));
93  localized_strings->SetString("restartButton",
94      l10n_util::GetStringUTF16(
95          IDS_OPTIONS_SETTINGS_LANGUAGES_SIGN_OUT_BUTTON));
96  localized_strings->SetString("extensionImeLable",
97      l10n_util::GetStringUTF16(
98          IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD_EXTENSION_IME));
99  localized_strings->SetString("extensionImeDescription",
100      l10n_util::GetStringUTF16(
101          IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD_EXTENSION_DESCRIPTION));
102  localized_strings->SetString("noInputMethods",
103      l10n_util::GetStringUTF16(
104          IDS_OPTIONS_SETTINGS_LANGUAGES_NO_INPUT_METHODS));
105
106  input_method::InputMethodManager* manager =
107      input_method::InputMethodManager::Get();
108  // GetSupportedInputMethods() never return NULL.
109  scoped_ptr<input_method::InputMethodDescriptors> descriptors(
110      manager->GetSupportedInputMethods());
111  localized_strings->Set("languageList", GetAcceptLanguageList(*descriptors));
112  localized_strings->Set("inputMethodList", GetInputMethodList(*descriptors));
113
114  input_method::InputMethodDescriptors ext_ime_descriptors;
115  manager->GetInputMethodExtensions(&ext_ime_descriptors);
116  localized_strings->Set("extensionImeList",
117                         ConvertInputMethodDescriptosToIMEList(
118                             ext_ime_descriptors));
119
120  ComponentExtensionIMEManager* component_extension_manager =
121      input_method::InputMethodManager::Get()
122          ->GetComponentExtensionIMEManager();
123  if (component_extension_manager->IsInitialized()) {
124    localized_strings->Set(
125        "componentExtensionImeList",
126        ConvertInputMethodDescriptosToIMEList(
127            component_extension_manager->GetAllIMEAsInputMethodDescriptor()));
128    composition_extension_appended_ = true;
129  } else {
130    // If component extension IME manager is not ready for use, it will be
131    // added in |InitializePage()|.
132    localized_strings->Set("componentExtensionImeList",
133                           new ListValue());
134  }
135}
136
137void CrosLanguageOptionsHandler::RegisterMessages() {
138  ::options::LanguageOptionsHandlerCommon::RegisterMessages();
139
140  web_ui()->RegisterMessageCallback("inputMethodDisable",
141      base::Bind(&CrosLanguageOptionsHandler::InputMethodDisableCallback,
142                 base::Unretained(this)));
143  web_ui()->RegisterMessageCallback("inputMethodEnable",
144      base::Bind(&CrosLanguageOptionsHandler::InputMethodEnableCallback,
145                 base::Unretained(this)));
146  web_ui()->RegisterMessageCallback("inputMethodOptionsOpen",
147      base::Bind(&CrosLanguageOptionsHandler::InputMethodOptionsOpenCallback,
148                 base::Unretained(this)));
149  web_ui()->RegisterMessageCallback("uiLanguageRestart",
150      base::Bind(&CrosLanguageOptionsHandler::RestartCallback,
151                 base::Unretained(this)));
152}
153
154ListValue* CrosLanguageOptionsHandler::GetInputMethodList(
155    const input_method::InputMethodDescriptors& descriptors) {
156  input_method::InputMethodManager* manager =
157      input_method::InputMethodManager::Get();
158
159  ListValue* input_method_list = new ListValue();
160
161  for (size_t i = 0; i < descriptors.size(); ++i) {
162    const input_method::InputMethodDescriptor& descriptor =
163        descriptors[i];
164    const std::string language_code = descriptor.language_code();
165    const std::string display_name =
166        manager->GetInputMethodUtil()->GetInputMethodDisplayNameFromId(
167            descriptor.id());
168    DictionaryValue* dictionary = new DictionaryValue();
169    dictionary->SetString("id", descriptor.id());
170    dictionary->SetString("displayName", display_name);
171
172    // One input method can be associated with multiple languages, hence
173    // we use a dictionary here.
174    DictionaryValue* language_codes = new DictionaryValue();
175    language_codes->SetBoolean(language_code, true);
176    // Check extra languages to see if there are languages associated with
177    // this input method. If these are present, add these.
178    const std::vector<std::string> extra_language_codes =
179        manager->GetInputMethodUtil()->GetExtraLanguageCodesFromId(
180            descriptor.id());
181    for (size_t j = 0; j < extra_language_codes.size(); ++j)
182      language_codes->SetBoolean(extra_language_codes[j], true);
183    dictionary->Set("languageCodeSet", language_codes);
184
185    input_method_list->Append(dictionary);
186  }
187
188  return input_method_list;
189}
190
191// static
192ListValue* CrosLanguageOptionsHandler::GetLanguageListInternal(
193    const input_method::InputMethodDescriptors& descriptors,
194    const std::vector<std::string>& base_language_codes) {
195  const std::string app_locale = g_browser_process->GetApplicationLocale();
196
197  std::set<std::string> language_codes;
198  // Collect the language codes from the supported input methods.
199  for (size_t i = 0; i < descriptors.size(); ++i) {
200    const input_method::InputMethodDescriptor& descriptor = descriptors[i];
201    const std::string language_code = descriptor.language_code();
202    language_codes.insert(language_code);
203  }
204  // Collect the language codes from extra languages.
205  const std::vector<std::string> extra_language_codes =
206      input_method::InputMethodManager::Get()->GetInputMethodUtil()
207          ->GetExtraLanguageCodeList();
208  for (size_t i = 0; i < extra_language_codes.size(); ++i)
209    language_codes.insert(extra_language_codes[i]);
210
211  // Map of display name -> {language code, native_display_name}.
212  // In theory, we should be able to create a map that is sorted by
213  // display names using ICU comparator, but doing it is hard, thus we'll
214  // use an auxiliary vector to achieve the same result.
215  typedef std::pair<std::string, string16> LanguagePair;
216  typedef std::map<string16, LanguagePair> LanguageMap;
217  LanguageMap language_map;
218  // The auxiliary vector mentioned above.
219  std::vector<string16> display_names;
220
221  // Build the list of display names, and build the language map.
222  for (std::set<std::string>::const_iterator iter = language_codes.begin();
223       iter != language_codes.end(); ++iter) {
224     // Exclude the language which is not in |base_langauge_codes| even it has
225     // input methods.
226    if (std::find(base_language_codes.begin(),
227                  base_language_codes.end(),
228                  *iter) == base_language_codes.end()) {
229      continue;
230    }
231
232    const string16 display_name =
233        l10n_util::GetDisplayNameForLocale(*iter, app_locale, true);
234    const string16 native_display_name =
235        l10n_util::GetDisplayNameForLocale(*iter, *iter, true);
236
237    display_names.push_back(display_name);
238    language_map[display_name] =
239        std::make_pair(*iter, native_display_name);
240  }
241  DCHECK_EQ(display_names.size(), language_map.size());
242
243  // Build the list of display names, and build the language map.
244  for (size_t i = 0; i < base_language_codes.size(); ++i) {
245    // Skip this language if it was already added.
246    if (language_codes.find(base_language_codes[i]) != language_codes.end())
247      continue;
248
249    // TODO(zork): Remove this blacklist when fonts are added to Chrome OS.
250    // see: crbug.com/240586
251    if (IsBlacklisted(base_language_codes[i]))
252      continue;
253
254    string16 display_name =
255        l10n_util::GetDisplayNameForLocale(
256            base_language_codes[i], app_locale, false);
257    string16 native_display_name =
258        l10n_util::GetDisplayNameForLocale(
259            base_language_codes[i], base_language_codes[i], false);
260    display_names.push_back(display_name);
261    language_map[display_name] =
262        std::make_pair(base_language_codes[i], native_display_name);
263  }
264
265  // Sort display names using locale specific sorter.
266  l10n_util::SortStrings16(app_locale, &display_names);
267
268  // Build the language list from the language map.
269  ListValue* language_list = new ListValue();
270  for (size_t i = 0; i < display_names.size(); ++i) {
271    // Sets the directionality of the display language name.
272    string16 display_name(display_names[i]);
273    bool markup_removal =
274        base::i18n::UnadjustStringForLocaleDirection(&display_name);
275    DCHECK(markup_removal);
276    bool has_rtl_chars = base::i18n::StringContainsStrongRTLChars(display_name);
277    std::string directionality = has_rtl_chars ? "rtl" : "ltr";
278
279    const LanguagePair& pair = language_map[display_names[i]];
280    DictionaryValue* dictionary = new DictionaryValue();
281    dictionary->SetString("code", pair.first);
282    dictionary->SetString("displayName", display_names[i]);
283    dictionary->SetString("textDirection", directionality);
284    dictionary->SetString("nativeDisplayName", pair.second);
285    language_list->Append(dictionary);
286  }
287
288  return language_list;
289}
290
291// static
292base::ListValue* CrosLanguageOptionsHandler::GetAcceptLanguageList(
293    const input_method::InputMethodDescriptors& descriptors) {
294  // Collect the language codes from the supported accept-languages.
295  const std::string app_locale = g_browser_process->GetApplicationLocale();
296  std::vector<std::string> accept_language_codes;
297  l10n_util::GetAcceptLanguagesForLocale(app_locale, &accept_language_codes);
298  return GetLanguageListInternal(descriptors, accept_language_codes);
299}
300
301// static
302base::ListValue* CrosLanguageOptionsHandler::GetUILanguageList(
303    const input_method::InputMethodDescriptors& descriptors) {
304  // Collect the language codes from the available locales.
305  return GetLanguageListInternal(descriptors, l10n_util::GetAvailableLocales());
306}
307
308base::ListValue*
309    CrosLanguageOptionsHandler::ConvertInputMethodDescriptosToIMEList(
310        const input_method::InputMethodDescriptors& descriptors) {
311  scoped_ptr<ListValue> ime_ids_list(new ListValue());
312  for (size_t i = 0; i < descriptors.size(); ++i) {
313    const input_method::InputMethodDescriptor& descriptor = descriptors[i];
314    scoped_ptr<DictionaryValue> dictionary(new DictionaryValue());
315    dictionary->SetString("id", descriptor.id());
316    dictionary->SetString("displayName", descriptor.name());
317    dictionary->SetString("optionsPage", descriptor.options_page_url().spec());
318    scoped_ptr<DictionaryValue> language_codes(new DictionaryValue());
319    language_codes->SetBoolean(descriptor.language_code(), true);
320    dictionary->Set("languageCodeSet", language_codes.release());
321    ime_ids_list->Append(dictionary.release());
322  }
323  return ime_ids_list.release();
324}
325
326string16 CrosLanguageOptionsHandler::GetProductName() {
327  return l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_OS_NAME);
328}
329
330void CrosLanguageOptionsHandler::SetApplicationLocale(
331    const std::string& language_code) {
332  Profile::FromWebUI(web_ui())->ChangeAppLocale(
333      language_code, Profile::APP_LOCALE_CHANGED_VIA_SETTINGS);
334}
335
336void CrosLanguageOptionsHandler::RestartCallback(const ListValue* args) {
337  content::RecordAction(UserMetricsAction("LanguageOptions_SignOut"));
338  chrome::AttemptUserExit();
339}
340
341void CrosLanguageOptionsHandler::InputMethodDisableCallback(
342    const ListValue* args) {
343  const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args));
344  const std::string action = base::StringPrintf(
345      "LanguageOptions_DisableInputMethod_%s", input_method_id.c_str());
346  content::RecordComputedAction(action);
347}
348
349void CrosLanguageOptionsHandler::InputMethodEnableCallback(
350    const ListValue* args) {
351  const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args));
352  const std::string action = base::StringPrintf(
353      "LanguageOptions_EnableInputMethod_%s", input_method_id.c_str());
354  content::RecordComputedAction(action);
355}
356
357void CrosLanguageOptionsHandler::InputMethodOptionsOpenCallback(
358    const ListValue* args) {
359  const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args));
360  const std::string action = base::StringPrintf(
361      "InputMethodOptions_Open_%s", input_method_id.c_str());
362  content::RecordComputedAction(action);
363}
364
365void CrosLanguageOptionsHandler::OnInitialized() {
366  if (composition_extension_appended_ || !is_page_initialized_) {
367    // If an option page is not ready to call JavaScript, appending component
368    // extension IMEs will be done in InitializePage function later.
369    return;
370  }
371
372  ComponentExtensionIMEManager* manager =
373      input_method::InputMethodManager::Get()
374          ->GetComponentExtensionIMEManager();
375
376  DCHECK(manager->IsInitialized());
377  scoped_ptr<ListValue> ime_list(
378      ConvertInputMethodDescriptosToIMEList(
379          manager->GetAllIMEAsInputMethodDescriptor()));
380  web_ui()->CallJavascriptFunction(
381      "options.LanguageOptions.onComponentManagerInitialized",
382      *ime_list);
383  composition_extension_appended_ = true;
384}
385
386void CrosLanguageOptionsHandler::InitializePage() {
387  is_page_initialized_ = true;
388  if (composition_extension_appended_)
389    return;
390
391  ComponentExtensionIMEManager* component_extension_manager =
392      input_method::InputMethodManager::Get()
393          ->GetComponentExtensionIMEManager();
394  if (!component_extension_manager->IsInitialized()) {
395    // If the component extension IME manager is not available yet, append the
396    // component extension list in |OnInitialized()|.
397    return;
398  }
399
400  scoped_ptr<ListValue> ime_list(
401      ConvertInputMethodDescriptosToIMEList(
402          component_extension_manager->GetAllIMEAsInputMethodDescriptor()));
403  web_ui()->CallJavascriptFunction(
404      "options.LanguageOptions.onComponentManagerInitialized",
405      *ime_list);
406  composition_extension_appended_ = true;
407}
408
409}  // namespace options
410}  // namespace chromeos
411