From 17b2b2d45854b56de7aa6d1581526e45991274d4 Mon Sep 17 00:00:00 2001 From: Roozbeh Pournader Date: Thu, 19 Nov 2015 22:32:36 -0800 Subject: 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 --- api/current.txt | 2 +- api/system-current.txt | 2 +- core/java/android/util/LocaleList.java | 63 ++++++++++++++++++++-- .../android/server/am/ActivityManagerService.java | 2 +- 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/api/current.txt b/api/current.txt index fdcda2b35c01..0abda561fa23 100644 --- a/api/current.txt +++ b/api/current.txt @@ -38700,9 +38700,9 @@ package android.util { method public int describeContents(); method public static android.util.LocaleList forLanguageTags(java.lang.String); 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 getEmptyLocaleList(); + method public java.util.Locale getFirstMatch(java.lang.String[]); method public java.util.Locale getPrimary(); method public boolean isEmpty(); method public int size(); diff --git a/api/system-current.txt b/api/system-current.txt index a4e982141785..dd14caadd6da 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -41033,9 +41033,9 @@ package android.util { method public int describeContents(); method public static android.util.LocaleList forLanguageTags(java.lang.String); 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 getEmptyLocaleList(); + method public java.util.Locale getFirstMatch(java.lang.String[]); method public java.util.Locale getPrimary(); method public boolean isEmpty(); method public int size(); diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java index b2ee045db451..1becfb4a0e23 100644 --- a/core/java/android/util/LocaleList.java +++ b/core/java/android/util/LocaleList.java @@ -19,6 +19,7 @@ package android.util; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.icu.util.ULocale; import android.os.Parcel; 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 - public Locale getBestMatch(String[] locales) { - // TODO: Fix this to actually do locale negotiation and choose the best match - return getPrimary(); + public Locale getFirstMatch(String[] supportedLocales) { + if (mList.length == 1) { // just one locale, perhaps the most common scenario + 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(); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 507233f4920d..e4a6f3c7c96d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -17901,7 +17901,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (mSupportedSystemLocales == null) { 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()); mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG, locale)); -- cgit v1.2.3-59-g8ed1b