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