diff options
author | Jay Shrauner <shrauner@google.com> | 2013-10-17 13:59:48 -0700 |
---|---|---|
committer | Jay Shrauner <shrauner@google.com> | 2014-04-18 11:23:49 -0700 |
commit | d4dbd063cf88e70b045607aa865b2fdb2329bf45 (patch) | |
tree | 19152c84d27e399f62f79b645205953e0b682ab2 /src | |
parent | 30268f9198a8b25aa0c9d90c0c2390f7a4bffb0d (diff) | |
download | packages_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')
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(); + } +} |