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 isLanguageCJK(String language)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 LocaleWrapper(Locale locale)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 hasLocale()50 public boolean hasLocale() { 51 return mLocale != null; 52 } 53 getLocale()54 public Locale getLocale() { 55 return mLocale; 56 } 57 isLocale(Locale locale)58 public boolean isLocale(Locale locale) { 59 return mLocale == null ? (locale == null) : mLocale.equals(locale); 60 } 61 isLocaleCJK()62 public boolean isLocaleCJK() { 63 return mLocaleIsCJK; 64 } 65 isLanguage(String language)66 public boolean isLanguage(String language) { 67 return mLanguage == null ? (language == null) 68 : mLanguage.equalsIgnoreCase(language); 69 } 70 toString()71 public String toString() { 72 return mLocale != null ? mLocale.toLanguageTag() : "(null)"; 73 } 74 } 75 getDefault()76 public static LocaleSet getDefault() { 77 return new LocaleSet(Locale.getDefault()); 78 } 79 LocaleSet(Locale locale)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 */ getLocaleSet(String localeString)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 LocaleSet(Locale primaryLocale, Locale secondaryLocale)121 public LocaleSet(Locale primaryLocale, Locale secondaryLocale) { 122 mPrimaryLocale = new LocaleWrapper(primaryLocale); 123 mSecondaryLocale = new LocaleWrapper( 124 mPrimaryLocale.equals(secondaryLocale) ? null : secondaryLocale); 125 } 126 normalize()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 hasSecondaryLocale()147 public boolean hasSecondaryLocale() { 148 return mSecondaryLocale.hasLocale(); 149 } 150 getPrimaryLocale()151 public Locale getPrimaryLocale() { 152 return mPrimaryLocale.getLocale(); 153 } 154 getSecondaryLocale()155 public Locale getSecondaryLocale() { 156 return mSecondaryLocale.getLocale(); 157 } 158 isPrimaryLocale(Locale locale)159 public boolean isPrimaryLocale(Locale locale) { 160 return mPrimaryLocale.isLocale(locale); 161 } 162 isSecondaryLocale(Locale locale)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 isLocaleSimplifiedChinese(Locale locale)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 isPrimaryLocaleSimplifiedChinese()184 public boolean isPrimaryLocaleSimplifiedChinese() { 185 return isLocaleSimplifiedChinese(getPrimaryLocale()); 186 } 187 isSecondaryLocaleSimplifiedChinese()188 public boolean isSecondaryLocaleSimplifiedChinese() { 189 return isLocaleSimplifiedChinese(getSecondaryLocale()); 190 } 191 192 @VisibleForTesting isLocaleTraditionalChinese(Locale locale)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 isPrimaryLocaleTraditionalChinese()206 public boolean isPrimaryLocaleTraditionalChinese() { 207 return isLocaleTraditionalChinese(getPrimaryLocale()); 208 } 209 isSecondaryLocaleTraditionalChinese()210 public boolean isSecondaryLocaleTraditionalChinese() { 211 return isLocaleTraditionalChinese(getSecondaryLocale()); 212 } 213 isPrimaryLocaleCJK()214 public boolean isPrimaryLocaleCJK() { 215 return mPrimaryLocale.isLocaleCJK(); 216 } 217 isSecondaryLocaleCJK()218 public boolean isSecondaryLocaleCJK() { 219 return mSecondaryLocale.isLocaleCJK(); 220 } 221 isPrimaryLanguage(String language)222 public boolean isPrimaryLanguage(String language) { 223 return mPrimaryLocale.isLanguage(language); 224 } 225 isSecondaryLanguage(String language)226 public boolean isSecondaryLanguage(String language) { 227 return mSecondaryLocale.isLanguage(language); 228 } 229 230 @Override equals(Object object)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 toString()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