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