1/*
2 * Copyright (C) 2010 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
17package com.android.providers.contacts;
18
19import android.provider.ContactsContract.FullNameStyle;
20import android.test.AndroidTestCase;
21import android.test.suitebuilder.annotation.SmallTest;
22import android.util.Log;
23
24import java.text.Collator;
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.HashSet;
28import java.util.Iterator;
29import java.util.List;
30import java.util.Locale;
31
32@SmallTest
33public class ContactLocaleUtilsTest extends AndroidTestCase {
34    private static final String TAG = "ContactLocaleUtilsTest";
35
36    private static final String PHONE_NUMBER_1 = "+1 (650) 555-1212";
37    private static final String PHONE_NUMBER_2 = "650-555-1212";
38    private static final String LATIN_NAME = "John Smith";
39    private static final String LATIN_NAME_2 = "John Paul Jones";
40    private static final String KANJI_NAME = "\u65e5";
41    private static final String ARABIC_NAME = "\u0646\u0648\u0631"; /* Noor */
42    private static final String CHINESE_NAME = "\u675C\u9D51";
43    private static final String SERBIAN_NAME = "\u0408\u0435\u043B\u0435\u043D\u0430";
44    private static final String UKRAINIAN_NAME = "\u0407";
45    private static final String UKRAINIAN_NAME_2 = "\u0490";
46    private static final String CHINESE_LATIN_MIX_NAME_1 = "D\u675C\u9D51";
47    private static final String CHINESE_LATIN_MIX_NAME_2 = "MARY \u675C\u9D51";
48    private static final String[] CHINESE_NAME_KEY = {"\u9D51", "\u675C\u9D51", "JUAN", "DUJUAN",
49            "J", "DJ"};
50    private static final String[] CHINESE_LATIN_MIX_NAME_1_KEY = {"\u9D51", "\u675C\u9D51",
51        "D \u675C\u9D51", "JUAN", "DUJUAN", "J", "DJ", "D DUJUAN", "DDJ"};
52    private static final String[] CHINESE_LATIN_MIX_NAME_2_KEY = {"\u9D51", "\u675C\u9D51",
53        "MARY \u675C\u9D51", "JUAN", "DUJUAN", "MARY DUJUAN", "J", "DJ", "MDJ"};
54    private static final String[] LATIN_NAME_KEY = {"John Smith", "Smith", "JS", "S"};
55    private static final String[] LATIN_NAME_KEY_2 = {
56        "John Paul Jones", "Paul Jones", "Jones", "JPJ", "PJ", "J"};
57    private static final String[] LABELS_EN_US = {
58        "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
59        "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
60        "#", ""};
61    private static final String[] LABELS_JA_JP = {
62        "", "\u3042", "\u304B", "\u3055", "\u305F", "\u306A", "\u306F",
63        "\u307E", "\u3084", "\u3089", "\u308F", "\u4ED6",
64        "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
65        "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
66        "#", ""};
67    private static final String[] LABELS_ZH_TW = {
68        "", "1\u5283", "2\u5283", "3\u5283", "4\u5283", "5\u5283", "6\u5283",
69        "7\u5283", "8\u5283", "9\u5283", "10\u5283", "11\u5283", "12\u5283",
70        "13\u5283", "14\u5283", "15\u5283", "16\u5283", "17\u5283", "18\u5283",
71        "19\u5283", "20\u5283", "21\u5283", "22\u5283", "23\u5283", "24\u5283",
72        "25\u5283", "26\u5283", "27\u5283", "28\u5283", "29\u5283", "30\u5283",
73        "31\u5283", "32\u5283", "33\u5283",
74        "35\u5283", "36\u5283", "39\u5283", "48\u5283",
75        "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
76        "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
77        "#", ""};
78    private static final String[] LABELS_KO = {
79        "", "\u3131", "\u3134", "\u3137", "\u3139", "\u3141", "\u3142",
80        "\u3145", "\u3147", "\u3148", "\u314A", "\u314B", "\u314C", "\u314D",
81        "\u314E",
82        "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
83        "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
84        "#", ""};
85    private static final String[] LABELS_AR = {
86        "", "\u0627", "\u0628", "\u062a", "\u062b", "\u062c", "\u062d",
87        "\u062e", "\u062f", "\u0630", "\u0631", "\u0632", "\u0633", "\u0634",
88        "\u0635", "\u0636", "\u0637", "\u0638", "\u0639", "\u063a", "\u0641",
89        "\u0642", "\u0643", "\u0644", "\u0645", "\u0646", "\u0647", "\u0648",
90        "\u064a",
91        "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
92        "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
93        "#", ""};
94
95    private static final String JAPANESE_MISC = "\u4ed6";
96
97    private static final Locale LOCALE_ARABIC = new Locale("ar");
98    private static final Locale LOCALE_SERBIAN = new Locale("sr");
99    private static final Locale LOCALE_UKRAINIAN = new Locale("uk");
100    private boolean hasSimplifiedChineseCollator;
101    private boolean hasTraditionalChineseCollator;
102    private boolean hasJapaneseCollator;
103    private boolean hasKoreanCollator;
104    private boolean hasArabicCollator;
105    private boolean hasGermanCollator;
106    private boolean hasSerbianCollator;
107    private boolean hasUkrainianCollator;
108
109    @Override
110    protected void setUp() throws Exception {
111        super.setUp();
112        final Locale locale[] = Collator.getAvailableLocales();
113        for (int i = 0; i < locale.length; i++) {
114            if (LocaleSet.isLocaleSimplifiedChinese(locale[i])) {
115                hasSimplifiedChineseCollator = true;
116            } else if (LocaleSet.isLocaleTraditionalChinese(locale[i])) {
117                hasTraditionalChineseCollator = true;
118            } else if (locale[i].equals(Locale.JAPAN)) {
119                hasJapaneseCollator = true;
120            } else if (locale[i].equals(Locale.KOREA)) {
121                hasKoreanCollator = true;
122            } else if (locale[i].equals(LOCALE_ARABIC)) {
123                hasArabicCollator = true;
124            } else if (locale[i].equals(Locale.GERMANY)) {
125                hasGermanCollator = true;
126            } else if (locale[i].equals(LOCALE_SERBIAN)) {
127                hasSerbianCollator = true;
128            } else if (locale[i].equals(LOCALE_UKRAINIAN)) {
129                hasUkrainianCollator = true;
130            }
131        }
132    }
133
134    private String getLabel(String name) {
135        ContactLocaleUtils utils = ContactLocaleUtils.getInstance();
136        int bucketIndex = utils.getBucketIndex(name);
137        return utils.getBucketLabel(bucketIndex);
138    }
139
140    private Iterator<String> getNameLookupKeys(String name, int nameStyle) {
141        ContactLocaleUtils utils = ContactLocaleUtils.getInstance();
142        return utils.getNameLookupKeys(name, nameStyle);
143    }
144
145    private ArrayList<String> getLabels() {
146        ContactLocaleUtils utils = ContactLocaleUtils.getInstance();
147        return utils.getLabels();
148    }
149
150    public void testEnglishContactLocaleUtils() throws Exception {
151        ContactLocaleUtils.setLocale(Locale.ENGLISH);
152        assertEquals("#", getLabel(PHONE_NUMBER_1));
153        assertEquals("#", getLabel(PHONE_NUMBER_2));
154        assertEquals("J", getLabel(LATIN_NAME));
155        assertEquals("", getLabel(CHINESE_NAME));
156        assertEquals("D", getLabel(CHINESE_LATIN_MIX_NAME_1));
157        assertEquals("B", getLabel("Bob Smith"));
158
159        if (hasArabicCollator) {
160            assertEquals("\u0646", getLabel(ARABIC_NAME));
161        }
162        if (hasSerbianCollator) {
163            assertEquals("\u0408", getLabel(SERBIAN_NAME));
164        }
165        if (hasUkrainianCollator) {
166            assertEquals("\u0407", getLabel(UKRAINIAN_NAME));
167        }
168
169        assertNull(getNameLookupKeys(LATIN_NAME, FullNameStyle.UNDEFINED));
170        verifyLabels(getLabels(), LABELS_EN_US);
171    }
172
173    public void testJapaneseContactLocaleUtils() throws Exception {
174        if (!hasJapaneseCollator) {
175            Log.w(TAG, "Japanese collator not found; skipping test");
176            return;
177        }
178
179        ContactLocaleUtils.setLocale(Locale.JAPAN);
180        assertEquals("#", getLabel(PHONE_NUMBER_1));
181        assertEquals("#", getLabel(PHONE_NUMBER_2));
182        assertEquals(JAPANESE_MISC, getLabel(KANJI_NAME));
183        assertEquals("J", getLabel(LATIN_NAME));
184        assertEquals(JAPANESE_MISC, getLabel(CHINESE_NAME));
185        assertEquals("D", getLabel(CHINESE_LATIN_MIX_NAME_1));
186
187        assertNull(getNameLookupKeys(CHINESE_NAME, FullNameStyle.CJK));
188        assertNull(getNameLookupKeys(CHINESE_NAME, FullNameStyle.CHINESE));
189
190        assertEquals("B", getLabel("Bob Smith"));
191        verifyLabels(getLabels(), LABELS_JA_JP);
192    }
193
194    public void testChineseContactLocaleUtils() throws Exception {
195        if (!hasSimplifiedChineseCollator) {
196            Log.w(TAG, "Simplified Chinese collator not found; skipping test");
197            return;
198        }
199
200        ContactLocaleUtils.setLocale(Locale.SIMPLIFIED_CHINESE);
201        assertEquals("#", getLabel(PHONE_NUMBER_1));
202        assertEquals("#", getLabel(PHONE_NUMBER_2));
203        assertEquals("J", getLabel(LATIN_NAME));
204        assertEquals("D", getLabel(CHINESE_NAME));
205        assertEquals("D", getLabel(CHINESE_LATIN_MIX_NAME_1));
206        assertEquals("B", getLabel("Bob Smith"));
207        verifyLabels(getLabels(), LABELS_EN_US);
208
209        if (hasTraditionalChineseCollator) {
210            ContactLocaleUtils.setLocale(Locale.TRADITIONAL_CHINESE);
211            assertEquals("#", getLabel(PHONE_NUMBER_1));
212            assertEquals("#", getLabel(PHONE_NUMBER_2));
213            assertEquals("J", getLabel(LATIN_NAME));
214            assertEquals("7\u5283", getLabel(CHINESE_NAME));
215            assertEquals("D", getLabel(CHINESE_LATIN_MIX_NAME_1));
216        } else {
217            Log.w(TAG, "Traditional Chinese collator not found");
218        }
219
220        ContactLocaleUtils.setLocale(Locale.SIMPLIFIED_CHINESE);
221        Iterator<String> keys = getNameLookupKeys(CHINESE_NAME,
222                FullNameStyle.CHINESE);
223        verifyKeys(keys, CHINESE_NAME_KEY);
224
225        keys = getNameLookupKeys(CHINESE_LATIN_MIX_NAME_1, FullNameStyle.CHINESE);
226        verifyKeys(keys, CHINESE_LATIN_MIX_NAME_1_KEY);
227
228        keys = getNameLookupKeys(CHINESE_LATIN_MIX_NAME_2, FullNameStyle.CHINESE);
229        verifyKeys(keys, CHINESE_LATIN_MIX_NAME_2_KEY);
230
231        if (hasTraditionalChineseCollator) {
232            ContactLocaleUtils.setLocale(Locale.TRADITIONAL_CHINESE);
233            assertEquals("B", getLabel("Bob Smith"));
234            verifyLabels(getLabels(), LABELS_ZH_TW);
235        }
236    }
237
238    public void testPinyinEnabledSecondaryLocale() throws Exception {
239        if (!hasSimplifiedChineseCollator) {
240            Log.w(TAG, "Simplified Chinese collator not found; skipping test");
241            return;
242        }
243
244        ContactLocaleUtils.setLocales(
245                new LocaleSet(Locale.ENGLISH, Locale.SIMPLIFIED_CHINESE));
246        assertEquals("D", getLabel(CHINESE_NAME));
247
248        Iterator<String> keys = getNameLookupKeys(CHINESE_NAME,
249                FullNameStyle.CHINESE);
250        verifyKeys(keys, CHINESE_NAME_KEY);
251    }
252
253    public void testPinyinDisabledSecondaryLocale() throws Exception {
254        if (!hasSimplifiedChineseCollator) {
255            Log.w(TAG, "Simplified Chinese collator not found; skipping test");
256            return;
257        }
258
259        ContactLocaleUtils.setLocales(
260                new LocaleSet(Locale.ENGLISH, Locale.JAPAN));
261        assertEquals("", getLabel(CHINESE_NAME));
262
263        assertNull(getNameLookupKeys(CHINESE_NAME, FullNameStyle.CHINESE));
264        assertNull(getNameLookupKeys(CHINESE_NAME, FullNameStyle.CJK));
265    }
266
267    public void testChineseStyleNameWithDifferentLocale() throws Exception {
268        if (!hasSimplifiedChineseCollator) {
269            Log.w(TAG, "Simplified Chinese collator not found; skipping test");
270            return;
271        }
272
273        ContactLocaleUtils.setLocale(Locale.ENGLISH);
274        assertNull(getNameLookupKeys(CHINESE_NAME, FullNameStyle.CHINESE));
275        assertNull(getNameLookupKeys(CHINESE_NAME, FullNameStyle.CJK));
276
277        ContactLocaleUtils.setLocale(Locale.SIMPLIFIED_CHINESE);
278        Iterator<String> keys = getNameLookupKeys(CHINESE_NAME,
279                FullNameStyle.CJK);
280        verifyKeys(keys, CHINESE_NAME_KEY);
281        keys = getNameLookupKeys(LATIN_NAME, FullNameStyle.WESTERN);
282        verifyKeys(keys, LATIN_NAME_KEY);
283        keys = getNameLookupKeys(LATIN_NAME_2, FullNameStyle.WESTERN);
284        verifyKeys(keys, LATIN_NAME_KEY_2);
285
286        if (hasTraditionalChineseCollator) {
287            ContactLocaleUtils.setLocale(Locale.TRADITIONAL_CHINESE);
288            assertNull(getNameLookupKeys(CHINESE_NAME, FullNameStyle.CJK));
289        }
290    }
291
292    public void testKoreanContactLocaleUtils() throws Exception {
293        if (!hasKoreanCollator) {
294            Log.w(TAG, "Korean collator not found; skipping test");
295            return;
296        }
297
298        ContactLocaleUtils.setLocale(Locale.KOREA);
299        assertEquals("\u3131", getLabel("\u1100"));
300        assertEquals("\u3131", getLabel("\u3131"));
301        assertEquals("\u3131", getLabel("\u1101"));
302        assertEquals("\u314e", getLabel("\u1161"));
303        assertEquals("B", getLabel("Bob Smith"));
304        verifyLabels(getLabels(), LABELS_KO);
305    }
306
307    public void testArabicContactLocaleUtils() throws Exception {
308        if (!hasArabicCollator) {
309            Log.w(TAG, "Arabic collator not found; skipping test");
310            return;
311        }
312
313        ContactLocaleUtils.setLocale(LOCALE_ARABIC);
314        assertEquals("\u0646", getLabel(ARABIC_NAME));
315        assertEquals("B", getLabel("Bob Smith"));
316        verifyLabels(getLabels(), LABELS_AR);
317    }
318
319    public void testSerbianContactLocaleUtils() throws Exception {
320        if (!hasSerbianCollator) {
321            Log.w(TAG, "Serbian collator not found; skipping test");
322            return;
323        }
324
325        ContactLocaleUtils.setLocale(LOCALE_SERBIAN);
326        assertEquals("\u0408", getLabel(SERBIAN_NAME));
327        assertEquals("B", getLabel("Bob Smith"));
328    }
329
330    public void testUkrainianContactLocaleUtils() throws Exception {
331        if (!hasUkrainianCollator) {
332            Log.w(TAG, "Ukrainian collator not found; skipping test");
333            return;
334        }
335
336        ContactLocaleUtils.setLocale(LOCALE_UKRAINIAN);
337        assertEquals("\u0407", getLabel(UKRAINIAN_NAME));
338        // ICU 52 has a bug whereby this letter has a bucket created only if
339        // Ukrainian is the primary language. Once this is fixed also test this
340        // label when in English locale.
341        assertEquals("\u0490", getLabel(UKRAINIAN_NAME_2));
342        assertEquals("B", getLabel("Bob Smith"));
343    }
344
345    public void testGermanContactLocaleUtils() throws Exception {
346        if (!hasGermanCollator) {
347            return;
348        }
349
350        ContactLocaleUtils.setLocale(Locale.GERMANY);
351        assertEquals("S", getLabel("Sacher"));
352
353        // ICU 51 has labels Sch and St. These were removed in ICU 52
354        assertEquals("S", getLabel("Schiller"));
355        assertEquals("S", getLabel("Steiff"));
356        verifyLabels(getLabels(), LABELS_EN_US);
357    }
358
359    private void verifyKeys(final Iterator<String> resultKeys, final String[] expectedKeys)
360            throws Exception {
361        HashSet<String> allKeys = new HashSet<String>();
362        while (resultKeys.hasNext()) {
363            allKeys.add(resultKeys.next());
364        }
365        assertEquals(new HashSet<String>(Arrays.asList(expectedKeys)), allKeys);
366    }
367
368    // Verify that the initial set of resultLabels matches the expectedLabels.
369    // Ignore the (large) number of secondary locale labels that make up the
370    // tail labels in the result set right before the final "#" and "" buckets.
371    private void verifyLabels(final ArrayList<String> resultLabels,
372            final String[] expectedLabels) throws Exception {
373        final List<String> expectedLabelList = Arrays.asList(expectedLabels);
374        final int numLabels = expectedLabelList.size() - 2;
375        assertEquals(expectedLabelList.subList(0, numLabels),
376                resultLabels.subList(0, numLabels));
377    }
378}
379