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