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