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 577