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

Commit 17b2b2d4 authored by Roozbeh Pournader's avatar Roozbeh Pournader
Browse files

Implement locale matching in LocalesList.

The algorithm tries to find the first locale in the locale list
(usually from the user preference) that somewhat matches one of the
locales supported (usually from the list of locales supported by the
system or an app).

Locales match if both their language and their likely script is the
same. Otherwise they don't.

Bug: 25800576
Change-Id: I4fbc3f44ab16d41efebbf941e94a0bb30d598f82
parent 103d61b9
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
@@ -38700,9 +38700,9 @@ package android.util {
    method public int describeContents();
    method public int describeContents();
    method public static android.util.LocaleList forLanguageTags(java.lang.String);
    method public static android.util.LocaleList forLanguageTags(java.lang.String);
    method public java.util.Locale get(int);
    method public java.util.Locale get(int);
    method public java.util.Locale getBestMatch(java.lang.String[]);
    method public static android.util.LocaleList getDefault();
    method public static android.util.LocaleList getDefault();
    method public static android.util.LocaleList getEmptyLocaleList();
    method public static android.util.LocaleList getEmptyLocaleList();
    method public java.util.Locale getFirstMatch(java.lang.String[]);
    method public java.util.Locale getPrimary();
    method public java.util.Locale getPrimary();
    method public boolean isEmpty();
    method public boolean isEmpty();
    method public int size();
    method public int size();
+1 −1
Original line number Original line Diff line number Diff line
@@ -41033,9 +41033,9 @@ package android.util {
    method public int describeContents();
    method public int describeContents();
    method public static android.util.LocaleList forLanguageTags(java.lang.String);
    method public static android.util.LocaleList forLanguageTags(java.lang.String);
    method public java.util.Locale get(int);
    method public java.util.Locale get(int);
    method public java.util.Locale getBestMatch(java.lang.String[]);
    method public static android.util.LocaleList getDefault();
    method public static android.util.LocaleList getDefault();
    method public static android.util.LocaleList getEmptyLocaleList();
    method public static android.util.LocaleList getEmptyLocaleList();
    method public java.util.Locale getFirstMatch(java.lang.String[]);
    method public java.util.Locale getPrimary();
    method public java.util.Locale getPrimary();
    method public boolean isEmpty();
    method public boolean isEmpty();
    method public int size();
    method public int size();
+60 −3
Original line number Original line Diff line number Diff line
@@ -19,6 +19,7 @@ package android.util;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.Size;
import android.icu.util.ULocale;
import android.os.Parcel;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable;


@@ -208,10 +209,66 @@ public final class LocaleList implements Parcelable {
        }
        }
    }
    }


    private static String getLikelyScript(Locale locale) {
        final String script = locale.getScript();
        if (!script.isEmpty()) {
            return script;
        } else {
            // TODO: Cache the results if this proves to be too slow
            return ULocale.addLikelySubtags(ULocale.forLocale(locale)).getScript();
        }
    }

    private static int matchScore(Locale supported, Locale desired) {
        if (supported.equals(desired)) {
            return 1;  // return early so we don't do unnecessary computation
        }
        if (!supported.getLanguage().equals(desired.getLanguage())) {
            return 0;
        }
        // There is no match if the two locales use different scripts. This will most imporantly
        // take care of traditional vs simplified Chinese.
        final String supportedScr = getLikelyScript(supported);
        final String desiredScr = getLikelyScript(desired);
        return supportedScr.equals(desiredScr) ? 1 : 0;
    }

    /**
     * Returns the first match in the locale list given an unordered array of supported locales
     * in BCP47 format.
     *
     * If the locale list is empty, null would be returned.
     */
    @Nullable
    @Nullable
    public Locale getBestMatch(String[] locales) {
    public Locale getFirstMatch(String[] supportedLocales) {
        // TODO: Fix this to actually do locale negotiation and choose the best match
        if (mList.length == 1) {  // just one locale, perhaps the most common scenario
        return getPrimary();
            return mList[0];
        }
        if (mList.length == 0) {  // empty locale list
            return null;
        }
        // TODO: Figure out what to if en-XA or ar-XB are in the locale list
        int bestIndex = Integer.MAX_VALUE;
        for (String tag : supportedLocales) {
            final Locale supportedLocale = Locale.forLanguageTag(tag);
            // We expect the average length of locale lists used for locale resolution to be
            // smaller than three, so it's OK to do this as an O(mn) algorithm.
            for (int idx = 0; idx < mList.length; idx++) {
                final int score = matchScore(supportedLocale, mList[idx]);
                if (score > 0) {
                    if (idx == 0) {  // We have a match on the first locale, which is good enough
                        return mList[0];
                    } else if (idx < bestIndex) {
                        bestIndex = idx;
                    }
                }
            }
        }
        if (bestIndex == Integer.MAX_VALUE) {  // no match was found
            return mList[0];
        } else {
            return mList[bestIndex];
        }
    }
    }


    private final static Object sLock = new Object();
    private final static Object sLock = new Object();
+1 −1
Original line number Original line Diff line number Diff line
@@ -17901,7 +17901,7 @@ public final class ActivityManagerService extends ActivityManagerNative
                    if (mSupportedSystemLocales == null) {
                    if (mSupportedSystemLocales == null) {
                        mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales();
                        mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales();
                    }
                    }
                    final Locale locale = values.getLocales().getBestMatch(mSupportedSystemLocales);
                    final Locale locale = values.getLocales().getFirstMatch(mSupportedSystemLocales);
                    SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
                    SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
                    mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
                    mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
                            locale));
                            locale));