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