1 /* 2 * Copyright (C) 2016 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 package com.android.contacts.database; 17 18 import static android.os.Build.VERSION_CODES; 19 20 import static com.android.contacts.tests.ContactsMatchers.DataCursor.hasEmail; 21 import static com.android.contacts.tests.ContactsMatchers.DataCursor.hasName; 22 import static com.android.contacts.tests.ContactsMatchers.DataCursor.hasPhone; 23 import static com.android.contacts.tests.ContactsMatchers.isSimContactWithNameAndPhone; 24 25 import static org.hamcrest.Matchers.allOf; 26 import static org.hamcrest.Matchers.equalTo; 27 import static org.junit.Assert.assertThat; 28 import static org.junit.Assert.assertTrue; 29 import static org.mockito.Matchers.any; 30 import static org.mockito.Matchers.anyString; 31 import static org.mockito.Mockito.mock; 32 import static org.mockito.Mockito.when; 33 34 import android.content.ContentProvider; 35 import android.content.ContentProviderOperation; 36 import android.content.ContentResolver; 37 import android.content.Context; 38 import android.content.OperationApplicationException; 39 import android.database.Cursor; 40 import android.net.Uri; 41 import android.os.CancellationSignal; 42 import android.os.RemoteException; 43 import android.provider.ContactsContract; 44 import android.provider.ContactsContract.CommonDataKinds.Email; 45 import android.provider.ContactsContract.CommonDataKinds.Phone; 46 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 47 import android.provider.ContactsContract.Data; 48 import android.support.annotation.RequiresApi; 49 import android.support.test.InstrumentationRegistry; 50 import android.support.test.filters.LargeTest; 51 import android.support.test.filters.SdkSuppress; 52 import android.support.test.filters.SmallTest; 53 import android.support.test.filters.Suppress; 54 import android.support.test.runner.AndroidJUnit4; 55 import android.test.mock.MockContentResolver; 56 import android.test.mock.MockContext; 57 58 import com.android.contacts.model.SimCard; 59 import com.android.contacts.model.SimContact; 60 import com.android.contacts.model.account.AccountWithDataSet; 61 import com.android.contacts.test.mocks.MockContentProvider; 62 import com.android.contacts.tests.AccountsTestHelper; 63 import com.android.contacts.tests.ContactsMatchers; 64 import com.android.contacts.tests.SimContactsTestHelper; 65 import com.android.contacts.tests.StringableCursor; 66 67 import com.google.common.collect.ImmutableMap; 68 import com.google.common.collect.ImmutableSet; 69 70 import org.hamcrest.Matchers; 71 import org.junit.After; 72 import org.junit.AfterClass; 73 import org.junit.Before; 74 import org.junit.BeforeClass; 75 import org.junit.Test; 76 import org.junit.experimental.runners.Enclosed; 77 import org.junit.runner.RunWith; 78 79 import java.util.ArrayList; 80 import java.util.Arrays; 81 import java.util.List; 82 import java.util.Locale; 83 import java.util.Map; 84 import java.util.Random; 85 import java.util.Set; 86 87 @RunWith(Enclosed.class) 88 public class SimContactDaoTests { 89 90 // Some random area codes for generating realistic US phones when 91 // generating fake data for the SIM contacts or CP2 92 private static final String[] AREA_CODES = 93 {"360", "509", "416", "831", "212", "208"}; 94 private static final Random sRandom = new Random(); 95 96 // Approximate maximum number of contacts that can be stored on a SIM card for testing 97 // boundary cases 98 public static final int MAX_SIM_CONTACTS = 600; 99 100 // On pre-M addAccountExplicitly (which we call via AccountsTestHelper) causes a 101 // SecurityException to be thrown unless we add AUTHENTICATE_ACCOUNTS permission to the app 102 // manifest. Instead of adding the extra permission just for tests we'll just only run them 103 // on M or newer 104 @SdkSuppress(minSdkVersion = VERSION_CODES.M) 105 // Lollipop MR1 is required for removeAccountExplicitly 106 @RequiresApi(api = VERSION_CODES.LOLLIPOP_MR1) 107 @LargeTest 108 @RunWith(AndroidJUnit4.class) 109 public static class ImportIntegrationTest { 110 private AccountWithDataSet mAccount; 111 private AccountsTestHelper mAccountsHelper; 112 private ContentResolver mResolver; 113 114 @Before setUp()115 public void setUp() throws Exception { 116 mAccountsHelper = new AccountsTestHelper(); 117 mAccount = mAccountsHelper.addTestAccount(); 118 mResolver = getContext().getContentResolver(); 119 } 120 121 @After tearDown()122 public void tearDown() throws Exception { 123 mAccountsHelper.cleanup(); 124 } 125 126 @Test importFromSim()127 public void importFromSim() throws Exception { 128 final SimContactDao sut = SimContactDao.create(getContext()); 129 130 sut.importContacts(Arrays.asList( 131 new SimContact(1, "Test One", "15095550101"), 132 new SimContact(2, "Test Two", "15095550102"), 133 new SimContact(3, "Test Three", "15095550103", new String[] { 134 "user@example.com", "user2@example.com" 135 }) 136 ), mAccount); 137 138 Cursor cursor = queryContactWithName("Test One"); 139 assertThat(cursor, ContactsMatchers.hasCount(2)); 140 assertThat(cursor, hasName("Test One")); 141 assertThat(cursor, hasPhone("15095550101")); 142 cursor.close(); 143 144 cursor = queryContactWithName("Test Two"); 145 assertThat(cursor, ContactsMatchers.hasCount(2)); 146 assertThat(cursor, hasName("Test Two")); 147 assertThat(cursor, hasPhone("15095550102")); 148 cursor.close(); 149 150 cursor = queryContactWithName("Test Three"); 151 assertThat(cursor, ContactsMatchers.hasCount(4)); 152 assertThat(cursor, hasName("Test Three")); 153 assertThat(cursor, hasPhone("15095550103")); 154 assertThat(cursor, allOf(hasEmail("user@example.com"), hasEmail("user2@example.com"))); 155 cursor.close(); 156 } 157 158 @Test importContactWhichOnlyHasName()159 public void importContactWhichOnlyHasName() throws Exception { 160 final SimContactDao sut = SimContactDao.create(getContext()); 161 162 sut.importContacts(Arrays.asList( 163 new SimContact(1, "Test importJustName", null, null) 164 ), mAccount); 165 166 Cursor cursor = queryAllDataInAccount(); 167 168 assertThat(cursor, ContactsMatchers.hasCount(1)); 169 assertThat(cursor, hasName("Test importJustName")); 170 cursor.close(); 171 } 172 173 @Test importContactWhichOnlyHasPhone()174 public void importContactWhichOnlyHasPhone() throws Exception { 175 final SimContactDao sut = SimContactDao.create(getContext()); 176 177 sut.importContacts(Arrays.asList( 178 new SimContact(1, null, "15095550111", null) 179 ), mAccount); 180 181 Cursor cursor = queryAllDataInAccount(); 182 183 assertThat(cursor, ContactsMatchers.hasCount(1)); 184 assertThat(cursor, hasPhone("15095550111")); 185 cursor.close(); 186 } 187 188 @Test ignoresEmptyContacts()189 public void ignoresEmptyContacts() throws Exception { 190 final SimContactDao sut = SimContactDao.create(getContext()); 191 192 // This probably isn't possible but we'll test it to demonstrate expected behavior and 193 // just in case it does occur 194 sut.importContacts(Arrays.asList( 195 new SimContact(1, null, null, null), 196 new SimContact(2, null, null, null), 197 new SimContact(3, null, null, null), 198 new SimContact(4, "Not null", null, null) 199 ), mAccount); 200 201 final Cursor contactsCursor = queryAllRawContactsInAccount(); 202 assertThat(contactsCursor, ContactsMatchers.hasCount(1)); 203 contactsCursor.close(); 204 205 final Cursor dataCursor = queryAllDataInAccount(); 206 assertThat(dataCursor, ContactsMatchers.hasCount(1)); 207 208 dataCursor.close(); 209 } 210 211 /** 212 * Tests importing a large number of contacts 213 * 214 * Make sure that {@link android.os.TransactionTooLargeException} is not thrown 215 */ 216 @Test largeImport()217 public void largeImport() throws Exception { 218 final SimContactDao sut = SimContactDao.create(getContext()); 219 220 final List<SimContact> contacts = new ArrayList<>(); 221 222 for (int i = 0; i < MAX_SIM_CONTACTS; i++) { 223 contacts.add(new SimContact(i + 1, "Contact " + (i + 1), randomPhone(), 224 new String[] { randomEmail("contact" + (i + 1) + "_")})); 225 } 226 227 sut.importContacts(contacts, mAccount); 228 229 final Cursor contactsCursor = queryAllRawContactsInAccount(); 230 assertThat(contactsCursor, ContactsMatchers.hasCount(MAX_SIM_CONTACTS)); 231 contactsCursor.close(); 232 233 final Cursor dataCursor = queryAllDataInAccount(); 234 // Each contact has one data row for each of name, phone and email 235 assertThat(dataCursor, ContactsMatchers.hasCount(MAX_SIM_CONTACTS * 3)); 236 237 dataCursor.close(); 238 } 239 queryAllRawContactsInAccount()240 private Cursor queryAllRawContactsInAccount() { 241 return new StringableCursor(mResolver.query(ContactsContract.RawContacts.CONTENT_URI, 242 null, ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " + 243 ContactsContract.RawContacts.ACCOUNT_TYPE+ "=?", 244 new String[] { 245 mAccount.name, 246 mAccount.type 247 }, null)); 248 } 249 queryAllDataInAccount()250 private Cursor queryAllDataInAccount() { 251 return new StringableCursor(mResolver.query(Data.CONTENT_URI, null, 252 ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " + 253 ContactsContract.RawContacts.ACCOUNT_TYPE+ "=?", 254 new String[] { 255 mAccount.name, 256 mAccount.type 257 }, null)); 258 } 259 queryContactWithName(String name)260 private Cursor queryContactWithName(String name) { 261 return new StringableCursor(mResolver.query(Data.CONTENT_URI, null, 262 ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " + 263 ContactsContract.RawContacts.ACCOUNT_TYPE+ "=? AND " + 264 Data.DISPLAY_NAME + "=?", 265 new String[] { 266 mAccount.name, 267 mAccount.type, 268 name 269 }, null)); 270 } 271 } 272 273 /** 274 * Tests for {@link SimContactDao#findAccountsOfExistingSimContacts(List)} 275 * 276 * These are integration tests that query CP2 so that the SQL will be validated in addition 277 * to the detection algorithm 278 */ 279 @SdkSuppress(minSdkVersion = VERSION_CODES.M) 280 // Lollipop MR1 is required for removeAccountExplicitly 281 @RequiresApi(api = VERSION_CODES.LOLLIPOP_MR1) 282 @LargeTest 283 @RunWith(AndroidJUnit4.class) 284 public static class FindAccountsIntegrationTests { 285 286 private Context mContext; 287 private AccountsTestHelper mAccountHelper; 288 private List<AccountWithDataSet> mAccounts; 289 // We need to generate something distinct to prevent flakiness on devices that may not 290 // start with an empty CP2 DB 291 private String mNameSuffix; 292 293 private static AccountWithDataSet sSeedAccount; 294 295 @BeforeClass setUpClass()296 public static void setUpClass() throws Exception { 297 final AccountsTestHelper helper = new AccountsTestHelper( 298 InstrumentationRegistry.getContext()); 299 sSeedAccount = helper.addTestAccount(helper.generateAccountName("seedAccount")); 300 301 seedCp2(); 302 } 303 304 @AfterClass tearDownClass()305 public static void tearDownClass() { 306 final AccountsTestHelper helper = new AccountsTestHelper( 307 InstrumentationRegistry.getContext()); 308 helper.removeTestAccount(sSeedAccount); 309 sSeedAccount = null; 310 } 311 312 @Before setUp()313 public void setUp() throws Exception { 314 mContext = InstrumentationRegistry.getTargetContext(); 315 mAccountHelper = new AccountsTestHelper(InstrumentationRegistry.getContext()); 316 mAccounts = new ArrayList<>(); 317 mNameSuffix = getClass().getSimpleName() + "At" + System.nanoTime(); 318 319 seedCp2(); 320 } 321 322 @After tearDown()323 public void tearDown() { 324 for (AccountWithDataSet account : mAccounts) { 325 mAccountHelper.removeTestAccount(account); 326 } 327 } 328 329 @Test returnsEmptyMapWhenNoMatchingContactsExist()330 public void returnsEmptyMapWhenNoMatchingContactsExist() { 331 mAccounts.add(mAccountHelper.addTestAccount()); 332 333 final SimContactDao sut = createDao(); 334 335 final List<SimContact> contacts = Arrays.asList( 336 new SimContact(1, "Name 1 " + mNameSuffix, "5550101"), 337 new SimContact(2, "Name 2 " + mNameSuffix, "5550102"), 338 new SimContact(3, "Name 3 " + mNameSuffix, "5550103"), 339 new SimContact(4, "Name 4 " + mNameSuffix, "5550104")); 340 341 final Map<AccountWithDataSet, Set<SimContact>> existing = sut 342 .findAccountsOfExistingSimContacts(contacts); 343 344 assertTrue(existing.isEmpty()); 345 } 346 347 @Test hasAccountWithMatchingContactsWhenSingleMatchingContactExists()348 public void hasAccountWithMatchingContactsWhenSingleMatchingContactExists() 349 throws Exception { 350 final SimContactDao sut = createDao(); 351 352 final AccountWithDataSet account = mAccountHelper.addTestAccount( 353 mAccountHelper.generateAccountName("primary_")); 354 mAccounts.add(account); 355 356 final SimContact existing1 = 357 new SimContact(2, "Exists 2 " + mNameSuffix, "5550102"); 358 final SimContact existing2 = 359 new SimContact(4, "Exists 4 " + mNameSuffix, "5550104"); 360 361 final List<SimContact> contacts = Arrays.asList( 362 new SimContact(1, "Missing 1 " + mNameSuffix, "5550101"), 363 new SimContact(existing1), 364 new SimContact(3, "Missing 3 " + mNameSuffix, "5550103"), 365 new SimContact(existing2)); 366 367 sut.importContacts(Arrays.asList( 368 new SimContact(existing1), 369 new SimContact(existing2) 370 ), account); 371 372 373 final Map<AccountWithDataSet, Set<SimContact>> existing = sut 374 .findAccountsOfExistingSimContacts(contacts); 375 376 assertThat(existing.size(), equalTo(1)); 377 assertThat(existing.get(account), 378 Matchers.<Set<SimContact>>equalTo(ImmutableSet.of(existing1, existing2))); 379 } 380 381 @Test hasMultipleAccountsWhenMultipleMatchingContactsExist()382 public void hasMultipleAccountsWhenMultipleMatchingContactsExist() throws Exception { 383 final SimContactDao sut = createDao(); 384 385 final AccountWithDataSet account1 = mAccountHelper.addTestAccount( 386 mAccountHelper.generateAccountName("account1_")); 387 mAccounts.add(account1); 388 final AccountWithDataSet account2 = mAccountHelper.addTestAccount( 389 mAccountHelper.generateAccountName("account2_")); 390 mAccounts.add(account2); 391 392 final SimContact existsInBoth = 393 new SimContact(2, "Exists Both " + mNameSuffix, "5550102"); 394 final SimContact existsInAccount1 = 395 new SimContact(4, "Exists 1 " + mNameSuffix, "5550104"); 396 final SimContact existsInAccount2 = 397 new SimContact(5, "Exists 2 " + mNameSuffix, "5550105"); 398 399 final List<SimContact> contacts = Arrays.asList( 400 new SimContact(1, "Missing 1 " + mNameSuffix, "5550101"), 401 new SimContact(existsInBoth), 402 new SimContact(3, "Missing 3 " + mNameSuffix, "5550103"), 403 new SimContact(existsInAccount1), 404 new SimContact(existsInAccount2)); 405 406 sut.importContacts(Arrays.asList( 407 new SimContact(existsInBoth), 408 new SimContact(existsInAccount1) 409 ), account1); 410 411 sut.importContacts(Arrays.asList( 412 new SimContact(existsInBoth), 413 new SimContact(existsInAccount2) 414 ), account2); 415 416 417 final Map<AccountWithDataSet, Set<SimContact>> existing = sut 418 .findAccountsOfExistingSimContacts(contacts); 419 420 assertThat(existing.size(), equalTo(2)); 421 assertThat(existing, Matchers.<Map<AccountWithDataSet, Set<SimContact>>>equalTo( 422 ImmutableMap.<AccountWithDataSet, Set<SimContact>>of( 423 account1, ImmutableSet.of(existsInBoth, existsInAccount1), 424 account2, ImmutableSet.of(existsInBoth, existsInAccount2)))); 425 } 426 427 @Test matchesByNameIfSimContactHasNoPhone()428 public void matchesByNameIfSimContactHasNoPhone() throws Exception { 429 final SimContactDao sut = createDao(); 430 431 final AccountWithDataSet account = mAccountHelper.addTestAccount( 432 mAccountHelper.generateAccountName("account_")); 433 mAccounts.add(account); 434 435 final SimContact noPhone = new SimContact(1, "Nophone " + mNameSuffix, null); 436 final SimContact otherExisting = new SimContact( 437 5, "Exists 1 " + mNameSuffix, "5550105"); 438 439 final List<SimContact> contacts = Arrays.asList( 440 new SimContact(noPhone), 441 new SimContact(2, "Name 2 " + mNameSuffix, "5550102"), 442 new SimContact(3, "Name 3 " + mNameSuffix, "5550103"), 443 new SimContact(4, "Name 4 " + mNameSuffix, "5550104"), 444 new SimContact(otherExisting)); 445 446 sut.importContacts(Arrays.asList( 447 new SimContact(noPhone), 448 new SimContact(otherExisting) 449 ), account); 450 451 final Map<AccountWithDataSet, Set<SimContact>> existing = sut 452 .findAccountsOfExistingSimContacts(contacts); 453 454 assertThat(existing.size(), equalTo(1)); 455 assertThat(existing.get(account), Matchers.<Set<SimContact>>equalTo( 456 ImmutableSet.of(noPhone, otherExisting))); 457 } 458 459 @Test largeNumberOfSimContacts()460 public void largeNumberOfSimContacts() throws Exception { 461 final SimContactDao sut = createDao(); 462 463 final List<SimContact> contacts = new ArrayList<>(); 464 for (int i = 0; i < MAX_SIM_CONTACTS; i++) { 465 contacts.add(new SimContact( 466 i + 1, "Contact " + (i + 1) + " " + mNameSuffix, randomPhone())); 467 } 468 // The work has to be split into batches to avoid hitting SQL query parameter limits 469 // so test contacts that will be at boundary points 470 final SimContact imported1 = contacts.get(0); 471 final SimContact imported2 = contacts.get(99); 472 final SimContact imported3 = contacts.get(100); 473 final SimContact imported4 = contacts.get(101); 474 final SimContact imported5 = contacts.get(MAX_SIM_CONTACTS - 1); 475 476 final AccountWithDataSet account = mAccountHelper.addTestAccount( 477 mAccountHelper.generateAccountName("account_")); 478 mAccounts.add(account); 479 480 sut.importContacts(Arrays.asList(imported1, imported2, imported3, imported4, imported5), 481 account); 482 483 mAccounts.add(account); 484 485 final Map<AccountWithDataSet, Set<SimContact>> existing = sut 486 .findAccountsOfExistingSimContacts(contacts); 487 488 assertThat(existing.size(), equalTo(1)); 489 assertThat(existing.get(account), Matchers.<Set<SimContact>>equalTo( 490 ImmutableSet.of(imported1, imported2, imported3, imported4, imported5))); 491 492 } 493 createDao()494 private SimContactDao createDao() { 495 return SimContactDao.create(mContext); 496 } 497 498 /** 499 * Adds a bunch of random contact data to CP2 to make the test environment more realistic 500 */ seedCp2()501 private static void seedCp2() throws RemoteException, OperationApplicationException { 502 503 final ArrayList<ContentProviderOperation> ops = new ArrayList<>(); 504 505 appendCreateContact("John Smith", sSeedAccount, ops); 506 appendCreateContact("Marcus Seed", sSeedAccount, ops); 507 appendCreateContact("Gary Seed", sSeedAccount, ops); 508 appendCreateContact("Michael Seed", sSeedAccount, ops); 509 appendCreateContact("Isaac Seed", sSeedAccount, ops); 510 appendCreateContact("Sean Seed", sSeedAccount, ops); 511 appendCreateContact("Nate Seed", sSeedAccount, ops); 512 appendCreateContact("Andrey Seed", sSeedAccount, ops); 513 appendCreateContact("Cody Seed", sSeedAccount, ops); 514 appendCreateContact("John Seed", sSeedAccount, ops); 515 appendCreateContact("Alex Seed", sSeedAccount, ops); 516 517 InstrumentationRegistry.getTargetContext() 518 .getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); 519 } 520 appendCreateContact(String name, AccountWithDataSet account, ArrayList<ContentProviderOperation> ops)521 private static void appendCreateContact(String name, AccountWithDataSet account, 522 ArrayList<ContentProviderOperation> ops) { 523 final int emailCount = sRandom.nextInt(10); 524 final int phoneCount = sRandom.nextInt(5); 525 526 final List<String> phones = new ArrayList<>(); 527 for (int i = 0; i < phoneCount; i++) { 528 phones.add(randomPhone()); 529 } 530 final List<String> emails = new ArrayList<>(); 531 for (int i = 0; i < emailCount; i++) { 532 emails.add(randomEmail(name)); 533 } 534 appendCreateContact(name, phones, emails, account, ops); 535 } 536 537 appendCreateContact(String name, List<String> phoneNumbers, List<String> emails, AccountWithDataSet account, List<ContentProviderOperation> ops)538 private static void appendCreateContact(String name, List<String> phoneNumbers, 539 List<String> emails, AccountWithDataSet account, List<ContentProviderOperation> ops) { 540 int index = ops.size(); 541 542 ops.add(account.newRawContactOperation()); 543 ops.add(insertIntoData(name, StructuredName.CONTENT_ITEM_TYPE, index)); 544 for (String phone : phoneNumbers) { 545 ops.add(insertIntoData(phone, Phone.CONTENT_ITEM_TYPE, Phone.TYPE_MOBILE, index)); 546 } 547 for (String email : emails) { 548 ops.add(insertIntoData(email, Email.CONTENT_ITEM_TYPE, Email.TYPE_HOME, index)); 549 } 550 } 551 insertIntoData(String value, String mimeType, int idBackReference)552 private static ContentProviderOperation insertIntoData(String value, String mimeType, 553 int idBackReference) { 554 return ContentProviderOperation.newInsert(Data.CONTENT_URI) 555 .withValue(Data.DATA1, value) 556 .withValue(Data.MIMETYPE, mimeType) 557 .withValueBackReference(Data.RAW_CONTACT_ID, idBackReference).build(); 558 } 559 insertIntoData(String value, String mimeType, int type, int idBackReference)560 private static ContentProviderOperation insertIntoData(String value, String mimeType, 561 int type, int idBackReference) { 562 return ContentProviderOperation.newInsert(Data.CONTENT_URI) 563 .withValue(Data.DATA1, value) 564 .withValue(ContactsContract.Data.DATA2, type) 565 .withValue(Data.MIMETYPE, mimeType) 566 .withValueBackReference(Data.RAW_CONTACT_ID, idBackReference).build(); 567 } 568 } 569 570 /** 571 * Tests for {@link SimContactDao#loadContactsForSim(SimCard)} 572 * 573 * These are unit tests that verify that {@link SimContact}s are created correctly from 574 * the cursors that are returned by queries to the IccProvider 575 */ 576 @SmallTest 577 @RunWith(AndroidJUnit4.class) 578 public static class LoadContactsUnitTests { 579 580 private MockContentProvider mMockIccProvider; 581 private Context mContext; 582 583 @Before setUp()584 public void setUp() { 585 mContext = mock(MockContext.class); 586 final MockContentResolver mockResolver = new MockContentResolver(); 587 mMockIccProvider = new MockContentProvider(); 588 mockResolver.addProvider("icc", mMockIccProvider); 589 when(mContext.getContentResolver()).thenReturn(mockResolver); 590 } 591 592 593 @Test createsContactsFromCursor()594 public void createsContactsFromCursor() { 595 mMockIccProvider.expect(MockContentProvider.Query.forAnyUri()) 596 .withDefaultProjection( 597 SimContactDaoImpl._ID, SimContactDaoImpl.NAME, 598 SimContactDaoImpl.NUMBER, SimContactDaoImpl.EMAILS) 599 .withAnyProjection() 600 .withAnySelection() 601 .withAnySortOrder() 602 .returnRow(1, "Name One", "5550101", null) 603 .returnRow(2, "Name Two", "5550102", null) 604 .returnRow(3, "Name Three", null, null) 605 .returnRow(4, null, "5550104", null) 606 .returnRow(5, "Name Five", "5550105", 607 "five@example.com,nf@example.com,name.five@example.com") 608 .returnRow(6, "Name Six", "5550106", "thesix@example.com"); 609 610 final SimContactDao sut = SimContactDao.create(mContext); 611 final List<SimContact> contacts = sut 612 .loadContactsForSim(new SimCard("123", "carrier", "sim", null, "us")); 613 614 assertThat(contacts, equalTo( 615 Arrays.asList( 616 new SimContact(1, "Name One", "5550101", null), 617 new SimContact(2, "Name Two", "5550102", null), 618 new SimContact(3, "Name Three", null, null), 619 new SimContact(4, null, "5550104", null), 620 new SimContact(5, "Name Five", "5550105", new String[] { 621 "five@example.com", "nf@example.com", "name.five@example.com" 622 }), 623 new SimContact(6, "Name Six", "5550106", new String[] { 624 "thesix@example.com" 625 }) 626 ))); 627 } 628 629 @Test excludesEmptyContactsFromResult()630 public void excludesEmptyContactsFromResult() { 631 mMockIccProvider.expect(MockContentProvider.Query.forAnyUri()) 632 .withDefaultProjection( 633 SimContactDaoImpl._ID, SimContactDaoImpl.NAME, 634 SimContactDaoImpl.NUMBER, SimContactDaoImpl.EMAILS) 635 .withAnyProjection() 636 .withAnySelection() 637 .withAnySortOrder() 638 .returnRow(1, "Non Empty1", "5550101", null) 639 .returnRow(2, "", "", "") 640 .returnRow(3, "Non Empty2", null, null) 641 .returnRow(4, null, null, null) 642 .returnRow(5, "", null, null) 643 .returnRow(6, null, "5550102", null) 644 .returnRow(7, null, null, "user@example.com"); 645 646 final SimContactDao sut = SimContactDao.create(mContext); 647 final List<SimContact> contacts = sut 648 .loadContactsForSim(new SimCard("123", "carrier", "sim", null, "us")); 649 650 assertThat(contacts, equalTo( 651 Arrays.asList( 652 new SimContact(1, "Non Empty1", "5550101", null), 653 new SimContact(3, "Non Empty2", null, null), 654 new SimContact(6, null, "5550102", null), 655 new SimContact(7, null, null, new String[] { "user@example.com" }) 656 ))); 657 } 658 659 @Test usesSimCardSubscriptionIdIfAvailable()660 public void usesSimCardSubscriptionIdIfAvailable() { 661 mMockIccProvider.expectQuery(SimContactDaoImpl.ICC_CONTENT_URI.buildUpon() 662 .appendPath("subId").appendPath("2").build()) 663 .withDefaultProjection( 664 SimContactDaoImpl._ID, SimContactDaoImpl.NAME, 665 SimContactDaoImpl.NUMBER, SimContactDaoImpl.EMAILS) 666 .withAnyProjection() 667 .withAnySelection() 668 .withAnySortOrder() 669 .returnEmptyCursor(); 670 671 final SimContactDao sut = SimContactDao.create(mContext); 672 sut.loadContactsForSim(new SimCard("123", 2, "carrier", "sim", null, "us")); 673 mMockIccProvider.verify(); 674 } 675 676 @Test omitsSimCardSubscriptionIdIfUnavailable()677 public void omitsSimCardSubscriptionIdIfUnavailable() { 678 mMockIccProvider.expectQuery(SimContactDaoImpl.ICC_CONTENT_URI) 679 .withDefaultProjection( 680 SimContactDaoImpl._ID, SimContactDaoImpl.NAME, 681 SimContactDaoImpl.NUMBER, SimContactDaoImpl.EMAILS) 682 .withAnyProjection() 683 .withAnySelection() 684 .withAnySortOrder() 685 .returnEmptyCursor(); 686 687 final SimContactDao sut = SimContactDao.create(mContext); 688 sut.loadContactsForSim(new SimCard("123", SimCard.NO_SUBSCRIPTION_ID, 689 "carrier", "sim", null, "us")); 690 mMockIccProvider.verify(); 691 } 692 693 @Test returnsEmptyListForEmptyCursor()694 public void returnsEmptyListForEmptyCursor() { 695 mMockIccProvider.expect(MockContentProvider.Query.forAnyUri()) 696 .withDefaultProjection( 697 SimContactDaoImpl._ID, SimContactDaoImpl.NAME, 698 SimContactDaoImpl.NUMBER, SimContactDaoImpl.EMAILS) 699 .withAnyProjection() 700 .withAnySelection() 701 .withAnySortOrder() 702 .returnEmptyCursor(); 703 704 final SimContactDao sut = SimContactDao.create(mContext); 705 List<SimContact> result = sut 706 .loadContactsForSim(new SimCard("123", "carrier", "sim", null, "us")); 707 assertTrue(result.isEmpty()); 708 } 709 710 @Test returnsEmptyListForNullCursor()711 public void returnsEmptyListForNullCursor() { 712 mContext = mock(MockContext.class); 713 final MockContentResolver mockResolver = new MockContentResolver(); 714 final ContentProvider mockProvider = mock(android.test.mock.MockContentProvider.class); 715 when(mockProvider.query(any(Uri.class), any(String[].class), anyString(), 716 any(String[].class), anyString())) 717 .thenReturn(null); 718 when(mockProvider.query(any(Uri.class), any(String[].class), anyString(), 719 any(String[].class), anyString(), any(CancellationSignal.class))) 720 .thenReturn(null); 721 722 mockResolver.addProvider("icc", mockProvider); 723 when(mContext.getContentResolver()).thenReturn(mockResolver); 724 725 final SimContactDao sut = SimContactDao.create(mContext); 726 final List<SimContact> result = sut 727 .loadContactsForSim(new SimCard("123", "carrier", "sim", null, "us")); 728 assertTrue(result.isEmpty()); 729 } 730 } 731 732 @LargeTest 733 // suppressed because failed assumptions are reported as test failures by the build server 734 @Suppress 735 @RunWith(AndroidJUnit4.class) 736 public static class LoadContactsIntegrationTest { 737 private SimContactsTestHelper mSimTestHelper; 738 private ArrayList<ContentProviderOperation> mSimSnapshot; 739 740 @Before setUp()741 public void setUp() throws Exception { 742 mSimTestHelper = new SimContactsTestHelper(); 743 744 mSimTestHelper.assumeSimWritable(); 745 if (!mSimTestHelper.isSimWritable()) return; 746 747 mSimSnapshot = mSimTestHelper.captureRestoreSnapshot(); 748 mSimTestHelper.deleteAllSimContacts(); 749 } 750 751 @After tearDown()752 public void tearDown() throws Exception { 753 mSimTestHelper.restore(mSimSnapshot); 754 } 755 756 @Test readFromSim()757 public void readFromSim() { 758 mSimTestHelper.addSimContact("Test Simone", "15095550101"); 759 mSimTestHelper.addSimContact("Test Simtwo", "15095550102"); 760 mSimTestHelper.addSimContact("Test Simthree", "15095550103"); 761 762 final SimContactDao sut = SimContactDao.create(getContext()); 763 final SimCard sim = sut.getSimCards().get(0); 764 final ArrayList<SimContact> contacts = sut.loadContactsForSim(sim); 765 766 assertThat(contacts.get(0), isSimContactWithNameAndPhone("Test Simone", "15095550101")); 767 assertThat(contacts.get(1), isSimContactWithNameAndPhone("Test Simtwo", "15095550102")); 768 assertThat(contacts.get(2), 769 isSimContactWithNameAndPhone("Test Simthree", "15095550103")); 770 } 771 } 772 randomPhone()773 private static String randomPhone() { 774 return String.format(Locale.US, "1%s55501%02d", 775 AREA_CODES[sRandom.nextInt(AREA_CODES.length)], 776 sRandom.nextInt(100)); 777 } 778 randomEmail(String name)779 private static String randomEmail(String name) { 780 return String.format("%s%d@example.com", name.replace(" ", ".").toLowerCase(Locale.US), 781 1000 + sRandom.nextInt(1000)); 782 } 783 784 getContext()785 static Context getContext() { 786 return InstrumentationRegistry.getTargetContext(); 787 } 788 } 789