1/*
2 * Copyright (C) 2009 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.content.ContentValues;
20import android.net.Uri;
21import android.net.Uri.Builder;
22import android.provider.ContactsContract.CommonDataKinds.Im;
23import android.provider.ContactsContract.CommonDataKinds.Organization;
24import android.provider.ContactsContract.CommonDataKinds.StructuredName;
25import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
26import android.provider.ContactsContract.Contacts;
27import android.provider.ContactsContract.SearchSnippetColumns;
28import android.test.MoreAsserts;
29import android.test.suitebuilder.annotation.MediumTest;
30import android.test.suitebuilder.annotation.Suppress;
31
32import com.android.providers.contacts.testutil.DataUtil;
33import com.android.providers.contacts.testutil.RawContactUtil;
34
35import java.text.Collator;
36import java.util.Arrays;
37import java.util.Locale;
38
39/**
40 * Unit tests for {@link SearchIndexManager}.
41 *
42 * Run the test like this:
43 * <code>
44 * adb shell am instrument -e class com.android.providers.contacts.SearchIndexManagerTest -w \
45 *         com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
46 * </code>
47 */
48@MediumTest
49public class SearchIndexManagerTest extends BaseContactsProvider2Test {
50
51    public void testSearchIndexForStructuredName() {
52        long rawContactId = RawContactUtil.createRawContact(mResolver);
53        long contactId = queryContactId(rawContactId);
54        DataUtil.insertStructuredName(mResolver, rawContactId, "John", "Doe");
55        ContentValues values = new ContentValues();
56        values.put(StructuredName.DISPLAY_NAME, "Bob I. Parr");
57        DataUtil.insertStructuredName(mResolver, rawContactId, values);
58        values.clear();
59        values.put(StructuredName.PREFIX, "Mrs.");
60        values.put(StructuredName.GIVEN_NAME, "Helen");
61        values.put(StructuredName.MIDDLE_NAME, "I.");
62        values.put(StructuredName.FAMILY_NAME, "Parr");
63        values.put(StructuredName.SUFFIX, "PhD");
64        values.put(StructuredName.PHONETIC_FAMILY_NAME, "par");
65        values.put(StructuredName.PHONETIC_GIVEN_NAME, "helen");
66        DataUtil.insertStructuredName(mResolver, rawContactId, values);
67
68        assertSearchIndex(
69                contactId, null, "John Doe Bob I Parr Helen I Parr PhD par helen parhelen", null);
70    }
71
72    public void testSearchIndexForChineseName() {
73        // Only run this test when Chinese collation is supported
74        if (!Arrays.asList(Collator.getAvailableLocales()).contains(Locale.CHINA)) {
75            return;
76        }
77
78        long rawContactId = RawContactUtil.createRawContact(mResolver);
79        long contactId = queryContactId(rawContactId);
80        ContentValues values = new ContentValues();
81        values.put(StructuredName.DISPLAY_NAME, "\u695A\u8FAD");    // CHUCI
82        DataUtil.insertStructuredName(mResolver, rawContactId, values);
83
84        assertSearchIndex(
85                contactId, null, "\u695A\u8FAD \u695A\u8FAD CI \u8FAD CHUCI CC C", null);
86    }
87
88    public void testSearchByChineseName() {
89        // Only run this test when Chinese collation is supported
90        if (!Arrays.asList(Collator.getAvailableLocales()).contains(Locale.CHINA)) {
91            return;
92        }
93        ContactLocaleUtils.setLocale(Locale.SIMPLIFIED_CHINESE);
94
95        long rawContactId = RawContactUtil.createRawContact(mResolver);
96        ContentValues values = new ContentValues();
97        values.put(StructuredName.DISPLAY_NAME, "\u695A\u8FAD");    // CHUCI
98        DataUtil.insertStructuredName(mResolver, rawContactId, values);
99
100        assertStoredValue(buildSearchUri("\u695A\u8FAD"), SearchSnippetColumns.SNIPPET, null);
101        assertStoredValue(buildSearchUri("\u8FAD"), SearchSnippetColumns.SNIPPET, null);
102        assertStoredValue(buildSearchUri("CI"), SearchSnippetColumns.SNIPPET, null);
103        assertStoredValue(buildSearchUri("CHUCI"), SearchSnippetColumns.SNIPPET, null);
104        assertStoredValue(buildSearchUri("CC"), SearchSnippetColumns.SNIPPET, null);
105        assertStoredValue(buildSearchUri("C"), SearchSnippetColumns.SNIPPET, null);
106    }
107
108    public void testSearchIndexForKoreanName() {
109        // Only run this test when Korean collation is supported
110        if (!Arrays.asList(Collator.getAvailableLocales()).contains(Locale.KOREA)) {
111            return;
112        }
113
114        long rawContactId = RawContactUtil.createRawContact(mResolver);
115        long contactId = queryContactId(rawContactId);
116        ContentValues values = new ContentValues();
117        values.put(StructuredName.DISPLAY_NAME, "\uC774\uC0C1\uC77C");    // Lee Sang Il
118        DataUtil.insertStructuredName(mResolver, rawContactId, values);
119
120        assertSearchIndex(contactId, null,
121                "\uC774\uC0C1\uC77C \uC0C1\uC77C \u1109\u110B \u110B\u1109\u110B", null);
122    }
123
124    public void testSearchByKoreanName() {
125        // Only run this test when Korean collation is supported
126        if (!Arrays.asList(Collator.getAvailableLocales()).contains(Locale.KOREA)) {
127            return;
128        }
129
130        long rawContactId = RawContactUtil.createRawContact(mResolver);
131        ContentValues values = new ContentValues();
132        values.put(StructuredName.DISPLAY_NAME, "\uC774\uC0C1\uC77C");   // Lee Sang Il
133        DataUtil.insertStructuredName(mResolver, rawContactId, values);
134
135        // Full name: Lee Sang Il
136        assertStoredValue(buildSearchUri("\uC774\uC0C1\uC77C"), SearchSnippetColumns.SNIPPET, null);
137
138        // Given name: Sang Il
139        assertStoredValue(buildSearchUri("\uC0C1\uC77C"), SearchSnippetColumns.SNIPPET, null);
140
141        // Consonants of given name: SIOS IEUNG
142        assertStoredValue(buildSearchUri("\u1109\u110B"), SearchSnippetColumns.SNIPPET, null);
143
144        // Consonants of full name: RIEUL SIOS IEUNG
145        assertStoredValue(buildSearchUri("\u110B\u1109\u110B"), SearchSnippetColumns.SNIPPET, null);
146    }
147
148    public void testSearchByKoreanNameWithTwoCharactersFamilyName() {
149        // Only run this test when Korean collation is supported.
150        if (!Arrays.asList(Collator.getAvailableLocales()).contains(Locale.KOREA)) {
151            return;
152        }
153
154        long rawContactId = RawContactUtil.createRawContact(mResolver);
155
156        // Sun Woo Young Nyeu
157        ContentValues values = new ContentValues();
158        values.put(StructuredName.DISPLAY_NAME, "\uC120\uC6B0\uC6A9\uB140");
159
160        DataUtil.insertStructuredName(mResolver, rawContactId, values);
161
162        // Full name: Sun Woo Young Nyeu
163        assertStoredValue(
164                buildSearchUri("\uC120\uC6B0\uC6A9\uB140"), SearchSnippetColumns.SNIPPET, null);
165
166        // Given name: Young Nyeu
167        assertStoredValue(buildSearchUri("\uC6A9\uB140"), SearchSnippetColumns.SNIPPET, null);
168
169        // Consonants of given name: IEUNG NIEUN
170        assertStoredValue(buildSearchUri("\u110B\u1102"), SearchSnippetColumns.SNIPPET, null);
171
172        // Consonants of full name: SIOS IEUNG IEUNG NIEUN
173        assertStoredValue(
174                buildSearchUri("\u1109\u110B\u110B\u1102"), SearchSnippetColumns.SNIPPET, null);
175    }
176
177    public void testSearchIndexForOrganization() {
178        long rawContactId = RawContactUtil.createRawContact(mResolver);
179        long contactId = queryContactId(rawContactId);
180        ContentValues values = new ContentValues();
181        values.put(Organization.COMPANY, "Acme Inc.");
182        values.put(Organization.TITLE, "Director");
183        values.put(Organization.DEPARTMENT, "Phones and tablets");
184        values.put(Organization.JOB_DESCRIPTION, "full text search");
185        values.put(Organization.SYMBOL, "ACME");
186        values.put(Organization.PHONETIC_NAME, "ack-me");
187        values.put(Organization.OFFICE_LOCATION, "virtual");
188        insertOrganization(rawContactId, values);
189
190        assertSearchIndex(contactId,
191                "Director, Acme Inc. (ack-me) (ACME)/Phones and tablets/virtual/full text search",
192                null, null);
193    }
194
195    public void testSearchIndexForPhoneNumber() {
196        long rawContactId = RawContactUtil.createRawContact(mResolver);
197        long contactId = queryContactId(rawContactId);
198        insertPhoneNumber(rawContactId, "800555GOOG");
199        insertPhoneNumber(rawContactId, "8005551234");
200
201        assertSearchIndex(contactId, null, null, "8005554664 +18005554664 8005551234 +18005551234");
202    }
203
204    public void testSearchIndexForEmail() {
205        long rawContactId = RawContactUtil.createRawContact(mResolver);
206        long contactId = queryContactId(rawContactId);
207        insertEmail(rawContactId, "Bob Parr <incredible@android.com>");
208        insertEmail(rawContactId, "bob_parr@android.com");
209
210        assertSearchIndex(contactId, "Bob Parr <incredible@android.com>\nbob_parr@android.com",
211                null, null);
212    }
213
214    public void testSearchIndexForNickname() {
215        long rawContactId = RawContactUtil.createRawContact(mResolver);
216        long contactId = queryContactId(rawContactId);
217        insertNickname(rawContactId, "incredible");
218
219        assertSearchIndex(contactId, "incredible", null, null);
220    }
221
222    public void testSearchIndexForStructuredPostal() {
223        long rawContactId = RawContactUtil.createRawContact(mResolver);
224        long contactId = queryContactId(rawContactId);
225        insertPostalAddress(rawContactId, "1600 Amphitheatre Pkwy\nMountain View, CA 94043");
226        ContentValues values = new ContentValues();
227        values.put(StructuredPostal.CITY, "London");
228        values.put(StructuredPostal.STREET, "76 Buckingham Palace Road");
229        values.put(StructuredPostal.POSTCODE, "SW1W 9TQ");
230        values.put(StructuredPostal.COUNTRY, "United Kingdom");
231        insertPostalAddress(rawContactId, values);
232
233        assertSearchIndex(contactId, "1600 Amphitheatre Pkwy Mountain View, CA 94043\n"
234                + "76 Buckingham Palace Road London SW1W 9TQ United Kingdom", null, null);
235    }
236
237    public void testSearchIndexForIm() {
238        long rawContactId = RawContactUtil.createRawContact(mResolver);
239        long contactId = queryContactId(rawContactId);
240        insertImHandle(rawContactId, Im.PROTOCOL_JABBER, null, "bp@android.com");
241        insertImHandle(rawContactId, Im.PROTOCOL_CUSTOM, "android_im", "android@android.com");
242
243        assertSearchIndex(
244                contactId, "Jabber/bp@android.com\nandroid_im/android@android.com", null, null);
245    }
246
247    public void testSearchIndexForNote() {
248        long rawContactId = RawContactUtil.createRawContact(mResolver);
249        long contactId = queryContactId(rawContactId);
250        insertNote(rawContactId, "Please note: three notes or more make up a chord.");
251
252        assertSearchIndex(
253                contactId, "Please note: three notes or more make up a chord.", null, null);
254    }
255
256    public void testSnippetArgs() {
257        long rawContactId = RawContactUtil.createRawContact(mResolver);
258        insertNote(rawContactId, "Please note: three notes or more make up a chord.");
259
260        assertStoredValue(
261                buildSearchUri("thr", "[,],-,2", false), SearchSnippetColumns.SNIPPET,
262                "-note: [three]-");
263    }
264
265    public void testEmptyFilter() {
266        RawContactUtil.createRawContactWithName(mResolver, "John", "Doe");
267        assertEquals(0, getCount(buildSearchUri(""), null, null));
268    }
269
270    public void testSearchByName() {
271        RawContactUtil.createRawContactWithName(mResolver, "John Jay", "Doe");
272
273        // We are supposed to find the contact, but return a null snippet
274        assertStoredValue(buildSearchUri("john"), SearchSnippetColumns.SNIPPET, null);
275        assertStoredValue(buildSearchUri("jay"), SearchSnippetColumns.SNIPPET, null);
276        assertStoredValue(buildSearchUri("doe"), SearchSnippetColumns.SNIPPET, null);
277    }
278
279    public void testSearchByPrefixName() {
280        RawContactUtil.createRawContactWithName(mResolver, "John Jay", "Doe");
281
282        // prefix searches
283        assertStoredValue(buildSearchUri("jo ja"), SearchSnippetColumns.SNIPPET, null);
284        assertStoredValue(buildSearchUri("J D"), SearchSnippetColumns.SNIPPET, null);
285        assertStoredValue(buildSearchUri("Doe, John"), SearchSnippetColumns.SNIPPET, null);
286    }
287
288    public void testGermanUmlautFullameCapitalizationSearch() {
289        RawContactUtil.createRawContactWithName(mResolver, "Matthäus BJÖRN Bünyamin", "Reißer");
290
291        // make sure we can find those, independent of the capitalization
292        assertStoredValue(buildSearchUri("matthäus"), SearchSnippetColumns.SNIPPET, null);
293        assertStoredValue(buildSearchUri("Matthäus"), SearchSnippetColumns.SNIPPET, null);
294        assertStoredValue(buildSearchUri("MATTHÄUS"), SearchSnippetColumns.SNIPPET, null);
295
296        assertStoredValue(buildSearchUri("björn"), SearchSnippetColumns.SNIPPET, null);
297        assertStoredValue(buildSearchUri("Björn"), SearchSnippetColumns.SNIPPET, null);
298        assertStoredValue(buildSearchUri("BJÖRN"), SearchSnippetColumns.SNIPPET, null);
299
300        assertStoredValue(buildSearchUri("bünyamin"), SearchSnippetColumns.SNIPPET, null);
301        assertStoredValue(buildSearchUri("Bünyamin"), SearchSnippetColumns.SNIPPET, null);
302        assertStoredValue(buildSearchUri("BUNYAMIN"), SearchSnippetColumns.SNIPPET, null);
303
304        // There is no capital version of ß. It is capitalized as double-S instead
305        assertStoredValue(buildSearchUri("Reißer"), SearchSnippetColumns.SNIPPET, null);
306        assertStoredValue(buildSearchUri("Reisser"), SearchSnippetColumns.SNIPPET, null);
307        assertStoredValue(buildSearchUri("REISSER"), SearchSnippetColumns.SNIPPET, null);
308    }
309
310    public void testHangulNameLeadConsonantAsYouTypeSearch() {
311        createRawContactWithDisplayName("홍길동");
312        // the korean name uses three compound characters. this test makes sure
313        // that the name can be found by typing in only the lead consonant
314        assertStoredValue(buildSearchUri("ㅎ"), SearchSnippetColumns.SNIPPET, null);
315        assertStoredValue(buildSearchUri("ㅎㄱ"), SearchSnippetColumns.SNIPPET, null);
316        assertStoredValue(buildSearchUri("ㅎㄱㄷ"), SearchSnippetColumns.SNIPPET, null);
317
318        // same again, this time only for the first name
319        assertStoredValue(buildSearchUri("ㄱ"), SearchSnippetColumns.SNIPPET, null);
320        assertStoredValue(buildSearchUri("ㄱㄷ"), SearchSnippetColumns.SNIPPET, null);
321    }
322
323    public void testHangulNameFullAsYouTypeSearch() {
324        createRawContactWithDisplayName("홍길동");
325
326        // the korean name uses three compound characters. this test makes sure
327        // that the name can be found by typing in the full nine letters. the search string
328        // shows the name is being built "as you type"
329        assertStoredValue(buildSearchUri("ㅎ"), SearchSnippetColumns.SNIPPET, null);
330        assertStoredValue(buildSearchUri("호"), SearchSnippetColumns.SNIPPET, null);
331        assertStoredValue(buildSearchUri("홍"), SearchSnippetColumns.SNIPPET, null);
332        assertStoredValue(buildSearchUri("홍ㄱ"), SearchSnippetColumns.SNIPPET, null);
333        assertStoredValue(buildSearchUri("홍기"), SearchSnippetColumns.SNIPPET, null);
334        assertStoredValue(buildSearchUri("홍길"), SearchSnippetColumns.SNIPPET, null);
335        assertStoredValue(buildSearchUri("홍길ㄷ"), SearchSnippetColumns.SNIPPET, null);
336        assertStoredValue(buildSearchUri("홍길도"), SearchSnippetColumns.SNIPPET, null);
337        assertStoredValue(buildSearchUri("홍길동"), SearchSnippetColumns.SNIPPET, null);
338
339        // same again, this time only for the first name
340        assertStoredValue(buildSearchUri("ㄱ"), SearchSnippetColumns.SNIPPET, null);
341        assertStoredValue(buildSearchUri("기"), SearchSnippetColumns.SNIPPET, null);
342        assertStoredValue(buildSearchUri("길"), SearchSnippetColumns.SNIPPET, null);
343        assertStoredValue(buildSearchUri("길ㄷ"), SearchSnippetColumns.SNIPPET, null);
344        assertStoredValue(buildSearchUri("길도"), SearchSnippetColumns.SNIPPET, null);
345        assertStoredValue(buildSearchUri("길동"), SearchSnippetColumns.SNIPPET, null);
346    }
347
348
349    /** Decomposed Hangul is not yet supported. This text is how we would test it */
350    @Suppress
351    public void testHangulNameDecomposedSearch() {
352        createRawContactWithDisplayName("홍길동");
353
354        // the korean name uses three compound characters. this test makes sure
355        // that the name can be found by typing each syllable as a single character.
356        // This can be achieved using the Korean IM by pressing ㅎ, space, backspace, ㅗ and so on
357        assertStoredValue(buildSearchUri("ㅎ"), SearchSnippetColumns.SNIPPET, null);
358        assertStoredValue(buildSearchUri("ㅎㅗ"), SearchSnippetColumns.SNIPPET, null);
359        assertStoredValue(buildSearchUri("ㅎㅗㅇ"), SearchSnippetColumns.SNIPPET, null);
360        assertStoredValue(buildSearchUri("ㅎㅗㅇㄱ"), SearchSnippetColumns.SNIPPET, null);
361        assertStoredValue(buildSearchUri("ㅎㅗㅇㄱㅣ"), SearchSnippetColumns.SNIPPET, null);
362        assertStoredValue(buildSearchUri("ㅎㅗㅇㄱㅣㄹ"), SearchSnippetColumns.SNIPPET, null);
363        assertStoredValue(buildSearchUri("ㅎㅗㅇㄱㅣㄹㄷ"), SearchSnippetColumns.SNIPPET, null);
364        assertStoredValue(buildSearchUri("ㅎㅗㅇㄱㅣㄹㄷㅗ"), SearchSnippetColumns.SNIPPET, null);
365        assertStoredValue(buildSearchUri("ㅎㅗㅇㄱㅣㄹㄷㅗㅇ"), SearchSnippetColumns.SNIPPET, null);
366
367        // same again, this time only for the first name
368        assertStoredValue(buildSearchUri("ㄱ"), SearchSnippetColumns.SNIPPET, null);
369        assertStoredValue(buildSearchUri("ㄱㅣ"), SearchSnippetColumns.SNIPPET, null);
370        assertStoredValue(buildSearchUri("ㄱㅣㄹ"), SearchSnippetColumns.SNIPPET, null);
371        assertStoredValue(buildSearchUri("ㄱㅣㄹㄷ"), SearchSnippetColumns.SNIPPET, null);
372        assertStoredValue(buildSearchUri("ㄱㅣㄹㄷㅗ"), SearchSnippetColumns.SNIPPET, null);
373        assertStoredValue(buildSearchUri("ㄱㅣㄹㄷㅗㅇ"), SearchSnippetColumns.SNIPPET, null);
374    }
375
376    public void testNameWithHyphen() {
377        RawContactUtil.createRawContactWithName(mResolver, "First", "Last-name");
378
379        assertStoredValue(buildSearchUri("First"), SearchSnippetColumns.SNIPPET, null);
380        assertStoredValue(buildSearchUri("Last"), SearchSnippetColumns.SNIPPET, null);
381        assertStoredValue(buildSearchUri("Last-"), SearchSnippetColumns.SNIPPET, null);
382        assertStoredValue(buildSearchUri("Last-n"), SearchSnippetColumns.SNIPPET, null);
383        assertStoredValue(buildSearchUri("Last-name"), SearchSnippetColumns.SNIPPET, null);
384
385        // This will work too.
386        assertStoredValue(buildSearchUri("Lastname"), SearchSnippetColumns.SNIPPET, null);
387
388        // This doesn't have to work, but it does with the current implementation.
389        assertStoredValue(buildSearchUri("name"), SearchSnippetColumns.SNIPPET, null);
390    }
391
392    /** Same as {@link #testNameWithHyphen} except the name has double hyphens. */
393    public void testNameWithDoubleHyphens() {
394        RawContactUtil.createRawContactWithName(mResolver, "First", "Last--name");
395
396        assertStoredValue(buildSearchUri("First"), SearchSnippetColumns.SNIPPET, null);
397        assertStoredValue(buildSearchUri("Last"), SearchSnippetColumns.SNIPPET, null);
398        assertStoredValue(buildSearchUri("Last-"), SearchSnippetColumns.SNIPPET, null);
399        assertStoredValue(buildSearchUri("Last-n"), SearchSnippetColumns.SNIPPET, null);
400        assertStoredValue(buildSearchUri("Last-name"), SearchSnippetColumns.SNIPPET, null);
401
402        // This will work too.
403        assertStoredValue(buildSearchUri("Lastname"), SearchSnippetColumns.SNIPPET, null);
404    }
405
406    public void testNameWithPunctuations() {
407        RawContactUtil.createRawContactWithName(mResolver, "First", "O'Neill");
408
409        assertStoredValue(buildSearchUri("first"), SearchSnippetColumns.SNIPPET, null);
410        assertStoredValue(buildSearchUri("oneill"), SearchSnippetColumns.SNIPPET, null);
411        assertStoredValue(buildSearchUri("o'neill"), SearchSnippetColumns.SNIPPET, null);
412    }
413
414    public void testSearchByEmailAddress() {
415        long rawContactId = RawContactUtil.createRawContact(mResolver);
416        insertPhoneNumber(rawContactId, "1234567890");
417        insertEmail(rawContactId, "john@doe.com");
418        insertNote(rawContactId, "a hundred dollar note for doe@john.com and bob parr");
419
420        assertStoredValue(buildSearchUri("john@d", true), SearchSnippetColumns.SNIPPET,
421                "[john@doe.com]");
422        assertStoredValue(buildSearchUri("doe@j", true), SearchSnippetColumns.SNIPPET,
423                "...note for [doe@john.com] and bob...");
424        assertStoredValue(buildSearchUri("bob@p", true), SearchSnippetColumns.SNIPPET, null);
425    }
426
427    public void testSearchByPhoneNumber() {
428        long rawContactId = RawContactUtil.createRawContact(mResolver);
429        insertPhoneNumber(rawContactId, "330142685300");
430        insertPhoneNumber(rawContactId, "(800)GOOG-123");
431        insertEmail(rawContactId, "john@doe.com");
432        insertNote(rawContactId, "the eighteenth episode of Seinfeld, 650-253-0000");
433
434        assertStoredValue(buildSearchUri("33 (0)1 42 68 53 00"), SearchSnippetColumns.SNIPPET,
435                "[330142685300]");
436        assertStoredValue(buildSearchUri("8004664"), SearchSnippetColumns.SNIPPET,
437                "[(800)GOOG-123]");
438        assertStoredValue(buildSearchUri("650-2"), SearchSnippetColumns.SNIPPET,
439                "...doe.com\nthe eighteenth episode of Seinfeld, [650]-[253]-0000");
440
441        // for numbers outside of the real phone field, any order (and prefixing) is allowed
442        assertStoredValue(buildSearchUri("25 650"), SearchSnippetColumns.SNIPPET,
443                "...doe.com\nthe eighteenth episode of Seinfeld, [650]-[253]-0000");
444    }
445
446    /**
447     * Test case for bug 5904515
448     */
449    public void testSearchByPhoneNumber_diferSnippetting() {
450        long rawContactId = RawContactUtil.createRawContact(mResolver);
451        insertPhoneNumber(rawContactId, "505-123-4567");
452
453        // The bug happened with the old code only when we use \u0001 as the snippet marker.
454        // But note that the expected result has [ and ] instead of \u0001.  This is because when
455        // we differ snippetizing, the marker passe to the provider will be ignored; instead
456        // assertStoredValue internally do the client-side snippetizing, which done by
457        // getCursorStringValue(), which is hardcoded to use [ and ].
458        assertStoredValue(buildSearchUri("505", "\u0001,\u0001,\u2026,5", true),
459                SearchSnippetColumns.SNIPPET, "[505]-123-4567");
460    }
461
462    /**
463     * Equivalent to {@link #testSearchByPhoneNumber_diferSnippetting} for email addresses, although
464     * the original bug didn't happen with email addresses... (It *did* happen internally, but
465     * there's no visible breakage.)
466     */
467    public void testSearchByEmail_diferSnippetting() {
468        long rawContactId = RawContactUtil.createRawContact(mResolver);
469        insertEmail(rawContactId, "john@doe.com");
470
471        assertStoredValue(buildSearchUri("john", "\u0001,\u0001,\u2026,5", true),
472                SearchSnippetColumns.SNIPPET, "[john@doe.com]");
473    }
474
475    public void testSplitIntoFtsTokens() {
476        checkSplitIntoFtsTokens("a", "a");
477        checkSplitIntoFtsTokens("a_b c%d-e'f", "a_b", "c", "d", "e", "f");
478        checkSplitIntoFtsTokens("  ", new String[0]);
479        // There's are all "control" characters, but treated as "letters".
480        // (See http://en.wikipedia.org/wiki/C1_Controls_and_Latin-1_Supplement for what they are)
481        checkSplitIntoFtsTokens("\u0080 \u0081 \u0082", "\u0080", "\u0081", "\u0082");
482
483        // FFF0 is also a token.
484        checkSplitIntoFtsTokens(" \ufff0  ", "\ufff0");
485    }
486
487    private void checkSplitIntoFtsTokens(String input, String... expectedTokens) {
488        MoreAsserts.assertEquals(expectedTokens,
489                SearchIndexManager.splitIntoFtsTokens(input).toArray(new String[0]));
490    }
491
492    private Uri buildSearchUri(String filter) {
493        return buildSearchUri(filter, false);
494    }
495
496    private Uri buildSearchUri(String filter, boolean deferredSnippeting) {
497        return buildSearchUri(filter, null, deferredSnippeting);
498    }
499
500    private Uri buildSearchUri(String filter, String args, boolean deferredSnippeting) {
501        Builder builder = Contacts.CONTENT_FILTER_URI.buildUpon().appendPath(filter);
502        if (args != null) {
503            builder.appendQueryParameter(SearchSnippetColumns.SNIPPET_ARGS_PARAM_KEY, args);
504        }
505        if (deferredSnippeting) {
506            builder.appendQueryParameter(SearchSnippetColumns.DEFERRED_SNIPPETING_KEY, "1");
507        }
508        return builder.build();
509    }
510
511    private void createRawContactWithDisplayName(String name) {
512        long rawContactId = RawContactUtil.createRawContact(mResolver);
513        ContentValues values = new ContentValues();
514        values.put(StructuredName.DISPLAY_NAME, name);
515        DataUtil.insertStructuredName(mResolver, rawContactId, values);
516    }
517
518    // TODO: expectedName must be tested. Many tests in here are quite useless at the moment
519    private void assertSearchIndex(
520            long contactId, String expectedContent, String expectedName, String expectedTokens) {
521        ContactsDatabaseHelper dbHelper = (ContactsDatabaseHelper) getContactsProvider()
522                .getDatabaseHelper();
523        assertEquals(expectedContent, dbHelper.querySearchIndexContentForTest(contactId));
524        assertEquals(expectedTokens, dbHelper.querySearchIndexTokensForTest(contactId));
525    }
526}
527
528