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