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