blob: 2274c815016a0fa8b75ca81a5889590e874a61c4 [file] [log] [blame]
// Copyright 2017 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.
package org.chromium.chrome.browser.language.settings;
import android.text.TextUtils;
import androidx.annotation.IntDef;
import androidx.core.util.Predicate;
import org.chromium.base.LocaleUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.language.AppLocaleUtils;
import org.chromium.chrome.browser.translate.TranslateBridge;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
/**
* Manages languages details for languages settings.
*
*The LanguagesManager is responsible for fetching languages details from native.
*/
public class LanguagesManager {
/**
* An observer interface that allows other classes to know when the accept language list is
* updated in native side.
*/
interface AcceptLanguageObserver {
/**
* Called when the accept languages for the current user are updated.
*/
void onDataUpdated();
}
// Constants used to log UMA enum histogram, must stay in sync with
// LanguageSettingsActionType. Further actions can only be appended, existing
// entries must not be overwritten.
@IntDef({LanguageSettingsActionType.LANGUAGE_ADDED, LanguageSettingsActionType.LANGUAGE_REMOVED,
LanguageSettingsActionType.DISABLE_TRANSLATE_GLOBALLY,
LanguageSettingsActionType.ENABLE_TRANSLATE_GLOBALLY,
LanguageSettingsActionType.DISABLE_TRANSLATE_FOR_SINGLE_LANGUAGE,
LanguageSettingsActionType.ENABLE_TRANSLATE_FOR_SINGLE_LANGUAGE,
LanguageSettingsActionType.LANGUAGE_LIST_REORDERED,
LanguageSettingsActionType.CHANGE_CHROME_LANGUAGE,
LanguageSettingsActionType.CHANGE_TARGET_LANGUAGE,
LanguageSettingsActionType.REMOVE_FROM_NEVER_TRANSLATE,
LanguageSettingsActionType.ADD_TO_NEVER_TRANSLATE,
LanguageSettingsActionType.REMOVE_FROM_ALWAYS_TRANSLATE,
LanguageSettingsActionType.ADD_TO_ALWAYS_TRANSLATE,
LanguageSettingsActionType.REMOVE_SITE_FROM_NEVER_TRANSLATE})
@Retention(RetentionPolicy.SOURCE)
@interface LanguageSettingsActionType {
// int CLICK_ON_ADD_LANGUAGE = 1; // Removed M89
int LANGUAGE_ADDED = 2;
int LANGUAGE_REMOVED = 3;
int DISABLE_TRANSLATE_GLOBALLY = 4;
int ENABLE_TRANSLATE_GLOBALLY = 5;
int DISABLE_TRANSLATE_FOR_SINGLE_LANGUAGE = 6;
int ENABLE_TRANSLATE_FOR_SINGLE_LANGUAGE = 7;
int LANGUAGE_LIST_REORDERED = 8;
int CHANGE_CHROME_LANGUAGE = 9;
int CHANGE_TARGET_LANGUAGE = 10;
int REMOVE_FROM_NEVER_TRANSLATE = 11;
int ADD_TO_NEVER_TRANSLATE = 12;
int REMOVE_FROM_ALWAYS_TRANSLATE = 13;
int ADD_TO_ALWAYS_TRANSLATE = 14;
int REMOVE_SITE_FROM_NEVER_TRANSLATE = 15;
int NUM_ENTRIES = 16;
}
// Constants used to log UMA enum histogram, must stay in sync with
// LanguageSettingsPageType. Further actions can only be appended, existing
// entries must not be overwritten.
@IntDef({LanguageSettingsPageType.PAGE_MAIN,
LanguageSettingsPageType.CONTENT_LANGUAGE_ADD_LANGUAGE,
LanguageSettingsPageType.CHANGE_CHROME_LANGUAGE,
LanguageSettingsPageType.ADVANCED_LANGUAGE_SETTINGS,
LanguageSettingsPageType.CHANGE_TARGET_LANGUAGE,
LanguageSettingsPageType.LANGUAGE_OVERFLOW_MENU_OPENED,
LanguageSettingsPageType.VIEW_NEVER_TRANSLATE_LANGUAGES,
LanguageSettingsPageType.NEVER_TRANSLATE_ADD_LANGUAGE,
LanguageSettingsPageType.VIEW_ALWAYS_TRANSLATE_LANGUAGES,
LanguageSettingsPageType.ALWAYS_TRANSLATE_ADD_LANGUAGE,
LanguageSettingsPageType.VIEW_NEVER_TRANSLATE_SITES})
@Retention(RetentionPolicy.SOURCE)
@interface LanguageSettingsPageType {
int PAGE_MAIN = 0;
int CONTENT_LANGUAGE_ADD_LANGUAGE = 1;
// int LANGUAGE_DETAILS = 2; // iOS only
int CHANGE_CHROME_LANGUAGE = 3;
int ADVANCED_LANGUAGE_SETTINGS = 4;
int CHANGE_TARGET_LANGUAGE = 5;
int LANGUAGE_OVERFLOW_MENU_OPENED = 6;
int VIEW_NEVER_TRANSLATE_LANGUAGES = 7;
int NEVER_TRANSLATE_ADD_LANGUAGE = 8;
int VIEW_ALWAYS_TRANSLATE_LANGUAGES = 9;
int ALWAYS_TRANSLATE_ADD_LANGUAGE = 10;
int VIEW_NEVER_TRANSLATE_SITES = 11;
int NUM_ENTRIES = 12;
}
// Int keys to determine the list of potential languages for different language preferences.
@IntDef({LanguageListType.ACCEPT_LANGUAGES, LanguageListType.UI_LANGUAGES,
LanguageListType.TARGET_LANGUAGES, LanguageListType.NEVER_LANGUAGES,
LanguageListType.ALWAYS_LANGUAGES})
@Retention(RetentionPolicy.SOURCE)
@interface LanguageListType {
int ACCEPT_LANGUAGES = 0; // Default
int UI_LANGUAGES = 1;
int TARGET_LANGUAGES = 2;
int NEVER_LANGUAGES = 3;
int ALWAYS_LANGUAGES = 4;
}
private static LanguagesManager sManager;
private final Map<String, LanguageItem> mLanguagesMap;
private AcceptLanguageObserver mObserver;
private LanguagesManager() {
// Get all language data from native.
mLanguagesMap = new LinkedHashMap<>();
for (LanguageItem item : TranslateBridge.getChromeLanguageList()) {
mLanguagesMap.put(item.getCode(), item);
}
}
private void notifyAcceptLanguageObserver() {
if (mObserver != null) mObserver.onDataUpdated();
}
/**
* Sets the observer tracking the user accept languages changes.
*/
public void setAcceptLanguageObserver(AcceptLanguageObserver observer) {
mObserver = observer;
}
/**
* @return A list of LanguageItems for the current user's accept languages.
*/
public List<LanguageItem> getUserAcceptLanguageItems() {
// Always read the latest user accept language code list from native.
List<String> codes = TranslateBridge.getUserLanguageCodes();
List<LanguageItem> results = new ArrayList<>();
// Keep the same order as accept language codes list.
for (String code : codes) {
// Check language code and only languages supported on Android are added in.
if (mLanguagesMap.containsKey(code)) results.add(mLanguagesMap.get(code));
}
return results;
}
/**
* Get the list of potential languages to show in the {@link AddLanguageFragment} based on which
* list or preference a language will be added to. By default the potential languages for the
* Accept-Language list is returned.
* @param LanguageListType key to select which languages to get.
* @return A list of LanguageItems to choose from for the given preference.
*/
public List<LanguageItem> getPotentialLanguages(@LanguageListType int potentialLanguages) {
switch (potentialLanguages) {
case LanguageListType.ALWAYS_LANGUAGES:
return getPotentialTranslateLanguages(
TranslateBridge.getAlwaysTranslateLanguages());
case LanguageListType.NEVER_LANGUAGES:
return getPotentialTranslateLanguages(TranslateBridge.getNeverTranslateLanguages());
case LanguageListType.TARGET_LANGUAGES:
return getPotentialTranslateLanguages(
Arrays.asList(TranslateBridge.getTargetLanguageForChromium()));
case LanguageListType.UI_LANGUAGES:
return getPotentialUiLanguages();
case LanguageListType.ACCEPT_LANGUAGES:
return getPotentialAcceptLanguages();
default:
assert false : "No valid LanguageListType";
return null;
}
}
/**
* Get a list of LanguageItems that can be used as a Translate language but excluding
* |codesToSkip|. The current Accept-Languages are added to the front of the list.
* @param codesToSkip Collection of String language codes to exclude from the list.
* @return List of LanguageItems.
*/
private List<LanguageItem> getPotentialTranslateLanguages(Collection<String> codesToSkip) {
HashSet<String> codesToSkipSet = new HashSet<String>(codesToSkip);
LinkedHashSet<LanguageItem> results = new LinkedHashSet<>();
// Filter for translatable languages not in |codesToSkipSet|.
Predicate<LanguageItem> filter = (item) -> {
return item.isSupportedBaseLanguage() && !codesToSkipSet.contains(item.getCode());
};
addItemsToResult(results, getUserAcceptLanguageItems(), filter);
addItemsToResult(results, mLanguagesMap.values(), filter);
return new ArrayList<>(results);
}
/**
* Get a list of potential LanguageItems Chrome UI languages excluding the current UI language.
* The current Accept-Languages are added to the front of the the list.
* @return List of LanguageItems.
*/
public List<LanguageItem> getPotentialUiLanguages() {
LinkedHashSet<LanguageItem> results = new LinkedHashSet<>();
LanguageItem currentUiLanguage = getLanguageItem(AppLocaleUtils.getAppLanguagePref());
// Add the system default language if an override language is set.
if (!currentUiLanguage.isSystemDefault()) {
results.add(LanguageItem.makeSystemDefaultLanguageItem());
}
// Filter for UI languages that are not the current UI language.
Predicate<LanguageItem> filter = (item) -> {
return item.isUISupported() && !item.equals(currentUiLanguage);
};
addItemsToResult(results, getUserAcceptLanguageItems(), filter);
addItemsToResult(results, mLanguagesMap.values(), filter);
return new ArrayList<>(results);
}
/**
* Get a list of potential Accept-Languages excluding the current Accept-Languages.
* @return A list of LanguageItems, excluding the current user's accept languages.
*/
private List<LanguageItem> getPotentialAcceptLanguages() {
// Always read the latest user accept language code list from native.
HashSet<String> codesToSkip = new HashSet(TranslateBridge.getUserLanguageCodes());
LinkedHashSet<LanguageItem> results = new LinkedHashSet<>();
addItemsToResult(
results, mLanguagesMap.values(), (item) -> !codesToSkip.contains(item.getCode()));
return new ArrayList<>(results);
}
/**
* Add LanguageItems in |items| to |results| keeping their order and excluding items that match
* |filter|.
* @param results LinkedHashSet of LanguageItems to add items to.
* @param items Collection of LanguageItems to potentially add to results.
* @param filter Predicate to return true for items that should be added to results.
*/
private void addItemsToResult(LinkedHashSet<LanguageItem> results,
Collection<LanguageItem> items, Predicate<LanguageItem> filter) {
for (LanguageItem item : items) {
if (filter.test(item)) {
results.add(item);
}
}
}
/**
* Get a list of LanguageItems that the user has set to always translate. The list is sorted
* alphabetically by display name.
* @return List of LanguageItems.
*/
public Collection<LanguageItem> getAlwaysTranslateLanguageItems() {
// Get the latest always translate list from native. This list has no guaranteed order.
List<String> codes = TranslateBridge.getAlwaysTranslateLanguages();
TreeSet<LanguageItem> results = new TreeSet(LanguageItem.COMPARE_BY_DISPLAY_NAME);
for (String code : codes) {
if (mLanguagesMap.containsKey(code)) results.add(mLanguagesMap.get(code));
}
return results;
}
/**
* Get a list of LanguageItems that the user has set to never prompt for translation. The list
* is sorted alphabetically by display name.
* @return List of LanguageItems.
*/
public Collection<LanguageItem> getNeverTranslateLanguageItems() {
// Get the latest never translate list from native. This list has no guaranteed order.
List<String> codes = TranslateBridge.getNeverTranslateLanguages();
TreeSet<LanguageItem> results = new TreeSet(LanguageItem.COMPARE_BY_DISPLAY_NAME);
for (String code : codes) {
if (mLanguagesMap.containsKey(code)) results.add(mLanguagesMap.get(code));
}
return results;
}
/**
* Get a LanguageItem given the iso639 locale code (e.g. en-US). If no match is found the base
* language is checked (e.g. "en" for "en-AU"). If there is still no match null is returned.
* @return LanguageItem or null if none found
*/
public LanguageItem getLanguageItem(String localeCode) {
if (TextUtils.equals(localeCode, AppLocaleUtils.SYSTEM_LANGUAGE_VALUE)) {
return LanguageItem.makeSystemDefaultLanguageItem();
}
LanguageItem result = mLanguagesMap.get(localeCode);
if (result != null) return result;
String baseLanguage = LocaleUtils.toLanguage(localeCode);
return mLanguagesMap.get(baseLanguage);
}
/**
* Add a language to the current user's accept languages.
* @param code The language code to remove.
*/
public void addToAcceptLanguages(String code) {
TranslateBridge.updateUserAcceptLanguages(code, true /* is_add */);
notifyAcceptLanguageObserver();
}
/**
* Remove a language from the current user's accept languages.
* @param code The language code to remove.
*/
public void removeFromAcceptLanguages(String code) {
TranslateBridge.updateUserAcceptLanguages(code, false /* is_add */);
notifyAcceptLanguageObserver();
}
/**
* Move a language's position in the user's accept languages list.
* @param code The language code to move.
* @param offset The offset from the original position.
* Negative value means up and positive value means down.
* @param reload Whether to reload the language list.
*/
public void moveLanguagePosition(String code, int offset, boolean reload) {
if (offset == 0) return;
TranslateBridge.moveAcceptLanguage(code, offset);
recordAction(LanguageSettingsActionType.LANGUAGE_LIST_REORDERED);
if (reload) notifyAcceptLanguageObserver();
}
/**
* Sets the preference order of the user's accepted languages to the provided order.
*
* @param codes The new order for the user's languages.
* @param reload True iff the language list should be reloaded.
*/
public void setOrder(String[] codes, boolean reload) {
TranslateBridge.setLanguageOrder(codes);
recordAction(LanguageSettingsActionType.LANGUAGE_LIST_REORDERED);
if (reload) notifyAcceptLanguageObserver();
}
/**
* Called to get all languages available in chrome.
* @return A map of language code to {@code LanguageItem} for all available languages.
*/
public Map<String, LanguageItem> getLanguageMap() {
return mLanguagesMap;
}
/**
* Get the static instance of ChromePreferenceManager if it exists else create it.
* @return the LanguagesManager singleton.
*/
public static LanguagesManager getInstance() {
if (sManager == null) sManager = new LanguagesManager();
return sManager;
}
/**
* Called to release unused resources.
*/
public static void recycle() {
sManager = null;
}
/**
* Record language settings page impression.
*/
public static void recordImpression(@LanguageSettingsPageType int pageType) {
RecordHistogram.recordEnumeratedHistogram(
"LanguageSettings.PageImpression", pageType, LanguageSettingsPageType.NUM_ENTRIES);
}
/**
* Record actions taken on language settings page.
*/
public static void recordAction(@LanguageSettingsActionType int actionType) {
RecordHistogram.recordEnumeratedHistogram(
"LanguageSettings.Actions", actionType, LanguageSettingsActionType.NUM_ENTRIES);
}
}