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