blob: 69825fc0d4ea932d2e6cb8bd9e6f2b308c238930 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/chromeos/login/l10n_util.h"
#include <stddef.h>
#include <iterator>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/i18n/rtl.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/sequenced_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/task_runner_util.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/customization/customization_document.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
#include "ui/base/ime/chromeos/component_extension_ime_manager.h"
#include "ui/base/ime/chromeos/input_method_descriptor.h"
#include "ui/base/ime/chromeos/input_method_manager.h"
#include "ui/base/ime/chromeos/input_method_util.h"
#include "ui/base/l10n/l10n_util.h"
namespace chromeos {
const char kMostRelevantLanguagesDivider[] = "MOST_RELEVANT_LANGUAGES_DIVIDER";
namespace {
std::unique_ptr<base::DictionaryValue> CreateInputMethodsEntry(
const input_method::InputMethodDescriptor& method,
const std::string selected) {
input_method::InputMethodUtil* util =
input_method::InputMethodManager::Get()->GetInputMethodUtil();
const std::string& ime_id = method.id();
std::unique_ptr<base::DictionaryValue> input_method(
new base::DictionaryValue);
input_method->SetString("value", ime_id);
input_method->SetString(
"title", util->GetInputMethodLongNameStripped(method));
input_method->SetBoolean("selected", ime_id == selected);
return input_method;
}
// Returns true if element was inserted.
bool InsertString(const std::string& str, std::set<std::string>* to) {
const std::pair<std::set<std::string>::iterator, bool> result =
to->insert(str);
return result.second;
}
void AddOptgroupOtherLayouts(base::ListValue* input_methods_list) {
std::unique_ptr<base::DictionaryValue> optgroup(new base::DictionaryValue);
optgroup->SetString(
"optionGroupName",
l10n_util::GetStringUTF16(IDS_OOBE_OTHER_KEYBOARD_LAYOUTS));
input_methods_list->Append(std::move(optgroup));
}
std::unique_ptr<base::DictionaryValue> CreateLanguageEntry(
const std::string& language_code,
const base::string16& language_display_name,
const base::string16& language_native_display_name) {
base::string16 display_name = language_display_name;
const bool markup_removal =
base::i18n::UnadjustStringForLocaleDirection(&display_name);
DCHECK(markup_removal);
const bool has_rtl_chars =
base::i18n::StringContainsStrongRTLChars(display_name);
const std::string directionality = has_rtl_chars ? "rtl" : "ltr";
auto dictionary = std::make_unique<base::DictionaryValue>();
dictionary->SetString("code", language_code);
dictionary->SetString("displayName", language_display_name);
dictionary->SetString("textDirection", directionality);
dictionary->SetString("nativeDisplayName", language_native_display_name);
return dictionary;
}
// Gets the list of languages with |descriptors| based on |base_language_codes|.
// The |most_relevant_language_codes| will be first in the list. If
// |insert_divider| is true, an entry with its "code" attribute set to
// kMostRelevantLanguagesDivider is placed between the most relevant languages
// and all others.
std::unique_ptr<base::ListValue> GetLanguageList(
const input_method::InputMethodDescriptors& descriptors,
const std::vector<std::string>& base_language_codes,
const std::vector<std::string>& most_relevant_language_codes,
bool insert_divider) {
const std::string app_locale = g_browser_process->GetApplicationLocale();
std::set<std::string> language_codes;
// Collect the language codes from the supported input methods.
for (size_t i = 0; i < descriptors.size(); ++i) {
const input_method::InputMethodDescriptor& descriptor = descriptors[i];
const std::vector<std::string>& languages = descriptor.language_codes();
for (size_t i = 0; i < languages.size(); ++i)
language_codes.insert(languages[i]);
}
// Language sort order.
std::map<std::string, int /* index */> language_index;
for (size_t i = 0; i < most_relevant_language_codes.size(); ++i)
language_index[most_relevant_language_codes[i]] = i;
// Map of display name -> {language code, native_display_name}.
// In theory, we should be able to create a map that is sorted by
// display names using ICU comparator, but doing it is hard, thus we'll
// use an auxiliary vector to achieve the same result.
typedef std::pair<std::string, base::string16> LanguagePair;
typedef std::map<base::string16, LanguagePair> LanguageMap;
LanguageMap language_map;
// The auxiliary vector mentioned above (except the most relevant locales).
std::vector<base::string16> display_names;
// Separate vector of the most relevant locales.
std::vector<base::string16> most_relevant_locales_display_names(
most_relevant_language_codes.size());
size_t most_relevant_locales_count = 0;
// Build the list of display names, and build the language map.
// The list of configured locales might have entries not in
// base_language_codes. If there are unsupported language variants,
// but they resolve to backup locale within base_language_codes, also
// add them to the list.
for (std::map<std::string, int>::const_iterator it = language_index.begin();
it != language_index.end(); ++it) {
const std::string& language_id = it->first;
const std::string lang = l10n_util::GetLanguage(language_id);
// Ignore non-specific codes.
if (lang.empty() || lang == language_id)
continue;
if (base::ContainsValue(base_language_codes, language_id)) {
// Language is supported. No need to replace
continue;
}
std::string resolved_locale;
if (!l10n_util::CheckAndResolveLocale(language_id, &resolved_locale))
continue;
if (!base::ContainsValue(base_language_codes, resolved_locale)) {
// Resolved locale is not supported.
continue;
}
const base::string16 display_name =
l10n_util::GetDisplayNameForLocale(language_id, app_locale, true);
const base::string16 native_display_name =
l10n_util::GetDisplayNameForLocale(
language_id, language_id, true);
language_map[display_name] =
std::make_pair(language_id, native_display_name);
most_relevant_locales_display_names[it->second] = display_name;
++most_relevant_locales_count;
}
// Translate language codes, generated from input methods.
for (std::set<std::string>::const_iterator it = language_codes.begin();
it != language_codes.end(); ++it) {
// Exclude the language which is not in |base_langauge_codes| even it has
// input methods.
if (!base::ContainsValue(base_language_codes, *it))
continue;
const base::string16 display_name =
l10n_util::GetDisplayNameForLocale(*it, app_locale, true);
const base::string16 native_display_name =
l10n_util::GetDisplayNameForLocale(*it, *it, true);
language_map[display_name] = std::make_pair(*it, native_display_name);
const std::map<std::string, int>::const_iterator index_pos =
language_index.find(*it);
if (index_pos != language_index.end()) {
base::string16& stored_display_name =
most_relevant_locales_display_names[index_pos->second];
if (stored_display_name.empty()) {
stored_display_name = display_name;
++most_relevant_locales_count;
}
} else {
display_names.push_back(display_name);
}
}
DCHECK_EQ(display_names.size() + most_relevant_locales_count,
language_map.size());
// Build the list of display names, and build the language map.
for (size_t i = 0; i < base_language_codes.size(); ++i) {
// Skip this language if it was already added.
if (language_codes.find(base_language_codes[i]) != language_codes.end())
continue;
base::string16 display_name =
l10n_util::GetDisplayNameForLocale(
base_language_codes[i], app_locale, false);
base::string16 native_display_name =
l10n_util::GetDisplayNameForLocale(
base_language_codes[i], base_language_codes[i], false);
language_map[display_name] =
std::make_pair(base_language_codes[i], native_display_name);
const std::map<std::string, int>::const_iterator index_pos =
language_index.find(base_language_codes[i]);
if (index_pos != language_index.end()) {
most_relevant_locales_display_names[index_pos->second] = display_name;
++most_relevant_locales_count;
} else {
display_names.push_back(display_name);
}
}
// Sort display names using locale specific sorter.
l10n_util::SortStrings16(app_locale, &display_names);
// Concatenate most_relevant_locales_display_names and display_names.
// Insert special divider in between.
std::vector<base::string16> out_display_names;
for (size_t i = 0; i < most_relevant_locales_display_names.size(); ++i) {
if (most_relevant_locales_display_names[i].size() == 0)
continue;
out_display_names.push_back(most_relevant_locales_display_names[i]);
}
base::string16 divider16;
if (insert_divider && !out_display_names.empty()) {
// Insert a divider if requested, but only if
// |most_relevant_locales_display_names| is not empty.
divider16 = base::ASCIIToUTF16(kMostRelevantLanguagesDivider);
out_display_names.push_back(divider16);
}
std::copy(display_names.begin(),
display_names.end(),
std::back_inserter(out_display_names));
// Build the language list from the language map.
std::unique_ptr<base::ListValue> language_list(new base::ListValue());
for (size_t i = 0; i < out_display_names.size(); ++i) {
// Sets the directionality of the display language name.
base::string16 display_name(out_display_names[i]);
if (insert_divider && display_name == divider16) {
// Insert divider.
auto dictionary = std::make_unique<base::DictionaryValue>();
dictionary->SetString("code", kMostRelevantLanguagesDivider);
language_list->Append(std::move(dictionary));
continue;
}
const LanguagePair& pair = language_map[out_display_names[i]];
language_list->Append(
CreateLanguageEntry(pair.first, out_display_names[i], pair.second));
}
return language_list;
}
// Note: this method updates |selected_locale| only if it is empty.
void GetAndMergeKeyboardLayoutsForLocale(input_method::InputMethodUtil* util,
const std::string& locale,
std::string* selected_locale,
std::vector<std::string>* layouts) {
std::vector<std::string> layouts_from_locale;
util->GetInputMethodIdsFromLanguageCode(
locale, input_method::kKeyboardLayoutsOnly, &layouts_from_locale);
layouts->insert(layouts->end(), layouts_from_locale.begin(),
layouts_from_locale.end());
if (selected_locale->empty() && !layouts_from_locale.empty()) {
*selected_locale =
util->GetInputMethodDescriptorFromId(layouts_from_locale[0])->id();
}
}
// Invokes |callback| with a list of keyboard layouts that can be used for
// |resolved_locale|.
void GetKeyboardLayoutsForResolvedLocale(
const std::string& requested_locale,
const GetKeyboardLayoutsForLocaleCallback& callback,
const std::string& resolved_locale) {
input_method::InputMethodUtil* util =
input_method::InputMethodManager::Get()->GetInputMethodUtil();
std::vector<std::string> layouts = util->GetHardwareInputMethodIds();
// "Selected" will be set from the fist non-empty list.
std::string selected;
GetAndMergeKeyboardLayoutsForLocale(util, requested_locale, &selected,
&layouts);
GetAndMergeKeyboardLayoutsForLocale(util, resolved_locale, &selected,
&layouts);
std::unique_ptr<base::ListValue> input_methods_list(new base::ListValue);
std::set<std::string> input_methods_added;
for (std::vector<std::string>::const_iterator it = layouts.begin();
it != layouts.end(); ++it) {
const input_method::InputMethodDescriptor* ime =
util->GetInputMethodDescriptorFromId(*it);
if (!InsertString(ime->id(), &input_methods_added))
continue;
input_methods_list->Append(CreateInputMethodsEntry(*ime, selected));
}
callback.Run(std::move(input_methods_list));
}
// For "UI Language" drop-down menu at OOBE screen we need to decide which
// entry to mark "selected". If user has just selected "requested_locale",
// but "loaded_locale" was actually loaded, we mark original user choice
// "selected" only if loaded_locale is a backup for "requested_locale".
std::string CalculateSelectedLanguage(const std::string& requested_locale,
const std::string& loaded_locale) {
std::string resolved_locale;
if (!l10n_util::CheckAndResolveLocale(requested_locale, &resolved_locale))
return loaded_locale;
if (resolved_locale == loaded_locale)
return requested_locale;
return loaded_locale;
}
void ResolveLanguageListInThreadPool(
std::unique_ptr<chromeos::locale_util::LanguageSwitchResult>
language_switch_result,
const scoped_refptr<base::TaskRunner> task_runner,
const UILanguageListResolvedCallback& resolved_callback) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
std::string selected_language;
if (!language_switch_result) {
selected_language =
StartupCustomizationDocument::GetInstance()->initial_locale_default();
} else {
if (language_switch_result->success) {
if (language_switch_result->requested_locale ==
language_switch_result->loaded_locale) {
selected_language = language_switch_result->requested_locale;
} else {
selected_language =
CalculateSelectedLanguage(language_switch_result->requested_locale,
language_switch_result->loaded_locale);
}
} else {
selected_language = language_switch_result->loaded_locale;
}
}
const std::string selected_code =
selected_language.empty() ? g_browser_process->GetApplicationLocale()
: selected_language;
const std::string list_locale =
language_switch_result ? language_switch_result->loaded_locale
: g_browser_process->GetApplicationLocale();
std::unique_ptr<base::ListValue> language_list(
chromeos::GetUILanguageList(nullptr, selected_code));
task_runner->PostTask(
FROM_HERE, base::BindOnce(resolved_callback, base::Passed(&language_list),
list_locale, selected_language));
}
void AdjustUILanguageList(const std::string& selected,
base::ListValue* languages_list) {
for (size_t i = 0; i < languages_list->GetSize(); ++i) {
base::DictionaryValue* language_info = NULL;
if (!languages_list->GetDictionary(i, &language_info))
NOTREACHED();
std::string value;
language_info->GetString("code", &value);
std::string display_name;
language_info->GetString("displayName", &display_name);
std::string native_name;
language_info->GetString("nativeDisplayName", &native_name);
// If it's an option group divider, add field name.
if (value == kMostRelevantLanguagesDivider) {
language_info->SetString(
"optionGroupName",
l10n_util::GetStringUTF16(IDS_OOBE_OTHER_LANGUAGES));
}
if (display_name != native_name) {
display_name = base::StringPrintf("%s - %s",
display_name.c_str(),
native_name.c_str());
}
language_info->SetString("value", value);
language_info->SetString("title", display_name);
if (value == selected)
language_info->SetBoolean("selected", true);
}
}
} // namespace
void ResolveUILanguageList(
std::unique_ptr<chromeos::locale_util::LanguageSwitchResult>
language_switch_result,
const UILanguageListResolvedCallback& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::PostTaskWithTraits(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&ResolveLanguageListInThreadPool,
base::Passed(&language_switch_result),
base::SequencedTaskRunnerHandle::Get(), callback));
}
std::unique_ptr<base::ListValue> GetMinimalUILanguageList() {
const std::string application_locale =
g_browser_process->GetApplicationLocale();
base::string16 language_native_display_name =
l10n_util::GetDisplayNameForLocale(
application_locale, application_locale, true);
std::unique_ptr<base::ListValue> language_list(new base::ListValue());
language_list->Append(CreateLanguageEntry(application_locale,
language_native_display_name,
language_native_display_name));
AdjustUILanguageList(std::string(), language_list.get());
return language_list;
}
std::unique_ptr<base::ListValue> GetUILanguageList(
const std::vector<std::string>* most_relevant_language_codes,
const std::string& selected) {
ComponentExtensionIMEManager* manager =
input_method::InputMethodManager::Get()
->GetComponentExtensionIMEManager();
input_method::InputMethodDescriptors descriptors =
manager->GetXkbIMEAsInputMethodDescriptor();
std::unique_ptr<base::ListValue> languages_list(GetLanguageList(
descriptors, l10n_util::GetAvailableLocales(),
most_relevant_language_codes
? *most_relevant_language_codes
: StartupCustomizationDocument::GetInstance()->configured_locales(),
true));
AdjustUILanguageList(selected, languages_list.get());
return languages_list;
}
std::string FindMostRelevantLocale(
const std::vector<std::string>& most_relevant_language_codes,
const base::ListValue& available_locales,
const std::string& fallback_locale) {
for (std::vector<std::string>::const_iterator most_relevant_it =
most_relevant_language_codes.begin();
most_relevant_it != most_relevant_language_codes.end();
++most_relevant_it) {
for (base::ListValue::const_iterator available_it =
available_locales.begin();
available_it != available_locales.end(); ++available_it) {
const base::DictionaryValue* dict;
std::string available_locale;
if (!available_it->GetAsDictionary(&dict) ||
!dict->GetString("value", &available_locale)) {
NOTREACHED();
continue;
}
if (available_locale == *most_relevant_it)
return *most_relevant_it;
}
}
return fallback_locale;
}
std::unique_ptr<base::ListValue> GetAcceptLanguageList() {
// Collect the language codes from the supported accept-languages.
const std::string app_locale = g_browser_process->GetApplicationLocale();
std::vector<std::string> accept_language_codes;
l10n_util::GetAcceptLanguagesForLocale(app_locale, &accept_language_codes);
return GetLanguageList(
*input_method::InputMethodManager::Get()->GetSupportedInputMethods(),
accept_language_codes,
StartupCustomizationDocument::GetInstance()->configured_locales(),
false);
}
std::unique_ptr<base::ListValue> GetAndActivateLoginKeyboardLayouts(
const std::string& locale,
const std::string& selected,
bool activate_keyboards) {
std::unique_ptr<base::ListValue> input_methods_list(new base::ListValue);
input_method::InputMethodManager* manager =
input_method::InputMethodManager::Get();
input_method::InputMethodUtil* util = manager->GetInputMethodUtil();
const std::vector<std::string>& hardware_login_input_methods =
util->GetHardwareLoginInputMethodIds();
if (activate_keyboards) {
DCHECK(
ProfileHelper::IsSigninProfile(ProfileManager::GetActiveUserProfile()));
manager->GetActiveIMEState()->EnableLoginLayouts(
locale, hardware_login_input_methods);
}
std::unique_ptr<input_method::InputMethodDescriptors> input_methods(
manager->GetActiveIMEState()->GetActiveInputMethods());
std::set<std::string> input_methods_added;
for (std::vector<std::string>::const_iterator i =
hardware_login_input_methods.begin();
i != hardware_login_input_methods.end();
++i) {
const input_method::InputMethodDescriptor* ime =
util->GetInputMethodDescriptorFromId(*i);
// Do not crash in case of misconfiguration.
if (ime) {
input_methods_added.insert(*i);
input_methods_list->Append(CreateInputMethodsEntry(*ime, selected));
} else {
NOTREACHED();
}
}
bool optgroup_added = false;
for (size_t i = 0; i < input_methods->size(); ++i) {
// Makes sure the id is in legacy xkb id format.
const std::string& ime_id = (*input_methods)[i].id();
if (!InsertString(ime_id, &input_methods_added))
continue;
if (!optgroup_added) {
optgroup_added = true;
AddOptgroupOtherLayouts(input_methods_list.get());
}
input_methods_list->Append(
CreateInputMethodsEntry((*input_methods)[i], selected));
}
// "xkb:us::eng" should always be in the list of available layouts.
const std::string us_keyboard_id =
util->GetFallbackInputMethodDescriptor().id();
if (input_methods_added.find(us_keyboard_id) == input_methods_added.end()) {
const input_method::InputMethodDescriptor* us_eng_descriptor =
util->GetInputMethodDescriptorFromId(us_keyboard_id);
DCHECK(us_eng_descriptor);
if (!optgroup_added) {
optgroup_added = true;
AddOptgroupOtherLayouts(input_methods_list.get());
}
input_methods_list->Append(
CreateInputMethodsEntry(*us_eng_descriptor, selected));
manager->GetActiveIMEState()->EnableInputMethod(us_keyboard_id);
}
return input_methods_list;
}
void GetKeyboardLayoutsForLocale(
const GetKeyboardLayoutsForLocaleCallback& callback,
const std::string& locale) {
// Resolve |locale| on a background thread, then continue on the current
// thread.
std::string (*get_application_locale)(const std::string&, bool) =
&l10n_util::GetApplicationLocale;
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(get_application_locale, locale,
false /* set_icu_locale */),
base::BindOnce(&GetKeyboardLayoutsForResolvedLocale, locale, callback));
}
std::unique_ptr<base::DictionaryValue> GetCurrentKeyboardLayout() {
const input_method::InputMethodDescriptor current_input_method =
input_method::InputMethodManager::Get()
->GetActiveIMEState()
->GetCurrentInputMethod();
return CreateInputMethodsEntry(current_input_method,
current_input_method.id());
}
} // namespace chromeos