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