Make canada use en-US by default if the LI is "en"
Narayan Kamath [Wed, 29 Jul 2015 10:50:05 +0000 (11:50 +0100)]
Introduce the notion of a fallback mapping to choose a fallback
locale with the same language if an exact match is found. "en-CA"
will fall back to "en-US" under this arrangement. If no fallback
is specified, we continue to arbitrarily choose the first locale
in the list where the language matches.

Also, take the SIM records (EF-LI, EF-PL) into consideration when
matching locales. If we can't find a match based on the sim language,
we fall back to the "likely" language based on ICU data.

bug: 22684963

Change-Id: I8259fd16fc45bbaf2632256cd6f29b46317a5e23

src/java/com/android/internal/telephony/MccTable.java

index 147e2bf..cccd142 100644 (file)
@@ -24,7 +24,6 @@ import android.net.wifi.WifiManager;
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -32,8 +31,11 @@ import android.util.Slog;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+
 import libcore.icu.ICU;
 import libcore.icu.TimeZoneNames;
 
@@ -219,6 +221,41 @@ public final class MccTable {
     }
 
     /**
+     * Maps a given locale to a fallback locale that approximates it. This is a hack.
+     */
+    private static final Map<Locale, Locale> FALLBACKS = new HashMap<Locale, Locale>();
+
+    static {
+        FALLBACKS.put(Locale.CANADA, Locale.US);
+    }
+
+    /**
+     * Find the best match we actually have a localization for. This function assumes we
+     * couldn't find an exact match.
+     *
+     * TODO: This should really follow the CLDR chain of parent locales! That might be a bit
+     * of a problem because we don't really have an en-001 locale on android.
+     */
+    private static Locale chooseBestFallback(Locale target, List<Locale> candidates) {
+        if (candidates.isEmpty()) {
+            return null;
+        }
+
+        Locale fallback = target;
+        while ((fallback = FALLBACKS.get(fallback)) != null) {
+            if (candidates.contains(fallback)) {
+                return fallback;
+            }
+        }
+
+        // Somewhat arbitrarily take the first locale for the language,
+        // unless we get a perfect match later. Note that these come back in no
+        // particular order, so there's no reason to think the first match is
+        // a particularly good match.
+        return candidates.get(0);
+    }
+
+    /**
      * Return Locale for the language and country or null if no good match.
      *
      * @param context Context to act on.
@@ -237,8 +274,6 @@ public final class MccTable {
             country = ""; // The Locale constructor throws if passed null.
         }
 
-        // Find the best match we actually have a localization for.
-        // TODO: this should really follow the CLDR chain of parent locales!
         final Locale target = new Locale(language, country);
         try {
             String[] localeArray = context.getAssets().getLocales();
@@ -248,7 +283,7 @@ public final class MccTable {
             locales.remove("ar-XB");
             locales.remove("en-XA");
 
-            Locale firstMatch = null;
+            List<Locale> languageMatches = new ArrayList<>();
             for (String locale : locales) {
                 final Locale l = Locale.forLanguageTag(locale.replace('_', '-'));
 
@@ -264,22 +299,17 @@ public final class MccTable {
                                l.toLanguageTag());
                         return l;
                     }
-                    // Otherwise somewhat arbitrarily take the first locale for the language,
-                    // unless we get a perfect match later. Note that these come back in no
-                    // particular order, so there's no reason to think the first match is
-                    // a particularly good match.
-                    if (firstMatch == null) {
-                        firstMatch = l;
-                    }
+
+                    // We've only matched the language, not the country.
+                    languageMatches.add(l);
                 }
             }
 
-            // We didn't find the exact locale, so return whichever locale we saw first where
-            // the language matched (if any).
-            if (firstMatch != null) {
+            Locale bestMatch = chooseBestFallback(target, languageMatches);
+            if (bestMatch != null) {
                 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: got a language-only match: " +
-                       firstMatch.toLanguageTag());
-                return firstMatch;
+                       bestMatch.toLanguageTag());
+                return bestMatch;
             } else {
                 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: no locales for language " +
                        language);
@@ -312,17 +342,29 @@ public final class MccTable {
 
     /**
      * Get Locale based on the MCC of the SIM.
+     *
      * @param context Context to act on.
      * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
+     * @param simLanguage (nullable) the language from the SIM records (if present).
      *
      * @return locale for the mcc or null if none
      */
-    public static Locale getLocaleFromMcc(Context context, int mcc) {
-        String language = MccTable.defaultLanguageForMcc(mcc);
+    public static Locale getLocaleFromMcc(Context context, int mcc, String simLanguage) {
+        String language = (simLanguage == null) ? MccTable.defaultLanguageForMcc(mcc) : simLanguage;
         String country = MccTable.countryCodeForMcc(mcc);
 
-        Slog.d(LOG_TAG, "getLocaleFromMcc to " + language + "_" + country + " mcc=" + mcc);
-        return getLocaleForLanguageCountry(context, language, country);
+        Slog.d(LOG_TAG, "getLocaleFromMcc(" + language + ", " + country + ", " + mcc);
+        final Locale locale = getLocaleForLanguageCountry(context, language, country);
+
+        // If we couldn't find a locale that matches the SIM language, give it a go again
+        // with the "likely" language for the given country.
+        if (locale == null && simLanguage != null) {
+            language = MccTable.defaultLanguageForMcc(mcc);
+            Slog.d(LOG_TAG, "[retry ] getLocaleFromMcc(" + language + ", " + country + ", " + mcc);
+            return getLocaleForLanguageCountry(context, null, country);
+        }
+
+        return locale;
     }
 
     /**