Upstream version 8.37.180.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / webui / options / chromeos / cros_language_options_handler.cc
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 <algorithm>
8 #include <iterator>
9 #include <map>
10 #include <set>
11 #include <vector>
12
13 #include "base/bind.h"
14 #include "base/bind_helpers.h"
15 #include "base/i18n/rtl.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/values.h"
19 #include "chrome/app/chrome_command_ids.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chromeos/customization_document.h"
22 #include "chrome/browser/chromeos/input_method/input_method_util.h"
23 #include "chrome/browser/chromeos/login/users/user_manager.h"
24 #include "chrome/browser/extensions/extension_service.h"
25 #include "chrome/browser/extensions/extension_tab_util.h"
26 #include "chrome/browser/lifetime/application_lifetime.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/ui/browser.h"
29 #include "chrome/browser/ui/browser_finder.h"
30 #include "chrome/browser/ui/browser_window.h"
31 #include "chrome/browser/ui/tabs/tab_strip_model.h"
32 #include "chrome/common/extensions/manifest_url_handler.h"
33 #include "chromeos/ime/component_extension_ime_manager.h"
34 #include "chromeos/ime/extension_ime_util.h"
35 #include "chromeos/ime/input_method_manager.h"
36 #include "content/public/browser/navigation_controller.h"
37 #include "content/public/browser/user_metrics.h"
38 #include "content/public/browser/web_contents.h"
39 #include "extensions/browser/extension_system.h"
40 #include "extensions/common/extension.h"
41 #include "grit/chromium_strings.h"
42 #include "grit/generated_resources.h"
43 #include "ui/base/l10n/l10n_util.h"
44
45 using base::UserMetricsAction;
46
47 namespace {
48 // TODO(zork): Remove this blacklist when fonts are added to Chrome OS.
49 // see: crbug.com/240586
50
51 bool IsBlacklisted(const std::string& language_code) {
52   return language_code == "si"; // Sinhala
53 }
54
55 } // namespace
56
57 namespace chromeos {
58 namespace options {
59
60 const char kVendorOtherLanguagesListDivider[] =
61     "VENDOR_OTHER_LANGUAGES_LIST_DIVIDER";
62
63 CrosLanguageOptionsHandler::CrosLanguageOptionsHandler()
64     : composition_extension_appended_(false),
65       is_page_initialized_(false) {
66   input_method::InputMethodManager::Get()->GetComponentExtensionIMEManager()->
67       AddObserver(this);
68 }
69
70 CrosLanguageOptionsHandler::~CrosLanguageOptionsHandler() {
71   input_method::InputMethodManager::Get()->GetComponentExtensionIMEManager()->
72       RemoveObserver(this);
73 }
74
75 void CrosLanguageOptionsHandler::GetLocalizedValues(
76     base::DictionaryValue* localized_strings) {
77   ::options::LanguageOptionsHandlerCommon::GetLocalizedValues(
78       localized_strings);
79
80   RegisterTitle(localized_strings, "languagePage",
81                 IDS_OPTIONS_SETTINGS_LANGUAGES_AND_INPUT_DIALOG_TITLE);
82   localized_strings->SetString("okButton", l10n_util::GetStringUTF16(IDS_OK));
83   localized_strings->SetString("configure",
84       l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_CONFIGURE));
85   localized_strings->SetString("inputMethod",
86       l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD));
87   localized_strings->SetString("pleaseAddAnotherInputMethod",
88       l10n_util::GetStringUTF16(
89           IDS_OPTIONS_SETTINGS_LANGUAGES_PLEASE_ADD_ANOTHER_INPUT_METHOD));
90   localized_strings->SetString("inputMethodInstructions",
91       l10n_util::GetStringUTF16(
92           IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD_INSTRUCTIONS));
93   localized_strings->SetString("switchInputMethodsHint",
94       l10n_util::GetStringUTF16(
95           IDS_OPTIONS_SETTINGS_LANGUAGES_SWITCH_INPUT_METHODS_HINT));
96   localized_strings->SetString("selectPreviousInputMethodHint",
97       l10n_util::GetStringUTF16(
98           IDS_OPTIONS_SETTINGS_LANGUAGES_SELECT_PREVIOUS_INPUT_METHOD_HINT));
99   localized_strings->SetString("restartButton",
100       l10n_util::GetStringUTF16(
101           IDS_OPTIONS_SETTINGS_LANGUAGES_SIGN_OUT_BUTTON));
102   localized_strings->SetString("extensionImeLable",
103       l10n_util::GetStringUTF16(
104           IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD_EXTENSION_IME));
105   localized_strings->SetString("extensionImeDescription",
106       l10n_util::GetStringUTF16(
107           IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD_EXTENSION_DESCRIPTION));
108   localized_strings->SetString("noInputMethods",
109       l10n_util::GetStringUTF16(
110           IDS_OPTIONS_SETTINGS_LANGUAGES_NO_INPUT_METHODS));
111
112   input_method::InputMethodManager* manager =
113       input_method::InputMethodManager::Get();
114   // GetSupportedInputMethods() never return NULL.
115   scoped_ptr<input_method::InputMethodDescriptors> descriptors(
116       manager->GetSupportedInputMethods());
117   localized_strings->Set("languageList", GetAcceptLanguageList(*descriptors));
118   localized_strings->Set("inputMethodList", GetInputMethodList(*descriptors));
119
120   input_method::InputMethodDescriptors ext_ime_descriptors;
121   manager->GetInputMethodExtensions(&ext_ime_descriptors);
122
123   base::ListValue* ext_ime_list = ConvertInputMethodDescriptorsToIMEList(
124       ext_ime_descriptors);
125   AddImeProvider(ext_ime_list);
126   localized_strings->Set("extensionImeList", ext_ime_list);
127
128   ComponentExtensionIMEManager* component_extension_manager =
129       input_method::InputMethodManager::Get()
130           ->GetComponentExtensionIMEManager();
131   if (component_extension_manager->IsInitialized()) {
132     localized_strings->Set(
133         "componentExtensionImeList",
134         ConvertInputMethodDescriptorsToIMEList(
135             component_extension_manager->GetAllIMEAsInputMethodDescriptor()));
136     composition_extension_appended_ = true;
137   } else {
138     // If component extension IME manager is not ready for use, it will be
139     // added in |InitializePage()|.
140     localized_strings->Set("componentExtensionImeList",
141                            new base::ListValue());
142   }
143 }
144
145 void CrosLanguageOptionsHandler::RegisterMessages() {
146   ::options::LanguageOptionsHandlerCommon::RegisterMessages();
147
148   web_ui()->RegisterMessageCallback("inputMethodDisable",
149       base::Bind(&CrosLanguageOptionsHandler::InputMethodDisableCallback,
150                  base::Unretained(this)));
151   web_ui()->RegisterMessageCallback("inputMethodEnable",
152       base::Bind(&CrosLanguageOptionsHandler::InputMethodEnableCallback,
153                  base::Unretained(this)));
154   web_ui()->RegisterMessageCallback("inputMethodOptionsOpen",
155       base::Bind(&CrosLanguageOptionsHandler::InputMethodOptionsOpenCallback,
156                  base::Unretained(this)));
157   web_ui()->RegisterMessageCallback("uiLanguageRestart",
158       base::Bind(&CrosLanguageOptionsHandler::RestartCallback,
159                  base::Unretained(this)));
160 }
161
162 // static
163 base::ListValue* CrosLanguageOptionsHandler::GetInputMethodList(
164     const input_method::InputMethodDescriptors& descriptors) {
165   input_method::InputMethodManager* manager =
166       input_method::InputMethodManager::Get();
167
168   base::ListValue* input_method_list = new base::ListValue();
169
170   for (size_t i = 0; i < descriptors.size(); ++i) {
171     const input_method::InputMethodDescriptor& descriptor =
172         descriptors[i];
173     const std::string display_name =
174         manager->GetInputMethodUtil()->GetInputMethodDisplayNameFromId(
175             descriptor.id());
176     base::DictionaryValue* dictionary = new base::DictionaryValue();
177     dictionary->SetString("id", descriptor.id());
178     dictionary->SetString("displayName", display_name);
179
180     // One input method can be associated with multiple languages, hence
181     // we use a dictionary here.
182     base::DictionaryValue* languages = new base::DictionaryValue();
183     for (size_t i = 0; i < descriptor.language_codes().size(); ++i) {
184       languages->SetBoolean(descriptor.language_codes().at(i), true);
185     }
186     dictionary->Set("languageCodeSet", languages);
187
188     input_method_list->Append(dictionary);
189   }
190
191   return input_method_list;
192 }
193
194 // static
195 base::ListValue* CrosLanguageOptionsHandler::GetLanguageListInternal(
196     const input_method::InputMethodDescriptors& descriptors,
197     const std::vector<std::string>& base_language_codes,
198     const bool insert_divider) {
199   const std::string app_locale = g_browser_process->GetApplicationLocale();
200
201   std::set<std::string> language_codes;
202   // Collect the language codes from the supported input methods.
203   for (size_t i = 0; i < descriptors.size(); ++i) {
204     const input_method::InputMethodDescriptor& descriptor = descriptors[i];
205     const std::vector<std::string>& languages =
206         descriptor.language_codes();
207     for (size_t i = 0; i < languages.size(); ++i)
208       language_codes.insert(languages[i]);
209   }
210
211   const StartupCustomizationDocument* startup_manifest =
212       StartupCustomizationDocument::GetInstance();
213
214   const std::vector<std::string>& configured_locales =
215       startup_manifest->configured_locales();
216
217   // Languages sort order.
218   std::map<std::string, int /* index */> language_index;
219   for (size_t i = 0; i < configured_locales.size(); ++i) {
220     language_index[configured_locales[i]] = i;
221   }
222
223   // Map of display name -> {language code, native_display_name}.
224   // In theory, we should be able to create a map that is sorted by
225   // display names using ICU comparator, but doing it is hard, thus we'll
226   // use an auxiliary vector to achieve the same result.
227   typedef std::pair<std::string, base::string16> LanguagePair;
228   typedef std::map<base::string16, LanguagePair> LanguageMap;
229   LanguageMap language_map;
230
231   // The auxiliary vector mentioned above. (except vendor locales)
232   std::vector<base::string16> display_names;
233
234   // Separate vector of vendor locales.
235   std::vector<base::string16> configured_locales_display_names(
236       configured_locales.size());
237
238   size_t configured_locales_count = 0;
239
240   // Build the list of display names, and build the language map.
241
242   // The list of configured locales might have entries not in
243   // base_language_codes. If there are unsupported language variants,
244   // but they resolve to backup locale within base_language_codes, also
245   // add them to the list.
246   for (std::map<std::string, int>::const_iterator iter = language_index.begin();
247        iter != language_index.end();
248        ++iter) {
249     const std::string& language_id = iter->first;
250     const int language_idx = iter->second;
251
252     const size_t dash_pos = language_id.find_first_of('-');
253
254     // Ignore non-specific codes.
255     if (dash_pos == std::string::npos || dash_pos == 0)
256       continue;
257
258     if (std::find(base_language_codes.begin(),
259                   base_language_codes.end(),
260                   language_id) != base_language_codes.end()) {
261       // Language is supported. No need to replace
262       continue;
263     }
264     std::string resolved_locale;
265     if (!l10n_util::CheckAndResolveLocale(language_id, &resolved_locale))
266       continue;
267
268     if (std::find(base_language_codes.begin(),
269                   base_language_codes.end(),
270                   resolved_locale) == base_language_codes.end()) {
271       // Resolved locale is not supported.
272       continue;
273     }
274
275     const base::string16 display_name =
276         l10n_util::GetDisplayNameForLocale(language_id, app_locale, true);
277     const base::string16 native_display_name =
278         l10n_util::GetDisplayNameForLocale(
279             language_id, language_id, true);
280
281     language_map[display_name] =
282         std::make_pair(language_id, native_display_name);
283
284     configured_locales_display_names[language_idx] = display_name;
285     ++configured_locales_count;
286   }
287
288   // Translate language codes, generated from input methods.
289   for (std::set<std::string>::const_iterator iter = language_codes.begin();
290        iter != language_codes.end(); ++iter) {
291      // Exclude the language which is not in |base_langauge_codes| even it has
292      // input methods.
293     if (std::find(base_language_codes.begin(),
294                   base_language_codes.end(),
295                   *iter) == base_language_codes.end()) {
296       continue;
297     }
298
299     const base::string16 display_name =
300         l10n_util::GetDisplayNameForLocale(*iter, app_locale, true);
301     const base::string16 native_display_name =
302         l10n_util::GetDisplayNameForLocale(*iter, *iter, true);
303
304     language_map[display_name] =
305         std::make_pair(*iter, native_display_name);
306
307     const std::map<std::string, int>::const_iterator index_pos =
308         language_index.find(*iter);
309     if (index_pos != language_index.end()) {
310       base::string16& stored_display_name =
311           configured_locales_display_names[index_pos->second];
312       if (stored_display_name.empty()) {
313         stored_display_name = display_name;
314         ++configured_locales_count;
315       }
316     } else {
317       display_names.push_back(display_name);
318     }
319   }
320   DCHECK_EQ(display_names.size() + configured_locales_count,
321             language_map.size());
322
323   // Build the list of display names, and build the language map.
324   for (size_t i = 0; i < base_language_codes.size(); ++i) {
325     // Skip this language if it was already added.
326     if (language_codes.find(base_language_codes[i]) != language_codes.end())
327       continue;
328
329     // TODO(zork): Remove this blacklist when fonts are added to Chrome OS.
330     // see: crbug.com/240586
331     if (IsBlacklisted(base_language_codes[i]))
332       continue;
333
334     base::string16 display_name =
335         l10n_util::GetDisplayNameForLocale(
336             base_language_codes[i], app_locale, false);
337     base::string16 native_display_name =
338         l10n_util::GetDisplayNameForLocale(
339             base_language_codes[i], base_language_codes[i], false);
340     language_map[display_name] =
341         std::make_pair(base_language_codes[i], native_display_name);
342
343     const std::map<std::string, int>::const_iterator index_pos =
344         language_index.find(base_language_codes[i]);
345     if (index_pos != language_index.end()) {
346       configured_locales_display_names[index_pos->second] = display_name;
347       ++configured_locales_count;
348     } else {
349       display_names.push_back(display_name);
350     }
351   }
352
353   // Sort display names using locale specific sorter.
354   l10n_util::SortStrings16(app_locale, &display_names);
355   // Concatenate configured_locales_display_names and display_names.
356   // Insert special divider in between.
357   std::vector<base::string16> out_display_names;
358   for (size_t i = 0; i < configured_locales_display_names.size(); ++i) {
359     if (configured_locales_display_names[i].size() == 0)
360       continue;
361     out_display_names.push_back(configured_locales_display_names[i]);
362   }
363
364   base::string16 divider16;
365   if (insert_divider) {
366     divider16 = base::ASCIIToUTF16(
367         insert_divider ? "" : kVendorOtherLanguagesListDivider);
368     out_display_names.push_back(divider16);
369   }
370
371   std::copy(display_names.begin(),
372             display_names.end(),
373             std::back_inserter(out_display_names));
374
375   // Build the language list from the language map.
376   base::ListValue* language_list = new base::ListValue();
377   for (size_t i = 0; i < out_display_names.size(); ++i) {
378     // Sets the directionality of the display language name.
379     base::string16 display_name(out_display_names[i]);
380     if (insert_divider && display_name == divider16) {
381       // Insert divider.
382       base::DictionaryValue* dictionary = new base::DictionaryValue();
383       dictionary->SetString("code", kVendorOtherLanguagesListDivider);
384       language_list->Append(dictionary);
385       continue;
386     }
387     bool markup_removal =
388         base::i18n::UnadjustStringForLocaleDirection(&display_name);
389     DCHECK(markup_removal);
390     bool has_rtl_chars = base::i18n::StringContainsStrongRTLChars(display_name);
391     std::string directionality = has_rtl_chars ? "rtl" : "ltr";
392
393     const LanguagePair& pair = language_map[out_display_names[i]];
394     base::DictionaryValue* dictionary = new base::DictionaryValue();
395     dictionary->SetString("code", pair.first);
396     dictionary->SetString("displayName", out_display_names[i]);
397     dictionary->SetString("textDirection", directionality);
398     dictionary->SetString("nativeDisplayName", pair.second);
399     language_list->Append(dictionary);
400   }
401
402   return language_list;
403 }
404
405 // static
406 base::ListValue* CrosLanguageOptionsHandler::GetAcceptLanguageList(
407     const input_method::InputMethodDescriptors& descriptors) {
408   // Collect the language codes from the supported accept-languages.
409   const std::string app_locale = g_browser_process->GetApplicationLocale();
410   std::vector<std::string> accept_language_codes;
411   l10n_util::GetAcceptLanguagesForLocale(app_locale, &accept_language_codes);
412   return GetLanguageListInternal(descriptors, accept_language_codes, false);
413 }
414
415 // static
416 base::ListValue* CrosLanguageOptionsHandler::GetUILanguageList(
417     const input_method::InputMethodDescriptors& descriptors) {
418   // Collect the language codes from the available locales.
419   return GetLanguageListInternal(
420       descriptors, l10n_util::GetAvailableLocales(), true);
421 }
422
423 base::ListValue*
424     CrosLanguageOptionsHandler::ConvertInputMethodDescriptorsToIMEList(
425         const input_method::InputMethodDescriptors& descriptors) {
426   scoped_ptr<base::ListValue> ime_ids_list(new base::ListValue());
427   for (size_t i = 0; i < descriptors.size(); ++i) {
428     const input_method::InputMethodDescriptor& descriptor = descriptors[i];
429     scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue());
430     dictionary->SetString("id", descriptor.id());
431     dictionary->SetString("displayName", descriptor.name());
432     dictionary->SetString("optionsPage", descriptor.options_page_url().spec());
433     scoped_ptr<base::DictionaryValue> language_codes(
434         new base::DictionaryValue());
435     for (size_t i = 0; i < descriptor.language_codes().size(); ++i)
436       language_codes->SetBoolean(descriptor.language_codes().at(i), true);
437     dictionary->Set("languageCodeSet", language_codes.release());
438     ime_ids_list->Append(dictionary.release());
439   }
440   return ime_ids_list.release();
441 }
442
443 base::string16 CrosLanguageOptionsHandler::GetProductName() {
444   return l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_OS_NAME);
445 }
446
447 void CrosLanguageOptionsHandler::SetApplicationLocale(
448     const std::string& language_code) {
449   Profile* profile = Profile::FromWebUI(web_ui());
450   UserManager* user_manager = UserManager::Get();
451
452   // Only the primary user can change the locale.
453   User* user = user_manager->GetUserByProfile(profile);
454   if (user && user->email() == user_manager->GetPrimaryUser()->email()) {
455     profile->ChangeAppLocale(language_code,
456                              Profile::APP_LOCALE_CHANGED_VIA_SETTINGS);
457   }
458 }
459
460 void CrosLanguageOptionsHandler::RestartCallback(const base::ListValue* args) {
461   content::RecordAction(UserMetricsAction("LanguageOptions_SignOut"));
462   chrome::AttemptUserExit();
463 }
464
465 void CrosLanguageOptionsHandler::InputMethodDisableCallback(
466     const base::ListValue* args) {
467   const std::string input_method_id =
468       base::UTF16ToASCII(ExtractStringValue(args));
469   const std::string action = base::StringPrintf(
470       "LanguageOptions_DisableInputMethod_%s", input_method_id.c_str());
471   content::RecordComputedAction(action);
472 }
473
474 void CrosLanguageOptionsHandler::InputMethodEnableCallback(
475     const base::ListValue* args) {
476   const std::string input_method_id =
477       base::UTF16ToASCII(ExtractStringValue(args));
478   const std::string action = base::StringPrintf(
479       "LanguageOptions_EnableInputMethod_%s", input_method_id.c_str());
480   content::RecordComputedAction(action);
481 }
482
483 void CrosLanguageOptionsHandler::InputMethodOptionsOpenCallback(
484     const base::ListValue* args) {
485   const std::string input_method_id =
486       base::UTF16ToASCII(ExtractStringValue(args));
487   const std::string extension_id =
488       extension_ime_util::GetExtensionIDFromInputMethodID(input_method_id);
489   if (extension_id.empty())
490     return;
491
492   const input_method::InputMethodDescriptor* ime =
493       input_method::InputMethodManager::Get()->GetInputMethodFromId(
494           input_method_id);
495   if (!ime)
496     return;
497
498   Browser* browser = chrome::FindBrowserWithWebContents(
499       web_ui()->GetWebContents());
500   content::OpenURLParams params(ime->options_page_url(),
501       content::Referrer(),
502       SINGLETON_TAB,
503       content::PAGE_TRANSITION_LINK,
504       false);
505   browser->OpenURL(params);
506   browser->window()->Show();
507   content::WebContents* web_contents =
508       browser->tab_strip_model()->GetActiveWebContents();
509   web_contents->GetDelegate()->ActivateContents(web_contents);
510 }
511
512 void CrosLanguageOptionsHandler::OnImeComponentExtensionInitialized() {
513   if (composition_extension_appended_ || !is_page_initialized_) {
514     // If an option page is not ready to call JavaScript, appending component
515     // extension IMEs will be done in InitializePage function later.
516     return;
517   }
518
519   ComponentExtensionIMEManager* manager =
520       input_method::InputMethodManager::Get()
521           ->GetComponentExtensionIMEManager();
522
523   DCHECK(manager->IsInitialized());
524   scoped_ptr<base::ListValue> ime_list(
525       ConvertInputMethodDescriptorsToIMEList(
526           manager->GetAllIMEAsInputMethodDescriptor()));
527   web_ui()->CallJavascriptFunction(
528       "options.LanguageOptions.onComponentManagerInitialized",
529       *ime_list);
530   composition_extension_appended_ = true;
531 }
532
533 void CrosLanguageOptionsHandler::InitializePage() {
534   is_page_initialized_ = true;
535   if (composition_extension_appended_)
536     return;
537
538   ComponentExtensionIMEManager* component_extension_manager =
539       input_method::InputMethodManager::Get()
540           ->GetComponentExtensionIMEManager();
541   if (!component_extension_manager->IsInitialized()) {
542     // If the component extension IME manager is not available yet, append the
543     // component extension list in |OnInitialized()|.
544     return;
545   }
546
547   scoped_ptr<base::ListValue> ime_list(
548       ConvertInputMethodDescriptorsToIMEList(
549           component_extension_manager->GetAllIMEAsInputMethodDescriptor()));
550   web_ui()->CallJavascriptFunction(
551       "options.LanguageOptions.onComponentManagerInitialized",
552       *ime_list);
553   composition_extension_appended_ = true;
554 }
555
556 void CrosLanguageOptionsHandler::AddImeProvider(base::ListValue* list) {
557   Profile* profile = Profile::FromWebUI(web_ui());
558   ExtensionService* extension_service = profile->GetExtensionService();
559   for (size_t i = 0; i < list->GetSize(); i++) {
560     base::DictionaryValue* entry;
561     list->GetDictionary(i, &entry);
562
563     std::string input_method_id;
564     entry->GetString("id", &input_method_id);
565
566     std::string extension_id =
567         extension_ime_util::GetExtensionIDFromInputMethodID(input_method_id);
568     const extensions::Extension* extension =
569         extension_service->GetExtensionById(extension_id, false);
570     if (extension)
571       entry->SetString("extensionName", extension->name());
572   }
573 }
574
575 }  // namespace options
576 }  // namespace chromeos