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 17 package com.android.providers.contacts; 18 19 import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY; 20 21 import android.accounts.Account; 22 import android.content.ContentProvider; 23 import android.content.ContentResolver; 24 import android.content.ContentUris; 25 import android.content.ContentValues; 26 import android.content.Context; 27 import android.content.Entity; 28 import android.content.res.Resources; 29 import android.database.Cursor; 30 import android.net.Uri; 31 import android.provider.ContactsContract; 32 import android.provider.ContactsContract.AggregationExceptions; 33 import android.provider.ContactsContract.Contacts; 34 import android.provider.ContactsContract.Data; 35 import android.provider.ContactsContract.Groups; 36 import android.provider.ContactsContract.RawContacts; 37 import android.provider.ContactsContract.Settings; 38 import android.provider.ContactsContract.StatusUpdates; 39 import android.provider.ContactsContract.CommonDataKinds.Email; 40 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 41 import android.provider.ContactsContract.CommonDataKinds.Im; 42 import android.provider.ContactsContract.CommonDataKinds.Nickname; 43 import android.provider.ContactsContract.CommonDataKinds.Organization; 44 import android.provider.ContactsContract.CommonDataKinds.Phone; 45 import android.provider.ContactsContract.CommonDataKinds.Photo; 46 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 47 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 48 import android.test.AndroidTestCase; 49 import android.test.mock.MockContentResolver; 50 import android.util.Log; 51 52 import java.io.ByteArrayOutputStream; 53 import java.io.IOException; 54 import java.io.InputStream; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.Comparator; 58 import java.util.Iterator; 59 import java.util.Map; 60 import java.util.Set; 61 import java.util.Map.Entry; 62 63 /** 64 * A common superclass for {@link ContactsProvider2}-related tests. 65 */ 66 public abstract class BaseContactsProvider2Test extends AndroidTestCase { 67 68 protected static final String PACKAGE = "ContactsProvider2Test"; 69 public static final String READ_ONLY_ACCOUNT_TYPE = 70 SynchronousContactsProvider2.READ_ONLY_ACCOUNT_TYPE; 71 72 protected ContactsActor mActor; 73 protected MockContentResolver mResolver; 74 protected Account mAccount = new Account("account1", "account type1"); 75 protected Account mAccountTwo = new Account("account2", "account type2"); 76 77 private byte[] mTestPhoto; 78 79 protected final static Long NO_LONG = new Long(0); 80 protected final static String NO_STRING = new String(""); 81 protected final static Account NO_ACCOUNT = new Account("a", "b"); 82 83 protected Class<? extends ContentProvider> getProviderClass() { 84 return SynchronousContactsProvider2.class; 85 } 86 87 protected String getAuthority() { 88 return ContactsContract.AUTHORITY; 89 } 90 91 @Override 92 protected void setUp() throws Exception { 93 super.setUp(); 94 95 mActor = new ContactsActor(getContext(), PACKAGE_GREY, getProviderClass(), getAuthority()); 96 mResolver = mActor.resolver; 97 if (mActor.provider instanceof SynchronousContactsProvider2) { 98 ((SynchronousContactsProvider2) mActor.provider) 99 .getDatabaseHelper(mActor.context).wipeData(); 100 } 101 } 102 103 @Override 104 protected void tearDown() throws Exception { 105 if (mActor.provider instanceof SynchronousContactsProvider2) { 106 ((SynchronousContactsProvider2) mActor.provider) 107 .getDatabaseHelper(mActor.context).close(); 108 } 109 super.tearDown(); 110 } 111 112 public Context getMockContext() { 113 return mActor.context; 114 } 115 116 public void addAuthority(String authority) { 117 mActor.addAuthority(authority); 118 } 119 120 public ContentProvider addProvider(Class<? extends ContentProvider> providerClass, 121 String authority) throws Exception { 122 return mActor.addProvider(providerClass, authority); 123 } 124 125 public ContentProvider getProvider() { 126 return mActor.provider; 127 } 128 129 protected Uri maybeAddAccountQueryParameters(Uri uri, Account account) { 130 if (account == null) { 131 return uri; 132 } 133 return uri.buildUpon() 134 .appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name) 135 .appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type) 136 .build(); 137 } 138 139 protected long createRawContact() { 140 return createRawContact(null); 141 } 142 143 protected long createRawContactWithName() { 144 return createRawContactWithName(null); 145 } 146 147 protected long createRawContactWithName(Account account) { 148 return createRawContactWithName("John", "Doe", account); 149 } 150 151 protected long createRawContactWithName(String firstName, String lastName) { 152 return createRawContactWithName(firstName, lastName, null); 153 } 154 155 protected long createRawContactWithName(String firstName, String lastName, Account account) { 156 long rawContactId = createRawContact(account); 157 insertStructuredName(rawContactId, firstName, lastName); 158 return rawContactId; 159 } 160 161 protected Uri setCallerIsSyncAdapter(Uri uri, Account account) { 162 if (account == null) { 163 return uri; 164 } 165 final Uri.Builder builder = uri.buildUpon(); 166 builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name); 167 builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type); 168 builder.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true"); 169 return builder.build(); 170 } 171 172 protected long createRawContact(Account account, String... extras) { 173 ContentValues values = new ContentValues(); 174 for (int i = 0; i < extras.length; ) { 175 values.put(extras[i], extras[i + 1]); 176 i += 2; 177 } 178 final Uri uri = maybeAddAccountQueryParameters(RawContacts.CONTENT_URI, account); 179 Uri contactUri = mResolver.insert(uri, values); 180 return ContentUris.parseId(contactUri); 181 } 182 183 protected long createGroup(Account account, String sourceId, String title) { 184 return createGroup(account, sourceId, title, 1); 185 } 186 187 protected long createGroup(Account account, String sourceId, String title, int visible) { 188 ContentValues values = new ContentValues(); 189 values.put(Groups.SOURCE_ID, sourceId); 190 values.put(Groups.TITLE, title); 191 values.put(Groups.GROUP_VISIBLE, visible); 192 final Uri uri = maybeAddAccountQueryParameters(Groups.CONTENT_URI, account); 193 return ContentUris.parseId(mResolver.insert(uri, values)); 194 } 195 196 protected void createSettings(Account account, String shouldSync, String ungroupedVisible) { 197 ContentValues values = new ContentValues(); 198 values.put(Settings.ACCOUNT_NAME, account.name); 199 values.put(Settings.ACCOUNT_TYPE, account.type); 200 values.put(Settings.SHOULD_SYNC, shouldSync); 201 values.put(Settings.UNGROUPED_VISIBLE, ungroupedVisible); 202 mResolver.insert(Settings.CONTENT_URI, values); 203 } 204 205 protected Uri insertStructuredName(long rawContactId, String givenName, String familyName) { 206 ContentValues values = new ContentValues(); 207 StringBuilder sb = new StringBuilder(); 208 if (givenName != null) { 209 sb.append(givenName); 210 } 211 if (givenName != null && familyName != null) { 212 sb.append(" "); 213 } 214 if (familyName != null) { 215 sb.append(familyName); 216 } 217 values.put(StructuredName.DISPLAY_NAME, sb.toString()); 218 values.put(StructuredName.GIVEN_NAME, givenName); 219 values.put(StructuredName.FAMILY_NAME, familyName); 220 221 return insertStructuredName(rawContactId, values); 222 } 223 224 protected Uri insertStructuredName(long rawContactId, ContentValues values) { 225 values.put(Data.RAW_CONTACT_ID, rawContactId); 226 values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); 227 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 228 return resultUri; 229 } 230 231 protected Uri insertOrganization(long rawContactId, ContentValues values) { 232 return insertOrganization(rawContactId, values, false); 233 } 234 235 protected Uri insertOrganization(long rawContactId, ContentValues values, boolean primary) { 236 values.put(Data.RAW_CONTACT_ID, rawContactId); 237 values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE); 238 values.put(Organization.TYPE, Organization.TYPE_WORK); 239 if (primary) { 240 values.put(Data.IS_PRIMARY, 1); 241 } 242 243 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 244 return resultUri; 245 } 246 247 protected Uri insertPhoneNumber(long rawContactId, String phoneNumber) { 248 return insertPhoneNumber(rawContactId, phoneNumber, false); 249 } 250 251 protected Uri insertPhoneNumber(long rawContactId, String phoneNumber, boolean primary) { 252 ContentValues values = new ContentValues(); 253 values.put(Data.RAW_CONTACT_ID, rawContactId); 254 values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 255 values.put(Phone.NUMBER, phoneNumber); 256 values.put(Phone.TYPE, Phone.TYPE_HOME); 257 if (primary) { 258 values.put(Data.IS_PRIMARY, 1); 259 } 260 261 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 262 return resultUri; 263 } 264 265 protected Uri insertEmail(long rawContactId, String email) { 266 return insertEmail(rawContactId, email, false); 267 } 268 269 protected Uri insertEmail(long rawContactId, String email, boolean primary) { 270 return insertEmail(rawContactId, email, primary, Email.TYPE_HOME, null); 271 } 272 273 protected Uri insertEmail(long rawContactId, String email, boolean primary, int type, 274 String label) { 275 ContentValues values = new ContentValues(); 276 values.put(Data.RAW_CONTACT_ID, rawContactId); 277 values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); 278 values.put(Email.DATA, email); 279 values.put(Email.TYPE, type); 280 values.put(Email.LABEL, label); 281 if (primary) { 282 values.put(Data.IS_PRIMARY, 1); 283 } 284 285 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 286 return resultUri; 287 } 288 289 protected Uri insertNickname(long rawContactId, String nickname) { 290 ContentValues values = new ContentValues(); 291 values.put(Data.RAW_CONTACT_ID, rawContactId); 292 values.put(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE); 293 values.put(Nickname.NAME, nickname); 294 values.put(Nickname.TYPE, Nickname.TYPE_OTHER_NAME); 295 296 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 297 return resultUri; 298 } 299 300 protected Uri insertPostalAddress(long rawContactId, String formattedAddress) { 301 ContentValues values = new ContentValues(); 302 values.put(Data.RAW_CONTACT_ID, rawContactId); 303 values.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE); 304 values.put(StructuredPostal.FORMATTED_ADDRESS, formattedAddress); 305 306 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 307 return resultUri; 308 } 309 310 protected Uri insertPhoto(long rawContactId) { 311 ContentValues values = new ContentValues(); 312 values.put(Data.RAW_CONTACT_ID, rawContactId); 313 values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); 314 values.put(Photo.PHOTO, loadTestPhoto()); 315 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 316 return resultUri; 317 } 318 319 protected Uri insertGroupMembership(long rawContactId, String sourceId) { 320 ContentValues values = new ContentValues(); 321 values.put(Data.RAW_CONTACT_ID, rawContactId); 322 values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 323 values.put(GroupMembership.GROUP_SOURCE_ID, sourceId); 324 return mResolver.insert(Data.CONTENT_URI, values); 325 } 326 327 protected Uri insertGroupMembership(long rawContactId, Long groupId) { 328 ContentValues values = new ContentValues(); 329 values.put(Data.RAW_CONTACT_ID, rawContactId); 330 values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 331 values.put(GroupMembership.GROUP_ROW_ID, groupId); 332 return mResolver.insert(Data.CONTENT_URI, values); 333 } 334 335 protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle, 336 int presence, String status, int chatMode) { 337 return insertStatusUpdate(protocol, customProtocol, handle, presence, status, 0, chatMode); 338 } 339 340 protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle, 341 int presence, String status, long timestamp, int chatMode) { 342 ContentValues values = new ContentValues(); 343 values.put(StatusUpdates.PROTOCOL, protocol); 344 values.put(StatusUpdates.CUSTOM_PROTOCOL, customProtocol); 345 values.put(StatusUpdates.IM_HANDLE, handle); 346 if (presence != 0) { 347 values.put(StatusUpdates.PRESENCE, presence); 348 values.put(StatusUpdates.CHAT_CAPABILITY, chatMode); 349 } 350 if (status != null) { 351 values.put(StatusUpdates.STATUS, status); 352 } 353 if (timestamp != 0) { 354 values.put(StatusUpdates.STATUS_TIMESTAMP, timestamp); 355 } 356 357 Uri resultUri = mResolver.insert(StatusUpdates.CONTENT_URI, values); 358 return resultUri; 359 } 360 361 protected Uri insertImHandle(long rawContactId, int protocol, String customProtocol, 362 String handle) { 363 ContentValues values = new ContentValues(); 364 values.put(Data.RAW_CONTACT_ID, rawContactId); 365 values.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); 366 values.put(Im.PROTOCOL, protocol); 367 values.put(Im.CUSTOM_PROTOCOL, customProtocol); 368 values.put(Im.DATA, handle); 369 values.put(Im.TYPE, Im.TYPE_HOME); 370 371 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 372 return resultUri; 373 } 374 375 protected void setContactAccount(long rawContactId, String accountType, String accountName) { 376 ContentValues values = new ContentValues(); 377 values.put(RawContacts.ACCOUNT_TYPE, accountType); 378 values.put(RawContacts.ACCOUNT_NAME, accountName); 379 380 mResolver.update(ContentUris.withAppendedId( 381 RawContacts.CONTENT_URI, rawContactId), values, null, null); 382 } 383 384 protected void setAggregationException(int type, long rawContactId1, long rawContactId2) { 385 ContentValues values = new ContentValues(); 386 values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); 387 values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); 388 values.put(AggregationExceptions.TYPE, type); 389 assertEquals(1, mResolver.update(AggregationExceptions.CONTENT_URI, values, null, null)); 390 } 391 392 protected Cursor queryRawContact(long rawContactId) { 393 return mResolver.query(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), 394 null, null, null, null); 395 } 396 397 protected Cursor queryContact(long contactId) { 398 return mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), 399 null, null, null, null); 400 } 401 402 protected Cursor queryContact(long contactId, String[] projection) { 403 return mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), 404 projection, null, null, null); 405 } 406 407 protected long queryContactId(long rawContactId) { 408 Cursor c = queryRawContact(rawContactId); 409 assertTrue(c.moveToFirst()); 410 long contactId = c.getLong(c.getColumnIndex(RawContacts.CONTACT_ID)); 411 c.close(); 412 return contactId; 413 } 414 415 protected long queryPhotoId(long contactId) { 416 Cursor c = queryContact(contactId); 417 assertTrue(c.moveToFirst()); 418 long photoId = c.getInt(c.getColumnIndex(Contacts.PHOTO_ID)); 419 c.close(); 420 return photoId; 421 } 422 423 protected String queryDisplayName(long contactId) { 424 Cursor c = queryContact(contactId); 425 assertTrue(c.moveToFirst()); 426 String displayName = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME)); 427 c.close(); 428 return displayName; 429 } 430 431 private String queryLookupKey(long contactId) { 432 Cursor c = queryContact(contactId); 433 assertTrue(c.moveToFirst()); 434 String lookupKey = c.getString(c.getColumnIndex(Contacts.LOOKUP_KEY)); 435 c.close(); 436 return lookupKey; 437 } 438 439 protected void assertAggregated(long rawContactId1, long rawContactId2) { 440 long contactId1 = queryContactId(rawContactId1); 441 long contactId2 = queryContactId(rawContactId2); 442 assertTrue(contactId1 == contactId2); 443 } 444 445 protected void assertAggregated(long rawContactId1, long rawContactId2, 446 String expectedDisplayName) { 447 long contactId1 = queryContactId(rawContactId1); 448 long contactId2 = queryContactId(rawContactId2); 449 assertTrue(contactId1 == contactId2); 450 451 String displayName = queryDisplayName(contactId1); 452 assertEquals(expectedDisplayName, displayName); 453 } 454 455 protected void assertNotAggregated(long rawContactId1, long rawContactId2) { 456 long contactId1 = queryContactId(rawContactId1); 457 long contactId2 = queryContactId(rawContactId2); 458 assertTrue(contactId1 != contactId2); 459 } 460 461 protected void assertStructuredName(long rawContactId, String prefix, String givenName, 462 String middleName, String familyName, String suffix) { 463 Uri uri = 464 Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), 465 RawContacts.Data.CONTENT_DIRECTORY); 466 467 final String[] projection = new String[] { 468 StructuredName.PREFIX, StructuredName.GIVEN_NAME, StructuredName.MIDDLE_NAME, 469 StructuredName.FAMILY_NAME, StructuredName.SUFFIX 470 }; 471 472 Cursor c = mResolver.query(uri, projection, Data.MIMETYPE + "='" 473 + StructuredName.CONTENT_ITEM_TYPE + "'", null, null); 474 475 assertTrue(c.moveToFirst()); 476 assertEquals(prefix, c.getString(0)); 477 assertEquals(givenName, c.getString(1)); 478 assertEquals(middleName, c.getString(2)); 479 assertEquals(familyName, c.getString(3)); 480 assertEquals(suffix, c.getString(4)); 481 c.close(); 482 } 483 484 protected long assertSingleGroup(Long rowId, Account account, String sourceId, String title) { 485 Cursor c = mResolver.query(Groups.CONTENT_URI, null, null, null, null); 486 try { 487 assertTrue(c.moveToNext()); 488 long actualRowId = assertGroup(c, rowId, account, sourceId, title); 489 assertFalse(c.moveToNext()); 490 return actualRowId; 491 } finally { 492 c.close(); 493 } 494 } 495 496 protected long assertSingleGroupMembership(Long rowId, Long rawContactId, Long groupRowId, 497 String sourceId) { 498 Cursor c = mResolver.query(ContactsContract.Data.CONTENT_URI, null, null, null, null); 499 try { 500 assertTrue(c.moveToNext()); 501 long actualRowId = assertGroupMembership(c, rowId, rawContactId, groupRowId, sourceId); 502 assertFalse(c.moveToNext()); 503 return actualRowId; 504 } finally { 505 c.close(); 506 } 507 } 508 509 protected long assertGroupMembership(Cursor c, Long rowId, Long rawContactId, Long groupRowId, 510 String sourceId) { 511 assertNullOrEquals(c, rowId, Data._ID); 512 assertNullOrEquals(c, rawContactId, GroupMembership.RAW_CONTACT_ID); 513 assertNullOrEquals(c, groupRowId, GroupMembership.GROUP_ROW_ID); 514 assertNullOrEquals(c, sourceId, GroupMembership.GROUP_SOURCE_ID); 515 return c.getLong(c.getColumnIndexOrThrow("_id")); 516 } 517 518 protected long assertGroup(Cursor c, Long rowId, Account account, String sourceId, String title) { 519 assertNullOrEquals(c, rowId, Groups._ID); 520 assertNullOrEquals(c, account); 521 assertNullOrEquals(c, sourceId, Groups.SOURCE_ID); 522 assertNullOrEquals(c, title, Groups.TITLE); 523 return c.getLong(c.getColumnIndexOrThrow("_id")); 524 } 525 526 private void assertNullOrEquals(Cursor c, Account account) { 527 if (account == NO_ACCOUNT) { 528 return; 529 } 530 if (account == null) { 531 assertTrue(c.isNull(c.getColumnIndexOrThrow(Groups.ACCOUNT_NAME))); 532 assertTrue(c.isNull(c.getColumnIndexOrThrow(Groups.ACCOUNT_TYPE))); 533 } else { 534 assertEquals(account.name, c.getString(c.getColumnIndexOrThrow(Groups.ACCOUNT_NAME))); 535 assertEquals(account.type, c.getString(c.getColumnIndexOrThrow(Groups.ACCOUNT_TYPE))); 536 } 537 } 538 539 private void assertNullOrEquals(Cursor c, Long value, String columnName) { 540 if (value != NO_LONG) { 541 if (value == null) assertTrue(c.isNull(c.getColumnIndexOrThrow(columnName))); 542 else assertEquals((long) value, c.getLong(c.getColumnIndexOrThrow(columnName))); 543 } 544 } 545 546 private void assertNullOrEquals(Cursor c, String value, String columnName) { 547 if (value != NO_STRING) { 548 if (value == null) assertTrue(c.isNull(c.getColumnIndexOrThrow(columnName))); 549 else assertEquals(value, c.getString(c.getColumnIndexOrThrow(columnName))); 550 } 551 } 552 553 protected void assertDataRow(ContentValues actual, String expectedMimetype, 554 Object... expectedArguments) { 555 assertEquals(actual.toString(), expectedMimetype, actual.getAsString(Data.MIMETYPE)); 556 for (int i = 0; i < expectedArguments.length; i += 2) { 557 String columnName = (String) expectedArguments[i]; 558 Object expectedValue = expectedArguments[i + 1]; 559 if (expectedValue instanceof Uri) { 560 expectedValue = ContentUris.parseId((Uri) expectedValue); 561 } 562 if (expectedValue == null) { 563 assertNull(actual.toString(), actual.get(columnName)); 564 } 565 if (expectedValue instanceof Long) { 566 assertEquals("mismatch at " + columnName + " from " + actual.toString(), 567 expectedValue, actual.getAsLong(columnName)); 568 } else if (expectedValue instanceof Integer) { 569 assertEquals("mismatch at " + columnName + " from " + actual.toString(), 570 expectedValue, actual.getAsInteger(columnName)); 571 } else if (expectedValue instanceof String) { 572 assertEquals("mismatch at " + columnName + " from " + actual.toString(), 573 expectedValue, actual.getAsString(columnName)); 574 } else { 575 assertEquals("mismatch at " + columnName + " from " + actual.toString(), 576 expectedValue, actual.get(columnName)); 577 } 578 } 579 } 580 581 protected static class IdComparator implements Comparator<ContentValues> { 582 public int compare(ContentValues o1, ContentValues o2) { 583 long id1 = o1.getAsLong(ContactsContract.Data._ID); 584 long id2 = o2.getAsLong(ContactsContract.Data._ID); 585 if (id1 == id2) return 0; 586 return (id1 < id2) ? -1 : 1; 587 } 588 } 589 590 protected ContentValues[] asSortedContentValuesArray( 591 ArrayList<Entity.NamedContentValues> subValues) { 592 ContentValues[] result = new ContentValues[subValues.size()]; 593 int i = 0; 594 for (Entity.NamedContentValues subValue : subValues) { 595 result[i] = subValue.values; 596 i++; 597 } 598 Arrays.sort(result, new IdComparator()); 599 return result; 600 } 601 602 protected void assertDirty(Uri uri, boolean state) { 603 Cursor c = mResolver.query(uri, new String[]{"dirty"}, null, null, null); 604 assertTrue(c.moveToNext()); 605 assertEquals(state, c.getLong(0) != 0); 606 assertFalse(c.moveToNext()); 607 c.close(); 608 } 609 610 protected long getVersion(Uri uri) { 611 Cursor c = mResolver.query(uri, new String[]{"version"}, null, null, null); 612 assertTrue(c.moveToNext()); 613 long version = c.getLong(0); 614 assertFalse(c.moveToNext()); 615 c.close(); 616 return version; 617 } 618 619 protected void clearDirty(Uri uri) { 620 ContentValues values = new ContentValues(); 621 values.put("dirty", 0); 622 mResolver.update(uri, values, null, null); 623 } 624 625 protected void storeValue(Uri contentUri, long id, String column, String value) { 626 storeValue(ContentUris.withAppendedId(contentUri, id), column, value); 627 } 628 629 protected void storeValue(Uri contentUri, String column, String value) { 630 ContentValues values = new ContentValues(); 631 values.put(column, value); 632 633 mResolver.update(contentUri, values, null, null); 634 } 635 636 protected void storeValue(Uri contentUri, long id, String column, long value) { 637 storeValue(ContentUris.withAppendedId(contentUri, id), column, value); 638 } 639 640 protected void storeValue(Uri contentUri, String column, long value) { 641 ContentValues values = new ContentValues(); 642 values.put(column, value); 643 644 mResolver.update(contentUri, values, null, null); 645 } 646 647 protected void assertStoredValue(Uri contentUri, long id, String column, Object expectedValue) { 648 assertStoredValue(ContentUris.withAppendedId(contentUri, id), column, expectedValue); 649 } 650 651 protected void assertStoredValue(Uri rowUri, String column, Object expectedValue) { 652 String value = getStoredValue(rowUri, column); 653 if (expectedValue == null) { 654 assertNull("Column value " + column, value); 655 } else { 656 assertEquals("Column value " + column, String.valueOf(expectedValue), value); 657 } 658 } 659 660 protected void assertStoredValue(Uri rowUri, String selection, String[] selectionArgs, 661 String column, Object expectedValue) { 662 String value = getStoredValue(rowUri, selection, selectionArgs, column); 663 if (expectedValue == null) { 664 assertNull("Column value " + column, value); 665 } else { 666 assertEquals("Column value " + column, String.valueOf(expectedValue), value); 667 } 668 } 669 670 protected String getStoredValue(Uri rowUri, String column) { 671 return getStoredValue(rowUri, null, null, column); 672 } 673 674 protected String getStoredValue(Uri uri, String selection, String[] selectionArgs, 675 String column) { 676 String value = null; 677 Cursor c = mResolver.query(uri, new String[] { column }, selection, selectionArgs, null); 678 try { 679 if (c.moveToFirst()) { 680 value = c.getString(c.getColumnIndex(column)); 681 } 682 } finally { 683 c.close(); 684 } 685 return value; 686 } 687 688 protected void assertStoredValues(Uri rowUri, ContentValues expectedValues) { 689 assertStoredValues(rowUri, null, null, expectedValues); 690 } 691 692 protected void assertStoredValues(Uri rowUri, String selection, String[] selectionArgs, 693 ContentValues expectedValues) { 694 Cursor c = mResolver.query(rowUri, null, selection, selectionArgs, null); 695 try { 696 assertEquals("Record count", 1, c.getCount()); 697 c.moveToFirst(); 698 assertCursorValues(c, expectedValues); 699 } finally { 700 c.close(); 701 } 702 } 703 704 protected void assertStoredValuesWithProjection(Uri rowUri, ContentValues expectedValues) { 705 Cursor c = mResolver.query(rowUri, buildProjection(expectedValues), null, null, null); 706 try { 707 assertEquals("Record count", 1, c.getCount()); 708 c.moveToFirst(); 709 assertCursorValues(c, expectedValues); 710 } finally { 711 c.close(); 712 } 713 } 714 715 /** 716 * Constructs a selection (where clause) out of all supplied values, uses it 717 * to query the provider and verifies that a single row is returned and it 718 * has the same values as requested. 719 */ 720 protected void assertSelection(Uri uri, ContentValues values, String idColumn, long id) { 721 assertSelection(uri, values, idColumn, id, null); 722 } 723 724 public void assertSelectionWithProjection(Uri uri, ContentValues values, String idColumn, 725 long id) { 726 assertSelection(uri, values, idColumn, id, buildProjection(values)); 727 } 728 729 private void assertSelection(Uri uri, ContentValues values, String idColumn, long id, 730 String[] projection) { 731 StringBuilder sb = new StringBuilder(); 732 ArrayList<String> selectionArgs = new ArrayList<String>(values.size()); 733 if (idColumn != null) { 734 sb.append(idColumn).append("=").append(id); 735 } 736 Set<Map.Entry<String, Object>> entries = values.valueSet(); 737 for (Map.Entry<String, Object> entry : entries) { 738 String column = entry.getKey(); 739 Object value = entry.getValue(); 740 if (sb.length() != 0) { 741 sb.append(" AND "); 742 } 743 sb.append(column); 744 if (value == null) { 745 sb.append(" IS NULL"); 746 } else { 747 sb.append("=?"); 748 selectionArgs.add(String.valueOf(value)); 749 } 750 } 751 752 Cursor c = mResolver.query(uri, projection, sb.toString(), selectionArgs.toArray(new String[0]), 753 null); 754 try { 755 assertEquals("Record count", 1, c.getCount()); 756 c.moveToFirst(); 757 assertCursorValues(c, values); 758 } finally { 759 c.close(); 760 } 761 } 762 763 protected void assertCursorValues(Cursor cursor, ContentValues expectedValues) { 764 Set<Map.Entry<String, Object>> entries = expectedValues.valueSet(); 765 for (Map.Entry<String, Object> entry : entries) { 766 String column = entry.getKey(); 767 int index = cursor.getColumnIndex(column); 768 assertTrue("No such column: " + column, index != -1); 769 Object expectedValue = expectedValues.get(column); 770 String value; 771 if (expectedValue instanceof byte[]) { 772 expectedValue = Hex.encodeHex((byte[])expectedValue, false); 773 value = Hex.encodeHex(cursor.getBlob(index), false); 774 } else { 775 expectedValue = expectedValues.getAsString(column); 776 value = cursor.getString(index); 777 } 778 assertEquals("Column value " + column, expectedValue, value); 779 } 780 } 781 782 private String[] buildProjection(ContentValues values) { 783 String[] projection = new String[values.size()]; 784 Iterator<Entry<String, Object>> iter = values.valueSet().iterator(); 785 for (int i = 0; i < projection.length; i++) { 786 projection[i] = iter.next().getKey(); 787 } 788 return projection; 789 } 790 791 protected int getCount(Uri uri, String selection, String[] selectionArgs) { 792 Cursor c = mResolver.query(uri, null, selection, selectionArgs, null); 793 try { 794 return c.getCount(); 795 } finally { 796 c.close(); 797 } 798 } 799 800 protected byte[] loadTestPhoto() { 801 if (mTestPhoto == null) { 802 final Resources resources = getContext().getResources(); 803 InputStream is = resources 804 .openRawResource(com.android.internal.R.drawable.ic_contact_picture); 805 ByteArrayOutputStream os = new ByteArrayOutputStream(); 806 byte[] buffer = new byte[1000]; 807 int count; 808 try { 809 while ((count = is.read(buffer)) != -1) { 810 os.write(buffer, 0, count); 811 } 812 } catch (IOException e) { 813 throw new RuntimeException(e); 814 } 815 mTestPhoto = os.toByteArray(); 816 } 817 return mTestPhoto; 818 } 819 820 public static void dump(ContentResolver resolver, boolean aggregatedOnly) { 821 String[] projection = new String[] { 822 Contacts._ID, 823 Contacts.DISPLAY_NAME 824 }; 825 String selection = null; 826 if (aggregatedOnly) { 827 selection = Contacts._ID 828 + " IN (SELECT contact_id" + 829 " FROM raw_contacts GROUP BY contact_id HAVING count(*) > 1)"; 830 } 831 832 Cursor c = resolver.query(Contacts.CONTENT_URI, projection, selection, null, 833 Contacts.DISPLAY_NAME); 834 while(c.moveToNext()) { 835 long contactId = c.getLong(0); 836 Log.i("Contact ", String.format("%5d %s", contactId, c.getString(1))); 837 dumpRawContacts(resolver, contactId); 838 Log.i(" ", "."); 839 } 840 c.close(); 841 } 842 843 private static void dumpRawContacts(ContentResolver resolver, long contactId) { 844 String[] projection = new String[] { 845 RawContacts._ID, 846 }; 847 Cursor c = resolver.query(RawContacts.CONTENT_URI, projection, RawContacts.CONTACT_ID + "=" 848 + contactId, null, null); 849 while(c.moveToNext()) { 850 long rawContactId = c.getLong(0); 851 Log.i("RawContact", String.format(" %-5d", rawContactId)); 852 dumpData(resolver, rawContactId); 853 } 854 c.close(); 855 } 856 857 private static void dumpData(ContentResolver resolver, long rawContactId) { 858 String[] projection = new String[] { 859 Data.MIMETYPE, 860 Data.DATA1, 861 Data.DATA2, 862 Data.DATA3, 863 }; 864 Cursor c = resolver.query(Data.CONTENT_URI, projection, Data.RAW_CONTACT_ID + "=" 865 + rawContactId, null, Data.MIMETYPE); 866 while(c.moveToNext()) { 867 String mimetype = c.getString(0); 868 if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) { 869 Log.i("Photo ", ""); 870 } else { 871 mimetype = mimetype.substring(mimetype.indexOf('/') + 1); 872 Log.i("Data ", String.format(" %-10s %s,%s,%s", mimetype, 873 c.getString(1), c.getString(2), c.getString(3))); 874 } 875 } 876 c.close(); 877 } 878 879 protected void assertNetworkNotified(boolean expected) { 880 assertEquals(expected, ((SynchronousContactsProvider2)mActor.provider).isNetworkNotified()); 881 } 882 883 /** 884 * A contact in the database, and the attributes used to create it. Construct using 885 * {@link GoldenContactBuilder#build()}. 886 */ 887 public final class GoldenContact { 888 889 private final long rawContactId; 890 891 private final long contactId; 892 893 private final String givenName; 894 895 private final String familyName; 896 897 private final String nickname; 898 899 private final byte[] photo; 900 901 private final String company; 902 903 private final String title; 904 905 private final String phone; 906 907 private final String email; 908 909 private GoldenContact(GoldenContactBuilder builder, long rawContactId, long contactId) { 910 911 this.rawContactId = rawContactId; 912 this.contactId = contactId; 913 givenName = builder.givenName; 914 familyName = builder.familyName; 915 nickname = builder.nickname; 916 photo = builder.photo; 917 company = builder.company; 918 title = builder.title; 919 phone = builder.phone; 920 email = builder.email; 921 } 922 923 public void delete() { 924 Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); 925 mResolver.delete(rawContactUri, null, null); 926 } 927 928 /** 929 * Returns the index of the contact in table "raw_contacts" 930 */ 931 public long getRawContactId() { 932 return rawContactId; 933 } 934 935 /** 936 * Returns the index of the contact in table "contacts" 937 */ 938 public long getContactId() { 939 return contactId; 940 } 941 942 /** 943 * Returns the lookup key for the contact. 944 */ 945 public String getLookupKey() { 946 return queryLookupKey(contactId); 947 } 948 949 /** 950 * Returns the contact's given name. 951 */ 952 public String getGivenName() { 953 return givenName; 954 } 955 956 /** 957 * Returns the contact's family name. 958 */ 959 public String getFamilyName() { 960 return familyName; 961 } 962 963 /** 964 * Returns the contact's nickname. 965 */ 966 public String getNickname() { 967 return nickname; 968 } 969 970 /** 971 * Return's the contact's photo 972 */ 973 public byte[] getPhoto() { 974 return photo; 975 } 976 977 /** 978 * Return's the company at which the contact works. 979 */ 980 public String getCompany() { 981 return company; 982 } 983 984 /** 985 * Returns the contact's job title. 986 */ 987 public String getTitle() { 988 return title; 989 } 990 991 /** 992 * Returns the contact's phone number 993 */ 994 public String getPhone() { 995 return phone; 996 } 997 998 /** 999 * Returns the contact's email address 1000 */ 1001 public String getEmail() { 1002 return email; 1003 } 1004 } 1005 1006 /** 1007 * Builds {@link GoldenContact} objects. Unspecified boolean objects default to false. 1008 * Unspecified String objects default to null. 1009 */ 1010 public final class GoldenContactBuilder { 1011 1012 private String givenName; 1013 1014 private String familyName; 1015 1016 private String nickname; 1017 1018 private byte[] photo; 1019 1020 private String company; 1021 1022 private String title; 1023 1024 private String phone; 1025 1026 private String email; 1027 1028 /** 1029 * The contact's given and family names. 1030 * 1031 * TODO(dplotnikov): inline, or should we require them to set both names if they set either? 1032 */ 1033 public GoldenContactBuilder name(String givenName, String familyName) { 1034 return givenName(givenName).familyName(familyName); 1035 } 1036 1037 /** 1038 * The contact's given name. 1039 */ 1040 public GoldenContactBuilder givenName(String value) { 1041 givenName = value; 1042 return this; 1043 } 1044 1045 /** 1046 * The contact's family name. 1047 */ 1048 public GoldenContactBuilder familyName(String value) { 1049 familyName = value; 1050 return this; 1051 } 1052 1053 /** 1054 * The contact's nickname. 1055 */ 1056 public GoldenContactBuilder nickname(String value) { 1057 nickname = value; 1058 return this; 1059 } 1060 1061 /** 1062 * The contact's photo. 1063 */ 1064 public GoldenContactBuilder photo(byte[] value) { 1065 photo = value; 1066 return this; 1067 } 1068 1069 /** 1070 * The company at which the contact works. 1071 */ 1072 public GoldenContactBuilder company(String value) { 1073 company = value; 1074 return this; 1075 } 1076 1077 /** 1078 * The contact's job title. 1079 */ 1080 public GoldenContactBuilder title(String value) { 1081 title = value; 1082 return this; 1083 } 1084 1085 /** 1086 * The contact's phone number. 1087 */ 1088 public GoldenContactBuilder phone(String value) { 1089 phone = value; 1090 return this; 1091 } 1092 1093 /** 1094 * The contact's email address; also sets their IM status to {@link StatusUpdates#OFFLINE} 1095 * with a presence of "Coding for Android". 1096 */ 1097 public GoldenContactBuilder email(String value) { 1098 email = value; 1099 return this; 1100 } 1101 1102 /** 1103 * Builds the {@link GoldenContact} specified by this builder. 1104 */ 1105 public GoldenContact build() { 1106 1107 final long groupId = createGroup(mAccount, "gsid1", "title1"); 1108 1109 long rawContactId = createRawContact(); 1110 insertGroupMembership(rawContactId, groupId); 1111 1112 if (givenName != null || familyName != null) { 1113 insertStructuredName(rawContactId, givenName, familyName); 1114 } 1115 if (nickname != null) { 1116 insertNickname(rawContactId, nickname); 1117 } 1118 if (photo != null) { 1119 insertPhoto(rawContactId); 1120 } 1121 if (company != null || title != null) { 1122 insertOrganization(rawContactId); 1123 } 1124 if (email != null) { 1125 insertEmail(rawContactId); 1126 } 1127 if (phone != null) { 1128 insertPhone(rawContactId); 1129 } 1130 1131 long contactId = queryContactId(rawContactId); 1132 1133 return new GoldenContact(this, rawContactId, contactId); 1134 } 1135 1136 private void insertPhoto(long rawContactId) { 1137 ContentValues values = new ContentValues(); 1138 values.put(Data.RAW_CONTACT_ID, rawContactId); 1139 values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); 1140 values.put(Photo.PHOTO, photo); 1141 mResolver.insert(Data.CONTENT_URI, values); 1142 } 1143 1144 private void insertOrganization(long rawContactId) { 1145 1146 ContentValues values = new ContentValues(); 1147 values.put(Data.RAW_CONTACT_ID, rawContactId); 1148 values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE); 1149 values.put(Organization.TYPE, Organization.TYPE_WORK); 1150 if (company != null) { 1151 values.put(Organization.COMPANY, company); 1152 } 1153 if (title != null) { 1154 values.put(Organization.TITLE, title); 1155 } 1156 mResolver.insert(Data.CONTENT_URI, values); 1157 } 1158 1159 private void insertEmail(long rawContactId) { 1160 1161 ContentValues values = new ContentValues(); 1162 values.put(Data.RAW_CONTACT_ID, rawContactId); 1163 values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); 1164 values.put(Email.TYPE, Email.TYPE_WORK); 1165 values.put(Email.DATA, "foo (at) acme.com"); 1166 mResolver.insert(Data.CONTENT_URI, values); 1167 1168 int protocol = Im.PROTOCOL_GOOGLE_TALK; 1169 1170 values.clear(); 1171 values.put(StatusUpdates.PROTOCOL, protocol); 1172 values.put(StatusUpdates.IM_HANDLE, email); 1173 values.put(StatusUpdates.IM_ACCOUNT, "foo"); 1174 values.put(StatusUpdates.PRESENCE_STATUS, StatusUpdates.OFFLINE); 1175 values.put(StatusUpdates.CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA); 1176 values.put(StatusUpdates.PRESENCE_CUSTOM_STATUS, "Coding for Android"); 1177 mResolver.insert(StatusUpdates.CONTENT_URI, values); 1178 } 1179 1180 private void insertPhone(long rawContactId) { 1181 ContentValues values = new ContentValues(); 1182 values.put(Data.RAW_CONTACT_ID, rawContactId); 1183 values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 1184 values.put(Data.IS_PRIMARY, 1); 1185 values.put(Phone.TYPE, Phone.TYPE_HOME); 1186 values.put(Phone.NUMBER, phone); 1187 mResolver.insert(Data.CONTENT_URI, values); 1188 } 1189 } 1190 } 1191