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 import static com.android.providers.contacts.TestUtils.cv; 21 22 import android.accounts.Account; 23 import android.content.ContentProvider; 24 import android.content.ContentResolver; 25 import android.content.ContentUris; 26 import android.content.ContentValues; 27 import android.content.Context; 28 import android.content.Entity; 29 import android.database.Cursor; 30 import android.database.sqlite.SQLiteDatabase; 31 import android.net.Uri; 32 import android.provider.BaseColumns; 33 import android.provider.ContactsContract; 34 import android.provider.ContactsContract.AggregationExceptions; 35 import android.provider.ContactsContract.CommonDataKinds.Email; 36 import android.provider.ContactsContract.CommonDataKinds.Event; 37 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 38 import android.provider.ContactsContract.CommonDataKinds.Identity; 39 import android.provider.ContactsContract.CommonDataKinds.Im; 40 import android.provider.ContactsContract.CommonDataKinds.Nickname; 41 import android.provider.ContactsContract.CommonDataKinds.Note; 42 import android.provider.ContactsContract.CommonDataKinds.Organization; 43 import android.provider.ContactsContract.CommonDataKinds.Phone; 44 import android.provider.ContactsContract.CommonDataKinds.Photo; 45 import android.provider.ContactsContract.CommonDataKinds.SipAddress; 46 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 47 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 48 import android.provider.ContactsContract.Contacts; 49 import android.provider.ContactsContract.Data; 50 import android.provider.ContactsContract.Groups; 51 import android.provider.ContactsContract.RawContacts; 52 import android.provider.ContactsContract.Settings; 53 import android.provider.ContactsContract.StatusUpdates; 54 import android.provider.ContactsContract.StreamItems; 55 import android.test.MoreAsserts; 56 import android.test.mock.MockContentResolver; 57 import android.util.Log; 58 59 import com.android.providers.contacts.ContactsDatabaseHelper.Tables; 60 import com.android.providers.contacts.testutil.CommonDatabaseUtils; 61 import com.android.providers.contacts.testutil.DataUtil; 62 import com.android.providers.contacts.testutil.RawContactUtil; 63 import com.android.providers.contacts.testutil.TestUtil; 64 import com.android.providers.contacts.util.Hex; 65 import com.android.providers.contacts.util.MockClock; 66 import com.google.android.collect.Sets; 67 68 import java.util.ArrayList; 69 import java.util.Arrays; 70 import java.util.BitSet; 71 import java.util.Comparator; 72 import java.util.Iterator; 73 import java.util.Map; 74 import java.util.Map.Entry; 75 import java.util.Set; 76 77 /** 78 * A common superclass for {@link ContactsProvider2}-related tests. 79 */ 80 public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase { 81 82 static final String ADD_VOICEMAIL_PERMISSION = 83 "com.android.voicemail.permission.ADD_VOICEMAIL"; 84 /* 85 * Permission to allow querying voicemails 86 */ 87 static final String READ_VOICEMAIL_PERMISSION = 88 "com.android.voicemail.permission.READ_VOICEMAIL"; 89 /* 90 * Permission to allow deleting and updating voicemails 91 */ 92 static final String WRITE_VOICEMAIL_PERMISSION = 93 "com.android.voicemail.permission.WRITE_VOICEMAIL"; 94 95 protected static final String PACKAGE = "ContactsProvider2Test"; 96 public static final String READ_ONLY_ACCOUNT_TYPE = 97 SynchronousContactsProvider2.READ_ONLY_ACCOUNT_TYPE; 98 99 protected ContactsActor mActor; 100 protected MockContentResolver mResolver; 101 protected Account mAccount = new Account("account1", "account type1"); 102 protected Account mAccountTwo = new Account("account2", "account type2"); 103 104 protected final static Long NO_LONG = new Long(0); 105 protected final static String NO_STRING = new String(""); 106 protected final static Account NO_ACCOUNT = new Account("a", "b"); 107 108 /** 109 * Use {@link MockClock#install()} to start using it. 110 * It'll be automatically uninstalled by {@link #tearDown()}. 111 */ 112 protected static final MockClock sMockClock = new MockClock(); 113 114 protected Class<? extends ContentProvider> getProviderClass() { 115 return SynchronousContactsProvider2.class; 116 } 117 118 protected String getAuthority() { 119 return ContactsContract.AUTHORITY; 120 } 121 122 @Override 123 protected void setUp() throws Exception { 124 super.setUp(); 125 126 mActor = new ContactsActor(getContext(), PACKAGE_GREY, getProviderClass(), getAuthority()); 127 mResolver = mActor.resolver; 128 if (mActor.provider instanceof SynchronousContactsProvider2) { 129 getContactsProvider().wipeData(); 130 } 131 132 // Give the actor access to read/write contacts and profile data by default. 133 mActor.addPermissions( 134 "android.permission.READ_CONTACTS", 135 "android.permission.WRITE_CONTACTS", 136 "android.permission.READ_SOCIAL_STREAM", 137 "android.permission.WRITE_SOCIAL_STREAM", 138 "android.permission.READ_PROFILE", 139 "android.permission.WRITE_PROFILE"); 140 } 141 142 @Override 143 protected void tearDown() throws Exception { 144 sMockClock.uninstall(); 145 super.tearDown(); 146 } 147 148 public SynchronousContactsProvider2 getContactsProvider() { 149 return (SynchronousContactsProvider2) mActor.provider; 150 } 151 152 public Context getMockContext() { 153 return mActor.context; 154 } 155 156 public void addAuthority(String authority) { 157 mActor.addAuthority(authority); 158 } 159 160 public ContentProvider addProvider(Class<? extends ContentProvider> providerClass, 161 String authority) throws Exception { 162 return mActor.addProvider(providerClass, authority); 163 } 164 165 public ContentProvider getProvider() { 166 return mActor.provider; 167 } 168 169 protected Uri setCallerIsSyncAdapter(Uri uri, Account account) { 170 if (account == null) { 171 return uri; 172 } 173 final Uri.Builder builder = uri.buildUpon(); 174 builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name); 175 builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type); 176 builder.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true"); 177 return builder.build(); 178 } 179 180 protected int updateItem(Uri uri, long id, String... extras) { 181 Uri itemUri = ContentUris.withAppendedId(uri, id); 182 return updateItem(itemUri, extras); 183 } 184 185 protected int updateItem(Uri uri, String... extras) { 186 ContentValues values = new ContentValues(); 187 CommonDatabaseUtils.extrasVarArgsToValues(values, extras); 188 return mResolver.update(uri, values, null, null); 189 } 190 191 protected long createGroup(Account account, String sourceId, String title) { 192 return createGroup(account, sourceId, title, 1, false, false); 193 } 194 195 protected long createGroup(Account account, String sourceId, String title, int visible) { 196 return createGroup(account, sourceId, title, visible, false, false); 197 } 198 199 protected long createAutoAddGroup(Account account) { 200 return createGroup(account, "auto", "auto", 201 0 /* visible */, true /* auto-add */, false /* fav */); 202 } 203 204 protected long createGroup(Account account, String sourceId, String title, 205 int visible, boolean autoAdd, boolean favorite) { 206 ContentValues values = new ContentValues(); 207 values.put(Groups.SOURCE_ID, sourceId); 208 values.put(Groups.TITLE, title); 209 values.put(Groups.GROUP_VISIBLE, visible); 210 values.put(Groups.AUTO_ADD, autoAdd ? 1 : 0); 211 values.put(Groups.FAVORITES, favorite ? 1 : 0); 212 final Uri uri = TestUtil.maybeAddAccountQueryParameters(Groups.CONTENT_URI, account); 213 return ContentUris.parseId(mResolver.insert(uri, values)); 214 } 215 216 protected void createSettings(Account account, String shouldSync, String ungroupedVisible) { 217 createSettings(new AccountWithDataSet(account.name, account.type, null), 218 shouldSync, ungroupedVisible); 219 } 220 221 protected void createSettings(AccountWithDataSet account, String shouldSync, 222 String ungroupedVisible) { 223 ContentValues values = new ContentValues(); 224 values.put(Settings.ACCOUNT_NAME, account.getAccountName()); 225 values.put(Settings.ACCOUNT_TYPE, account.getAccountType()); 226 if (account.getDataSet() != null) { 227 values.put(Settings.DATA_SET, account.getDataSet()); 228 } 229 values.put(Settings.SHOULD_SYNC, shouldSync); 230 values.put(Settings.UNGROUPED_VISIBLE, ungroupedVisible); 231 mResolver.insert(Settings.CONTENT_URI, values); 232 } 233 234 protected Uri insertOrganization(long rawContactId, ContentValues values) { 235 return insertOrganization(rawContactId, values, false); 236 } 237 238 protected Uri insertOrganization(long rawContactId, ContentValues values, boolean primary) { 239 values.put(Data.RAW_CONTACT_ID, rawContactId); 240 values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE); 241 values.put(Organization.TYPE, Organization.TYPE_WORK); 242 if (primary) { 243 values.put(Data.IS_PRIMARY, 1); 244 } 245 246 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 247 return resultUri; 248 } 249 250 protected Uri insertPhoneNumber(long rawContactId, String phoneNumber) { 251 return insertPhoneNumber(rawContactId, phoneNumber, false); 252 } 253 254 protected Uri insertPhoneNumber(long rawContactId, String phoneNumber, boolean primary) { 255 return insertPhoneNumber(rawContactId, phoneNumber, primary, Phone.TYPE_HOME); 256 } 257 258 protected Uri insertPhoneNumber(long rawContactId, String phoneNumber, boolean primary, 259 int type) { 260 ContentValues values = new ContentValues(); 261 values.put(Data.RAW_CONTACT_ID, rawContactId); 262 values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 263 values.put(Phone.NUMBER, phoneNumber); 264 values.put(Phone.TYPE, type); 265 if (primary) { 266 values.put(Data.IS_PRIMARY, 1); 267 } 268 269 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 270 return resultUri; 271 } 272 273 protected Uri insertEmail(long rawContactId, String email) { 274 return insertEmail(rawContactId, email, false); 275 } 276 277 protected Uri insertEmail(long rawContactId, String email, boolean primary) { 278 return insertEmail(rawContactId, email, primary, Email.TYPE_HOME, null); 279 } 280 281 protected Uri insertEmail(long rawContactId, String email, boolean primary, 282 boolean superPrimary) { 283 return insertEmail(rawContactId, email, primary, superPrimary, Email.TYPE_HOME, null); 284 } 285 286 protected Uri insertEmail(long rawContactId, String email, boolean primary, int type, 287 String label) { 288 return insertEmail(rawContactId, email, primary, false, type, label); 289 } 290 291 protected Uri insertEmail(long rawContactId, String email, boolean primary, 292 boolean superPrimary, int type, String label) { 293 ContentValues values = new ContentValues(); 294 values.put(Data.RAW_CONTACT_ID, rawContactId); 295 values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); 296 values.put(Email.DATA, email); 297 values.put(Email.TYPE, type); 298 values.put(Email.LABEL, label); 299 if (primary) { 300 values.put(Data.IS_PRIMARY, 1); 301 } 302 if (superPrimary) { 303 values.put(Data.IS_SUPER_PRIMARY, 1); 304 } 305 306 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 307 return resultUri; 308 } 309 310 protected Uri insertSipAddress(long rawContactId, String sipAddress) { 311 return insertSipAddress(rawContactId, sipAddress, false); 312 } 313 314 protected Uri insertSipAddress(long rawContactId, String sipAddress, boolean primary) { 315 ContentValues values = new ContentValues(); 316 values.put(Data.RAW_CONTACT_ID, rawContactId); 317 values.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE); 318 values.put(SipAddress.SIP_ADDRESS, sipAddress); 319 if (primary) { 320 values.put(Data.IS_PRIMARY, 1); 321 } 322 323 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 324 return resultUri; 325 } 326 327 protected Uri insertNickname(long rawContactId, String nickname) { 328 ContentValues values = new ContentValues(); 329 values.put(Data.RAW_CONTACT_ID, rawContactId); 330 values.put(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE); 331 values.put(Nickname.NAME, nickname); 332 values.put(Nickname.TYPE, Nickname.TYPE_OTHER_NAME); 333 334 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 335 return resultUri; 336 } 337 338 protected Uri insertPostalAddress(long rawContactId, String formattedAddress) { 339 ContentValues values = new ContentValues(); 340 values.put(Data.RAW_CONTACT_ID, rawContactId); 341 values.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE); 342 values.put(StructuredPostal.FORMATTED_ADDRESS, formattedAddress); 343 344 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 345 return resultUri; 346 } 347 348 protected Uri insertPostalAddress(long rawContactId, ContentValues values) { 349 values.put(Data.RAW_CONTACT_ID, rawContactId); 350 values.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE); 351 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 352 return resultUri; 353 } 354 355 protected Uri insertPhoto(long rawContactId) { 356 ContentValues values = new ContentValues(); 357 values.put(Data.RAW_CONTACT_ID, rawContactId); 358 values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); 359 values.put(Photo.PHOTO, loadTestPhoto()); 360 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 361 return resultUri; 362 } 363 364 protected Uri insertPhoto(long rawContactId, int resourceId) { 365 ContentValues values = new ContentValues(); 366 values.put(Data.RAW_CONTACT_ID, rawContactId); 367 values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); 368 values.put(Photo.PHOTO, loadPhotoFromResource(resourceId, PhotoSize.ORIGINAL)); 369 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 370 return resultUri; 371 } 372 373 protected Uri insertGroupMembership(long rawContactId, String sourceId) { 374 ContentValues values = new ContentValues(); 375 values.put(Data.RAW_CONTACT_ID, rawContactId); 376 values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 377 values.put(GroupMembership.GROUP_SOURCE_ID, sourceId); 378 return mResolver.insert(Data.CONTENT_URI, values); 379 } 380 381 protected Uri insertGroupMembership(long rawContactId, Long groupId) { 382 ContentValues values = new ContentValues(); 383 values.put(Data.RAW_CONTACT_ID, rawContactId); 384 values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 385 values.put(GroupMembership.GROUP_ROW_ID, groupId); 386 return mResolver.insert(Data.CONTENT_URI, values); 387 } 388 389 public void removeGroupMemberships(long rawContactId) { 390 mResolver.delete(Data.CONTENT_URI, 391 Data.MIMETYPE + "=? AND " + GroupMembership.RAW_CONTACT_ID + "=?", 392 new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(rawContactId) }); 393 } 394 395 protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle, 396 int presence, String status, int chatMode) { 397 return insertStatusUpdate(protocol, customProtocol, handle, presence, status, chatMode, 398 false); 399 } 400 401 protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle, 402 int presence, String status, int chatMode, boolean isUserProfile) { 403 return insertStatusUpdate(protocol, customProtocol, handle, presence, status, 0, chatMode, 404 isUserProfile); 405 } 406 407 protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle, 408 int presence, String status, long timestamp, int chatMode, boolean isUserProfile) { 409 ContentValues values = new ContentValues(); 410 values.put(StatusUpdates.PROTOCOL, protocol); 411 values.put(StatusUpdates.CUSTOM_PROTOCOL, customProtocol); 412 values.put(StatusUpdates.IM_HANDLE, handle); 413 return insertStatusUpdate(values, presence, status, timestamp, chatMode, isUserProfile); 414 } 415 416 protected Uri insertStatusUpdate( 417 long dataId, int presence, String status, long timestamp, int chatMode) { 418 return insertStatusUpdate(dataId, presence, status, timestamp, chatMode, false); 419 } 420 421 protected Uri insertStatusUpdate( 422 long dataId, int presence, String status, long timestamp, int chatMode, 423 boolean isUserProfile) { 424 ContentValues values = new ContentValues(); 425 values.put(StatusUpdates.DATA_ID, dataId); 426 return insertStatusUpdate(values, presence, status, timestamp, chatMode, isUserProfile); 427 } 428 429 private Uri insertStatusUpdate( 430 ContentValues values, int presence, String status, long timestamp, int chatMode, 431 boolean isUserProfile) { 432 if (presence != 0) { 433 values.put(StatusUpdates.PRESENCE, presence); 434 values.put(StatusUpdates.CHAT_CAPABILITY, chatMode); 435 } 436 if (status != null) { 437 values.put(StatusUpdates.STATUS, status); 438 } 439 if (timestamp != 0) { 440 values.put(StatusUpdates.STATUS_TIMESTAMP, timestamp); 441 } 442 443 Uri insertUri = isUserProfile 444 ? StatusUpdates.PROFILE_CONTENT_URI 445 : StatusUpdates.CONTENT_URI; 446 Uri resultUri = mResolver.insert(insertUri, values); 447 return resultUri; 448 } 449 450 protected Uri insertStreamItem(long rawContactId, ContentValues values, Account account) { 451 return mResolver.insert( 452 TestUtil.maybeAddAccountQueryParameters(Uri.withAppendedPath( 453 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), 454 RawContacts.StreamItems.CONTENT_DIRECTORY), account), 455 values); 456 } 457 458 protected Uri insertStreamItemPhoto(long streamItemId, ContentValues values, Account account) { 459 return mResolver.insert( 460 TestUtil.maybeAddAccountQueryParameters(Uri.withAppendedPath( 461 ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), 462 StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), account), 463 values); 464 } 465 466 protected Uri insertImHandle(long rawContactId, int protocol, String customProtocol, 467 String handle) { 468 ContentValues values = new ContentValues(); 469 values.put(Data.RAW_CONTACT_ID, rawContactId); 470 values.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); 471 values.put(Im.PROTOCOL, protocol); 472 values.put(Im.CUSTOM_PROTOCOL, customProtocol); 473 values.put(Im.DATA, handle); 474 values.put(Im.TYPE, Im.TYPE_HOME); 475 476 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 477 return resultUri; 478 } 479 480 protected Uri insertEvent(long rawContactId, int type, String date) { 481 ContentValues values = new ContentValues(); 482 values.put(Data.RAW_CONTACT_ID, rawContactId); 483 values.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE); 484 values.put(Event.TYPE, type); 485 values.put(Event.START_DATE, date); 486 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 487 return resultUri; 488 } 489 490 protected Uri insertNote(long rawContactId, String note) { 491 ContentValues values = new ContentValues(); 492 values.put(Data.RAW_CONTACT_ID, rawContactId); 493 values.put(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE); 494 values.put(Note.NOTE, note); 495 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 496 return resultUri; 497 } 498 499 protected Uri insertIdentity(long rawContactId, String identity, String namespace) { 500 ContentValues values = new ContentValues(); 501 values.put(Data.RAW_CONTACT_ID, rawContactId); 502 values.put(Data.MIMETYPE, Identity.CONTENT_ITEM_TYPE); 503 values.put(Identity.NAMESPACE, namespace); 504 values.put(Identity.IDENTITY, identity); 505 506 Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); 507 return resultUri; 508 } 509 510 protected void setContactAccount(long rawContactId, String accountType, String accountName) { 511 ContentValues values = new ContentValues(); 512 values.put(RawContacts.ACCOUNT_TYPE, accountType); 513 values.put(RawContacts.ACCOUNT_NAME, accountName); 514 515 mResolver.update(ContentUris.withAppendedId( 516 RawContacts.CONTENT_URI, rawContactId), values, null, null); 517 } 518 519 protected void setAggregationException(int type, long rawContactId1, long rawContactId2) { 520 ContentValues values = new ContentValues(); 521 values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); 522 values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); 523 values.put(AggregationExceptions.TYPE, type); 524 assertEquals(1, mResolver.update(AggregationExceptions.CONTENT_URI, values, null, null)); 525 } 526 527 protected void markInvisible(long contactId) { 528 // There's no api for this, so we just tweak the DB directly. 529 SQLiteDatabase db = ((ContactsProvider2) getProvider()).getDatabaseHelper() 530 .getWritableDatabase(); 531 db.execSQL("DELETE FROM " + Tables.DEFAULT_DIRECTORY + 532 " WHERE " + BaseColumns._ID + "=" + contactId); 533 } 534 535 protected Cursor queryRawContact(long rawContactId) { 536 return mResolver.query(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), 537 null, null, null, null); 538 } 539 540 protected Cursor queryContact(long contactId) { 541 return mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), 542 null, null, null, null); 543 } 544 545 protected Cursor queryContact(long contactId, String[] projection) { 546 return mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), 547 projection, null, null, null); 548 } 549 550 protected Uri getContactUriForRawContact(long rawContactId) { 551 return ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId)); 552 } 553 554 protected long queryContactId(long rawContactId) { 555 Cursor c = queryRawContact(rawContactId); 556 assertTrue(c.moveToFirst()); 557 long contactId = c.getLong(c.getColumnIndex(RawContacts.CONTACT_ID)); 558 c.close(); 559 return contactId; 560 } 561 562 protected long queryPhotoId(long contactId) { 563 Cursor c = queryContact(contactId); 564 assertTrue(c.moveToFirst()); 565 long photoId = c.getInt(c.getColumnIndex(Contacts.PHOTO_ID)); 566 c.close(); 567 return photoId; 568 } 569 570 protected long queryPhotoFileId(long contactId) { 571 return getStoredLongValue(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), 572 Contacts.PHOTO_FILE_ID); 573 } 574 575 protected boolean queryRawContactIsStarred(long rawContactId) { 576 Cursor c = queryRawContact(rawContactId); 577 try { 578 assertTrue(c.moveToFirst()); 579 return c.getLong(c.getColumnIndex(RawContacts.STARRED)) != 0; 580 } finally { 581 c.close(); 582 } 583 } 584 585 protected String queryDisplayName(long contactId) { 586 Cursor c = queryContact(contactId); 587 assertTrue(c.moveToFirst()); 588 String displayName = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME)); 589 c.close(); 590 return displayName; 591 } 592 593 protected String queryLookupKey(long contactId) { 594 Cursor c = queryContact(contactId); 595 assertTrue(c.moveToFirst()); 596 String lookupKey = c.getString(c.getColumnIndex(Contacts.LOOKUP_KEY)); 597 c.close(); 598 return lookupKey; 599 } 600 601 protected void assertAggregated(long rawContactId1, long rawContactId2) { 602 long contactId1 = queryContactId(rawContactId1); 603 long contactId2 = queryContactId(rawContactId2); 604 assertTrue(contactId1 == contactId2); 605 } 606 607 protected void assertAggregated(long rawContactId1, long rawContactId2, 608 String expectedDisplayName) { 609 long contactId1 = queryContactId(rawContactId1); 610 long contactId2 = queryContactId(rawContactId2); 611 assertTrue(contactId1 == contactId2); 612 613 String displayName = queryDisplayName(contactId1); 614 assertEquals(expectedDisplayName, displayName); 615 } 616 617 protected void assertNotAggregated(long rawContactId1, long rawContactId2) { 618 long contactId1 = queryContactId(rawContactId1); 619 long contactId2 = queryContactId(rawContactId2); 620 assertTrue(contactId1 != contactId2); 621 } 622 623 protected void assertStructuredName(long rawContactId, String prefix, String givenName, 624 String middleName, String familyName, String suffix) { 625 Uri uri = Uri.withAppendedPath( 626 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), 627 RawContacts.Data.CONTENT_DIRECTORY); 628 629 final String[] projection = new String[] { 630 StructuredName.PREFIX, StructuredName.GIVEN_NAME, StructuredName.MIDDLE_NAME, 631 StructuredName.FAMILY_NAME, StructuredName.SUFFIX 632 }; 633 634 Cursor c = mResolver.query(uri, projection, Data.MIMETYPE + "='" 635 + StructuredName.CONTENT_ITEM_TYPE + "'", null, null); 636 637 assertTrue(c.moveToFirst()); 638 assertEquals(prefix, c.getString(0)); 639 assertEquals(givenName, c.getString(1)); 640 assertEquals(middleName, c.getString(2)); 641 assertEquals(familyName, c.getString(3)); 642 assertEquals(suffix, c.getString(4)); 643 c.close(); 644 } 645 646 protected long assertSingleGroup(Long rowId, Account account, String sourceId, String title) { 647 Cursor c = mResolver.query(Groups.CONTENT_URI, null, null, null, null); 648 try { 649 assertTrue(c.moveToNext()); 650 long actualRowId = assertGroup(c, rowId, account, sourceId, title); 651 assertFalse(c.moveToNext()); 652 return actualRowId; 653 } finally { 654 c.close(); 655 } 656 } 657 658 protected long assertSingleGroupMembership(Long rowId, Long rawContactId, Long groupRowId, 659 String sourceId) { 660 Cursor c = mResolver.query(ContactsContract.Data.CONTENT_URI, null, null, null, null); 661 try { 662 assertTrue(c.moveToNext()); 663 long actualRowId = assertGroupMembership(c, rowId, rawContactId, groupRowId, sourceId); 664 assertFalse(c.moveToNext()); 665 return actualRowId; 666 } finally { 667 c.close(); 668 } 669 } 670 671 protected long assertGroupMembership(Cursor c, Long rowId, Long rawContactId, Long groupRowId, 672 String sourceId) { 673 assertNullOrEquals(c, rowId, Data._ID); 674 assertNullOrEquals(c, rawContactId, GroupMembership.RAW_CONTACT_ID); 675 assertNullOrEquals(c, groupRowId, GroupMembership.GROUP_ROW_ID); 676 assertNullOrEquals(c, sourceId, GroupMembership.GROUP_SOURCE_ID); 677 return c.getLong(c.getColumnIndexOrThrow("_id")); 678 } 679 680 protected long assertGroup(Cursor c, Long rowId, Account account, String sourceId, String title) { 681 assertNullOrEquals(c, rowId, Groups._ID); 682 assertNullOrEquals(c, account); 683 assertNullOrEquals(c, sourceId, Groups.SOURCE_ID); 684 assertNullOrEquals(c, title, Groups.TITLE); 685 return c.getLong(c.getColumnIndexOrThrow("_id")); 686 } 687 688 private void assertNullOrEquals(Cursor c, Account account) { 689 if (account == NO_ACCOUNT) { 690 return; 691 } 692 if (account == null) { 693 assertTrue(c.isNull(c.getColumnIndexOrThrow(Groups.ACCOUNT_NAME))); 694 assertTrue(c.isNull(c.getColumnIndexOrThrow(Groups.ACCOUNT_TYPE))); 695 } else { 696 assertEquals(account.name, c.getString(c.getColumnIndexOrThrow(Groups.ACCOUNT_NAME))); 697 assertEquals(account.type, c.getString(c.getColumnIndexOrThrow(Groups.ACCOUNT_TYPE))); 698 } 699 } 700 701 private void assertNullOrEquals(Cursor c, Long value, String columnName) { 702 if (value != NO_LONG) { 703 if (value == null) assertTrue(c.isNull(c.getColumnIndexOrThrow(columnName))); 704 else assertEquals((long) value, c.getLong(c.getColumnIndexOrThrow(columnName))); 705 } 706 } 707 708 private void assertNullOrEquals(Cursor c, String value, String columnName) { 709 if (value != NO_STRING) { 710 if (value == null) assertTrue(c.isNull(c.getColumnIndexOrThrow(columnName))); 711 else assertEquals(value, c.getString(c.getColumnIndexOrThrow(columnName))); 712 } 713 } 714 715 protected void assertDataRow(ContentValues actual, String expectedMimetype, 716 Object... expectedArguments) { 717 assertEquals(actual.toString(), expectedMimetype, actual.getAsString(Data.MIMETYPE)); 718 for (int i = 0; i < expectedArguments.length; i += 2) { 719 String columnName = (String) expectedArguments[i]; 720 Object expectedValue = expectedArguments[i + 1]; 721 if (expectedValue instanceof Uri) { 722 expectedValue = ContentUris.parseId((Uri) expectedValue); 723 } 724 if (expectedValue == null) { 725 assertNull(actual.toString(), actual.get(columnName)); 726 } 727 if (expectedValue instanceof Long) { 728 assertEquals("mismatch at " + columnName + " from " + actual.toString(), 729 expectedValue, actual.getAsLong(columnName)); 730 } else if (expectedValue instanceof Integer) { 731 assertEquals("mismatch at " + columnName + " from " + actual.toString(), 732 expectedValue, actual.getAsInteger(columnName)); 733 } else if (expectedValue instanceof String) { 734 assertEquals("mismatch at " + columnName + " from " + actual.toString(), 735 expectedValue, actual.getAsString(columnName)); 736 } else { 737 assertEquals("mismatch at " + columnName + " from " + actual.toString(), 738 expectedValue, actual.get(columnName)); 739 } 740 } 741 } 742 743 protected void assertNoRowsAndClose(Cursor c) { 744 try { 745 assertFalse(c.moveToNext()); 746 } finally { 747 c.close(); 748 } 749 } 750 751 protected static class IdComparator implements Comparator<ContentValues> { 752 @Override 753 public int compare(ContentValues o1, ContentValues o2) { 754 long id1 = o1.getAsLong(ContactsContract.Data._ID); 755 long id2 = o2.getAsLong(ContactsContract.Data._ID); 756 if (id1 == id2) return 0; 757 return (id1 < id2) ? -1 : 1; 758 } 759 } 760 761 protected ContentValues[] asSortedContentValuesArray( 762 ArrayList<Entity.NamedContentValues> subValues) { 763 ContentValues[] result = new ContentValues[subValues.size()]; 764 int i = 0; 765 for (Entity.NamedContentValues subValue : subValues) { 766 result[i] = subValue.values; 767 i++; 768 } 769 Arrays.sort(result, new IdComparator()); 770 return result; 771 } 772 773 protected void assertDirty(Uri uri, boolean state) { 774 Cursor c = mResolver.query(uri, new String[]{"dirty"}, null, null, null); 775 assertTrue(c.moveToNext()); 776 assertEquals(state, c.getLong(0) != 0); 777 assertFalse(c.moveToNext()); 778 c.close(); 779 } 780 781 protected long getVersion(Uri uri) { 782 Cursor c = mResolver.query(uri, new String[]{"version"}, null, null, null); 783 assertTrue(c.moveToNext()); 784 long version = c.getLong(0); 785 assertFalse(c.moveToNext()); 786 c.close(); 787 return version; 788 } 789 790 protected void clearDirty(Uri uri) { 791 ContentValues values = new ContentValues(); 792 values.put("dirty", 0); 793 mResolver.update(uri, values, null, null); 794 } 795 796 protected void storeValue(Uri contentUri, long id, String column, String value) { 797 storeValue(ContentUris.withAppendedId(contentUri, id), column, value); 798 } 799 800 protected void storeValue(Uri contentUri, String column, String value) { 801 ContentValues values = new ContentValues(); 802 values.put(column, value); 803 804 mResolver.update(contentUri, values, null, null); 805 } 806 807 protected void storeValue(Uri contentUri, long id, String column, long value) { 808 storeValue(ContentUris.withAppendedId(contentUri, id), column, value); 809 } 810 811 protected void storeValue(Uri contentUri, String column, long value) { 812 ContentValues values = new ContentValues(); 813 values.put(column, value); 814 815 mResolver.update(contentUri, values, null, null); 816 } 817 818 protected void assertStoredValue(Uri contentUri, long id, String column, Object expectedValue) { 819 assertStoredValue(ContentUris.withAppendedId(contentUri, id), column, expectedValue); 820 } 821 822 protected void assertStoredValue(Uri rowUri, String column, Object expectedValue) { 823 String value = getStoredValue(rowUri, column); 824 if (expectedValue == null) { 825 assertNull("Column value " + column, value); 826 } else { 827 assertEquals("Column value " + column, String.valueOf(expectedValue), value); 828 } 829 } 830 831 protected void assertStoredValue(Uri rowUri, String selection, String[] selectionArgs, 832 String column, Object expectedValue) { 833 String value = getStoredValue(rowUri, selection, selectionArgs, column); 834 if (expectedValue == null) { 835 assertNull("Column value " + column, value); 836 } else { 837 assertEquals("Column value " + column, String.valueOf(expectedValue), value); 838 } 839 } 840 841 protected String getStoredValue(Uri rowUri, String column) { 842 return getStoredValue(rowUri, null, null, column); 843 } 844 845 protected String getStoredValue(Uri uri, String selection, String[] selectionArgs, 846 String column) { 847 String value = null; 848 Cursor c = mResolver.query(uri, new String[] { column }, selection, selectionArgs, null); 849 try { 850 assertEquals("Record count for " + uri, 1, c.getCount()); 851 852 if (c.moveToFirst()) { 853 value = c.getString(c.getColumnIndex(column)); 854 } 855 } finally { 856 c.close(); 857 } 858 return value; 859 } 860 861 protected Long getStoredLongValue(Uri uri, String selection, String[] selectionArgs, 862 String column) { 863 Long value = null; 864 Cursor c = mResolver.query(uri, new String[] { column }, selection, selectionArgs, null); 865 try { 866 assertEquals("Record count", 1, c.getCount()); 867 868 if (c.moveToFirst()) { 869 value = c.getLong(c.getColumnIndex(column)); 870 } 871 } finally { 872 c.close(); 873 } 874 return value; 875 } 876 877 protected Long getStoredLongValue(Uri uri, String column) { 878 return getStoredLongValue(uri, null, null, column); 879 } 880 881 protected void assertStoredValues(Uri rowUri, ContentValues expectedValues) { 882 assertStoredValues(rowUri, null, null, expectedValues); 883 } 884 885 protected void assertStoredValues(Uri rowUri, ContentValues... expectedValues) { 886 assertStoredValues(rowUri, null, null, expectedValues); 887 } 888 889 protected void assertStoredValues(Uri rowUri, String selection, String[] selectionArgs, 890 ContentValues expectedValues) { 891 Cursor c = mResolver.query(rowUri, null, selection, selectionArgs, null); 892 try { 893 assertEquals("Record count", 1, c.getCount()); 894 c.moveToFirst(); 895 assertCursorValues(c, expectedValues); 896 } catch (Error e) { 897 TestUtils.dumpCursor(c); 898 throw e; 899 } finally { 900 c.close(); 901 } 902 } 903 904 protected void assertContainsValues(Uri rowUri, ContentValues expectedValues) { 905 Cursor c = mResolver.query(rowUri, null, null, null, null); 906 try { 907 assertEquals("Record count", 1, c.getCount()); 908 c.moveToFirst(); 909 assertCursorValuesPartialMatch(c, expectedValues); 910 } catch (Error e) { 911 TestUtils.dumpCursor(c); 912 throw e; 913 } finally { 914 c.close(); 915 } 916 } 917 918 protected void assertStoredValuesWithProjection(Uri rowUri, ContentValues expectedValues) { 919 assertStoredValuesWithProjection(rowUri, new ContentValues[] {expectedValues}); 920 } 921 922 protected void assertStoredValuesWithProjection(Uri rowUri, ContentValues... expectedValues) { 923 assertTrue("Need at least one ContentValues for this test", expectedValues.length > 0); 924 Cursor c = mResolver.query(rowUri, buildProjection(expectedValues[0]), null, null, null); 925 try { 926 assertEquals("Record count", expectedValues.length, c.getCount()); 927 c.moveToFirst(); 928 assertCursorValues(c, expectedValues); 929 } catch (Error e) { 930 TestUtils.dumpCursor(c); 931 throw e; 932 } finally { 933 c.close(); 934 } 935 } 936 937 protected void assertStoredValues( 938 Uri rowUri, String selection, String[] selectionArgs, ContentValues... expectedValues) { 939 assertStoredValues(mResolver.query(rowUri, null, selection, selectionArgs, null), 940 expectedValues); 941 } 942 943 private void assertStoredValues(Cursor c, ContentValues... expectedValues) { 944 try { 945 assertEquals("Record count", expectedValues.length, c.getCount()); 946 assertCursorValues(c, expectedValues); 947 } catch (Error e) { 948 TestUtils.dumpCursor(c); 949 throw e; 950 } finally { 951 c.close(); 952 } 953 } 954 955 /** 956 * A variation of {@link #assertStoredValues}, but it queries directly to the DB. 957 */ 958 protected void assertStoredValuesDb( 959 String sql, String[] selectionArgs, ContentValues... expectedValues) { 960 SQLiteDatabase db = ((ContactsProvider2) getProvider()).getDatabaseHelper() 961 .getReadableDatabase(); 962 assertStoredValues(db.rawQuery(sql, selectionArgs), expectedValues); 963 } 964 965 protected void assertStoredValuesOrderly(Uri rowUri, ContentValues... expectedValues) { 966 assertStoredValuesOrderly(rowUri, null, null, expectedValues); 967 } 968 969 protected void assertStoredValuesOrderly(Uri rowUri, String selection, 970 String[] selectionArgs, ContentValues... expectedValues) { 971 Cursor c = mResolver.query(rowUri, null, selection, selectionArgs, null); 972 try { 973 assertEquals("Record count", expectedValues.length, c.getCount()); 974 assertCursorValuesOrderly(c, expectedValues); 975 } catch (Error e) { 976 TestUtils.dumpCursor(c); 977 throw e; 978 } finally { 979 c.close(); 980 } 981 } 982 983 /** 984 * Constructs a selection (where clause) out of all supplied values, uses it 985 * to query the provider and verifies that a single row is returned and it 986 * has the same values as requested. 987 */ 988 protected void assertSelection(Uri uri, ContentValues values, String idColumn, long id) { 989 assertSelection(uri, values, idColumn, id, null); 990 } 991 992 public void assertSelectionWithProjection(Uri uri, ContentValues values, String idColumn, 993 long id) { 994 assertSelection(uri, values, idColumn, id, buildProjection(values)); 995 } 996 997 private void assertSelection(Uri uri, ContentValues values, String idColumn, long id, 998 String[] projection) { 999 StringBuilder sb = new StringBuilder(); 1000 ArrayList<String> selectionArgs = new ArrayList<String>(values.size()); 1001 if (idColumn != null) { 1002 sb.append(idColumn).append("=").append(id); 1003 } 1004 Set<Map.Entry<String, Object>> entries = values.valueSet(); 1005 for (Map.Entry<String, Object> entry : entries) { 1006 String column = entry.getKey(); 1007 Object value = entry.getValue(); 1008 if (sb.length() != 0) { 1009 sb.append(" AND "); 1010 } 1011 sb.append(column); 1012 if (value == null) { 1013 sb.append(" IS NULL"); 1014 } else { 1015 sb.append("=?"); 1016 selectionArgs.add(String.valueOf(value)); 1017 } 1018 } 1019 1020 Cursor c = mResolver.query(uri, projection, sb.toString(), selectionArgs.toArray(new String[0]), 1021 null); 1022 try { 1023 assertEquals("Record count", 1, c.getCount()); 1024 c.moveToFirst(); 1025 assertCursorValues(c, values); 1026 } catch (Error e) { 1027 TestUtils.dumpCursor(c); 1028 throw e; 1029 } finally { 1030 c.close(); 1031 } 1032 } 1033 1034 protected void assertCursorValue(Cursor cursor, String column, Object expectedValue) { 1035 String actualValue = cursor.getString(cursor.getColumnIndex(column)); 1036 assertEquals("Column " + column, String.valueOf(expectedValue), 1037 String.valueOf(actualValue)); 1038 } 1039 1040 protected void assertCursorValues(Cursor cursor, ContentValues expectedValues) { 1041 StringBuilder message = new StringBuilder(); 1042 boolean result = equalsWithExpectedValues(cursor, expectedValues, message); 1043 assertTrue(message.toString(), result); 1044 } 1045 1046 protected void assertCursorValuesPartialMatch(Cursor cursor, ContentValues expectedValues) { 1047 StringBuilder message = new StringBuilder(); 1048 boolean result = expectedValuePartiallyMatches(cursor, expectedValues, message); 1049 assertTrue(message.toString(), result); 1050 } 1051 1052 protected void assertCursorHasAnyRecordMatch(Cursor cursor, ContentValues expectedValues) { 1053 final StringBuilder message = new StringBuilder(); 1054 boolean found = false; 1055 cursor.moveToPosition(-1); 1056 while (cursor.moveToNext()) { 1057 message.setLength(0); 1058 final int pos = cursor.getPosition(); 1059 found = equalsWithExpectedValues(cursor, expectedValues, message); 1060 if (found) { 1061 break; 1062 } 1063 } 1064 assertTrue("Expected values can not be found " + expectedValues + "," + message.toString(), 1065 found); 1066 } 1067 1068 protected void assertCursorValues(Cursor cursor, ContentValues... expectedValues) { 1069 StringBuilder message = new StringBuilder(); 1070 1071 // In case if expectedValues contains multiple identical values, remember which cursor 1072 // rows are "consumed" to prevent multiple ContentValues from hitting the same row. 1073 final BitSet used = new BitSet(cursor.getCount()); 1074 1075 for (ContentValues v : expectedValues) { 1076 boolean found = false; 1077 cursor.moveToPosition(-1); 1078 while (cursor.moveToNext()) { 1079 final int pos = cursor.getPosition(); 1080 if (used.get(pos)) continue; 1081 found = equalsWithExpectedValues(cursor, v, message); 1082 if (found) { 1083 used.set(pos); 1084 break; 1085 } 1086 } 1087 assertTrue("Expected values can not be found " + v + "," + message.toString(), found); 1088 } 1089 } 1090 1091 private void assertCursorValuesOrderly(Cursor cursor, ContentValues... expectedValues) { 1092 StringBuilder message = new StringBuilder(); 1093 cursor.moveToPosition(-1); 1094 for (ContentValues v : expectedValues) { 1095 assertTrue(cursor.moveToNext()); 1096 boolean ok = equalsWithExpectedValues(cursor, v, message); 1097 assertTrue("ContentValues didn't match. Pos=" + cursor.getPosition() + ", values=" + 1098 v + message.toString(), ok); 1099 } 1100 } 1101 1102 private boolean expectedValuePartiallyMatches(Cursor cursor, ContentValues expectedValues, 1103 StringBuilder msgBuffer) { 1104 for (String column : expectedValues.keySet()) { 1105 int index = cursor.getColumnIndex(column); 1106 if (index == -1) { 1107 msgBuffer.append(" No such column: ").append(column); 1108 return false; 1109 } 1110 String expectedValue = expectedValues.getAsString(column); 1111 String value = cursor.getString(cursor.getColumnIndex(column)); 1112 if (value != null && !value.contains(expectedValue)) { 1113 msgBuffer.append(" Column value ").append(column).append(" expected to contain <") 1114 .append(expectedValue).append(">, but was <").append(value).append('>'); 1115 return false; 1116 } 1117 } 1118 return true; 1119 } 1120 1121 private boolean equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues, 1122 StringBuilder msgBuffer) { 1123 for (String column : expectedValues.keySet()) { 1124 int index = cursor.getColumnIndex(column); 1125 if (index == -1) { 1126 msgBuffer.append(" No such column: ").append(column); 1127 return false; 1128 } 1129 Object expectedValue = expectedValues.get(column); 1130 String value; 1131 if (expectedValue instanceof byte[]) { 1132 expectedValue = Hex.encodeHex((byte[])expectedValue, false); 1133 value = Hex.encodeHex(cursor.getBlob(index), false); 1134 } else { 1135 expectedValue = expectedValues.getAsString(column); 1136 value = cursor.getString(cursor.getColumnIndex(column)); 1137 } 1138 if (expectedValue != null && !expectedValue.equals(value) || value != null 1139 && !value.equals(expectedValue)) { 1140 msgBuffer 1141 .append(" Column value ") 1142 .append(column) 1143 .append(" expected <") 1144 .append(expectedValue) 1145 .append(">, but was <") 1146 .append(value) 1147 .append('>'); 1148 return false; 1149 } 1150 } 1151 return true; 1152 } 1153 1154 private static final String[] DATA_USAGE_PROJECTION = 1155 new String[] {Data.DATA1, Data.TIMES_USED, Data.LAST_TIME_USED}; 1156 1157 protected void assertDataUsageCursorContains(Uri uri, String data1, int timesUsed, 1158 int lastTimeUsed) { 1159 final Cursor cursor = mResolver.query(uri, DATA_USAGE_PROJECTION, null, null, 1160 null); 1161 try { 1162 assertCursorHasAnyRecordMatch(cursor, cv(Data.DATA1, data1, Data.TIMES_USED, timesUsed, 1163 Data.LAST_TIME_USED, lastTimeUsed)); 1164 } finally { 1165 cursor.close(); 1166 } 1167 } 1168 1169 private String[] buildProjection(ContentValues values) { 1170 String[] projection = new String[values.size()]; 1171 Iterator<Entry<String, Object>> iter = values.valueSet().iterator(); 1172 for (int i = 0; i < projection.length; i++) { 1173 projection[i] = iter.next().getKey(); 1174 } 1175 return projection; 1176 } 1177 1178 protected int getCount(Uri uri) { 1179 return getCount(uri, null, null); 1180 } 1181 1182 protected int getCount(Uri uri, String selection, String[] selectionArgs) { 1183 Cursor c = mResolver.query(uri, null, selection, selectionArgs, null); 1184 try { 1185 return c.getCount(); 1186 } finally { 1187 c.close(); 1188 } 1189 } 1190 1191 public static void dump(ContentResolver resolver, boolean aggregatedOnly) { 1192 String[] projection = new String[] { 1193 Contacts._ID, 1194 Contacts.DISPLAY_NAME 1195 }; 1196 String selection = null; 1197 if (aggregatedOnly) { 1198 selection = Contacts._ID 1199 + " IN (SELECT contact_id" + 1200 " FROM raw_contacts GROUP BY contact_id HAVING count(*) > 1)"; 1201 } 1202 1203 Cursor c = resolver.query(Contacts.CONTENT_URI, projection, selection, null, 1204 Contacts.DISPLAY_NAME); 1205 while(c.moveToNext()) { 1206 long contactId = c.getLong(0); 1207 Log.i("Contact ", String.format("%5d %s", contactId, c.getString(1))); 1208 dumpRawContacts(resolver, contactId); 1209 Log.i(" ", "."); 1210 } 1211 c.close(); 1212 } 1213 1214 private static void dumpRawContacts(ContentResolver resolver, long contactId) { 1215 String[] projection = new String[] { 1216 RawContacts._ID, 1217 }; 1218 Cursor c = resolver.query(RawContacts.CONTENT_URI, projection, RawContacts.CONTACT_ID + "=" 1219 + contactId, null, null); 1220 while(c.moveToNext()) { 1221 long rawContactId = c.getLong(0); 1222 Log.i("RawContact", String.format(" %-5d", rawContactId)); 1223 dumpData(resolver, rawContactId); 1224 } 1225 c.close(); 1226 } 1227 1228 private static void dumpData(ContentResolver resolver, long rawContactId) { 1229 String[] projection = new String[] { 1230 Data.MIMETYPE, 1231 Data.DATA1, 1232 Data.DATA2, 1233 Data.DATA3, 1234 }; 1235 Cursor c = resolver.query(Data.CONTENT_URI, projection, Data.RAW_CONTACT_ID + "=" 1236 + rawContactId, null, Data.MIMETYPE); 1237 while(c.moveToNext()) { 1238 String mimetype = c.getString(0); 1239 if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) { 1240 Log.i("Photo ", ""); 1241 } else { 1242 mimetype = mimetype.substring(mimetype.indexOf('/') + 1); 1243 Log.i("Data ", String.format(" %-10s %s,%s,%s", mimetype, 1244 c.getString(1), c.getString(2), c.getString(3))); 1245 } 1246 } 1247 c.close(); 1248 } 1249 1250 protected void assertNetworkNotified(boolean expected) { 1251 assertEquals(expected, (getContactsProvider()).isNetworkNotified()); 1252 } 1253 1254 protected void assertProjection(Uri uri, String[] expectedProjection) { 1255 Cursor cursor = mResolver.query(uri, null, "0", null, null); 1256 String[] actualProjection = cursor.getColumnNames(); 1257 MoreAsserts.assertEquals("Incorrect projection for URI: " + uri, 1258 Sets.newHashSet(expectedProjection), Sets.newHashSet(actualProjection)); 1259 cursor.close(); 1260 } 1261 1262 protected void assertRowCount(int expectedCount, Uri uri, String selection, String[] args) { 1263 Cursor cursor = mResolver.query(uri, null, selection, args, null); 1264 1265 try { 1266 assertEquals(expectedCount, cursor.getCount()); 1267 } catch (Error e) { 1268 TestUtils.dumpCursor(cursor); 1269 throw e; 1270 } finally { 1271 cursor.close(); 1272 } 1273 } 1274 1275 /** 1276 * A contact in the database, and the attributes used to create it. Construct using 1277 * {@link GoldenContactBuilder#build()}. 1278 */ 1279 public final class GoldenContact { 1280 1281 private final long rawContactId; 1282 1283 private final long contactId; 1284 1285 private final String givenName; 1286 1287 private final String familyName; 1288 1289 private final String nickname; 1290 1291 private final byte[] photo; 1292 1293 private final String company; 1294 1295 private final String title; 1296 1297 private final String phone; 1298 1299 private final String email; 1300 1301 private GoldenContact(GoldenContactBuilder builder, long rawContactId, long contactId) { 1302 1303 this.rawContactId = rawContactId; 1304 this.contactId = contactId; 1305 givenName = builder.givenName; 1306 familyName = builder.familyName; 1307 nickname = builder.nickname; 1308 photo = builder.photo; 1309 company = builder.company; 1310 title = builder.title; 1311 phone = builder.phone; 1312 email = builder.email; 1313 } 1314 1315 public void delete() { 1316 Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); 1317 mResolver.delete(rawContactUri, null, null); 1318 } 1319 1320 /** 1321 * Returns the index of the contact in table "raw_contacts" 1322 */ 1323 public long getRawContactId() { 1324 return rawContactId; 1325 } 1326 1327 /** 1328 * Returns the index of the contact in table "contacts" 1329 */ 1330 public long getContactId() { 1331 return contactId; 1332 } 1333 1334 /** 1335 * Returns the lookup key for the contact. 1336 */ 1337 public String getLookupKey() { 1338 return queryLookupKey(contactId); 1339 } 1340 1341 /** 1342 * Returns the contact's given name. 1343 */ 1344 public String getGivenName() { 1345 return givenName; 1346 } 1347 1348 /** 1349 * Returns the contact's family name. 1350 */ 1351 public String getFamilyName() { 1352 return familyName; 1353 } 1354 1355 /** 1356 * Returns the contact's nickname. 1357 */ 1358 public String getNickname() { 1359 return nickname; 1360 } 1361 1362 /** 1363 * Return's the contact's photo 1364 */ 1365 public byte[] getPhoto() { 1366 return photo; 1367 } 1368 1369 /** 1370 * Return's the company at which the contact works. 1371 */ 1372 public String getCompany() { 1373 return company; 1374 } 1375 1376 /** 1377 * Returns the contact's job title. 1378 */ 1379 public String getTitle() { 1380 return title; 1381 } 1382 1383 /** 1384 * Returns the contact's phone number 1385 */ 1386 public String getPhone() { 1387 return phone; 1388 } 1389 1390 /** 1391 * Returns the contact's email address 1392 */ 1393 public String getEmail() { 1394 return email; 1395 } 1396 } 1397 1398 /** 1399 * Builds {@link GoldenContact} objects. Unspecified boolean objects default to false. 1400 * Unspecified String objects default to null. 1401 */ 1402 public final class GoldenContactBuilder { 1403 1404 private String givenName; 1405 1406 private String familyName; 1407 1408 private String nickname; 1409 1410 private byte[] photo; 1411 1412 private String company; 1413 1414 private String title; 1415 1416 private String phone; 1417 1418 private String email; 1419 1420 /** 1421 * The contact's given and family names. 1422 * 1423 * TODO(dplotnikov): inline, or should we require them to set both names if they set either? 1424 */ 1425 public GoldenContactBuilder name(String givenName, String familyName) { 1426 return givenName(givenName).familyName(familyName); 1427 } 1428 1429 /** 1430 * The contact's given name. 1431 */ 1432 public GoldenContactBuilder givenName(String value) { 1433 givenName = value; 1434 return this; 1435 } 1436 1437 /** 1438 * The contact's family name. 1439 */ 1440 public GoldenContactBuilder familyName(String value) { 1441 familyName = value; 1442 return this; 1443 } 1444 1445 /** 1446 * The contact's nickname. 1447 */ 1448 public GoldenContactBuilder nickname(String value) { 1449 nickname = value; 1450 return this; 1451 } 1452 1453 /** 1454 * The contact's photo. 1455 */ 1456 public GoldenContactBuilder photo(byte[] value) { 1457 photo = value; 1458 return this; 1459 } 1460 1461 /** 1462 * The company at which the contact works. 1463 */ 1464 public GoldenContactBuilder company(String value) { 1465 company = value; 1466 return this; 1467 } 1468 1469 /** 1470 * The contact's job title. 1471 */ 1472 public GoldenContactBuilder title(String value) { 1473 title = value; 1474 return this; 1475 } 1476 1477 /** 1478 * The contact's phone number. 1479 */ 1480 public GoldenContactBuilder phone(String value) { 1481 phone = value; 1482 return this; 1483 } 1484 1485 /** 1486 * The contact's email address; also sets their IM status to {@link StatusUpdates#OFFLINE} 1487 * with a presence of "Coding for Android". 1488 */ 1489 public GoldenContactBuilder email(String value) { 1490 email = value; 1491 return this; 1492 } 1493 1494 /** 1495 * Builds the {@link GoldenContact} specified by this builder. 1496 */ 1497 public GoldenContact build() { 1498 1499 final long groupId = createGroup(mAccount, "gsid1", "title1"); 1500 1501 long rawContactId = RawContactUtil.createRawContact(mResolver); 1502 insertGroupMembership(rawContactId, groupId); 1503 1504 if (givenName != null || familyName != null) { 1505 DataUtil.insertStructuredName(mResolver, rawContactId, givenName, familyName); 1506 } 1507 if (nickname != null) { 1508 insertNickname(rawContactId, nickname); 1509 } 1510 if (photo != null) { 1511 insertPhoto(rawContactId); 1512 } 1513 if (company != null || title != null) { 1514 insertOrganization(rawContactId); 1515 } 1516 if (email != null) { 1517 insertEmail(rawContactId); 1518 } 1519 if (phone != null) { 1520 insertPhone(rawContactId); 1521 } 1522 1523 long contactId = queryContactId(rawContactId); 1524 1525 return new GoldenContact(this, rawContactId, contactId); 1526 } 1527 1528 private void insertPhoto(long rawContactId) { 1529 ContentValues values = new ContentValues(); 1530 values.put(Data.RAW_CONTACT_ID, rawContactId); 1531 values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); 1532 values.put(Photo.PHOTO, photo); 1533 mResolver.insert(Data.CONTENT_URI, values); 1534 } 1535 1536 private void insertOrganization(long rawContactId) { 1537 1538 ContentValues values = new ContentValues(); 1539 values.put(Data.RAW_CONTACT_ID, rawContactId); 1540 values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE); 1541 values.put(Organization.TYPE, Organization.TYPE_WORK); 1542 if (company != null) { 1543 values.put(Organization.COMPANY, company); 1544 } 1545 if (title != null) { 1546 values.put(Organization.TITLE, title); 1547 } 1548 mResolver.insert(Data.CONTENT_URI, values); 1549 } 1550 1551 private void insertEmail(long rawContactId) { 1552 1553 ContentValues values = new ContentValues(); 1554 values.put(Data.RAW_CONTACT_ID, rawContactId); 1555 values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); 1556 values.put(Email.TYPE, Email.TYPE_WORK); 1557 values.put(Email.DATA, "foo (at) acme.com"); 1558 mResolver.insert(Data.CONTENT_URI, values); 1559 1560 int protocol = Im.PROTOCOL_GOOGLE_TALK; 1561 1562 values.clear(); 1563 values.put(StatusUpdates.PROTOCOL, protocol); 1564 values.put(StatusUpdates.IM_HANDLE, email); 1565 values.put(StatusUpdates.IM_ACCOUNT, "foo"); 1566 values.put(StatusUpdates.PRESENCE_STATUS, StatusUpdates.OFFLINE); 1567 values.put(StatusUpdates.CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA); 1568 values.put(StatusUpdates.PRESENCE_CUSTOM_STATUS, "Coding for Android"); 1569 mResolver.insert(StatusUpdates.CONTENT_URI, values); 1570 } 1571 1572 private void insertPhone(long rawContactId) { 1573 ContentValues values = new ContentValues(); 1574 values.put(Data.RAW_CONTACT_ID, rawContactId); 1575 values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 1576 values.put(Data.IS_PRIMARY, 1); 1577 values.put(Phone.TYPE, Phone.TYPE_HOME); 1578 values.put(Phone.NUMBER, phone); 1579 mResolver.insert(Data.CONTENT_URI, values); 1580 } 1581 } 1582 } 1583