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