summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJay Shrauner <shrauner@google.com>2013-10-17 13:59:48 -0700
committerJay Shrauner <shrauner@google.com>2014-04-18 11:23:49 -0700
commitd4dbd063cf88e70b045607aa865b2fdb2329bf45 (patch)
tree19152c84d27e399f62f79b645205953e0b682ab2 /src
parent30268f9198a8b25aa0c9d90c0c2390f7a4bffb0d (diff)
downloadpackages_providers_ContactsProvider-d4dbd063cf88e70b045607aa865b2fdb2329bf45.zip
packages_providers_ContactsProvider-d4dbd063cf88e70b045607aa865b2fdb2329bf45.tar.gz
packages_providers_ContactsProvider-d4dbd063cf88e70b045607aa865b2fdb2329bf45.tar.bz2
Support secondary locales
Add support for tracking a secondary locale in addition to the current primary locale for CP2. Switch to using parseable ICU language tag (eg, "en-US") for locale tag written to DB. Secondary locale is set to previous locale on locale change and persisted in CP2 DB and prefs. Bug:8715226 Change-Id: Ia68397fd9118d89f3a45ac54f991f86bad42870e
Diffstat (limited to 'src')
-rw-r--r--src/com/android/providers/contacts/ContactLocaleUtils.java92
-rw-r--r--src/com/android/providers/contacts/ContactsDatabaseHelper.java25
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java79
-rw-r--r--src/com/android/providers/contacts/HanziToPinyin.java9
-rw-r--r--src/com/android/providers/contacts/LocaleSet.java253
5 files changed, 387 insertions, 71 deletions
diff --git a/src/com/android/providers/contacts/ContactLocaleUtils.java b/src/com/android/providers/contacts/ContactLocaleUtils.java
index 3d3ae82..340b6a5 100644
--- a/src/com/android/providers/contacts/ContactLocaleUtils.java
+++ b/src/com/android/providers/contacts/ContactLocaleUtils.java
@@ -22,6 +22,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.providers.contacts.HanziToPinyin.Token;
+import com.google.common.annotations.VisibleForTesting;
import java.lang.Character.UnicodeBlock;
import java.util.Arrays;
@@ -70,8 +71,9 @@ public class ContactLocaleUtils {
protected final ImmutableIndex mAlphabeticIndex;
private final int mAlphabeticIndexBucketCount;
private final int mNumberBucketIndex;
+ private final boolean mEnableSecondaryLocalePinyin;
- public ContactLocaleUtilsBase(Locale locale) {
+ public ContactLocaleUtilsBase(LocaleSet locales) {
// AlphabeticIndex.getBucketLabel() uses a binary search across
// the entire label set so care should be taken about growing this
// set too large. The following set determines for which locales
@@ -84,9 +86,14 @@ public class ContactLocaleUtils {
// Latin based alphabets. Ukrainian and Serbian are chosen for
// Cyrillic because their alphabets are complementary supersets
// of Russian.
- mAlphabeticIndex = new AlphabeticIndex(locale)
- .setMaxLabelCount(300)
- .addLabels(Locale.ENGLISH)
+ final Locale secondaryLocale = locales.getSecondaryLocale();
+ mEnableSecondaryLocalePinyin = locales.isSecondaryLocaleSimplifiedChinese();
+ AlphabeticIndex ai = new AlphabeticIndex(locales.getPrimaryLocale())
+ .setMaxLabelCount(300);
+ if (secondaryLocale != null) {
+ ai.addLabels(secondaryLocale);
+ }
+ mAlphabeticIndex = ai.addLabels(Locale.ENGLISH)
.addLabels(Locale.JAPANESE)
.addLabels(Locale.KOREAN)
.addLabels(LOCALE_THAI)
@@ -136,6 +143,13 @@ public class ContactLocaleUtils {
return mNumberBucketIndex;
}
+ /**
+ * TODO: ICU 52 AlphabeticIndex doesn't support Simplified Chinese
+ * as a secondary locale. Remove the following if that is added.
+ */
+ if (mEnableSecondaryLocalePinyin) {
+ name = HanziToPinyin.getInstance().transliterate(name);
+ }
final int bucket = mAlphabeticIndex.getBucketIndex(name);
if (bucket < 0) {
return -1;
@@ -199,8 +213,8 @@ public class ContactLocaleUtils {
private static final String JAPANESE_MISC_LABEL = "\u4ed6";
private final int mMiscBucketIndex;
- public JapaneseContactUtils(Locale locale) {
- super(locale);
+ public JapaneseContactUtils(LocaleSet locales) {
+ super(locales);
// Determine which bucket AlphabeticIndex is lumping unclassified
// Japanese characters into by looking up the bucket index for
// a representative Kanji/CJK unified ideograph (\u65e5 is the
@@ -341,8 +355,8 @@ public class ContactLocaleUtils {
*/
private static class SimplifiedChineseContactUtils
extends ContactLocaleUtilsBase {
- public SimplifiedChineseContactUtils(Locale locale) {
- super(locale);
+ public SimplifiedChineseContactUtils(LocaleSet locales) {
+ super(locales);
}
@Override
@@ -357,7 +371,7 @@ public class ContactLocaleUtils {
public static Iterator<String> getPinyinNameLookupKeys(String name) {
// TODO : Reduce the object allocation.
HashSet<String> keys = new HashSet<String>();
- ArrayList<Token> tokens = HanziToPinyin.getInstance().get(name);
+ ArrayList<Token> tokens = HanziToPinyin.getInstance().getTokens(name);
final int tokenCount = tokens.size();
final StringBuilder keyPinyin = new StringBuilder();
final StringBuilder keyInitial = new StringBuilder();
@@ -393,48 +407,49 @@ public class ContactLocaleUtils {
}
}
- private static final String CHINESE_LANGUAGE = Locale.CHINESE.getLanguage().toLowerCase();
private static final String JAPANESE_LANGUAGE = Locale.JAPANESE.getLanguage().toLowerCase();
- private static final String KOREAN_LANGUAGE = Locale.KOREAN.getLanguage().toLowerCase();
private static ContactLocaleUtils sSingleton;
- private final Locale mLocale;
- private final String mLanguage;
+ private final LocaleSet mLocales;
private final ContactLocaleUtilsBase mUtils;
- private ContactLocaleUtils(Locale locale) {
- if (locale == null) {
- mLocale = Locale.getDefault();
+ private ContactLocaleUtils(LocaleSet locales) {
+ if (locales == null) {
+ mLocales = LocaleSet.getDefault();
} else {
- mLocale = locale;
+ mLocales = locales;
}
- mLanguage = mLocale.getLanguage().toLowerCase();
- if (mLanguage.equals(JAPANESE_LANGUAGE)) {
- mUtils = new JapaneseContactUtils(mLocale);
- } else if (mLocale.equals(Locale.CHINA)) {
- mUtils = new SimplifiedChineseContactUtils(mLocale);
+ if (mLocales.isPrimaryLanguage(JAPANESE_LANGUAGE)) {
+ mUtils = new JapaneseContactUtils(mLocales);
+ } else if (mLocales.isPrimaryLocaleSimplifiedChinese()) {
+ mUtils = new SimplifiedChineseContactUtils(mLocales);
} else {
- mUtils = new ContactLocaleUtilsBase(mLocale);
+ mUtils = new ContactLocaleUtilsBase(mLocales);
}
- Log.i(TAG, "AddressBook Labels [" + mLocale.toString() + "]: "
- + getLabels().toString());
+ Log.i(TAG, "AddressBook Labels [" + mLocales.toString() + "]: "
+ + getLabels().toString());
}
- public boolean isLocale(Locale locale) {
- return mLocale.equals(locale);
+ public boolean isLocale(LocaleSet locales) {
+ return mLocales.equals(locales);
}
public static synchronized ContactLocaleUtils getInstance() {
if (sSingleton == null) {
- sSingleton = new ContactLocaleUtils(null);
+ sSingleton = new ContactLocaleUtils(LocaleSet.getDefault());
}
return sSingleton;
}
+ @VisibleForTesting
public static synchronized void setLocale(Locale locale) {
- if (sSingleton == null || !sSingleton.isLocale(locale)) {
- sSingleton = new ContactLocaleUtils(locale);
+ setLocales(new LocaleSet(locale));
+ }
+
+ public static synchronized void setLocales(LocaleSet locales) {
+ if (sSingleton == null || !sSingleton.isLocale(locales)) {
+ sSingleton = new ContactLocaleUtils(locales);
}
}
@@ -464,7 +479,7 @@ public class ContactLocaleUtils {
/**
* Determine which utility should be used for generating NameLookupKey.
- * (ie, whether we generate Pinyin lookup keys or not)
+ * (ie, whether we generate Romaji or Pinyin lookup keys or not)
*
* Hiragana and Katakana are tagged as JAPANESE; Kanji is unclassified
* and tagged as CJK. For Hiragana/Katakana names, generate Romaji
@@ -475,10 +490,17 @@ public class ContactLocaleUtils {
* b. For Simplified Chinese locale, generate Pinyin lookup keys.
*/
public Iterator<String> getNameLookupKeys(String name, int nameStyle) {
- if (nameStyle == FullNameStyle.JAPANESE &&
- !CHINESE_LANGUAGE.equals(mLanguage) &&
- !KOREAN_LANGUAGE.equals(mLanguage)) {
- return JapaneseContactUtils.getRomajiNameLookupKeys(name);
+ if (!mLocales.isPrimaryLocaleCJK()) {
+ if (mLocales.isSecondaryLocaleSimplifiedChinese()) {
+ if (nameStyle == FullNameStyle.CHINESE ||
+ nameStyle == FullNameStyle.CJK) {
+ return SimplifiedChineseContactUtils.getPinyinNameLookupKeys(name);
+ }
+ } else {
+ if (nameStyle == FullNameStyle.JAPANESE) {
+ return JapaneseContactUtils.getRomajiNameLookupKeys(name);
+ }
+ }
}
return mUtils.getNameLookupKeys(name, nameStyle);
}
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 82cf310..265846c 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -3266,9 +3266,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* Checks whether the current ICU code version matches that used to build
* the locale specific data in the ContactsDB.
*/
- public boolean needsToUpdateLocaleData(Locale locale) {
+ public boolean needsToUpdateLocaleData(LocaleSet locales) {
final String dbLocale = getProperty(DbProperties.LOCALE, "");
- if (!dbLocale.equals(locale.toString())) {
+ if (!dbLocale.equals(locales.toString())) {
return true;
}
final String curICUVersion = ICU.getIcuVersion();
@@ -3283,16 +3283,17 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
private void upgradeLocaleData(SQLiteDatabase db, boolean rebuildSqliteStats) {
- final Locale locale = Locale.getDefault();
- Log.i(TAG, "Upgrading locale data for " + locale
+ final String dbLocale = getProperty(DbProperties.LOCALE, "");
+ final LocaleSet locales = LocaleSet.getDefault();
+ Log.i(TAG, "Upgrading locale data for " + locales
+ " (ICU v" + ICU.getIcuVersion() + ")");
final long start = SystemClock.elapsedRealtime();
initializeCache(db);
- rebuildLocaleData(db, locale, rebuildSqliteStats);
+ rebuildLocaleData(db, locales, rebuildSqliteStats);
Log.i(TAG, "Locale update completed in " + (SystemClock.elapsedRealtime() - start) + "ms");
}
- private void rebuildLocaleData(SQLiteDatabase db, Locale locale, boolean rebuildSqliteStats) {
+ private void rebuildLocaleData(SQLiteDatabase db, LocaleSet locales, boolean rebuildSqliteStats) {
db.execSQL("DROP INDEX raw_contact_sort_key1_index");
db.execSQL("DROP INDEX raw_contact_sort_key2_index");
db.execSQL("DROP INDEX IF EXISTS name_lookup_index");
@@ -3306,7 +3307,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
// Update the ICU version used to generate the locale derived data
// so we can tell when we need to rebuild with new ICU versions.
setProperty(db, DbProperties.ICU_VERSION, ICU.getIcuVersion());
- setProperty(db, DbProperties.LOCALE, locale.toString());
+ setProperty(db, DbProperties.LOCALE, locales.toString());
}
/**
@@ -3314,19 +3315,19 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* nickname_lookup, name_lookup and sort keys. Invalidates the fast
* scrolling index cache.
*/
- public void setLocale(Locale locale) {
- if (!needsToUpdateLocaleData(locale)) {
+ public void setLocale(LocaleSet locales) {
+ if (!needsToUpdateLocaleData(locales)) {
return;
}
- Log.i(TAG, "Switching to locale " + locale
+ Log.i(TAG, "Switching to locale " + locales
+ " (ICU v" + ICU.getIcuVersion() + ")");
final long start = SystemClock.elapsedRealtime();
SQLiteDatabase db = getWritableDatabase();
- db.setLocale(locale);
+ db.setLocale(locales.getPrimaryLocale());
db.beginTransaction();
try {
- rebuildLocaleData(db, locale, true);
+ rebuildLocaleData(db, locales, true);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index cafd86b..e610a26 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -1406,7 +1406,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
private boolean mSyncToNetwork;
- private Locale mCurrentLocale;
+ private LocaleSet mCurrentLocales;
private int mContactsAccountCount;
private HandlerThread mBackgroundThread;
@@ -1504,6 +1504,40 @@ public class ContactsProvider2 extends AbstractContactsProvider
return true;
}
+ // Updates the locale set to reflect a new system locale.
+ private static LocaleSet updateLocaleSet(LocaleSet oldLocales, Locale newLocale) {
+ final Locale prevLocale = oldLocales.getPrimaryLocale();
+ // If primary locale is unchanged then no change to locale set.
+ if (newLocale.equals(prevLocale)) {
+ return oldLocales;
+ }
+ // Otherwise, construct a new locale set based on the new locale
+ // and the previous primary locale.
+ return new LocaleSet(newLocale, prevLocale).normalize();
+ }
+
+ private static LocaleSet getProviderPrefLocales(SharedPreferences prefs) {
+ final String providerLocaleString = prefs.getString(PREF_LOCALE, null);
+ return LocaleSet.getLocaleSet(providerLocaleString);
+ }
+
+ // Called by initForDefaultLocale. Returns an updated locale set using the
+ // current system locale.
+ private LocaleSet getLocaleSet() {
+ final Locale curLocale = getLocale();
+ if (mCurrentLocales != null) {
+ return updateLocaleSet(mCurrentLocales, curLocale);
+ }
+ // On startup need to reload the locale set from prefs for update.
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ return updateLocaleSet(getProviderPrefLocales(prefs), curLocale);
+ }
+
+ // Static routine called on startup by updateLocaleOffline.
+ private static LocaleSet getLocaleSet(SharedPreferences prefs, Locale curLocale) {
+ return updateLocaleSet(getProviderPrefLocales(prefs), curLocale);
+ }
+
/**
* (Re)allocates all locale-sensitive structures.
*/
@@ -1511,12 +1545,12 @@ public class ContactsProvider2 extends AbstractContactsProvider
Context context = getContext();
mLegacyApiSupport =
new LegacyApiSupport(context, mContactsHelper, this, mGlobalSearchSupport);
- mCurrentLocale = getLocale();
- mNameSplitter = mContactsHelper.createNameSplitter(mCurrentLocale);
+ mCurrentLocales = getLocaleSet();
+ mNameSplitter = mContactsHelper.createNameSplitter(mCurrentLocales.getPrimaryLocale());
mNameLookupBuilder = new StructuredNameLookupBuilder(mNameSplitter);
- mPostalSplitter = new PostalSplitter(mCurrentLocale);
+ mPostalSplitter = new PostalSplitter(mCurrentLocales.getPrimaryLocale());
mCommonNicknameCache = new CommonNicknameCache(mContactsHelper.getReadableDatabase());
- ContactLocaleUtils.setLocale(mCurrentLocale);
+ ContactLocaleUtils.setLocales(mCurrentLocales);
mContactAggregator = new ContactAggregator(this, mContactsHelper,
createPhotoPriorityResolver(context), mNameSplitter, mCommonNicknameCache);
mContactAggregator.setEnabled(SystemProperties.getBoolean(AGGREGATE_CONTACTS, true));
@@ -1694,20 +1728,20 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
private static boolean needsToUpdateLocaleData(SharedPreferences prefs,
- Locale locale,ContactsDatabaseHelper contactsHelper,
+ LocaleSet locales, ContactsDatabaseHelper contactsHelper,
ProfileDatabaseHelper profileHelper) {
- final String providerLocale = prefs.getString(PREF_LOCALE, null);
+ final String providerLocales = prefs.getString(PREF_LOCALE, null);
// If locale matches that of the provider, and neither DB needs
// updating, there's nothing to do. A DB might require updating
// as a result of a system upgrade.
- if (!locale.toString().equals(providerLocale)) {
- Log.i(TAG, "Locale has changed from " + providerLocale
- + " to " + locale.toString());
+ if (!locales.toString().equals(providerLocales)) {
+ Log.i(TAG, "Locale has changed from " + providerLocales
+ + " to " + locales);
return true;
}
- if (contactsHelper.needsToUpdateLocaleData(locale) ||
- profileHelper.needsToUpdateLocaleData(locale)) {
+ if (contactsHelper.needsToUpdateLocaleData(locales) ||
+ profileHelper.needsToUpdateLocaleData(locales)) {
return true;
}
return false;
@@ -1727,18 +1761,18 @@ public class ContactsProvider2 extends AbstractContactsProvider
return;
}
- final Locale currentLocale = mCurrentLocale;
+ final LocaleSet currentLocales = mCurrentLocales;
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
- if (!needsToUpdateLocaleData(prefs, currentLocale, mContactsHelper, mProfileHelper)) {
+ if (!needsToUpdateLocaleData(prefs, currentLocales, mContactsHelper, mProfileHelper)) {
return;
}
int providerStatus = mProviderStatus;
setProviderStatus(ProviderStatus.STATUS_CHANGING_LOCALE);
- mContactsHelper.setLocale(currentLocale);
- mProfileHelper.setLocale(currentLocale);
+ mContactsHelper.setLocale(currentLocales);
+ mProfileHelper.setLocale(currentLocales);
mSearchIndexManager.updateIndex(true);
- prefs.edit().putString(PREF_LOCALE, currentLocale.toString()).commit();
+ prefs.edit().putString(PREF_LOCALE, currentLocales.toString()).commit();
setProviderStatus(providerStatus);
}
@@ -1750,17 +1784,16 @@ public class ContactsProvider2 extends AbstractContactsProvider
Context context,
ContactsDatabaseHelper contactsHelper,
ProfileDatabaseHelper profileHelper) {
-
- final Locale currentLocale = Locale.getDefault();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- if (!needsToUpdateLocaleData(prefs, currentLocale, contactsHelper, profileHelper)) {
+ final LocaleSet currentLocales = getLocaleSet(prefs, Locale.getDefault());
+ if (!needsToUpdateLocaleData(prefs, currentLocales, contactsHelper, profileHelper)) {
return;
}
- contactsHelper.setLocale(currentLocale);
- profileHelper.setLocale(currentLocale);
+ contactsHelper.setLocale(currentLocales);
+ profileHelper.setLocale(currentLocales);
contactsHelper.rebuildSearchIndex();
- prefs.edit().putString(PREF_LOCALE, currentLocale.toString()).commit();
+ prefs.edit().putString(PREF_LOCALE, currentLocales.toString()).commit();
}
/**
diff --git a/src/com/android/providers/contacts/HanziToPinyin.java b/src/com/android/providers/contacts/HanziToPinyin.java
index 0c35e21..d140439 100644
--- a/src/com/android/providers/contacts/HanziToPinyin.java
+++ b/src/com/android/providers/contacts/HanziToPinyin.java
@@ -121,12 +121,19 @@ public class HanziToPinyin {
}
}
+ public String transliterate(final String input) {
+ if (!hasChineseTransliterator() || TextUtils.isEmpty(input)) {
+ return null;
+ }
+ return mPinyinTransliterator.transliterate(input);
+ }
+
/**
* Convert the input to a array of tokens. The sequence of ASCII or Unknown characters without
* space will be put into a Token, One Hanzi character which has pinyin will be treated as a
* Token. If there is no Chinese transliterator, the empty token array is returned.
*/
- public ArrayList<Token> get(final String input) {
+ public ArrayList<Token> getTokens(final String input) {
ArrayList<Token> tokens = new ArrayList<Token>();
if (!hasChineseTransliterator() || TextUtils.isEmpty(input)) {
// return empty tokens.
diff --git a/src/com/android/providers/contacts/LocaleSet.java b/src/com/android/providers/contacts/LocaleSet.java
new file mode 100644
index 0000000..63638c6
--- /dev/null
+++ b/src/com/android/providers/contacts/LocaleSet.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2014 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.providers.contacts;
+
+import android.text.TextUtils;
+import com.google.common.annotations.VisibleForTesting;
+import java.util.Locale;
+
+public class LocaleSet {
+ private static final String CHINESE_LANGUAGE = Locale.CHINESE.getLanguage().toLowerCase();
+ private static final String JAPANESE_LANGUAGE = Locale.JAPANESE.getLanguage().toLowerCase();
+ private static final String KOREAN_LANGUAGE = Locale.KOREAN.getLanguage().toLowerCase();
+
+ private static class LocaleWrapper {
+ private final Locale mLocale;
+ private final String mLanguage;
+ private final boolean mLocaleIsCJK;
+
+ private static boolean isLanguageCJK(String language) {
+ return CHINESE_LANGUAGE.equals(language) ||
+ JAPANESE_LANGUAGE.equals(language) ||
+ KOREAN_LANGUAGE.equals(language);
+ }
+
+ public LocaleWrapper(Locale locale) {
+ mLocale = locale;
+ if (mLocale != null) {
+ mLanguage = mLocale.getLanguage().toLowerCase();
+ mLocaleIsCJK = isLanguageCJK(mLanguage);
+ } else {
+ mLanguage = null;
+ mLocaleIsCJK = false;
+ }
+ }
+
+ public boolean hasLocale() {
+ return mLocale != null;
+ }
+
+ public Locale getLocale() {
+ return mLocale;
+ }
+
+ public boolean isLocale(Locale locale) {
+ return mLocale == null ? (locale == null) : mLocale.equals(locale);
+ }
+
+ public boolean isLocaleCJK() {
+ return mLocaleIsCJK;
+ }
+
+ public boolean isLanguage(String language) {
+ return mLanguage == null ? (language == null)
+ : mLanguage.equalsIgnoreCase(language);
+ }
+
+ public String toString() {
+ return mLocale != null ? mLocale.toLanguageTag() : "(null)";
+ }
+ }
+
+ public static LocaleSet getDefault() {
+ return new LocaleSet(Locale.getDefault());
+ }
+
+ public LocaleSet(Locale locale) {
+ this(locale, null);
+ }
+
+ /**
+ * Returns locale set for a given set of IETF BCP-47 tags separated by ';'.
+ * BCP-47 tags are what is used by ICU 52's toLanguageTag/forLanguageTag
+ * methods to represent individual Locales: "en-US" for Locale.US,
+ * "zh-CN" for Locale.CHINA, etc. So eg "en-US;zh-CN" specifies the locale
+ * set LocaleSet(Locale.US, Locale.CHINA).
+ *
+ * @param localeString One or more BCP-47 tags separated by ';'.
+ * @return LocaleSet for specified locale string, or default set if null
+ * or unable to parse.
+ */
+ public static LocaleSet getLocaleSet(String localeString) {
+ // Locale.toString() generates strings like "en_US" and "zh_CN_#Hans".
+ // Locale.toLanguageTag() generates strings like "en-US" and "zh-Hans-CN".
+ // We can only parse language tags.
+ if (localeString != null && localeString.indexOf('_') == -1) {
+ final String[] locales = localeString.split(";");
+ final Locale primaryLocale = Locale.forLanguageTag(locales[0]);
+ // ICU tags undefined/unparseable locales "und"
+ if (primaryLocale != null &&
+ !TextUtils.equals(primaryLocale.toLanguageTag(), "und")) {
+ if (locales.length > 1 && locales[1] != null) {
+ final Locale secondaryLocale = Locale.forLanguageTag(locales[1]);
+ if (secondaryLocale != null &&
+ !TextUtils.equals(secondaryLocale.toLanguageTag(), "und")) {
+ return new LocaleSet(primaryLocale, secondaryLocale);
+ }
+ }
+ return new LocaleSet(primaryLocale);
+ }
+ }
+ return getDefault();
+ }
+
+ private final LocaleWrapper mPrimaryLocale;
+ private final LocaleWrapper mSecondaryLocale;
+
+ public LocaleSet(Locale primaryLocale, Locale secondaryLocale) {
+ mPrimaryLocale = new LocaleWrapper(primaryLocale);
+ mSecondaryLocale = new LocaleWrapper(
+ mPrimaryLocale.equals(secondaryLocale) ? null : secondaryLocale);
+ }
+
+ public LocaleSet normalize() {
+ final Locale primaryLocale = getPrimaryLocale();
+ if (primaryLocale == null) {
+ return getDefault();
+ }
+ Locale secondaryLocale = getSecondaryLocale();
+ // disallow both locales with same language (redundant and/or conflicting)
+ // disallow both locales CJK (conflicting rules)
+ if (secondaryLocale == null ||
+ isPrimaryLanguage(secondaryLocale.getLanguage()) ||
+ (isPrimaryLocaleCJK() && isSecondaryLocaleCJK())) {
+ return new LocaleSet(primaryLocale);
+ }
+ // unnecessary to specify English as secondary locale (redundant)
+ if (isSecondaryLanguage(Locale.ENGLISH.getLanguage())) {
+ return new LocaleSet(primaryLocale);
+ }
+ return this;
+ }
+
+ public boolean hasSecondaryLocale() {
+ return mSecondaryLocale.hasLocale();
+ }
+
+ public Locale getPrimaryLocale() {
+ return mPrimaryLocale.getLocale();
+ }
+
+ public Locale getSecondaryLocale() {
+ return mSecondaryLocale.getLocale();
+ }
+
+ public boolean isPrimaryLocale(Locale locale) {
+ return mPrimaryLocale.isLocale(locale);
+ }
+
+ public boolean isSecondaryLocale(Locale locale) {
+ return mSecondaryLocale.isLocale(locale);
+ }
+
+ private static final String SCRIPT_SIMPLIFIED_CHINESE = "Hans";
+ private static final String SCRIPT_TRADITIONAL_CHINESE = "Hant";
+
+ @VisibleForTesting
+ public static boolean isLocaleSimplifiedChinese(Locale locale) {
+ // language must match
+ if (locale == null || !TextUtils.equals(locale.getLanguage(), CHINESE_LANGUAGE)) {
+ return false;
+ }
+ // script is optional but if present must match
+ if (!TextUtils.isEmpty(locale.getScript())) {
+ return locale.getScript().equals(SCRIPT_SIMPLIFIED_CHINESE);
+ }
+ // if no script, must match known country
+ return locale.equals(Locale.SIMPLIFIED_CHINESE);
+ }
+
+ public boolean isPrimaryLocaleSimplifiedChinese() {
+ return isLocaleSimplifiedChinese(getPrimaryLocale());
+ }
+
+ public boolean isSecondaryLocaleSimplifiedChinese() {
+ return isLocaleSimplifiedChinese(getSecondaryLocale());
+ }
+
+ @VisibleForTesting
+ public static boolean isLocaleTraditionalChinese(Locale locale) {
+ // language must match
+ if (locale == null || !TextUtils.equals(locale.getLanguage(), CHINESE_LANGUAGE)) {
+ return false;
+ }
+ // script is optional but if present must match
+ if (!TextUtils.isEmpty(locale.getScript())) {
+ return locale.getScript().equals(SCRIPT_TRADITIONAL_CHINESE);
+ }
+ // if no script, must match known country
+ return locale.equals(Locale.TRADITIONAL_CHINESE);
+ }
+
+ public boolean isPrimaryLocaleTraditionalChinese() {
+ return isLocaleTraditionalChinese(getPrimaryLocale());
+ }
+
+ public boolean isSecondaryLocaleTraditionalChinese() {
+ return isLocaleTraditionalChinese(getSecondaryLocale());
+ }
+
+ public boolean isPrimaryLocaleCJK() {
+ return mPrimaryLocale.isLocaleCJK();
+ }
+
+ public boolean isSecondaryLocaleCJK() {
+ return mSecondaryLocale.isLocaleCJK();
+ }
+
+ public boolean isPrimaryLanguage(String language) {
+ return mPrimaryLocale.isLanguage(language);
+ }
+
+ public boolean isSecondaryLanguage(String language) {
+ return mSecondaryLocale.isLanguage(language);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (object instanceof LocaleSet) {
+ final LocaleSet other = (LocaleSet) object;
+ return other.isPrimaryLocale(mPrimaryLocale.getLocale())
+ && other.isSecondaryLocale(mSecondaryLocale.getLocale());
+ }
+ return false;
+ }
+
+ @Override
+ public final String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(mPrimaryLocale.toString());
+ if (hasSecondaryLocale()) {
+ builder.append(";");
+ builder.append(mSecondaryLocale.toString());
+ }
+ return builder.toString();
+ }
+}