Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e2b82c89 authored by Tom Hsu's avatar Tom Hsu Committed by Android (Google) Code Review
Browse files

Merge "Limit locales in launched locale picker activity."

parents d42f2e9c 90729ed3
Loading
Loading
Loading
Loading
+19 −2
Original line number Diff line number Diff line
@@ -1060,14 +1060,31 @@ public final class Settings {
     * In some cases, a matching Activity may not exist, so ensure you
     * safeguard against this.
     * <p>
     * Input: Nothing.
     * <p>
     * Input: The optional {@code #EXTRA_EXPLICIT_LOCALES} with language tags that contains locales
     * to limit available locales. This is only supported when device is under demo mode.
     * If intent does not contain this extra, it will show system supported locale list.
     * <br/>
     * If {@code #EXTRA_EXPLICIT_LOCALES} contain a unsupported locale, it will still show this
     * locale on list, but may not be supported by the devcie.
     *
     * Output: Nothing.
     */
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_LOCALE_SETTINGS =
            "android.settings.LOCALE_SETTINGS";
    /**
     * Activity Extra: Show explicit locales in launched locale picker activity.
     *
     * This can be passed as an extra field in an Activity Intent with one or more language tags
     * as a {@link LocaleList}. This must be passed as an extra field to the
     * {@link #ACTION_LOCALE_SETTINGS}.
     *
     * @hide
     */
    public static final String EXTRA_EXPLICIT_LOCALES =
            "android.provider.extra.EXPLICIT_LOCALES";
    /**
     * Activity Action: Show settings to allow configuration of per application locale.
     * <p>
+11 −4
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.content.Context;
import android.os.Bundle;
import android.os.LocaleList;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
@@ -102,15 +103,21 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O

    public static LocalePickerWithRegion createLanguagePicker(Context context,
            LocaleSelectedListener listener, boolean translatedOnly) {
        return createLanguagePicker(context, listener, translatedOnly, null, null);
        return createLanguagePicker(context, listener, translatedOnly, null, null, null);
    }

    public static LocalePickerWithRegion createLanguagePicker(Context context,
            LocaleSelectedListener listener, boolean translatedOnly, String appPackageName,
            OnActionExpandListener onActionExpandListener) {
            LocaleSelectedListener listener, boolean translatedOnly, LocaleList explicitLocales) {
        return createLanguagePicker(context, listener, translatedOnly, explicitLocales, null, null);
    }

    /** Creates language picker UI */
    public static LocalePickerWithRegion createLanguagePicker(Context context,
            LocaleSelectedListener listener, boolean translatedOnly, LocaleList explicitLocales,
            String appPackageName, OnActionExpandListener onActionExpandListener) {
        LocaleCollectorBase localePickerController;
        if (TextUtils.isEmpty(appPackageName)) {
            localePickerController = new SystemLocaleCollector(context);
            localePickerController = new SystemLocaleCollector(context, explicitLocales);
        } else {
            localePickerController = new AppLocaleCollector(context, appPackageName);
        }
+81 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.content.Context;
import android.os.LocaleList;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;

@@ -29,6 +30,7 @@ import com.android.internal.annotations.VisibleForTesting;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IllformedLocaleException;
@@ -106,6 +108,9 @@ public class LocaleStore {
            return mParent;
        }

        /**
         * TODO: This method may rename to be more generic i.e. toLanguageTag().
         */
        @UnsupportedAppUsage
        public String getId() {
            return mId;
@@ -456,11 +461,30 @@ public class LocaleStore {
    @UnsupportedAppUsage
    public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
            LocaleInfo parent, boolean translatedOnly) {
        return getLevelLocales(context, ignorables, parent, translatedOnly, null);
    }

    /**
     * @param explicitLocales Indicates only the locales within this list should be shown in the
     *                       locale picker.
     *
     * Returns a list of locales for language or region selection.
     * If the parent is null, then it is the language list.
     * If it is not null, then the list will contain all the locales that belong to that parent.
     * Example: if the parent is "ar", then the region list will contain all Arabic locales.
     * (this is not language based, but language-script, so that it works for zh-Hant and so on.
     */
    public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
            LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) {
        fillCache(context);
        String parentId = parent == null ? null : parent.getId();

        HashSet<LocaleInfo> result = new HashSet<>();
        for (LocaleStore.LocaleInfo li : sLocaleCache.values()) {
        HashMap<String, LocaleInfo> supportedLcoaleInfos =
                explicitLocales == null
                        ? sLocaleCache
                        : convertExplicitLocales(explicitLocales, sLocaleCache.values());

        for (LocaleStore.LocaleInfo li : supportedLcoaleInfos.values()) {
            int level = getLevel(ignorables, li, translatedOnly);
            if (level == 2) {
                if (parent != null) { // region selection
@@ -479,6 +503,61 @@ public class LocaleStore {
        return result;
    }

    /** Converts string array of explicit locales to HashMap */
    public static HashMap<String, LocaleInfo> convertExplicitLocales(
            LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
        // Trys to find the matched locale within android supported locales. If there is no matched
        // locale, it will still keep the unsupported lcoale in list.
        // Note: This currently does not support unicode extension check.
        LocaleList localeList = matchLocaleFromSupportedLocaleList(
                explicitLocales, localeinfo);

        HashMap<String, LocaleInfo> localeInfos = new HashMap<>();
        for (int i = 0; i < localeList.size(); i++) {
            Locale locale = localeList.get(i);
            if (locale.toString().isEmpty()) {
                throw new IllformedLocaleException("Bad locale entry");
            }

            LocaleInfo li = new LocaleInfo(locale);
            if (localeInfos.containsKey(li.getId())) {
                continue;
            }
            localeInfos.put(li.getId(), li);
            Locale parent = li.getParent();
            if (parent != null) {
                String parentId = parent.toLanguageTag();
                if (!localeInfos.containsKey(parentId)) {
                    localeInfos.put(parentId, new LocaleInfo(parent));
                }
            }
        }
        return localeInfos;
    }

    private static LocaleList matchLocaleFromSupportedLocaleList(
            LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
        //TODO: Adds a function for unicode extension if needed.
        Locale[] resultLocales = new Locale[explicitLocales.size()];
        for (int i = 0; i < explicitLocales.size(); i++) {
            Locale locale = explicitLocales.get(i).stripExtensions();
            if (!TextUtils.isEmpty(locale.getCountry())) {
                for (LocaleInfo localeInfo :localeinfo) {
                    if (LocaleList.matchesLanguageAndScript(locale, localeInfo.getLocale())
                            && TextUtils.equals(locale.getCountry(),
                            localeInfo.getLocale().getCountry())) {
                        resultLocales[i] = localeInfo.getLocale();
                        continue;
                    }
                }
            }
            if (resultLocales[i] == null) {
                resultLocales[i] = locale;
            }
        }
        return new LocaleList(resultLocales);
    }

    @UnsupportedAppUsage
    public static LocaleInfo getLocaleInfo(Locale locale) {
        String id = locale.toLanguageTag();
+8 −4
Original line number Diff line number Diff line
@@ -26,9 +26,15 @@ import java.util.Set;
/** The Locale data collector for System language. */
class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
    private final Context mContext;
    private LocaleList mExplicitLocales;

    SystemLocaleCollector(Context context) {
        this(context, null);
    }

    SystemLocaleCollector(Context context, LocaleList explicitLocales) {
        mContext = context;
        mExplicitLocales = explicitLocales;
    }

    @Override
@@ -47,18 +53,16 @@ class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBas
            boolean translatedOnly, boolean isForCountryMode) {
        Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
        Set<LocaleStore.LocaleInfo> localeList;

        if (isForCountryMode) {
            localeList = LocaleStore.getLevelLocales(mContext,
                    langTagsToIgnore, parent, translatedOnly);
                    langTagsToIgnore, parent, translatedOnly, mExplicitLocales);
        } else {
            localeList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
                    null /* no parent */, translatedOnly);
                    null /* no parent */, translatedOnly, mExplicitLocales);
        }
        return localeList;
    }


    @Override
    public boolean hasSpecificPackageName() {
        return false;
+103 −8
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@
package com.android.internal.app;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import android.os.LocaleList;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;

@@ -27,23 +29,21 @@ 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.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.IllformedLocaleException;
import java.util.List;
import java.util.Locale;
import java.util.Set;

/**
 * Unit tests for the {@link LocaleStore}.
 */
/** 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(
@@ -60,4 +60,99 @@ public class LocaleStoreTest {
            assertTrue(expectedLanguageTag.contains(info.getId()));
        }
    }

    @Test
    public void convertExplicitLocales_noExplicitLcoales_returnEmptyHashMap() {
        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();

        HashMap<String, LocaleInfo> result =
                LocaleStore.convertExplicitLocales(
                        LocaleList.getEmptyLocaleList(), supportedLocale);

        assertTrue(result.isEmpty());
    }

    @Test
    public void convertExplicitLocales_hasEmptyLocale_receiveException() {
        Locale[] locales = {Locale.forLanguageTag(""), Locale.forLanguageTag("en-US")};
        LocaleList localelist = new LocaleList(locales);
        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();

        boolean isReceiveException = false;
        try {
            LocaleStore.convertExplicitLocales(localelist, supportedLocale);
        } catch (IllformedLocaleException e) {
            isReceiveException = true;
        }

        assertTrue(isReceiveException);
    }

    @Test
    public void convertExplicitLocales_hasSameLocale_returnNonSameLocales() {
        LocaleList locales = LocaleList.forLanguageTags("en-US,en-US");
        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();

        HashMap<String, LocaleInfo> result =
                LocaleStore.convertExplicitLocales(locales, supportedLocale);

        // Only has "en" and "en-US".
        assertTrue(result.size() == 2);
    }

    @Test
    public void convertExplicitLocales_hasEnUs_resultHasParentEn() {
        LocaleList locales = LocaleList.forLanguageTags("en-US,ja-JP");
        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();

        HashMap<String, LocaleInfo> result =
                LocaleStore.convertExplicitLocales(locales, supportedLocale);

        assertEquals(result.get("en").getId(), "en");
    }

    @Test
    public void convertExplicitLocales_hasZhTw_resultZhHantTw() {
        LocaleList locales = LocaleList.forLanguageTags("zh-TW,en-US,en");
        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();

        HashMap<String, LocaleInfo> result =
                LocaleStore.convertExplicitLocales(locales, supportedLocale);

        assertEquals("zh-Hant-TW", result.get("zh-Hant-TW").getId());
    }

    @Test
    public void convertExplicitLocales_nonRegularFormat_resultEmptyContry() {
        LocaleList locales = LocaleList.forLanguageTags("de-1996,de-1901");
        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();

        HashMap<String, LocaleInfo> result =
                LocaleStore.convertExplicitLocales(locales, supportedLocale);

        assertEquals("de-1996", result.get("de-1996").getId());
        assertTrue(result.get("de-1996").getLocale().getCountry().isEmpty());
    }

    @Test
    public void convertExplicitLocales_differentEnFormat() {
        LocaleList locales = LocaleList.forLanguageTags("en-Latn-US,en-US,en");
        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();

        HashMap<String, LocaleInfo> result =
                LocaleStore.convertExplicitLocales(locales, supportedLocale);

        assertEquals("en", result.get("en").getId());
        assertEquals("en-US", result.get("en-US").getId());
        assertNull(result.get("en-Latn-US"));
    }

    private ArrayList<LocaleInfo> getFakeSupportedLocales() {
        String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB"};
        ArrayList<LocaleInfo> supportedLocales = new ArrayList<>();
        for (String localeTag : locales) {
            supportedLocales.add(LocaleStore.fromLocale(Locale.forLanguageTag(localeTag)));
        }
        return supportedLocales;
    }
}