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