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 getProviderClass()83 protected Class<? extends ContentProvider> getProviderClass() { 84 return SynchronousContactsProvider2.class; 85 } 86 getAuthority()87 protected String getAuthority() { 88 return ContactsContract.AUTHORITY; 89 } 90 91 @Override setUp()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 tearDown()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 getMockContext()112 public Context getMockContext() { 113 return mActor.context; 114 } 115 addAuthority(String authority)116 public void addAuthority(String authority) { 117 mActor.addAuthority(authority); 118 } 119 addProvider(Class<? extends ContentProvider> providerClass, String authority)120 public ContentProvider addProvider(Class<? extends ContentProvider> providerClass, 121 String authority) throws Exception { 122 return mActor.addProvider(providerClass, authority); 123 } 124 getProvider()125 public ContentProvider getProvider() { 126 return mActor.provider; 127 } 128 maybeAddAccountQueryParameters(Uri uri, Account account)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 createRawContact()139 protected long createRawContact() { 140 return createRawContact(null); 141 } 142 createRawContactWithName()143 protected long createRawContactWithName() { 144 return createRawContactWithName(null); 145 } 146 createRawContactWithName(Account account)147 protected long createRawContactWithName(Account account) { 148 return createRawContactWithName("John", "Doe", account); 149 } 150 createRawContactWithName(String firstName, String lastName)151 protected long createRawContactWithName(String firstName, String lastName) { 152 return createRawContactWithName(firstName, lastName, null); 153 } 154 createRawContactWithName(String firstName, String lastName, Account account)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 setCallerIsSyncAdapter(Uri uri, Account account)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 createRawContact(Account account, String... extras)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 createGroup(Account account, String sourceId, String title)183 protected long createGroup(Account account, String sourceId, String title) { 184 return createGroup(account, sourceId, title, 1); 185 } 186 createGroup(Account account, String sourceId, String title, int visible)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 createSettings(Account account, String shouldSync, String ungroupedVisible)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 insertStructuredName(long rawContactId, String givenName, String familyName)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 insertStructuredName(long rawContactId, ContentValues values)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 insertOrganization(long rawContactId, ContentValues values)231 protected Uri insertOrganization(long rawContactId, ContentValues values) { 232 return insertOrganization(rawContactId, values, false); 233 } 234 insertOrganization(long rawContactId, ContentValues values, boolean primary)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 insertPhoneNumber(long rawContactId, String phoneNumber)247 protected Uri insertPhoneNumber(long rawContactId, String phoneNumber) { 248 return insertPhoneNumber(rawContactId, phoneNumber, false); 249 } 250 insertPhoneNumber(long rawContactId, String phoneNumber, boolean primary)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 insertEmail(long rawContactId, String email)265 protected Uri insertEmail(long rawContactId, String email) { 266 return insertEmail(rawContactId, email, false); 267 } 268 insertEmail(long rawContactId, String email, boolean primary)269 protected Uri insertEmail(long rawContactId, String email, boolean primary) { 270 return insertEmail(rawContactId, email, primary, Email.TYPE_HOME, null); 271 } 272 insertEmail(long rawContactId, String email, boolean primary, int type, String label)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 insertNickname(long rawContactId, String nickname)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 insertPostalAddress(long rawContactId, String formattedAddress)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 insertPhoto(long rawContactId)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 insertGroupMembership(long rawContactId, String sourceId)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 insertGroupMembership(long rawContactId, Long groupId)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 insertStatusUpdate(int protocol, String customProtocol, String handle, int presence, String status, int chatMode)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 insertStatusUpdate(int protocol, String customProtocol, String handle, int presence, String status, long timestamp, int chatMode)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 insertImHandle(long rawContactId, int protocol, String customProtocol, String handle)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 setContactAccount(long rawContactId, String accountType, String accountName)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 setAggregationException(int type, long rawContactId1, long rawContactId2)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 queryRawContact(long rawContactId)392 protected Cursor queryRawContact(long rawContactId) { 393 return mResolver.query(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), 394 null, null, null, null); 395 } 396 queryContact(long contactId)397 protected Cursor queryContact(long contactId) { 398 return mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), 399 null, null, null, null); 400 } 401 queryContact(long contactId, String[] projection)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 queryContactId(long rawContactId)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 queryPhotoId(long contactId)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 queryDisplayName(long contactId)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 queryLookupKey(long contactId)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 assertAggregated(long rawContactId1, long rawContactId2)439 protected void assertAggregated(long rawContactId1, long rawContactId2) { 440 long contactId1 = queryContactId(rawContactId1); 441 long contactId2 = queryContactId(rawContactId2); 442 assertTrue(contactId1 == contactId2); 443 } 444 assertAggregated(long rawContactId1, long rawContactId2, String expectedDisplayName)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 assertNotAggregated(long rawContactId1, long rawContactId2)455 protected void assertNotAggregated(long rawContactId1, long rawContactId2) { 456 long contactId1 = queryContactId(rawContactId1); 457 long contactId2 = queryContactId(rawContactId2); 458 assertTrue(contactId1 != contactId2); 459 } 460 assertStructuredName(long rawContactId, String prefix, String givenName, String middleName, String familyName, String suffix)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 assertSingleGroup(Long rowId, Account account, String sourceId, String title)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 assertSingleGroupMembership(Long rowId, Long rawContactId, Long groupRowId, String sourceId)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 assertGroupMembership(Cursor c, Long rowId, Long rawContactId, Long groupRowId, String sourceId)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 assertGroup(Cursor c, Long rowId, Account account, String sourceId, String title)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 assertNullOrEquals(Cursor c, Account account)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 assertNullOrEquals(Cursor c, Long value, String columnName)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 assertNullOrEquals(Cursor c, String value, String columnName)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 assertDataRow(ContentValues actual, String expectedMimetype, Object... expectedArguments)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> { compare(ContentValues o1, ContentValues o2)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 asSortedContentValuesArray( ArrayList<Entity.NamedContentValues> subValues)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 assertDirty(Uri uri, boolean state)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 getVersion(Uri uri)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 clearDirty(Uri uri)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 storeValue(Uri contentUri, long id, String column, String value)625 protected void storeValue(Uri contentUri, long id, String column, String value) { 626 storeValue(ContentUris.withAppendedId(contentUri, id), column, value); 627 } 628 storeValue(Uri contentUri, String column, String value)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 storeValue(Uri contentUri, long id, String column, long value)636 protected void storeValue(Uri contentUri, long id, String column, long value) { 637 storeValue(ContentUris.withAppendedId(contentUri, id), column, value); 638 } 639 storeValue(Uri contentUri, String column, long value)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 assertStoredValue(Uri contentUri, long id, String column, Object expectedValue)647 protected void assertStoredValue(Uri contentUri, long id, String column, Object expectedValue) { 648 assertStoredValue(ContentUris.withAppendedId(contentUri, id), column, expectedValue); 649 } 650 assertStoredValue(Uri rowUri, String column, Object expectedValue)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 assertStoredValue(Uri rowUri, String selection, String[] selectionArgs, String column, Object expectedValue)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 getStoredValue(Uri rowUri, String column)670 protected String getStoredValue(Uri rowUri, String column) { 671 return getStoredValue(rowUri, null, null, column); 672 } 673 getStoredValue(Uri uri, String selection, String[] selectionArgs, String column)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 assertStoredValues(Uri rowUri, ContentValues expectedValues)688 protected void assertStoredValues(Uri rowUri, ContentValues expectedValues) { 689 assertStoredValues(rowUri, null, null, expectedValues); 690 } 691 assertStoredValues(Uri rowUri, String selection, String[] selectionArgs, ContentValues expectedValues)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 assertStoredValuesWithProjection(Uri rowUri, ContentValues expectedValues)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 */ assertSelection(Uri uri, ContentValues values, String idColumn, long id)720 protected void assertSelection(Uri uri, ContentValues values, String idColumn, long id) { 721 assertSelection(uri, values, idColumn, id, null); 722 } 723 assertSelectionWithProjection(Uri uri, ContentValues values, String idColumn, long id)724 public void assertSelectionWithProjection(Uri uri, ContentValues values, String idColumn, 725 long id) { 726 assertSelection(uri, values, idColumn, id, buildProjection(values)); 727 } 728 assertSelection(Uri uri, ContentValues values, String idColumn, long id, String[] projection)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 assertCursorValues(Cursor cursor, ContentValues expectedValues)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 buildProjection(ContentValues values)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 getCount(Uri uri, String selection, String[] selectionArgs)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 loadTestPhoto()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 dump(ContentResolver resolver, boolean aggregatedOnly)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 dumpRawContacts(ContentResolver resolver, long contactId)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 dumpData(ContentResolver resolver, long rawContactId)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 assertNetworkNotified(boolean expected)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 GoldenContact(GoldenContactBuilder builder, long rawContactId, long contactId)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 delete()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 */ getRawContactId()931 public long getRawContactId() { 932 return rawContactId; 933 } 934 935 /** 936 * Returns the index of the contact in table "contacts" 937 */ getContactId()938 public long getContactId() { 939 return contactId; 940 } 941 942 /** 943 * Returns the lookup key for the contact. 944 */ getLookupKey()945 public String getLookupKey() { 946 return queryLookupKey(contactId); 947 } 948 949 /** 950 * Returns the contact's given name. 951 */ getGivenName()952 public String getGivenName() { 953 return givenName; 954 } 955 956 /** 957 * Returns the contact's family name. 958 */ getFamilyName()959 public String getFamilyName() { 960 return familyName; 961 } 962 963 /** 964 * Returns the contact's nickname. 965 */ getNickname()966 public String getNickname() { 967 return nickname; 968 } 969 970 /** 971 * Return's the contact's photo 972 */ getPhoto()973 public byte[] getPhoto() { 974 return photo; 975 } 976 977 /** 978 * Return's the company at which the contact works. 979 */ getCompany()980 public String getCompany() { 981 return company; 982 } 983 984 /** 985 * Returns the contact's job title. 986 */ getTitle()987 public String getTitle() { 988 return title; 989 } 990 991 /** 992 * Returns the contact's phone number 993 */ getPhone()994 public String getPhone() { 995 return phone; 996 } 997 998 /** 999 * Returns the contact's email address 1000 */ getEmail()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 */ name(String givenName, String familyName)1033 public GoldenContactBuilder name(String givenName, String familyName) { 1034 return givenName(givenName).familyName(familyName); 1035 } 1036 1037 /** 1038 * The contact's given name. 1039 */ givenName(String value)1040 public GoldenContactBuilder givenName(String value) { 1041 givenName = value; 1042 return this; 1043 } 1044 1045 /** 1046 * The contact's family name. 1047 */ familyName(String value)1048 public GoldenContactBuilder familyName(String value) { 1049 familyName = value; 1050 return this; 1051 } 1052 1053 /** 1054 * The contact's nickname. 1055 */ nickname(String value)1056 public GoldenContactBuilder nickname(String value) { 1057 nickname = value; 1058 return this; 1059 } 1060 1061 /** 1062 * The contact's photo. 1063 */ photo(byte[] value)1064 public GoldenContactBuilder photo(byte[] value) { 1065 photo = value; 1066 return this; 1067 } 1068 1069 /** 1070 * The company at which the contact works. 1071 */ company(String value)1072 public GoldenContactBuilder company(String value) { 1073 company = value; 1074 return this; 1075 } 1076 1077 /** 1078 * The contact's job title. 1079 */ title(String value)1080 public GoldenContactBuilder title(String value) { 1081 title = value; 1082 return this; 1083 } 1084 1085 /** 1086 * The contact's phone number. 1087 */ phone(String value)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 */ email(String value)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 */ build()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 insertPhoto(long rawContactId)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 insertOrganization(long rawContactId)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 insertEmail(long rawContactId)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@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 insertPhone(long rawContactId)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