Loading core/java/com/android/internal/app/AppLocaleCollector.java +228 −30 Original line number Diff line number Diff line Loading @@ -19,49 +19,184 @@ package com.android.internal.app; import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET; import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG; import android.app.LocaleManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.LocaleList; import android.os.SystemProperties; import android.provider.Settings; import android.util.Log; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.VisibleForTesting; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; /** The Locale data collector for per-app language. */ class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { private static final String TAG = AppLocaleCollector.class.getSimpleName(); private final Context mContext; private final String mAppPackageName; private final LocaleStore.LocaleInfo mAppCurrentLocale; private LocaleStore.LocaleInfo mAppCurrentLocale; private Set<LocaleStore.LocaleInfo> mAllAppActiveLocales; private Set<LocaleStore.LocaleInfo> mImeLocales; private static final String PROP_APP_LANGUAGE_SUGGESTION = "android.app.language.suggestion.enhanced"; private static final boolean ENABLED = true; AppLocaleCollector(Context context, String appPackageName) { public AppLocaleCollector(Context context, String appPackageName) { mContext = context; mAppPackageName = appPackageName; mAppCurrentLocale = LocaleStore.getAppCurrentLocaleInfo( mContext, mAppPackageName); } @VisibleForTesting public LocaleStore.LocaleInfo getAppCurrentLocale() { return LocaleStore.getAppActivatedLocaleInfo(mContext, mAppPackageName, true); } /** * Get all applications' activated locales. * @return A set which includes all applications' activated LocaleInfo. */ @VisibleForTesting public Set<LocaleStore.LocaleInfo> getAllAppActiveLocales() { PackageManager pm = mContext.getPackageManager(); LocaleManager lm = mContext.getSystemService(LocaleManager.class); HashSet<LocaleStore.LocaleInfo> result = new HashSet<>(); if (pm != null && lm != null) { HashMap<String, LocaleStore.LocaleInfo> map = new HashMap<>(); for (ApplicationInfo appInfo : pm.getInstalledApplications( PackageManager.ApplicationInfoFlags.of(0))) { LocaleStore.LocaleInfo localeInfo = LocaleStore.getAppActivatedLocaleInfo( mContext, appInfo.packageName, false); // For the locale to be added into the suggestion area, its country could not be // empty. if (localeInfo != null && localeInfo.getLocale().getCountry().length() > 0) { map.put(localeInfo.getId(), localeInfo); } } map.forEach((language, localeInfo) -> result.add(localeInfo)); } return result; } /** * Get all locales that active IME supports. * * @return A set which includes all LocaleInfo that active IME supports. */ @VisibleForTesting public Set<LocaleStore.LocaleInfo> getActiveImeLocales() { Set<LocaleStore.LocaleInfo> activeImeLocales = null; InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); if (imm != null) { InputMethodInfo activeIme = getActiveIme(imm); if (activeIme != null) { activeImeLocales = LocaleStore.transformImeLanguageTagToLocaleInfo( imm.getEnabledInputMethodSubtypeList(activeIme, true)); } } if (activeImeLocales == null) { return Set.of(); } else { return activeImeLocales.stream().filter( // For the locale to be added into the suggestion area, its country could not be // empty. info -> info.getLocale().getCountry().length() > 0).collect( Collectors.toSet()); } } private InputMethodInfo getActiveIme(InputMethodManager imm) { InputMethodInfo activeIme = null; List<InputMethodInfo> infoList = imm.getEnabledInputMethodList(); String imeId = Settings.Secure.getStringForUser(mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, mContext.getUserId()); for (InputMethodInfo method : infoList) { if (method.getId().equals(imeId)) { activeIme = method; } } return activeIme; } /** * Get the AppLocaleResult that the application supports. * @return The AppLocaleResult that the application supports. */ @VisibleForTesting public AppLocaleStore.AppLocaleResult getAppSupportedLocales() { return AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName); } /** * Get the locales that system supports excluding langTagsToIgnore. * * @param langTagsToIgnore A language set to be ignored. * @param parent The parent locale. * @param translatedOnly specified if is is only for translation. * @return A set which includes the LocaleInfo that system supports, excluding langTagsToIgnore. */ @VisibleForTesting public Set<LocaleStore.LocaleInfo> getSystemSupportedLocale(Set<String> langTagsToIgnore, LocaleStore.LocaleInfo parent, boolean translatedOnly) { return LocaleStore.getLevelLocales(mContext, langTagsToIgnore, parent, translatedOnly); } /** * Get the locales that system activates. * @return A set which includes all the locales that system activates. */ @VisibleForTesting public List<LocaleStore.LocaleInfo> getSystemCurrentLocale() { return LocaleStore.getSystemCurrentLocaleInfo(); } @Override public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) { HashSet<String> langTagsToIgnore = new HashSet<>(); if (mAppCurrentLocale != null) { langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag()); } if (SystemProperties.getBoolean(PROP_APP_LANGUAGE_SUGGESTION, ENABLED)) { // Add the locale that other App activated mAllAppActiveLocales.forEach( info -> langTagsToIgnore.add(info.getLocale().toLanguageTag())); // Add the locale that active IME enabled mImeLocales.forEach(info -> langTagsToIgnore.add(info.getLocale().toLanguageTag())); } // Add System locales LocaleList systemLangList = LocaleList.getDefault(); for (int i = 0; i < systemLangList.size(); i++) { langTagsToIgnore.add(systemLangList.get(i).toLanguageTag()); } if (mAppCurrentLocale != null) { langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag()); } return langTagsToIgnore; } @Override public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent, boolean translatedOnly, boolean isForCountryMode) { AppLocaleStore.AppLocaleResult result = AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName); if (mAppCurrentLocale == null) { mAppCurrentLocale = getAppCurrentLocale(); } if (mAllAppActiveLocales == null) { mAllAppActiveLocales = getAllAppActiveLocales(); } if (mImeLocales == null) { mImeLocales = getActiveImeLocales(); } AppLocaleStore.AppLocaleResult result = getAppSupportedLocales(); Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly); Set<LocaleStore.LocaleInfo> appLocaleList = new HashSet<>(); Set<LocaleStore.LocaleInfo> systemLocaleList; Loading @@ -71,11 +206,9 @@ class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { // Get system supported locale list if (isForCountryMode) { systemLocaleList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore, parent, translatedOnly); systemLocaleList = getSystemSupportedLocale(langTagsToIgnore, parent, translatedOnly); } else { systemLocaleList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore, null /* no parent */, translatedOnly); systemLocaleList = getSystemSupportedLocale(langTagsToIgnore, null, translatedOnly); } // Add current app locale Loading @@ -84,19 +217,46 @@ class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { } // Add current system language into suggestion list for(LocaleStore.LocaleInfo localeInfo: LocaleStore.getSystemCurrentLocaleInfo()) { boolean isNotCurrentLocale = mAppCurrentLocale == null || !localeInfo.getLocale().equals(mAppCurrentLocale.getLocale()); if (!isForCountryMode && isNotCurrentLocale) { if (!isForCountryMode) { boolean isCurrentLocale, isInAppOrIme; for (LocaleStore.LocaleInfo localeInfo : getSystemCurrentLocale()) { isCurrentLocale = mAppCurrentLocale != null && localeInfo.getLocale().equals(mAppCurrentLocale.getLocale()); isInAppOrIme = existsInAppOrIme(localeInfo.getLocale()); if (!isCurrentLocale && !isInAppOrIme) { appLocaleList.add(localeInfo); } } } // Add the languages that included in system supported locale // Add the languages that are included in system supported locale Set<LocaleStore.LocaleInfo> suggestedSet = null; if (shouldShowList) { appLocaleList.addAll(filterTheLanguagesNotIncludedInSystemLocale( systemLocaleList, result.mAppSupportedLocales)); appLocaleList.addAll(filterSupportedLocales(systemLocaleList, result.mAppSupportedLocales)); suggestedSet = getSuggestedLocales(appLocaleList); } if (!isForCountryMode && SystemProperties.getBoolean(PROP_APP_LANGUAGE_SUGGESTION, ENABLED)) { // Add the language that other apps activate into the suggestion list. Set<LocaleStore.LocaleInfo> localeSet = filterSupportedLocales(mAllAppActiveLocales, result.mAppSupportedLocales); if (suggestedSet != null) { // Filter out the locale with the same language and country // like zh-TW vs zh-Hant-TW. localeSet = filterSameLanguageAndCountry(localeSet, suggestedSet); } appLocaleList.addAll(localeSet); suggestedSet.addAll(localeSet); // Add the language that the active IME enables into the suggestion list. localeSet = filterSupportedLocales(mImeLocales, result.mAppSupportedLocales); if (suggestedSet != null) { localeSet = filterSameLanguageAndCountry(localeSet, suggestedSet); } appLocaleList.addAll(localeSet); suggestedSet.addAll(localeSet); } // Add "system language" option Loading @@ -117,12 +277,50 @@ class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { return true; } private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotIncludedInSystemLocale( Set<LocaleStore.LocaleInfo> systemLocaleList, private Set<LocaleStore.LocaleInfo> getSuggestedLocales(Set<LocaleStore.LocaleInfo> localeSet) { return localeSet.stream().filter(localeInfo -> localeInfo.isSuggested()).collect( Collectors.toSet()); } private Set<LocaleStore.LocaleInfo> filterSameLanguageAndCountry( Set<LocaleStore.LocaleInfo> newLocaleList, Set<LocaleStore.LocaleInfo> existingLocaleList) { Set<LocaleStore.LocaleInfo> result = new HashSet<>(newLocaleList.size()); for (LocaleStore.LocaleInfo appLocaleInfo : newLocaleList) { boolean same = false; Locale appLocale = appLocaleInfo.getLocale(); for (LocaleStore.LocaleInfo localeInfo : existingLocaleList) { Locale suggested = localeInfo.getLocale(); if (appLocale.getLanguage().equals(suggested.getLanguage()) && appLocale.getCountry().equals(suggested.getCountry())) { same = true; break; } } if (!same) { result.add(appLocaleInfo); } } return result; } private boolean existsInAppOrIme(Locale locale) { boolean existInApp = mAllAppActiveLocales.stream().anyMatch( localeInfo -> localeInfo.getLocale().equals(locale)); if (existInApp) { return true; } else { return mImeLocales.stream().anyMatch( localeInfo -> localeInfo.getLocale().equals(locale)); } } private Set<LocaleStore.LocaleInfo> filterSupportedLocales( Set<LocaleStore.LocaleInfo> suggestedLocales, HashSet<Locale> appSupportedLocales) { Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>(); for(LocaleStore.LocaleInfo li: systemLocaleList) { for (LocaleStore.LocaleInfo li : suggestedLocales) { if (appSupportedLocales.contains(li.getLocale())) { filteredList.add(li); } else { Loading core/java/com/android/internal/app/AppLocaleStore.java +10 −4 Original line number Diff line number Diff line Loading @@ -30,7 +30,10 @@ import java.util.HashSet; import java.util.Locale; import java.util.stream.Collectors; class AppLocaleStore { /** * A class used to access an application's supporting locales. */ public class AppLocaleStore { private static final String TAG = AppLocaleStore.class.getSimpleName(); public static AppLocaleResult getAppSupportedLocales( Loading Loading @@ -148,8 +151,11 @@ class AppLocaleStore { return false; } static class AppLocaleResult { enum LocaleStatus { /** * A class used to store an application's supporting locales. */ public static class AppLocaleResult { public enum LocaleStatus { UNKNOWN_FAILURE, NO_SUPPORTED_LANGUAGE_IN_APP, ASSET_LOCALE_IS_EMPTY, Loading @@ -158,7 +164,7 @@ class AppLocaleStore { } LocaleStatus mLocaleStatus; HashSet<Locale> mAppSupportedLocales; public HashSet<Locale> mAppSupportedLocales; public AppLocaleResult(LocaleStatus localeStatus, HashSet<Locale> appSupportedLocales) { this.mLocaleStatus = localeStatus; Loading core/java/com/android/internal/app/LocaleStore.java +47 −5 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.os.LocaleList; import android.provider.Settings; import android.telephony.TelephonyManager; import android.util.Log; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.VisibleForTesting; Loading @@ -45,10 +46,11 @@ public class LocaleStore { @VisibleForTesting static final int SUGGESTION_TYPE_SIM = 1 << 0; @VisibleForTesting static final int SUGGESTION_TYPE_CFG = 1 << 1; // Only for per-app language picker private static final int SUGGESTION_TYPE_CURRENT = 1 << 2; @VisibleForTesting static final int SUGGESTION_TYPE_CURRENT = 1 << 2; // Only for per-app language picker private static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3; @VisibleForTesting static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3; // Only for per-app language picker @VisibleForTesting static final int SUGGESTION_TYPE_OTHER_APP_LANGUAGE = 1 << 4; private final Locale mLocale; private final Locale mParent; private final String mId; Loading Loading @@ -259,7 +261,16 @@ public class LocaleStore { } } public static LocaleInfo getAppCurrentLocaleInfo(Context context, String appPackageName) { /** * Get the application's activated locale. * * @param context UI activity's context. * @param appPackageName The application's package name. * @param isAppSelected True if the application is selected in the UI; false otherwise. * @return A LocaleInfo with the application's activated locale. */ public static LocaleInfo getAppActivatedLocaleInfo(Context context, String appPackageName, boolean isAppSelected) { if (appPackageName == null) { return null; } Loading @@ -272,7 +283,11 @@ public class LocaleStore { if (locale != null) { LocaleInfo localeInfo = new LocaleInfo(locale); if (isAppSelected) { localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CURRENT; } else { localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE; } localeInfo.mIsTranslated = true; return localeInfo; } Loading @@ -282,6 +297,24 @@ public class LocaleStore { return null; } /** * Transform IME's language tag to LocaleInfo. * * @param list A list which includes IME's subtype. * @return A LocaleInfo set which includes IME's language tags. */ public static Set<LocaleInfo> transformImeLanguageTagToLocaleInfo( List<InputMethodSubtype> list) { Set<LocaleInfo> imeLocales = new HashSet<>(); for (InputMethodSubtype subtype : list) { LocaleInfo localeInfo = new LocaleInfo(subtype.getLanguageTag()); localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE; localeInfo.mIsTranslated = true; imeLocales.add(localeInfo); } return imeLocales; } /** * Returns a list of system languages with LocaleInfo */ Loading Loading @@ -458,4 +491,13 @@ public class LocaleStore { } return result; } /** * API for testing. */ @UnsupportedAppUsage @VisibleForTesting public static LocaleInfo fromLocale(Locale locale) { return new LocaleInfo(locale); } } tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java 0 → 100644 +170 −0 File added.Preview size limit exceeded, changes collapsed. Show changes tests/Internal/src/com/android/internal/app/LocaleStoreTest.java 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.app; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.app.LocaleStore.LocaleInfo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.List; import java.util.Set; /** * Unit tests for the {@link LocaleStore}. */ @SmallTest @RunWith(AndroidJUnit4.class) public class LocaleStoreTest { @Before public void setUp() { } @Test public void testTransformImeLanguageTagToLocaleInfo() { List<InputMethodSubtype> list = List.of( new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(), new InputMethodSubtypeBuilder().setLanguageTag("zh-TW").build(), new InputMethodSubtypeBuilder().setLanguageTag("ja-JP").build()); Set<LocaleInfo> localeSet = LocaleStore.transformImeLanguageTagToLocaleInfo(list); Set<String> expectedLanguageTag = Set.of("en-US", "zh-TW", "ja-JP"); assertEquals(localeSet.size(), expectedLanguageTag.size()); for (LocaleInfo info : localeSet) { assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE); assertTrue(expectedLanguageTag.contains(info.getId())); } } } Loading
core/java/com/android/internal/app/AppLocaleCollector.java +228 −30 Original line number Diff line number Diff line Loading @@ -19,49 +19,184 @@ package com.android.internal.app; import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET; import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG; import android.app.LocaleManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.LocaleList; import android.os.SystemProperties; import android.provider.Settings; import android.util.Log; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.VisibleForTesting; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; /** The Locale data collector for per-app language. */ class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { private static final String TAG = AppLocaleCollector.class.getSimpleName(); private final Context mContext; private final String mAppPackageName; private final LocaleStore.LocaleInfo mAppCurrentLocale; private LocaleStore.LocaleInfo mAppCurrentLocale; private Set<LocaleStore.LocaleInfo> mAllAppActiveLocales; private Set<LocaleStore.LocaleInfo> mImeLocales; private static final String PROP_APP_LANGUAGE_SUGGESTION = "android.app.language.suggestion.enhanced"; private static final boolean ENABLED = true; AppLocaleCollector(Context context, String appPackageName) { public AppLocaleCollector(Context context, String appPackageName) { mContext = context; mAppPackageName = appPackageName; mAppCurrentLocale = LocaleStore.getAppCurrentLocaleInfo( mContext, mAppPackageName); } @VisibleForTesting public LocaleStore.LocaleInfo getAppCurrentLocale() { return LocaleStore.getAppActivatedLocaleInfo(mContext, mAppPackageName, true); } /** * Get all applications' activated locales. * @return A set which includes all applications' activated LocaleInfo. */ @VisibleForTesting public Set<LocaleStore.LocaleInfo> getAllAppActiveLocales() { PackageManager pm = mContext.getPackageManager(); LocaleManager lm = mContext.getSystemService(LocaleManager.class); HashSet<LocaleStore.LocaleInfo> result = new HashSet<>(); if (pm != null && lm != null) { HashMap<String, LocaleStore.LocaleInfo> map = new HashMap<>(); for (ApplicationInfo appInfo : pm.getInstalledApplications( PackageManager.ApplicationInfoFlags.of(0))) { LocaleStore.LocaleInfo localeInfo = LocaleStore.getAppActivatedLocaleInfo( mContext, appInfo.packageName, false); // For the locale to be added into the suggestion area, its country could not be // empty. if (localeInfo != null && localeInfo.getLocale().getCountry().length() > 0) { map.put(localeInfo.getId(), localeInfo); } } map.forEach((language, localeInfo) -> result.add(localeInfo)); } return result; } /** * Get all locales that active IME supports. * * @return A set which includes all LocaleInfo that active IME supports. */ @VisibleForTesting public Set<LocaleStore.LocaleInfo> getActiveImeLocales() { Set<LocaleStore.LocaleInfo> activeImeLocales = null; InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); if (imm != null) { InputMethodInfo activeIme = getActiveIme(imm); if (activeIme != null) { activeImeLocales = LocaleStore.transformImeLanguageTagToLocaleInfo( imm.getEnabledInputMethodSubtypeList(activeIme, true)); } } if (activeImeLocales == null) { return Set.of(); } else { return activeImeLocales.stream().filter( // For the locale to be added into the suggestion area, its country could not be // empty. info -> info.getLocale().getCountry().length() > 0).collect( Collectors.toSet()); } } private InputMethodInfo getActiveIme(InputMethodManager imm) { InputMethodInfo activeIme = null; List<InputMethodInfo> infoList = imm.getEnabledInputMethodList(); String imeId = Settings.Secure.getStringForUser(mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, mContext.getUserId()); for (InputMethodInfo method : infoList) { if (method.getId().equals(imeId)) { activeIme = method; } } return activeIme; } /** * Get the AppLocaleResult that the application supports. * @return The AppLocaleResult that the application supports. */ @VisibleForTesting public AppLocaleStore.AppLocaleResult getAppSupportedLocales() { return AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName); } /** * Get the locales that system supports excluding langTagsToIgnore. * * @param langTagsToIgnore A language set to be ignored. * @param parent The parent locale. * @param translatedOnly specified if is is only for translation. * @return A set which includes the LocaleInfo that system supports, excluding langTagsToIgnore. */ @VisibleForTesting public Set<LocaleStore.LocaleInfo> getSystemSupportedLocale(Set<String> langTagsToIgnore, LocaleStore.LocaleInfo parent, boolean translatedOnly) { return LocaleStore.getLevelLocales(mContext, langTagsToIgnore, parent, translatedOnly); } /** * Get the locales that system activates. * @return A set which includes all the locales that system activates. */ @VisibleForTesting public List<LocaleStore.LocaleInfo> getSystemCurrentLocale() { return LocaleStore.getSystemCurrentLocaleInfo(); } @Override public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) { HashSet<String> langTagsToIgnore = new HashSet<>(); if (mAppCurrentLocale != null) { langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag()); } if (SystemProperties.getBoolean(PROP_APP_LANGUAGE_SUGGESTION, ENABLED)) { // Add the locale that other App activated mAllAppActiveLocales.forEach( info -> langTagsToIgnore.add(info.getLocale().toLanguageTag())); // Add the locale that active IME enabled mImeLocales.forEach(info -> langTagsToIgnore.add(info.getLocale().toLanguageTag())); } // Add System locales LocaleList systemLangList = LocaleList.getDefault(); for (int i = 0; i < systemLangList.size(); i++) { langTagsToIgnore.add(systemLangList.get(i).toLanguageTag()); } if (mAppCurrentLocale != null) { langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag()); } return langTagsToIgnore; } @Override public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent, boolean translatedOnly, boolean isForCountryMode) { AppLocaleStore.AppLocaleResult result = AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName); if (mAppCurrentLocale == null) { mAppCurrentLocale = getAppCurrentLocale(); } if (mAllAppActiveLocales == null) { mAllAppActiveLocales = getAllAppActiveLocales(); } if (mImeLocales == null) { mImeLocales = getActiveImeLocales(); } AppLocaleStore.AppLocaleResult result = getAppSupportedLocales(); Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly); Set<LocaleStore.LocaleInfo> appLocaleList = new HashSet<>(); Set<LocaleStore.LocaleInfo> systemLocaleList; Loading @@ -71,11 +206,9 @@ class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { // Get system supported locale list if (isForCountryMode) { systemLocaleList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore, parent, translatedOnly); systemLocaleList = getSystemSupportedLocale(langTagsToIgnore, parent, translatedOnly); } else { systemLocaleList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore, null /* no parent */, translatedOnly); systemLocaleList = getSystemSupportedLocale(langTagsToIgnore, null, translatedOnly); } // Add current app locale Loading @@ -84,19 +217,46 @@ class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { } // Add current system language into suggestion list for(LocaleStore.LocaleInfo localeInfo: LocaleStore.getSystemCurrentLocaleInfo()) { boolean isNotCurrentLocale = mAppCurrentLocale == null || !localeInfo.getLocale().equals(mAppCurrentLocale.getLocale()); if (!isForCountryMode && isNotCurrentLocale) { if (!isForCountryMode) { boolean isCurrentLocale, isInAppOrIme; for (LocaleStore.LocaleInfo localeInfo : getSystemCurrentLocale()) { isCurrentLocale = mAppCurrentLocale != null && localeInfo.getLocale().equals(mAppCurrentLocale.getLocale()); isInAppOrIme = existsInAppOrIme(localeInfo.getLocale()); if (!isCurrentLocale && !isInAppOrIme) { appLocaleList.add(localeInfo); } } } // Add the languages that included in system supported locale // Add the languages that are included in system supported locale Set<LocaleStore.LocaleInfo> suggestedSet = null; if (shouldShowList) { appLocaleList.addAll(filterTheLanguagesNotIncludedInSystemLocale( systemLocaleList, result.mAppSupportedLocales)); appLocaleList.addAll(filterSupportedLocales(systemLocaleList, result.mAppSupportedLocales)); suggestedSet = getSuggestedLocales(appLocaleList); } if (!isForCountryMode && SystemProperties.getBoolean(PROP_APP_LANGUAGE_SUGGESTION, ENABLED)) { // Add the language that other apps activate into the suggestion list. Set<LocaleStore.LocaleInfo> localeSet = filterSupportedLocales(mAllAppActiveLocales, result.mAppSupportedLocales); if (suggestedSet != null) { // Filter out the locale with the same language and country // like zh-TW vs zh-Hant-TW. localeSet = filterSameLanguageAndCountry(localeSet, suggestedSet); } appLocaleList.addAll(localeSet); suggestedSet.addAll(localeSet); // Add the language that the active IME enables into the suggestion list. localeSet = filterSupportedLocales(mImeLocales, result.mAppSupportedLocales); if (suggestedSet != null) { localeSet = filterSameLanguageAndCountry(localeSet, suggestedSet); } appLocaleList.addAll(localeSet); suggestedSet.addAll(localeSet); } // Add "system language" option Loading @@ -117,12 +277,50 @@ class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { return true; } private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotIncludedInSystemLocale( Set<LocaleStore.LocaleInfo> systemLocaleList, private Set<LocaleStore.LocaleInfo> getSuggestedLocales(Set<LocaleStore.LocaleInfo> localeSet) { return localeSet.stream().filter(localeInfo -> localeInfo.isSuggested()).collect( Collectors.toSet()); } private Set<LocaleStore.LocaleInfo> filterSameLanguageAndCountry( Set<LocaleStore.LocaleInfo> newLocaleList, Set<LocaleStore.LocaleInfo> existingLocaleList) { Set<LocaleStore.LocaleInfo> result = new HashSet<>(newLocaleList.size()); for (LocaleStore.LocaleInfo appLocaleInfo : newLocaleList) { boolean same = false; Locale appLocale = appLocaleInfo.getLocale(); for (LocaleStore.LocaleInfo localeInfo : existingLocaleList) { Locale suggested = localeInfo.getLocale(); if (appLocale.getLanguage().equals(suggested.getLanguage()) && appLocale.getCountry().equals(suggested.getCountry())) { same = true; break; } } if (!same) { result.add(appLocaleInfo); } } return result; } private boolean existsInAppOrIme(Locale locale) { boolean existInApp = mAllAppActiveLocales.stream().anyMatch( localeInfo -> localeInfo.getLocale().equals(locale)); if (existInApp) { return true; } else { return mImeLocales.stream().anyMatch( localeInfo -> localeInfo.getLocale().equals(locale)); } } private Set<LocaleStore.LocaleInfo> filterSupportedLocales( Set<LocaleStore.LocaleInfo> suggestedLocales, HashSet<Locale> appSupportedLocales) { Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>(); for(LocaleStore.LocaleInfo li: systemLocaleList) { for (LocaleStore.LocaleInfo li : suggestedLocales) { if (appSupportedLocales.contains(li.getLocale())) { filteredList.add(li); } else { Loading
core/java/com/android/internal/app/AppLocaleStore.java +10 −4 Original line number Diff line number Diff line Loading @@ -30,7 +30,10 @@ import java.util.HashSet; import java.util.Locale; import java.util.stream.Collectors; class AppLocaleStore { /** * A class used to access an application's supporting locales. */ public class AppLocaleStore { private static final String TAG = AppLocaleStore.class.getSimpleName(); public static AppLocaleResult getAppSupportedLocales( Loading Loading @@ -148,8 +151,11 @@ class AppLocaleStore { return false; } static class AppLocaleResult { enum LocaleStatus { /** * A class used to store an application's supporting locales. */ public static class AppLocaleResult { public enum LocaleStatus { UNKNOWN_FAILURE, NO_SUPPORTED_LANGUAGE_IN_APP, ASSET_LOCALE_IS_EMPTY, Loading @@ -158,7 +164,7 @@ class AppLocaleStore { } LocaleStatus mLocaleStatus; HashSet<Locale> mAppSupportedLocales; public HashSet<Locale> mAppSupportedLocales; public AppLocaleResult(LocaleStatus localeStatus, HashSet<Locale> appSupportedLocales) { this.mLocaleStatus = localeStatus; Loading
core/java/com/android/internal/app/LocaleStore.java +47 −5 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.os.LocaleList; import android.provider.Settings; import android.telephony.TelephonyManager; import android.util.Log; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.VisibleForTesting; Loading @@ -45,10 +46,11 @@ public class LocaleStore { @VisibleForTesting static final int SUGGESTION_TYPE_SIM = 1 << 0; @VisibleForTesting static final int SUGGESTION_TYPE_CFG = 1 << 1; // Only for per-app language picker private static final int SUGGESTION_TYPE_CURRENT = 1 << 2; @VisibleForTesting static final int SUGGESTION_TYPE_CURRENT = 1 << 2; // Only for per-app language picker private static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3; @VisibleForTesting static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3; // Only for per-app language picker @VisibleForTesting static final int SUGGESTION_TYPE_OTHER_APP_LANGUAGE = 1 << 4; private final Locale mLocale; private final Locale mParent; private final String mId; Loading Loading @@ -259,7 +261,16 @@ public class LocaleStore { } } public static LocaleInfo getAppCurrentLocaleInfo(Context context, String appPackageName) { /** * Get the application's activated locale. * * @param context UI activity's context. * @param appPackageName The application's package name. * @param isAppSelected True if the application is selected in the UI; false otherwise. * @return A LocaleInfo with the application's activated locale. */ public static LocaleInfo getAppActivatedLocaleInfo(Context context, String appPackageName, boolean isAppSelected) { if (appPackageName == null) { return null; } Loading @@ -272,7 +283,11 @@ public class LocaleStore { if (locale != null) { LocaleInfo localeInfo = new LocaleInfo(locale); if (isAppSelected) { localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CURRENT; } else { localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE; } localeInfo.mIsTranslated = true; return localeInfo; } Loading @@ -282,6 +297,24 @@ public class LocaleStore { return null; } /** * Transform IME's language tag to LocaleInfo. * * @param list A list which includes IME's subtype. * @return A LocaleInfo set which includes IME's language tags. */ public static Set<LocaleInfo> transformImeLanguageTagToLocaleInfo( List<InputMethodSubtype> list) { Set<LocaleInfo> imeLocales = new HashSet<>(); for (InputMethodSubtype subtype : list) { LocaleInfo localeInfo = new LocaleInfo(subtype.getLanguageTag()); localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE; localeInfo.mIsTranslated = true; imeLocales.add(localeInfo); } return imeLocales; } /** * Returns a list of system languages with LocaleInfo */ Loading Loading @@ -458,4 +491,13 @@ public class LocaleStore { } return result; } /** * API for testing. */ @UnsupportedAppUsage @VisibleForTesting public static LocaleInfo fromLocale(Locale locale) { return new LocaleInfo(locale); } }
tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java 0 → 100644 +170 −0 File added.Preview size limit exceeded, changes collapsed. Show changes
tests/Internal/src/com/android/internal/app/LocaleStoreTest.java 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.app; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.app.LocaleStore.LocaleInfo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.List; import java.util.Set; /** * Unit tests for the {@link LocaleStore}. */ @SmallTest @RunWith(AndroidJUnit4.class) public class LocaleStoreTest { @Before public void setUp() { } @Test public void testTransformImeLanguageTagToLocaleInfo() { List<InputMethodSubtype> list = List.of( new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(), new InputMethodSubtypeBuilder().setLanguageTag("zh-TW").build(), new InputMethodSubtypeBuilder().setLanguageTag("ja-JP").build()); Set<LocaleInfo> localeSet = LocaleStore.transformImeLanguageTagToLocaleInfo(list); Set<String> expectedLanguageTag = Set.of("en-US", "zh-TW", "ja-JP"); assertEquals(localeSet.size(), expectedLanguageTag.size()); for (LocaleInfo info : localeSet) { assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE); assertTrue(expectedLanguageTag.contains(info.getId())); } } }