• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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