All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.internal.telephony.util.LocaleUtils Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2019 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.telephony.util;

import android.content.Context;
import android.icu.util.ULocale;
import android.text.TextUtils;

import com.android.internal.telephony.MccTable;
import com.android.telephony.Rlog;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * This class provides various util functions about Locale.
 */
public class LocaleUtils {

    private static final String LOG_TAG = "LocaleUtils";

    /**
     * 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 simLanguage) {
        boolean hasSimLanguage = !TextUtils.isEmpty(simLanguage);
        String language = hasSimLanguage ? simLanguage : defaultLanguageForMcc(mcc);
        String country = MccTable.countryCodeForMcc(mcc);

        Rlog.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 && hasSimLanguage) {
            language = defaultLanguageForMcc(mcc);
            Rlog.d(LOG_TAG, "[retry ] getLocaleFromMcc(" + language + ", " + country + ", " + mcc);
            return getLocaleForLanguageCountry(context, language, country);
        }

        return locale;
    }

    /**
     * Return Locale for the language and country or null if no good match.
     *
     * @param context Context to act on.
     * @param language Two character language code desired
     * @param country Two character country code desired
     *
     * @return Locale or null if no appropriate value
     */
    private static Locale getLocaleForLanguageCountry(Context context, String language,
                                                      String country) {
        if (language == null) {
            Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: skipping no language");
            return null; // no match possible
        }
        if (country == null) {
            country = ""; // The Locale constructor throws if passed null.
        }

        final Locale target = new Locale(language, country);
        try {
            String[] localeArray = context.getAssets().getLocales();
            List locales = new ArrayList<>(Arrays.asList(localeArray));

            // Even in developer mode, you don't want the pseudolocales.
            locales.remove("ar-XB");
            locales.remove("en-XA");

            List languageMatches = new ArrayList<>();
            for (String locale : locales) {
                final Locale l = Locale.forLanguageTag(locale.replace('_', '-'));

                // Only consider locales with both language and country.
                if (l == null || "und".equals(l.getLanguage())
                        || l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
                    continue;
                }
                if (l.getLanguage().equals(target.getLanguage())) {
                    // If we got a perfect match, we're done.
                    if (l.getCountry().equals(target.getCountry())) {
                        Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: got perfect match: "
                                + l.toLanguageTag());
                        return l;
                    }

                    // We've only matched the language, not the country.
                    languageMatches.add(l);
                }
            }

            if (languageMatches.isEmpty()) {
                Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: no locales for language " + language);
                return null;
            }

            Locale bestMatch = lookupFallback(target, languageMatches);
            if (bestMatch != null) {
                Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: got a fallback match: "
                        + bestMatch.toLanguageTag());
                return bestMatch;
            } else {
                // If a locale is "translated", it is selectable in setup wizard, and can therefore
                // be considered a valid result for this method.
                if (!TextUtils.isEmpty(target.getCountry())) {
                    if (isTranslated(context, target)) {
                        Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: "
                                + "target locale is translated: " + target);
                        return target;
                    }
                }

                // 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.
                Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: got language-only match: "
                        + language);
                return languageMatches.get(0);
            }
        } catch (Exception e) {
            Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: exception", e);
        }

        return null;
    }

    /**
     * Given a GSM Mobile Country Code, returns
     * an ISO 2-3 character language code if available.
     * Returns null if unavailable.
     */
    public static String defaultLanguageForMcc(int mcc) {
        MccTable.MccEntry entry = MccTable.entryForMcc(mcc);
        if (entry == null) {
            Rlog.d(LOG_TAG, "defaultLanguageForMcc(" + mcc + "): no country for mcc");
            return null;
        }

        final String country = entry.mIso;

        // Choose English as the default language for India.
        if ("in".equals(country)) {
            return "en";
        }

        // Ask CLDR for the language this country uses...
        ULocale likelyLocale = ULocale.addLikelySubtags(new ULocale("und", country));
        String likelyLanguage = likelyLocale.getLanguage();
        Rlog.d(LOG_TAG, "defaultLanguageForMcc(" + mcc + "): country " + country + " uses "
                + likelyLanguage);
        return likelyLanguage;
    }

    private static boolean isTranslated(Context context, Locale targetLocale) {
        ULocale fullTargetLocale = ULocale.addLikelySubtags(ULocale.forLocale(targetLocale));
        String language = fullTargetLocale.getLanguage();
        String script = fullTargetLocale.getScript();

        for (String localeId : context.getAssets().getLocales()) {
            ULocale fullLocale = ULocale.addLikelySubtags(new ULocale(localeId));
            if (language.equals(fullLocale.getLanguage())
                    && script.equals(fullLocale.getScript())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Finds a suitable locale among {@code candidates} to use as the fallback locale for
     * {@code target}. This looks through the list of {@link MccTable#FALLBACKS},
     * and follows the chain until a locale in {@code candidates} is found.
     * This function assumes that {@code target} is not in {@code candidates}.
     *
     * 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.
     *
     * @return The fallback locale or {@code null} if there is no suitable fallback defined in the
     *         lookup.
     */
    private static Locale lookupFallback(Locale target, List candidates) {
        Locale fallback = target;
        while ((fallback = MccTable.FALLBACKS.get(fallback)) != null) {
            if (candidates.contains(fallback)) {
                return fallback;
            }
        }

        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy