Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.providers.contacts;
     18 
     19 import android.text.TextUtils;
     20 import com.google.common.annotations.VisibleForTesting;
     21 import java.util.Locale;
     22 
     23 public class LocaleSet {
     24     private static final String CHINESE_LANGUAGE = Locale.CHINESE.getLanguage().toLowerCase();
     25     private static final String JAPANESE_LANGUAGE = Locale.JAPANESE.getLanguage().toLowerCase();
     26     private static final String KOREAN_LANGUAGE = Locale.KOREAN.getLanguage().toLowerCase();
     27 
     28     private static class LocaleWrapper {
     29         private final Locale mLocale;
     30         private final String mLanguage;
     31         private final boolean mLocaleIsCJK;
     32 
     33         private static boolean isLanguageCJK(String language) {
     34             return CHINESE_LANGUAGE.equals(language) ||
     35                 JAPANESE_LANGUAGE.equals(language) ||
     36                 KOREAN_LANGUAGE.equals(language);
     37         }
     38 
     39         public LocaleWrapper(Locale locale) {
     40             mLocale = locale;
     41             if (mLocale != null) {
     42                 mLanguage = mLocale.getLanguage().toLowerCase();
     43                 mLocaleIsCJK = isLanguageCJK(mLanguage);
     44             } else {
     45                 mLanguage = null;
     46                 mLocaleIsCJK = false;
     47             }
     48         }
     49 
     50         public boolean hasLocale() {
     51             return mLocale != null;
     52         }
     53 
     54         public Locale getLocale() {
     55             return mLocale;
     56         }
     57 
     58         public boolean isLocale(Locale locale) {
     59             return mLocale == null ? (locale == null) : mLocale.equals(locale);
     60         }
     61 
     62         public boolean isLocaleCJK() {
     63             return mLocaleIsCJK;
     64         }
     65 
     66         public boolean isLanguage(String language) {
     67             return mLanguage == null ? (language == null)
     68                 : mLanguage.equalsIgnoreCase(language);
     69         }
     70 
     71         public String toString() {
     72             return mLocale != null ? mLocale.toLanguageTag() : "(null)";
     73         }
     74     }
     75 
     76     public static LocaleSet getDefault() {
     77         return new LocaleSet(Locale.getDefault());
     78     }
     79 
     80     public LocaleSet(Locale locale) {
     81         this(locale, null);
     82     }
     83 
     84     /**
     85      * Returns locale set for a given set of IETF BCP-47 tags separated by ';'.
     86      * BCP-47 tags are what is used by ICU 52's toLanguageTag/forLanguageTag
     87      * methods to represent individual Locales: "en-US" for Locale.US,
     88      * "zh-CN" for Locale.CHINA, etc. So eg "en-US;zh-CN" specifies the locale
     89      * set LocaleSet(Locale.US, Locale.CHINA).
     90      *
     91      * @param localeString One or more BCP-47 tags separated by ';'.
     92      * @return LocaleSet for specified locale string, or default set if null
     93      * or unable to parse.
     94      */
     95     public static LocaleSet getLocaleSet(String localeString) {
     96         // Locale.toString() generates strings like "en_US" and "zh_CN_#Hans".
     97         // Locale.toLanguageTag() generates strings like "en-US" and "zh-Hans-CN".
     98         // We can only parse language tags.
     99         if (localeString != null && localeString.indexOf('_') == -1) {
    100             final String[] locales = localeString.split(";");
    101             final Locale primaryLocale = Locale.forLanguageTag(locales[0]);
    102             // ICU tags undefined/unparseable locales "und"
    103             if (primaryLocale != null &&
    104                     !TextUtils.equals(primaryLocale.toLanguageTag(), "und")) {
    105                 if (locales.length > 1 && locales[1] != null) {
    106                     final Locale secondaryLocale = Locale.forLanguageTag(locales[1]);
    107                     if (secondaryLocale != null &&
    108                             !TextUtils.equals(secondaryLocale.toLanguageTag(), "und")) {
    109                         return new LocaleSet(primaryLocale, secondaryLocale);
    110                     }
    111                 }
    112                 return new LocaleSet(primaryLocale);
    113             }
    114         }
    115         return getDefault();
    116     }
    117 
    118     private final LocaleWrapper mPrimaryLocale;
    119     private final LocaleWrapper mSecondaryLocale;
    120 
    121     public LocaleSet(Locale primaryLocale, Locale secondaryLocale) {
    122         mPrimaryLocale = new LocaleWrapper(primaryLocale);
    123         mSecondaryLocale = new LocaleWrapper(
    124                 mPrimaryLocale.equals(secondaryLocale) ? null : secondaryLocale);
    125     }
    126 
    127     public LocaleSet normalize() {
    128         final Locale primaryLocale = getPrimaryLocale();
    129         if (primaryLocale == null) {
    130             return getDefault();
    131         }
    132         Locale secondaryLocale = getSecondaryLocale();
    133         // disallow both locales with same language (redundant and/or conflicting)
    134         // disallow both locales CJK (conflicting rules)
    135         if (secondaryLocale == null ||
    136                 isPrimaryLanguage(secondaryLocale.getLanguage()) ||
    137                 (isPrimaryLocaleCJK() && isSecondaryLocaleCJK())) {
    138             return new LocaleSet(primaryLocale);
    139         }
    140         // unnecessary to specify English as secondary locale (redundant)
    141         if (isSecondaryLanguage(Locale.ENGLISH.getLanguage())) {
    142             return new LocaleSet(primaryLocale);
    143         }
    144         return this;
    145     }
    146 
    147     public boolean hasSecondaryLocale() {
    148         return mSecondaryLocale.hasLocale();
    149     }
    150 
    151     public Locale getPrimaryLocale() {
    152         return mPrimaryLocale.getLocale();
    153     }
    154 
    155     public Locale getSecondaryLocale() {
    156         return mSecondaryLocale.getLocale();
    157     }
    158 
    159     public boolean isPrimaryLocale(Locale locale) {
    160         return mPrimaryLocale.isLocale(locale);
    161     }
    162 
    163     public boolean isSecondaryLocale(Locale locale) {
    164         return mSecondaryLocale.isLocale(locale);
    165     }
    166 
    167     private static final String SCRIPT_SIMPLIFIED_CHINESE = "Hans";
    168     private static final String SCRIPT_TRADITIONAL_CHINESE = "Hant";
    169 
    170     @VisibleForTesting
    171     public static boolean isLocaleSimplifiedChinese(Locale locale) {
    172         // language must match
    173         if (locale == null || !TextUtils.equals(locale.getLanguage(), CHINESE_LANGUAGE)) {
    174             return false;
    175         }
    176         // script is optional but if present must match
    177         if (!TextUtils.isEmpty(locale.getScript())) {
    178             return locale.getScript().equals(SCRIPT_SIMPLIFIED_CHINESE);
    179         }
    180         // if no script, must match known country
    181         return locale.equals(Locale.SIMPLIFIED_CHINESE);
    182     }
    183 
    184     public boolean isPrimaryLocaleSimplifiedChinese() {
    185         return isLocaleSimplifiedChinese(getPrimaryLocale());
    186     }
    187 
    188     public boolean isSecondaryLocaleSimplifiedChinese() {
    189         return isLocaleSimplifiedChinese(getSecondaryLocale());
    190     }
    191 
    192     @VisibleForTesting
    193     public static boolean isLocaleTraditionalChinese(Locale locale) {
    194         // language must match
    195         if (locale == null || !TextUtils.equals(locale.getLanguage(), CHINESE_LANGUAGE)) {
    196             return false;
    197         }
    198         // script is optional but if present must match
    199         if (!TextUtils.isEmpty(locale.getScript())) {
    200             return locale.getScript().equals(SCRIPT_TRADITIONAL_CHINESE);
    201         }
    202         // if no script, must match known country
    203         return locale.equals(Locale.TRADITIONAL_CHINESE);
    204     }
    205 
    206     public boolean isPrimaryLocaleTraditionalChinese() {
    207         return isLocaleTraditionalChinese(getPrimaryLocale());
    208     }
    209 
    210     public boolean isSecondaryLocaleTraditionalChinese() {
    211         return isLocaleTraditionalChinese(getSecondaryLocale());
    212     }
    213 
    214     public boolean isPrimaryLocaleCJK() {
    215         return mPrimaryLocale.isLocaleCJK();
    216     }
    217 
    218     public boolean isSecondaryLocaleCJK() {
    219         return mSecondaryLocale.isLocaleCJK();
    220     }
    221 
    222     public boolean isPrimaryLanguage(String language) {
    223         return mPrimaryLocale.isLanguage(language);
    224     }
    225 
    226     public boolean isSecondaryLanguage(String language) {
    227         return mSecondaryLocale.isLanguage(language);
    228     }
    229 
    230     @Override
    231     public boolean equals(Object object) {
    232         if (object == this) {
    233             return true;
    234         }
    235         if (object instanceof LocaleSet) {
    236             final LocaleSet other = (LocaleSet) object;
    237             return other.isPrimaryLocale(mPrimaryLocale.getLocale())
    238                 && other.isSecondaryLocale(mSecondaryLocale.getLocale());
    239         }
    240         return false;
    241     }
    242 
    243     @Override
    244     public final String toString() {
    245         StringBuilder builder = new StringBuilder();
    246         builder.append(mPrimaryLocale.toString());
    247         if (hasSecondaryLocale()) {
    248             builder.append(";");
    249             builder.append(mSecondaryLocale.toString());
    250         }
    251         return builder.toString();
    252     }
    253 }
    254