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