0023a57eea
Change-Id: I4a834549c2ea96ac30d7e2cb692d69af219cab1a
259 lines
12 KiB
Java
259 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2011 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.inputmethod.latin;
|
|
|
|
import android.content.res.Configuration;
|
|
import android.content.res.Resources;
|
|
import android.text.TextUtils;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Locale;
|
|
|
|
/**
|
|
* A class to help with handling Locales in string form.
|
|
*
|
|
* This file has the same meaning and features (and shares all of its code) with
|
|
* the one in the dictionary pack. They need to be kept synchronized; for any
|
|
* update/bugfix to this file, consider also updating/fixing the version in the
|
|
* dictionary pack.
|
|
*/
|
|
public class LocaleUtils {
|
|
private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
|
|
private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
|
|
|
|
private LocaleUtils() {
|
|
// Intentional empty constructor for utility class.
|
|
}
|
|
|
|
// Locale match level constants.
|
|
// A higher level of match is guaranteed to have a higher numerical value.
|
|
// Some room is left within constants to add match cases that may arise necessary
|
|
// in the future, for example differentiating between the case where the countries
|
|
// are both present and different, and the case where one of the locales does not
|
|
// specify the countries. This difference is not needed now.
|
|
|
|
// Nothing matches.
|
|
public static final int LOCALE_NO_MATCH = 0;
|
|
// The languages matches, but the country are different. Or, the reference locale requires a
|
|
// country and the tested locale does not have one.
|
|
public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3;
|
|
// The languages and country match, but the variants are different. Or, the reference locale
|
|
// requires a variant and the tested locale does not have one.
|
|
public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6;
|
|
// The required locale is null or empty so it will accept anything, and the tested locale
|
|
// is non-null and non-empty.
|
|
public static final int LOCALE_ANY_MATCH = 10;
|
|
// The language matches, and the tested locale specifies a country but the reference locale
|
|
// does not require one.
|
|
public static final int LOCALE_LANGUAGE_MATCH = 15;
|
|
// The language and the country match, and the tested locale specifies a variant but the
|
|
// reference locale does not require one.
|
|
public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20;
|
|
// The compared locales are fully identical. This is the best match level.
|
|
public static final int LOCALE_FULL_MATCH = 30;
|
|
|
|
// The level at which a match is "normally" considered a locale match with standard algorithms.
|
|
// Don't use this directly, use #isMatch to test.
|
|
private static final int LOCALE_MATCH = LOCALE_ANY_MATCH;
|
|
|
|
// Make this match the maximum match level. If this evolves to have more than 2 digits
|
|
// when written in base 10, also adjust the getMatchLevelSortedString method.
|
|
private static final int MATCH_LEVEL_MAX = 30;
|
|
|
|
/**
|
|
* Return how well a tested locale matches a reference locale.
|
|
*
|
|
* This will check the tested locale against the reference locale and return a measure of how
|
|
* a well it matches the reference. The general idea is that the tested locale has to match
|
|
* every specified part of the required locale. A full match occur when they are equal, a
|
|
* partial match when the tested locale agrees with the reference locale but is more specific,
|
|
* and a difference when the tested locale does not comply with all requirements from the
|
|
* reference locale.
|
|
* In more detail, if the reference locale specifies at least a language and the testedLocale
|
|
* does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the
|
|
* reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH
|
|
* if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and
|
|
* tested locale agree on the language, but not on the country,
|
|
* LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country,
|
|
* and LOCALE_LANGUAGE_MATCH otherwise.
|
|
* If they agree on both the language and the country, but not on the variant,
|
|
* LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale
|
|
* specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches,
|
|
* LOCALE_FULL_MATCH is returned.
|
|
* Examples:
|
|
* en <=> en_US => LOCALE_LANGUAGE_MATCH
|
|
* en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER
|
|
* en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER
|
|
* en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH
|
|
* sp_US <=> en_US => LOCALE_NO_MATCH
|
|
* de <=> de => LOCALE_FULL_MATCH
|
|
* en_US <=> en_US => LOCALE_FULL_MATCH
|
|
* "" <=> en_US => LOCALE_ANY_MATCH
|
|
*
|
|
* @param referenceLocale the reference locale to test against.
|
|
* @param testedLocale the locale to test.
|
|
* @return a constant that measures how well the tested locale matches the reference locale.
|
|
*/
|
|
public static int getMatchLevel(String referenceLocale, String testedLocale) {
|
|
if (TextUtils.isEmpty(referenceLocale)) {
|
|
return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH;
|
|
}
|
|
if (null == testedLocale) return LOCALE_NO_MATCH;
|
|
String[] referenceParams = referenceLocale.split("_", 3);
|
|
String[] testedParams = testedLocale.split("_", 3);
|
|
// By spec of String#split, [0] cannot be null and length cannot be 0.
|
|
if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH;
|
|
switch (referenceParams.length) {
|
|
case 1:
|
|
return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH;
|
|
case 2:
|
|
if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
|
|
if (!referenceParams[1].equals(testedParams[1]))
|
|
return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
|
|
if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH;
|
|
return LOCALE_FULL_MATCH;
|
|
case 3:
|
|
if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
|
|
if (!referenceParams[1].equals(testedParams[1]))
|
|
return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
|
|
if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
|
|
if (!referenceParams[2].equals(testedParams[2]))
|
|
return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
|
|
return LOCALE_FULL_MATCH;
|
|
}
|
|
// It should be impossible to come here
|
|
return LOCALE_NO_MATCH;
|
|
}
|
|
|
|
/**
|
|
* Return a string that represents this match level, with better matches first.
|
|
*
|
|
* The strings are sorted in lexicographic order: a better match will always be less than
|
|
* a worse match when compared together.
|
|
*/
|
|
public static String getMatchLevelSortedString(int matchLevel) {
|
|
// This works because the match levels are 0~99 (actually 0~30)
|
|
// Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel
|
|
return String.format("%02d", MATCH_LEVEL_MAX - matchLevel);
|
|
}
|
|
|
|
/**
|
|
* Find out whether a match level should be considered a match.
|
|
*
|
|
* This method takes a match level as returned by the #getMatchLevel method, and returns whether
|
|
* it should be considered a match in the usual sense with standard Locale functions.
|
|
*
|
|
* @param level the match level, as returned by getMatchLevel.
|
|
* @return whether this is a match or not.
|
|
*/
|
|
public static boolean isMatch(int level) {
|
|
return LOCALE_MATCH <= level;
|
|
}
|
|
|
|
static final Object sLockForRunInLocale = new Object();
|
|
|
|
public abstract static class RunInLocale<T> {
|
|
protected abstract T job(Resources res);
|
|
|
|
/**
|
|
* Execute {@link #job(Resources)} method in specified system locale exclusively.
|
|
*
|
|
* @param res the resources to use. Pass current resources.
|
|
* @param newLocale the locale to change to
|
|
* @return the value returned from {@link #job(Resources)}.
|
|
*/
|
|
public T runInLocale(final Resources res, final Locale newLocale) {
|
|
synchronized (sLockForRunInLocale) {
|
|
final Configuration conf = res.getConfiguration();
|
|
final Locale oldLocale = conf.locale;
|
|
try {
|
|
if (newLocale != null && !newLocale.equals(oldLocale)) {
|
|
conf.locale = newLocale;
|
|
res.updateConfiguration(conf, null);
|
|
}
|
|
return job(res);
|
|
} finally {
|
|
if (newLocale != null && !newLocale.equals(oldLocale)) {
|
|
conf.locale = oldLocale;
|
|
res.updateConfiguration(conf, null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap();
|
|
|
|
/**
|
|
* Creates a locale from a string specification.
|
|
*/
|
|
public static Locale constructLocaleFromString(final String localeStr) {
|
|
if (localeStr == null)
|
|
return null;
|
|
synchronized (sLocaleCache) {
|
|
if (sLocaleCache.containsKey(localeStr))
|
|
return sLocaleCache.get(localeStr);
|
|
Locale retval = null;
|
|
String[] localeParams = localeStr.split("_", 3);
|
|
if (localeParams.length == 1) {
|
|
retval = new Locale(localeParams[0]);
|
|
} else if (localeParams.length == 2) {
|
|
retval = new Locale(localeParams[0], localeParams[1]);
|
|
} else if (localeParams.length == 3) {
|
|
retval = new Locale(localeParams[0], localeParams[1], localeParams[2]);
|
|
}
|
|
if (retval != null) {
|
|
sLocaleCache.put(localeStr, retval);
|
|
}
|
|
return retval;
|
|
}
|
|
}
|
|
|
|
public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
|
|
if (TextUtils.isEmpty(str)) {
|
|
return EMPTY_LT_HASH_MAP;
|
|
}
|
|
final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER);
|
|
final int N = ss.length;
|
|
if (N < 2 || N % 2 != 0) {
|
|
return EMPTY_LT_HASH_MAP;
|
|
}
|
|
final HashMap<String, Long> retval = CollectionUtils.newHashMap();
|
|
for (int i = 0; i < N / 2; ++i) {
|
|
final String localeStr = ss[i * 2];
|
|
final long time = Long.valueOf(ss[i * 2 + 1]);
|
|
retval.put(localeStr, time);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) {
|
|
if (map == null || map.isEmpty()) {
|
|
return "";
|
|
}
|
|
final StringBuilder builder = new StringBuilder();
|
|
for (String localeStr : map.keySet()) {
|
|
if (builder.length() > 0) {
|
|
builder.append(LOCALE_AND_TIME_STR_SEPARATER);
|
|
}
|
|
final Long time = map.get(localeStr);
|
|
builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER);
|
|
builder.append(String.valueOf(time));
|
|
}
|
|
return builder.toString();
|
|
}
|
|
}
|