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