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