1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.phone;
18 
19 import static android.provider.SimPhonebookContract.ElementaryFiles.EF_ADN;
20 import static android.provider.SimPhonebookContract.ElementaryFiles.EF_FDN;
21 import static android.provider.SimPhonebookContract.ElementaryFiles.EF_SDN;
22 
23 import static com.android.internal.telephony.testing.CursorSubject.assertThat;
24 import static com.android.internal.telephony.testing.TelephonyAssertions.assertThrows;
25 
26 import static com.google.common.truth.Truth.assertThat;
27 
28 import static org.mockito.ArgumentMatchers.any;
29 import static org.mockito.ArgumentMatchers.anyInt;
30 import static org.mockito.ArgumentMatchers.eq;
31 import static org.mockito.Mockito.doNothing;
32 import static org.mockito.Mockito.mock;
33 import static org.mockito.Mockito.spy;
34 import static org.mockito.Mockito.times;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.when;
37 
38 import android.content.ContentResolver;
39 import android.content.ContentValues;
40 import android.database.Cursor;
41 import android.net.Uri;
42 import android.provider.SimPhonebookContract;
43 import android.provider.SimPhonebookContract.ElementaryFiles;
44 import android.provider.SimPhonebookContract.SimRecords;
45 import android.telephony.SubscriptionInfo;
46 import android.telephony.SubscriptionManager;
47 import android.util.Pair;
48 
49 import androidx.test.core.app.ApplicationProvider;
50 import androidx.test.ext.junit.runners.AndroidJUnit4;
51 import androidx.test.platform.app.InstrumentationRegistry;
52 import androidx.test.rule.provider.ProviderTestRule;
53 
54 import com.android.internal.telephony.IIccPhoneBook;
55 import com.android.internal.telephony.uicc.AdnCapacity;
56 import com.android.internal.telephony.uicc.AdnRecord;
57 import com.android.internal.telephony.uicc.IccConstants;
58 
59 import com.google.common.collect.ImmutableList;
60 import com.google.common.io.Closeables;
61 import com.google.common.truth.Correspondence;
62 
63 import org.junit.Before;
64 import org.junit.Rule;
65 import org.junit.Test;
66 import org.junit.runner.RunWith;
67 import org.mockito.AdditionalAnswers;
68 import org.mockito.ArgumentCaptor;
69 import org.mockito.Mockito;
70 
71 import java.util.ArrayList;
72 import java.util.Arrays;
73 import java.util.Collections;
74 import java.util.HashMap;
75 import java.util.List;
76 import java.util.Map;
77 import java.util.Objects;
78 import java.util.function.Predicate;
79 import java.util.stream.Collectors;
80 
81 @RunWith(AndroidJUnit4.class)
82 public final class SimPhonebookProviderTest {
83 
84     private static final String EMOJI = new String(Character.toChars(0x1F642));
85     private static final Correspondence<AdnRecord, AdnRecord> ADN_RECORD_IS_EQUAL =
86             Correspondence.from(AdnRecord::isEqual, "isEqual");
87 
88     @Rule
89     public final ProviderTestRule mProviderRule = new ProviderTestRule.Builder(
90             TestableSimPhonebookProvider.class, SimPhonebookContract.AUTHORITY).build();
91 
92     private ContentResolver mResolver;
93     private FakeIccPhoneBook mIccPhoneBook;
94     private SubscriptionManager mMockSubscriptionManager;
95 
createSubscriptionsWithIds(int... subscriptionIds)96     private static List<SubscriptionInfo> createSubscriptionsWithIds(int... subscriptionIds) {
97         ImmutableList.Builder<SubscriptionInfo> builder = ImmutableList.builderWithExpectedSize(
98                 subscriptionIds.length);
99         for (int i = 0; i < subscriptionIds.length; i++) {
100             builder.add(createSubscriptionInfo(i, subscriptionIds[i]));
101         }
102         return builder.build();
103     }
104 
createSubscriptionInfo(int slotIndex, int subscriptiondId)105     private static SubscriptionInfo createSubscriptionInfo(int slotIndex, int subscriptiondId) {
106         return new SubscriptionInfo(
107                 subscriptiondId, "", slotIndex, null, null, 0, 0, null, 0, null, null, null, null,
108                 false, null, null);
109     }
110 
111     @Before
setUp()112     public void setUp() {
113         mMockSubscriptionManager = spy(
114                 Objects.requireNonNull(ApplicationProvider.getApplicationContext()
115                         .getSystemService(SubscriptionManager.class)));
116         mIccPhoneBook = new FakeIccPhoneBook();
117         mResolver = mProviderRule.getResolver();
118 
119         TestableSimPhonebookProvider.setup(mResolver, mMockSubscriptionManager, mIccPhoneBook);
120     }
121 
122     @Test
query_entityFiles_returnsCursorWithCorrectProjection()123     public void query_entityFiles_returnsCursorWithCorrectProjection() {
124         // Null projection
125         try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, null, null,
126                 null)) {
127             assertThat(Objects.requireNonNull(cursor).getColumnNames()).asList()
128                     .containsExactlyElementsIn(
129                             SimPhonebookProvider.ELEMENTARY_FILES_ALL_COLUMNS);
130         }
131 
132         // Empty projection
133         try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, new String[0], null,
134                 null)) {
135             assertThat(cursor).hasColumnNames();
136         }
137 
138         // Single column
139         try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, new String[]{
140                 ElementaryFiles.EF_TYPE
141         }, null, null)) {
142             assertThat(cursor).hasColumnNames(ElementaryFiles.EF_TYPE);
143         }
144 
145         // Duplicate column
146         try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, new String[]{
147                 ElementaryFiles.SUBSCRIPTION_ID, ElementaryFiles.SUBSCRIPTION_ID
148         }, null, null)) {
149             assertThat(cursor).hasColumnNames(ElementaryFiles.SUBSCRIPTION_ID,
150                     ElementaryFiles.SUBSCRIPTION_ID);
151         }
152 
153         // Random order of all columns
154         String[] projection = Arrays.copyOf(
155                 SimPhonebookProvider.ELEMENTARY_FILES_ALL_COLUMNS,
156                 SimPhonebookProvider.ELEMENTARY_FILES_ALL_COLUMNS.length);
157         Collections.shuffle(Arrays.asList(projection));
158         try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, projection, null, null)) {
159             assertThat(cursor).hasColumnNames(projection);
160         }
161     }
162 
163     @Test
query_entityFiles_unrecognizedColumn_throwsIllegalArgumentException()164     public void query_entityFiles_unrecognizedColumn_throwsIllegalArgumentException() {
165         assertThrows(IllegalArgumentException.class, () ->
166                 mResolver.query(ElementaryFiles.CONTENT_URI, new String[]{"invalid_column"}, null,
167                         null));
168     }
169 
170     @Test
query_entityFiles_noSim_returnsEmptyCursor()171     public void query_entityFiles_noSim_returnsEmptyCursor() {
172         when(mMockSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
173                 ImmutableList.of());
174 
175         try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, null, null, null)) {
176             assertThat(cursor).hasCount(0);
177         }
178     }
179 
180     @Test
query_entityFiles_multiSim_returnsCursorWithRowForEachSimEf()181     public void query_entityFiles_multiSim_returnsCursorWithRowForEachSimEf() {
182         setupSimsWithSubscriptionIds(2, 3, 7);
183 
184         mIccPhoneBook.setupEfWithSizes(2, IccConstants.EF_ADN, 10, 25);
185         mIccPhoneBook.setupEfWithSizes(2, IccConstants.EF_FDN, 5, 20);
186         mIccPhoneBook.setupEfWithSizes(2, IccConstants.EF_SDN, 15, 20);
187         mIccPhoneBook.setupEfWithSizes(3, IccConstants.EF_ADN, 100, 30);
188         // These Will be omitted from results because zero size indicates the EF is not supported.
189         mIccPhoneBook.setupEfWithSizes(3, IccConstants.EF_FDN, 0, 0);
190         mIccPhoneBook.setupEfWithSizes(3, IccConstants.EF_SDN, 0, 0);
191         mIccPhoneBook.setupEfWithSizes(7, IccConstants.EF_ADN, 0, 0);
192         mIccPhoneBook.setupEfWithSizes(7, IccConstants.EF_FDN, 0, 0);
193         mIccPhoneBook.setupEfWithSizes(7, IccConstants.EF_SDN, 0, 0);
194 
195         String[] projection = {
196                 ElementaryFiles.SLOT_INDEX, ElementaryFiles.SUBSCRIPTION_ID,
197                 ElementaryFiles.EF_TYPE, ElementaryFiles.MAX_RECORDS,
198                 ElementaryFiles.NAME_MAX_LENGTH, ElementaryFiles.PHONE_NUMBER_MAX_LENGTH
199         };
200         try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, projection, null, null)) {
201             assertThat(cursor).hasColumnNames(projection);
202 
203             assertThat(cursor)
204                     .atRow(0).hasRowValues(0, 2, ElementaryFiles.EF_ADN, 10, 11, 20)
205                     .atRow(1).hasRowValues(0, 2, ElementaryFiles.EF_FDN, 5, 6, 20)
206                     .atRow(2).hasRowValues(0, 2, ElementaryFiles.EF_SDN, 15, 6, 20)
207                     .atRow(3).hasRowValues(1, 3, ElementaryFiles.EF_ADN, 100, 16, 20);
208         }
209     }
210 
211     @Test
query_entityFiles_simWithZeroSizes_returnsEmptyCursor()212     public void query_entityFiles_simWithZeroSizes_returnsEmptyCursor() {
213         setupSimsWithSubscriptionIds(1);
214 
215         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 0, 0);
216         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_FDN, 0, 0);
217         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_SDN, 0, 0);
218 
219         try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, null, null, null)) {
220             assertThat(cursor).hasCount(0);
221         }
222     }
223 
224     /**
225      * USIM cards support more than 255 records by having multiple files for one EF type but
226      * IIccPhoneBook.getAdnRecordsSizeForSubscriber returns the size for a single file and so is
227      * inaccurate for such SIMs.
228      *
229      * <p>See b/201385523#comment4 and b/201685690
230      */
231     @Test
query_entityFiles_adnRecordCountExceedsSize_returnsAdnRecordCountAsMaxRecords()232     public void query_entityFiles_adnRecordCountExceedsSize_returnsAdnRecordCountAsMaxRecords() {
233         setupSimsWithSubscriptionIds(1);
234 
235         // There are 400 records returned by getAdnRecordsInEfForSubscriber but the count returned
236         // by getAdnRecordsSizeForSubscriber is only 200.
237         AdnRecord[] records = mIccPhoneBook.createEmptyRecords(IccConstants.EF_ADN, 400);
238         mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 200, 20);
239         mIccPhoneBook.setRecords(1, IccConstants.EF_ADN, records);
240 
241         String[] projection = {
242                 ElementaryFiles.SUBSCRIPTION_ID, ElementaryFiles.EF_TYPE,
243                 ElementaryFiles.MAX_RECORDS
244         };
245         try (Cursor cursor = mResolver.query(
246                 ElementaryFiles.CONTENT_URI, projection, null, null)) {
247             assertThat(cursor).hasCount(1);
248             assertThat(cursor)
249                     .atRow(0).hasRowValues(1, ElementaryFiles.EF_ADN, 400);
250         }
251     }
252 
253     @Test
query_entityFilesItem_nullProjection_returnsCursorWithCorrectProjection()254     public void query_entityFilesItem_nullProjection_returnsCursorWithCorrectProjection() {
255         setupSimsWithSubscriptionIds(1);
256         mIccPhoneBook.makeAllEfsSupported(1);
257 
258         // Null projection
259         try (Cursor cursor = mResolver.query(ElementaryFiles.getItemUri(1, EF_ADN), null, null,
260                 null)) {
261             assertThat(Objects.requireNonNull(cursor).getColumnNames()).asList()
262                     .containsExactlyElementsIn(
263                             SimPhonebookProvider.ELEMENTARY_FILES_ALL_COLUMNS);
264         }
265     }
266 
267     @Test
query_elementaryFilesItem_nonExistentSubscriptionId_returnsEmptyCursor()268     public void query_elementaryFilesItem_nonExistentSubscriptionId_returnsEmptyCursor() {
269         setupSimsWithSubscriptionIds(1);
270         mIccPhoneBook.makeAllEfsSupported(1);
271 
272         // Subscription ID 2 does not exist
273         Uri nonExistentElementaryFileItemUri = ElementaryFiles.getItemUri(2, EF_ADN);
274 
275         try (Cursor cursor = mResolver.query(nonExistentElementaryFileItemUri, null, null, null)) {
276             assertThat(Objects.requireNonNull(cursor)).hasCount(0);
277         }
278     }
279 
280     @Test
query_adnRecords_returnsCursorWithMatchingProjection()281     public void query_adnRecords_returnsCursorWithMatchingProjection() {
282         setupSimsWithSubscriptionIds(1);
283         mIccPhoneBook.makeAllEfsSupported(1);
284         Uri contentAdn = SimRecords.getContentUri(1, EF_ADN);
285 
286         // Null projection
287         try (Cursor cursor = mResolver.query(contentAdn, null, null, null)) {
288             assertThat(Objects.requireNonNull(cursor).getColumnNames()).asList()
289                     .containsExactlyElementsIn(SimPhonebookProvider.SIM_RECORDS_ALL_COLUMNS);
290         }
291 
292         // Empty projection
293         try (Cursor cursor = mResolver.query(contentAdn, new String[0], null, null)) {
294             assertThat(cursor).hasColumnNames();
295         }
296 
297         // Single column
298         try (Cursor cursor = mResolver.query(contentAdn, new String[]{
299                 SimRecords.PHONE_NUMBER
300         }, null, null)) {
301             assertThat(cursor).hasColumnNames(SimRecords.PHONE_NUMBER);
302         }
303 
304         // Duplicate column
305         try (Cursor cursor = mResolver.query(contentAdn, new String[]{
306                 SimRecords.PHONE_NUMBER, SimRecords.PHONE_NUMBER
307         }, null, null)) {
308             assertThat(cursor).hasColumnNames(SimRecords.PHONE_NUMBER, SimRecords.PHONE_NUMBER);
309         }
310 
311         // Random order of all columns
312         String[] projection = Arrays.copyOf(
313                 SimPhonebookProvider.SIM_RECORDS_ALL_COLUMNS,
314                 SimPhonebookProvider.SIM_RECORDS_ALL_COLUMNS.length);
315         Collections.shuffle(Arrays.asList(projection));
316         try (Cursor cursor = mResolver.query(contentAdn, projection, null, null)) {
317             assertThat(cursor).hasColumnNames(projection);
318         }
319     }
320 
321     @Test
query_adnRecords_invalidColumnProjection_throwsIllegalArgumentException()322     public void query_adnRecords_invalidColumnProjection_throwsIllegalArgumentException() {
323         setupSimsWithSubscriptionIds(1);
324         mIccPhoneBook.makeAllEfsSupported(1);
325         Uri contentAdn = SimRecords.getContentUri(1, EF_ADN);
326 
327         assertThrows(IllegalArgumentException.class, () -> Closeables.close(
328                 mResolver.query(contentAdn, new String[] {
329                         "an_unsupported_column",
330                 }, null, null), false)
331         );
332 
333         assertThrows(IllegalArgumentException.class, () -> Closeables.close(
334                 mResolver.query(contentAdn, new String[] {
335                         SimRecords.RECORD_NUMBER,
336                         "an_unsupported_column"
337                 }, null, null), false)
338         );
339 
340         assertThrows(IllegalArgumentException.class, () -> Closeables.close(
341                 mResolver.query(contentAdn, new String[] {
342                         "an_unsupported_column",
343                         SimRecords.RECORD_NUMBER
344                 }, null, null), false)
345         );
346     }
347 
348     @Test
query_adnRecords_noRecords_returnsEmptyCursor()349     public void query_adnRecords_noRecords_returnsEmptyCursor() {
350         setupSimsWithSubscriptionIds(1);
351         mIccPhoneBook.makeAllEfsSupported(1);
352 
353         try (Cursor cursor = mResolver.query(SimRecords.getContentUri(1, EF_ADN), null, null,
354                 null)) {
355             assertThat(cursor).hasCount(0);
356         }
357     }
358 
359     @Test
query_simRecords_nullRecordList_returnsEmptyCursor()360     public void query_simRecords_nullRecordList_returnsEmptyCursor() throws Exception {
361         setupSimsWithSubscriptionIds(1);
362         mIccPhoneBook.makeAllEfsSupported(1);
363         // Use a mock so that a null list can be returned
364         IIccPhoneBook mockIccPhoneBook = mock(
365                 IIccPhoneBook.class, AdditionalAnswers.delegatesTo(mIccPhoneBook));
366         when(mockIccPhoneBook.getAdnRecordsInEfForSubscriber(anyInt(), anyInt())).thenReturn(null);
367         TestableSimPhonebookProvider.setup(mResolver, mMockSubscriptionManager, mockIccPhoneBook);
368 
369         try (Cursor adnCursor = mResolver.query(SimRecords.getContentUri(1, EF_ADN), null, null,
370                 null);
371              Cursor fdnCursor = mResolver.query(SimRecords.getContentUri(1, EF_FDN), null, null,
372                      null);
373              Cursor sdnCursor = mResolver.query(SimRecords.getContentUri(1, EF_SDN), null, null,
374                      null)
375         ) {
376             assertThat(adnCursor).hasCount(0);
377             assertThat(fdnCursor).hasCount(0);
378             assertThat(sdnCursor).hasCount(0);
379         }
380     }
381 
382     @Test
query_simRecords_singleSim_returnsDataForCorrectEf()383     public void query_simRecords_singleSim_returnsDataForCorrectEf() {
384         setupSimsWithSubscriptionIds(1);
385         mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Person Adn1", "8005550101");
386         mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Person Adn2", "8005550102");
387         mIccPhoneBook.addRecord(1, IccConstants.EF_FDN, "Person Fdn", "8005550103");
388         mIccPhoneBook.addRecord(1, IccConstants.EF_SDN, "Person Sdn", "8005550104");
389         mIccPhoneBook.setDefaultSubscriptionId(1);
390 
391         String[] projection = {
392                 SimRecords.SUBSCRIPTION_ID,
393                 SimRecords.ELEMENTARY_FILE_TYPE,
394                 SimRecords.RECORD_NUMBER,
395                 SimRecords.NAME,
396                 SimRecords.PHONE_NUMBER
397         };
398         try (Cursor adnCursor = mResolver.query(SimRecords.getContentUri(1, EF_ADN),
399                 projection, null, null);
400              Cursor fdnCursor = mResolver.query(SimRecords.getContentUri(1, EF_FDN),
401                      projection, null, null);
402              Cursor sdnCursor = mResolver.query(SimRecords.getContentUri(1, EF_SDN),
403                      projection, null, null)
404         ) {
405 
406             assertThat(adnCursor)
407                     .atRow(0).hasRowValues(1, ElementaryFiles.EF_ADN, 1, "Person Adn1",
408                     "8005550101")
409                     .atRow(1).hasRowValues(1, ElementaryFiles.EF_ADN, 2, "Person Adn2",
410                     "8005550102");
411             assertThat(fdnCursor)
412                     .atRow(0).hasRowValues(1, ElementaryFiles.EF_FDN, 1, "Person Fdn",
413                     "8005550103");
414             assertThat(sdnCursor)
415                     .atRow(0).hasRowValues(1, ElementaryFiles.EF_SDN, 1, "Person Sdn",
416                     "8005550104");
417         }
418     }
419 
420     @Test
query_adnRecords_returnsAdnData()421     public void query_adnRecords_returnsAdnData() {
422         setupSimsWithSubscriptionIds(1, 2, 4);
423         mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Person Sim1", "8005550101");
424         mIccPhoneBook.addRecord(1, IccConstants.EF_FDN, "Omitted Sim1", "8005550199");
425         mIccPhoneBook.addRecord(2, IccConstants.EF_ADN, "Person Sim2a", "8005550103");
426         mIccPhoneBook.addRecord(2, IccConstants.EF_ADN, "Person Sim2b", "8005550104");
427         mIccPhoneBook.addRecord(2, IccConstants.EF_ADN, "Person Sim2c", "8005550105");
428         mIccPhoneBook.addRecord(2, IccConstants.EF_SDN, "Omitted Sim2", "8005550198");
429         mIccPhoneBook.addRecord(4, IccConstants.EF_ADN, "Person Sim4", "8005550106");
430         mIccPhoneBook.setDefaultSubscriptionId(1);
431 
432         String[] projection = {
433                 SimRecords.SUBSCRIPTION_ID,
434                 SimRecords.ELEMENTARY_FILE_TYPE,
435                 SimRecords.RECORD_NUMBER,
436                 SimRecords.NAME,
437                 SimRecords.PHONE_NUMBER
438         };
439         try (Cursor cursorSim1 = mResolver.query(SimRecords.getContentUri(1, EF_ADN),
440                 projection, null, null);
441              Cursor cursorSim2 = mResolver.query(SimRecords.getContentUri(2, EF_ADN),
442                      projection, null, null);
443              Cursor cursorSim4 = mResolver.query(SimRecords.getContentUri(4, EF_ADN),
444                      projection, null, null)
445         ) {
446 
447             assertThat(cursorSim1).hasData(new Object[][]{
448                     {1, ElementaryFiles.EF_ADN, 1, "Person Sim1", "8005550101"},
449             });
450             assertThat(cursorSim2).hasData(new Object[][]{
451                     {2, ElementaryFiles.EF_ADN, 1, "Person Sim2a", "8005550103"},
452                     {2, ElementaryFiles.EF_ADN, 2, "Person Sim2b", "8005550104"},
453                     {2, ElementaryFiles.EF_ADN, 3, "Person Sim2c", "8005550105"},
454             });
455             assertThat(cursorSim4).hasData(new Object[][]{
456                     {4, ElementaryFiles.EF_ADN, 1, "Person Sim4", "8005550106"},
457             });
458         }
459     }
460 
461     @Test
query_fdnRecords_returnsFdnData()462     public void query_fdnRecords_returnsFdnData() {
463         setupSimsWithSubscriptionIds(1, 2, 4);
464         mIccPhoneBook.makeAllEfsSupported(1, 2, 4);
465         mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Person Sim1", "8005550101");
466         mIccPhoneBook.addRecord(2, IccConstants.EF_ADN, "Person Sim2a", "8005550103");
467         mIccPhoneBook.addRecord(2, IccConstants.EF_FDN, "Person Sim2b", "8005550104");
468         mIccPhoneBook.addRecord(2, IccConstants.EF_FDN, "Person Sim2c", "8005550105");
469         mIccPhoneBook.addRecord(4, IccConstants.EF_SDN, "Person Sim4", "8005550106");
470         mIccPhoneBook.setDefaultSubscriptionId(1);
471 
472         String[] projection = {
473                 SimRecords.SUBSCRIPTION_ID,
474                 SimRecords.ELEMENTARY_FILE_TYPE,
475                 SimRecords.RECORD_NUMBER,
476                 SimRecords.NAME,
477                 SimRecords.PHONE_NUMBER
478         };
479         try (Cursor cursorSim1Fdn = mResolver.query(SimRecords.getContentUri(1, EF_FDN),
480                 projection, null, null);
481              Cursor cursorSim2Fdn = mResolver.query(SimRecords.getContentUri(2, EF_FDN),
482                      projection, null, null);
483              Cursor cursorSim4Fdn = mResolver.query(SimRecords.getContentUri(4, EF_FDN),
484                      projection, null, null)
485         ) {
486 
487             assertThat(cursorSim1Fdn).hasCount(0);
488             assertThat(cursorSim2Fdn).hasData(new Object[][]{
489                     {2, ElementaryFiles.EF_FDN, 1, "Person Sim2b", "8005550104"},
490                     {2, ElementaryFiles.EF_FDN, 2, "Person Sim2c", "8005550105"},
491             });
492             assertThat(cursorSim4Fdn).hasCount(0);
493         }
494     }
495 
496     @Test
query_sdnRecords_returnsSdnData()497     public void query_sdnRecords_returnsSdnData() {
498         setupSimsWithSubscriptionIds(1, 2, 4);
499         mIccPhoneBook.makeAllEfsSupported(1, 2, 4);
500         mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Person Adn1", "8005550101");
501         mIccPhoneBook.addRecord(1, IccConstants.EF_FDN, "Person Fdn1", "8005550102");
502         mIccPhoneBook.addRecord(1, IccConstants.EF_SDN, "Person Sdn1", "8005550103");
503         mIccPhoneBook.addRecord(2, IccConstants.EF_ADN, "Person Adn2a", "8005550104");
504         mIccPhoneBook.addRecord(2, IccConstants.EF_FDN, "Person Fdn2b", "8005550105");
505         mIccPhoneBook.addRecord(4, IccConstants.EF_SDN, "Person Sdn4a", "8005550106");
506         mIccPhoneBook.addRecord(4, IccConstants.EF_SDN, "Person Sdn4b", "8005550107");
507         mIccPhoneBook.setDefaultSubscriptionId(1);
508 
509         String[] projection = {
510                 SimRecords.SUBSCRIPTION_ID,
511                 SimRecords.ELEMENTARY_FILE_TYPE,
512                 SimRecords.RECORD_NUMBER,
513                 SimRecords.NAME,
514                 SimRecords.PHONE_NUMBER
515         };
516         try (Cursor cursorSim1Sdn = mResolver.query(SimRecords.getContentUri(1, EF_SDN),
517                 projection, null, null);
518              Cursor cursorSim2Sdn = mResolver.query(SimRecords.getContentUri(2, EF_SDN),
519                      projection, null, null);
520              Cursor cursorSim4Sdn = mResolver.query(SimRecords.getContentUri(4, EF_SDN),
521                      projection, null, null)
522         ) {
523 
524             assertThat(cursorSim1Sdn)
525                     .atRow(0).hasRowValues(1, ElementaryFiles.EF_SDN, 1, "Person Sdn1",
526                     "8005550103");
527             assertThat(cursorSim2Sdn).hasCount(0);
528             assertThat(cursorSim4Sdn)
529                     .atRow(0).hasRowValues(4, ElementaryFiles.EF_SDN, 1, "Person Sdn4a",
530                     "8005550106")
531                     .atRow(1).hasRowValues(4, ElementaryFiles.EF_SDN, 2, "Person Sdn4b",
532                     "8005550107");
533         }
534     }
535 
536     @Test
query_adnRecords_nonExistentSim_throwsCorrectException()537     public void query_adnRecords_nonExistentSim_throwsCorrectException() {
538         setupSimsWithSubscriptionIds(1);
539 
540         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
541                 () -> mResolver.query(SimRecords.getContentUri(123, EF_ADN), null, null, null));
542         assertThat(e).hasMessageThat().isEqualTo("No active SIM with subscription ID 123");
543     }
544 
545     @Test
insert_nonExistentSim_throwsCorrectException()546     public void insert_nonExistentSim_throwsCorrectException() {
547         setupSimsWithSubscriptionIds(1);
548         ContentValues values = new ContentValues();
549         values.put(SimRecords.NAME, "Name");
550         values.put(SimRecords.PHONE_NUMBER, "123");
551 
552         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
553                 () -> mResolver.insert(SimRecords.getContentUri(123, EF_ADN), values));
554         assertThat(e).hasMessageThat().isEqualTo("No active SIM with subscription ID 123");
555     }
556 
557     @Test
update_nonExistentSim_throwsCorrectException()558     public void update_nonExistentSim_throwsCorrectException() {
559         setupSimsWithSubscriptionIds(1);
560         ContentValues values = new ContentValues();
561         values.put(SimRecords.NAME, "Name");
562         values.put(SimRecords.PHONE_NUMBER, "123");
563 
564         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
565                 () -> mResolver.update(SimRecords.getItemUri(123, EF_ADN, 1), values, null));
566         assertThat(e).hasMessageThat().isEqualTo("No active SIM with subscription ID 123");
567     }
568 
569     @Test
delete_nonExistentSim_throwsCorrectException()570     public void delete_nonExistentSim_throwsCorrectException() {
571         setupSimsWithSubscriptionIds(1);
572 
573         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
574                 () -> mResolver.delete(SimRecords.getItemUri(123, EF_ADN, 1), null));
575         assertThat(e).hasMessageThat().isEqualTo("No active SIM with subscription ID 123");
576     }
577 
578     @Test
query_adnRecords_zeroSizeEf_throwsCorrectException()579     public void query_adnRecords_zeroSizeEf_throwsCorrectException() {
580         setupSimsWithSubscriptionIds(1);
581         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 0, 0);
582 
583         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
584                 () -> mResolver.query(SimRecords.getContentUri(1, EF_ADN), null, null, null));
585         assertThat(e).hasMessageThat().isEqualTo(
586                 "adn is not supported for SIM with subscription ID 1");
587     }
588 
589     @Test
query_itemUri_returnsCorrectRow()590     public void query_itemUri_returnsCorrectRow() {
591         setupSimsWithSubscriptionIds(1, 2);
592         mIccPhoneBook.addRecord(1,
593                 new AdnRecord(IccConstants.EF_ADN, 1, "Name@Adn1[1]", "8005550101"));
594         mIccPhoneBook.addRecord(1,
595                 new AdnRecord(IccConstants.EF_ADN, 2, "Name@Adn1[2]", "8005550102"));
596         mIccPhoneBook.addRecord(1,
597                 new AdnRecord(IccConstants.EF_ADN, 3, "Name@Adn1[3]", "8005550103"));
598         mIccPhoneBook.addRecord(2,
599                 new AdnRecord(IccConstants.EF_ADN, 3, "Name@Adn2[3]", "8005550104"));
600         mIccPhoneBook.addRecord(1,
601                 new AdnRecord(IccConstants.EF_FDN, 1, "Name@Fdn1[1]", "8005550105"));
602         mIccPhoneBook.addRecord(2,
603                 new AdnRecord(IccConstants.EF_SDN, 1, "Name@Sdn2[1]", "8005550106"));
604 
605         String[] projection = {
606                 SimRecords.SUBSCRIPTION_ID, SimRecords.ELEMENTARY_FILE_TYPE,
607                 SimRecords.RECORD_NUMBER, SimRecords.NAME, SimRecords.PHONE_NUMBER
608         };
609         try (Cursor item1 = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
610                 projection, null, null);
611              Cursor item2 = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 3),
612                      projection, null, null);
613              Cursor item3 = mResolver.query(SimRecords.getItemUri(2, ElementaryFiles.EF_ADN, 3),
614                      projection, null, null);
615              Cursor item4 = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 1),
616                      projection, null, null);
617              Cursor item5 = mResolver.query(SimRecords.getItemUri(2, ElementaryFiles.EF_SDN, 1),
618                      projection, null, null)
619         ) {
620             assertThat(item1).hasSingleRow(1, ElementaryFiles.EF_ADN, 1, "Name@Adn1[1]",
621                     "8005550101");
622             assertThat(item2).hasSingleRow(1, ElementaryFiles.EF_ADN, 3, "Name@Adn1[3]",
623                     "8005550103");
624             assertThat(item3).hasSingleRow(2, ElementaryFiles.EF_ADN, 3, "Name@Adn2[3]",
625                     "8005550104");
626             assertThat(item4).hasSingleRow(1, ElementaryFiles.EF_FDN, 1, "Name@Fdn1[1]",
627                     "8005550105");
628             assertThat(item5).hasSingleRow(2, ElementaryFiles.EF_SDN, 1, "Name@Sdn2[1]",
629                     "8005550106");
630         }
631     }
632 
633     @Test
query_itemUriNullProjection_returnsCursorWithAllColumns()634     public void query_itemUriNullProjection_returnsCursorWithAllColumns() {
635         setupSimsWithSubscriptionIds(1);
636         mIccPhoneBook.makeAllEfsSupported(1);
637 
638         try (Cursor cursor = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
639                 null, null, null)
640         ) {
641             assertThat(Objects.requireNonNull(
642                     cursor).getColumnNames()).asList().containsExactlyElementsIn(
643                     SimPhonebookProvider.SIM_RECORDS_ALL_COLUMNS);
644         }
645     }
646 
647     @Test
query_itemUriEmptyRecord_returnsEmptyCursor()648     public void query_itemUriEmptyRecord_returnsEmptyCursor() {
649         setupSimsWithSubscriptionIds(1);
650         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 30);
651         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_FDN, 1, 30);
652         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_SDN, 1, 30);
653 
654         try (Cursor adnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
655                 null, null, null);
656              Cursor fdnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 1),
657                      null, null, null);
658              Cursor sdnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 1),
659                      null, null, null)
660         ) {
661             assertThat(adnItem).hasCount(0);
662             assertThat(fdnItem).hasCount(0);
663             assertThat(sdnItem).hasCount(0);
664         }
665     }
666 
667     @Test
query_itemUriIndexExceedsMax_returnsEmptyCursor()668     public void query_itemUriIndexExceedsMax_returnsEmptyCursor() {
669         setupSimsWithSubscriptionIds(1);
670         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 30);
671         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_FDN, 1, 30);
672         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_SDN, 1, 30);
673 
674         try (Cursor adnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2),
675                 null, null, null);
676              Cursor fdnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 2),
677                      null, null, null);
678              Cursor sdnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 2),
679                      null, null, null)
680         ) {
681             assertThat(adnItem).hasCount(0);
682             assertThat(fdnItem).hasCount(0);
683             assertThat(sdnItem).hasCount(0);
684         }
685     }
686 
687     @Test
query_invalidItemIndex_throwsIllegalArgumentException()688     public void query_invalidItemIndex_throwsIllegalArgumentException() {
689         setupSimsWithSubscriptionIds(1);
690         mIccPhoneBook.makeAllEfsSupported(1);
691 
692         assertThrows(IllegalArgumentException.class, () ->
693                 mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, -1),
694                         null, null, null));
695         assertThrows(IllegalArgumentException.class, () ->
696                 mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, -1),
697                         null, null, null));
698         assertThrows(IllegalArgumentException.class, () ->
699                 mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, -1),
700                         null, null, null));
701         assertThrows(IllegalArgumentException.class, () ->
702                 mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 0),
703                         null, null, null));
704         assertThrows(IllegalArgumentException.class, () ->
705                 mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 0),
706                         null, null, null));
707         assertThrows(IllegalArgumentException.class, () ->
708                 mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 0),
709                         null, null, null));
710     }
711 
712     @Test
insert_adnRecord_addsAdnRecordAndReturnsUriForNewRecord()713     public void insert_adnRecord_addsAdnRecordAndReturnsUriForNewRecord() {
714         setupSimsWithSubscriptionIds(1);
715         mIccPhoneBook.makeAllEfsSupported(1);
716 
717         ContentValues values = new ContentValues();
718         values.put(SimRecords.NAME, "First Last");
719         values.put(SimRecords.PHONE_NUMBER, "8005550101");
720 
721         Uri uri = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
722 
723         List<AdnRecord> records = mIccPhoneBook.getAdnRecordsInEfForSubscriber(
724                 1, IccConstants.EF_ADN).stream()
725                 .filter(((Predicate<AdnRecord>) AdnRecord::isEmpty).negate())
726                 .collect(Collectors.toList());
727 
728         assertThat(records)
729                 .comparingElementsUsing(ADN_RECORD_IS_EQUAL)
730                 .containsExactly(new AdnRecord(IccConstants.EF_ADN, 1, "First Last", "8005550101"));
731 
732         assertThat(uri).isEqualTo(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1));
733     }
734 
735     @Test
insert_adnRecordWithExistingRecords_returnsUriWithCorrectIndex()736     public void insert_adnRecordWithExistingRecords_returnsUriWithCorrectIndex() {
737         setupSimsWithSubscriptionIds(1);
738         mIccPhoneBook.setDefaultSubscriptionId(1);
739         mIccPhoneBook.addRecord(new AdnRecord(IccConstants.EF_ADN, 2, "Existing1", "8005550101"));
740         mIccPhoneBook.addRecord(new AdnRecord(IccConstants.EF_ADN, 3, "Existing2", "8005550102"));
741         mIccPhoneBook.addRecord(new AdnRecord(IccConstants.EF_ADN, 5, "Existing3", "8005550103"));
742 
743         ContentValues values = new ContentValues();
744         values.put(SimRecords.NAME, "New1");
745         values.put(SimRecords.PHONE_NUMBER, "8005550104");
746 
747         Uri insert1 = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
748         values.put(SimRecords.NAME, "New2");
749         values.put(SimRecords.PHONE_NUMBER, "8005550105");
750         Uri insert2 = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
751         values.put(SimRecords.NAME, "New3");
752         values.put(SimRecords.PHONE_NUMBER, "8005550106");
753         Uri insert3 = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
754 
755         assertThat(
756                 mIccPhoneBook.getAdnRecordsInEfForSubscriber(1, IccConstants.EF_ADN).subList(0, 6))
757                 .comparingElementsUsing(ADN_RECORD_IS_EQUAL)
758                 .containsExactly(
759                         new AdnRecord(IccConstants.EF_ADN, 1, "New1", "8005550104"),
760                         new AdnRecord(IccConstants.EF_ADN, 2, "Existing1", "8005550101"),
761                         new AdnRecord(IccConstants.EF_ADN, 3, "Existing2", "8005550102"),
762                         new AdnRecord(IccConstants.EF_ADN, 4, "New2", "8005550105"),
763                         new AdnRecord(IccConstants.EF_ADN, 5, "Existing3", "8005550103"),
764                         new AdnRecord(IccConstants.EF_ADN, 6, "New3", "8005550106"));
765         assertThat(insert1).isEqualTo(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1));
766         assertThat(insert2).isEqualTo(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 4));
767         assertThat(insert3).isEqualTo(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 6));
768     }
769 
770     @Test
insert_efFull_throwsCorrectException()771     public void insert_efFull_throwsCorrectException() {
772         setupSimsWithSubscriptionIds(1);
773         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 30);
774         mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Existing", "8005550101");
775 
776         ContentValues values = new ContentValues();
777         values.put(SimRecords.NAME, "New");
778         values.put(SimRecords.PHONE_NUMBER, "8005550102");
779 
780         Uri uri = SimRecords.getContentUri(1, EF_ADN);
781         IllegalStateException e = assertThrows(IllegalStateException.class,
782                 () -> mResolver.insert(uri, values));
783         assertThat(e).hasMessageThat().isEqualTo(
784                 uri + " is full. Please delete records to add new ones.");
785     }
786 
787     @Test
insert_nameWithNonGsmCharacters_addsAdnRecord()788     public void insert_nameWithNonGsmCharacters_addsAdnRecord() {
789         setupSimsWithSubscriptionIds(1);
790         mIccPhoneBook.makeAllEfsSupported(1);
791 
792         ContentValues values = new ContentValues();
793         String name = "abc日本" + EMOJI;
794         values.put(SimRecords.NAME, name);
795         values.put(SimRecords.PHONE_NUMBER, "8005550101");
796 
797         Uri uri = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
798 
799         List<AdnRecord> records = mIccPhoneBook.getAdnRecordsInEfForSubscriber(
800                 1, IccConstants.EF_ADN).stream()
801                 .filter(((Predicate<AdnRecord>) AdnRecord::isEmpty).negate())
802                 .collect(Collectors.toList());
803 
804         assertThat(records)
805                 .comparingElementsUsing(ADN_RECORD_IS_EQUAL)
806                 .containsExactly(new AdnRecord(IccConstants.EF_ADN, 1, name, "8005550101"));
807 
808         assertThat(uri).isEqualTo(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1));
809     }
810 
811     @Test
insert_nullValues_returnsNull()812     public void insert_nullValues_returnsNull() {
813         setupSimsWithSubscriptionIds(1);
814         mIccPhoneBook.makeAllEfsSupported(1);
815 
816         Uri result = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), null);
817 
818         assertThat(result).isNull();
819     }
820 
821     @Test
update_nullValues_returnsZero()822     public void update_nullValues_returnsZero() {
823         setupSimsWithSubscriptionIds(1);
824         mIccPhoneBook.makeAllEfsSupported(1);
825         mIccPhoneBook.addAdnRecord(1, "Name", "5550101");
826 
827         int result = mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1), null,
828                 null);
829 
830         assertThat(result).isEqualTo(0);
831     }
832 
833     @Test
insert_emptyValues_returnsNull()834     public void insert_emptyValues_returnsNull() {
835         setupSimsWithSubscriptionIds(1);
836         mIccPhoneBook.makeAllEfsSupported(1);
837 
838         Uri result = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), new ContentValues());
839 
840         assertThat(result).isNull();
841     }
842 
843     @Test
insert_nameOmitted_createsRecordWithJustPhoneNumber()844     public void insert_nameOmitted_createsRecordWithJustPhoneNumber() {
845         setupSimsWithSubscriptionIds(1);
846         mIccPhoneBook.makeAllEfsSupported(1);
847 
848         ContentValues values = new ContentValues();
849         // No name
850         values.put(SimRecords.PHONE_NUMBER, "18005550101");
851 
852         mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
853 
854         // Null name
855         values.putNull(SimRecords.NAME);
856         values.put(SimRecords.PHONE_NUMBER, "18005550102");
857         mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
858 
859         // Empty name
860         values.put(SimRecords.NAME, "");
861         values.put(SimRecords.PHONE_NUMBER, "18005550103");
862         mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
863 
864         assertThat(mIccPhoneBook.getAllValidRecords())
865                 .comparingElementsUsing(ADN_RECORD_IS_EQUAL)
866                 .containsExactly(
867                         new AdnRecord(IccConstants.EF_ADN, 1, "", "18005550101"),
868                         new AdnRecord(IccConstants.EF_ADN, 2, "", "18005550102"),
869                         new AdnRecord(IccConstants.EF_ADN, 3, "", "18005550103"));
870     }
871 
872     @Test
insert_phoneNumberOmitted_throwsCorrectException()873     public void insert_phoneNumberOmitted_throwsCorrectException() {
874         setupSimsWithSubscriptionIds(1);
875         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 25);
876 
877         ContentValues values = new ContentValues();
878         values.put(SimRecords.NAME, "Name");
879 
880         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
881                 () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
882         assertThat(e).hasMessageThat().isEqualTo(SimRecords.PHONE_NUMBER + " is required.");
883     }
884 
885     @Test
insert_nameTooLong_throwsCorrectException()886     public void insert_nameTooLong_throwsCorrectException() {
887         setupSimsWithSubscriptionIds(1);
888         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 25);
889 
890         ContentValues values = new ContentValues();
891         // Name is limited to 11 characters when the max record size is 25
892         values.put(SimRecords.NAME, "1234567890ab");
893         values.put(SimRecords.PHONE_NUMBER, "8005550102");
894 
895         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
896                 () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
897 
898         assertThat(e).hasMessageThat().isEqualTo(SimRecords.NAME + " is too long.");
899 
900         // 2 bytes per character and 4 for the emoji. So this is 14 characters long.
901         values.put(SimRecords.NAME, "abc日本" + EMOJI);
902         e = assertThrows(IllegalArgumentException.class,
903                 () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
904 
905         assertThat(e).hasMessageThat().isEqualTo(SimRecords.NAME + " is too long.");
906     }
907 
908     @Test
insert_phoneNumberTooLong_throwsCorrectException()909     public void insert_phoneNumberTooLong_throwsCorrectException() {
910         setupSimsWithSubscriptionIds(1);
911         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 25);
912 
913         ContentValues values = new ContentValues();
914         values.put(SimRecords.NAME, "Name");
915         // 21 digits is longer than max of 20
916         values.put(SimRecords.PHONE_NUMBER, "123456789012345678901");
917 
918         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
919                 () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
920 
921         assertThat(e).hasMessageThat().isEqualTo(SimRecords.PHONE_NUMBER + " is too long.");
922     }
923 
924     @Test
insert_numberWithInvalidCharacters_throwsCorrectException()925     public void insert_numberWithInvalidCharacters_throwsCorrectException() {
926         setupSimsWithSubscriptionIds(1);
927         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 32);
928 
929         ContentValues values = new ContentValues();
930         values.put(SimRecords.NAME, "Name");
931         values.put(SimRecords.PHONE_NUMBER, "(800)555-0190 x7777");
932 
933         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
934                 () -> mResolver.insert(SimRecords.getContentUri(1, ElementaryFiles.EF_ADN),
935                         values,
936                         null));
937         assertThat(e).hasMessageThat().isEqualTo(
938                 SimRecords.PHONE_NUMBER + " contains unsupported characters.");
939 
940         // The insert didn't actually change the data.
941         assertThat(mIccPhoneBook.getAllValidRecords()).isEmpty();
942     }
943 
944     @Test
insert_unsupportedColumn_throwsCorrectException()945     public void insert_unsupportedColumn_throwsCorrectException() {
946         setupSimsWithSubscriptionIds(1);
947         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 25);
948 
949         ContentValues values = new ContentValues();
950         values.put(SimRecords.NAME, "Name");
951         values.put(SimRecords.PHONE_NUMBER, "18005550101");
952         values.put(SimRecords.RECORD_NUMBER, 8);
953         values.put("extra_phone2", "18005550102");
954 
955         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
956                 () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
957         assertThat(e).hasMessageThat().isEqualTo("Unsupported columns: "
958                 + SimRecords.RECORD_NUMBER + ",extra_phone2");
959     }
960 
961     @Test
update_existingRecord_updatesRecord()962     public void update_existingRecord_updatesRecord() {
963         setupSimsWithSubscriptionIds(1, 2);
964         AdnRecord[] unchanged = new AdnRecord[]{
965                 new AdnRecord(IccConstants.EF_ADN, 3, "Other1", "8005550102"),
966                 new AdnRecord(IccConstants.EF_ADN, 2, "Other2", "8005550103"),
967                 new AdnRecord(IccConstants.EF_FDN, 2, "Other3", "8005550104")
968         };
969         mIccPhoneBook.addRecord(1, unchanged[0]);
970         mIccPhoneBook.addRecord(2, unchanged[1]);
971         mIccPhoneBook.addRecord(2, unchanged[2]);
972         mIccPhoneBook.addRecord(1,
973                 new AdnRecord(IccConstants.EF_ADN, 2, "Initial Name", "8005550101"));
974 
975         ContentValues values = new ContentValues();
976         values.put(SimRecords.NAME, "Updated Name");
977         values.put(SimRecords.PHONE_NUMBER, "8005550105");
978 
979         int result = mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2), values,
980                 null);
981 
982         assertThat(result).isEqualTo(1);
983 
984         List<AdnRecord> finalRecords = mIccPhoneBook.getAllValidRecords();
985 
986         assertThat(finalRecords).comparingElementsUsing(ADN_RECORD_IS_EQUAL)
987                 .containsAtLeastElementsIn(unchanged);
988         assertThat(finalRecords).comparingElementsUsing(ADN_RECORD_IS_EQUAL)
989                 .doesNotContain(
990                         new AdnRecord(IccConstants.EF_ADN, 2, "Initial Name", "80005550101"));
991         assertThat(finalRecords).comparingElementsUsing(ADN_RECORD_IS_EQUAL)
992                 .contains(new AdnRecord(IccConstants.EF_ADN, 2, "Updated Name", "8005550105"));
993     }
994 
995     @Test
update_emptyRecord_updatesRecord()996     public void update_emptyRecord_updatesRecord() {
997         setupSimsWithSubscriptionIds(1);
998         mIccPhoneBook.makeAllEfsSupported(1);
999 
1000         ContentValues values = new ContentValues();
1001         values.put(SimRecords.NAME, "name");
1002         values.put(SimRecords.PHONE_NUMBER, "18005550101");
1003         // No record actually exists with record number 10 but we allow clients to update it
1004         // as a way to set the information at a specific record number.
1005         int result = mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 10),
1006                 values, null);
1007 
1008         assertThat(result).isEqualTo(1);
1009         List<AdnRecord> finalRecords = mIccPhoneBook.getAllValidRecords();
1010         assertThat(finalRecords).comparingElementsUsing(ADN_RECORD_IS_EQUAL)
1011                 .containsExactly(new AdnRecord(IccConstants.EF_ADN, 10, "name", "18005550101"));
1012     }
1013 
1014     @Test
delete_existingRecord_deletesRecord()1015     public void delete_existingRecord_deletesRecord() {
1016         setupSimsWithSubscriptionIds(1, 2);
1017         AdnRecord[] unchanged = new AdnRecord[]{
1018                 new AdnRecord(IccConstants.EF_ADN, 3, "Other1", "8005550102"),
1019                 new AdnRecord(IccConstants.EF_ADN, 2, "Other2", "8005550103"),
1020                 new AdnRecord(IccConstants.EF_FDN, 2, "Other3", "8005550104")
1021         };
1022         mIccPhoneBook.addRecord(1,
1023                 new AdnRecord(IccConstants.EF_ADN, 2, "Initial Name", "8005550101"));
1024         mIccPhoneBook.addRecord(1, unchanged[0]);
1025         mIccPhoneBook.addRecord(2, unchanged[1]);
1026         mIccPhoneBook.addRecord(2, unchanged[2]);
1027 
1028         int result = mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2), null);
1029 
1030         assertThat(result).isEqualTo(1);
1031 
1032         assertThat(mIccPhoneBook.getAllValidRecords()).comparingElementsUsing(ADN_RECORD_IS_EQUAL)
1033                 .containsExactlyElementsIn(unchanged);
1034     }
1035 
1036     @Test
update_indexExceedingMax_returnsZero()1037     public void update_indexExceedingMax_returnsZero() {
1038         setupSimsWithSubscriptionIds(1);
1039         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 30);
1040 
1041         ContentValues values = new ContentValues();
1042         values.put(SimRecords.NAME, "name");
1043         values.put(SimRecords.PHONE_NUMBER, "18005551010");
1044         int result = mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2),
1045                 values, null);
1046 
1047         assertThat(result).isEqualTo(0);
1048     }
1049 
1050     @Test
update_indexOverflow_throwsIllegalArgumentException()1051     public void update_indexOverflow_throwsIllegalArgumentException() {
1052         setupSimsWithSubscriptionIds(1);
1053         mIccPhoneBook.makeAllEfsSupported(1);
1054 
1055         ContentValues values = new ContentValues();
1056         values.put(SimRecords.NAME, "name");
1057         values.put(SimRecords.PHONE_NUMBER, "18005551010");
1058         assertThrows(IllegalArgumentException.class, () -> mResolver.update(
1059                 SimRecords.getContentUri(1, EF_ADN).buildUpon().appendPath(
1060                         String.valueOf((Long.MAX_VALUE))).build(),
1061                 values, null));
1062     }
1063 
1064     @Test
delete_emptyRecord_returnsZero()1065     public void delete_emptyRecord_returnsZero() {
1066         setupSimsWithSubscriptionIds(1);
1067         mIccPhoneBook.makeAllEfsSupported(1);
1068 
1069         int result = mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2), null);
1070 
1071         assertThat(result).isEqualTo(0);
1072     }
1073 
1074     @Test
delete_indexExceedingMax_returnsZero()1075     public void delete_indexExceedingMax_returnsZero() {
1076         setupSimsWithSubscriptionIds(1);
1077         mIccPhoneBook.makeAllEfsSupported(1);
1078         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 30);
1079 
1080         int result = mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2), null);
1081 
1082         assertThat(result).isEqualTo(0);
1083     }
1084 
1085     @Test
delete_indexOverflow_throwsIllegalArgumentException()1086     public void delete_indexOverflow_throwsIllegalArgumentException() {
1087         setupSimsWithSubscriptionIds(1);
1088         mIccPhoneBook.makeAllEfsSupported(1);
1089 
1090         assertThrows(IllegalArgumentException.class, () -> mResolver.delete(
1091                 SimRecords.getContentUri(1, EF_ADN).buildUpon().appendPath(
1092                         String.valueOf((Long.MAX_VALUE))).build(),
1093                 null));
1094     }
1095 
1096     @Test
update_nameOrNumberTooLong_throwsCorrectException()1097     public void update_nameOrNumberTooLong_throwsCorrectException() {
1098         setupSimsWithSubscriptionIds(1);
1099         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 25);
1100         mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Initial", "8005550101");
1101 
1102         ContentValues values = new ContentValues();
1103         // Name is limited to 11 characters
1104         values.put(SimRecords.NAME, "1234567890ab");
1105         values.put(SimRecords.PHONE_NUMBER, "8005550102");
1106 
1107         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
1108                 () -> mResolver.update(SimRecords.getItemUri(
1109                         1, ElementaryFiles.EF_ADN, 1), values, null));
1110         assertThat(e).hasMessageThat().isEqualTo(SimRecords.NAME + " is too long.");
1111 
1112         values.put(SimRecords.NAME, "abc");
1113         values.put(SimRecords.PHONE_NUMBER, "123456789012345678901");
1114 
1115         e = assertThrows(IllegalArgumentException.class,
1116                 () -> mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
1117                         values,
1118                         null));
1119         assertThat(e).hasMessageThat().isEqualTo(SimRecords.PHONE_NUMBER + " is too long.");
1120         // The updates didn't actually change the data
1121         assertThat(mIccPhoneBook.getAllValidRecords())
1122                 .comparingElementsUsing(Correspondence.from(AdnRecord::isEqual, "isEqual"))
1123                 .containsExactly(new AdnRecord(IccConstants.EF_ADN, 1, "Initial", "8005550101"));
1124     }
1125 
1126     @Test
update_numberWithInvalidCharacters_throwsCorrectException()1127     public void update_numberWithInvalidCharacters_throwsCorrectException() {
1128         setupSimsWithSubscriptionIds(1);
1129         mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 32);
1130         mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Initial", "8005550101");
1131 
1132         ContentValues values = new ContentValues();
1133         values.put(SimRecords.NAME, "Name");
1134         values.put(SimRecords.PHONE_NUMBER, "(800)555-0190 x7777");
1135 
1136         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
1137                 () -> mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
1138                         values,
1139                         null));
1140         assertThat(e).hasMessageThat().isEqualTo(
1141                 SimRecords.PHONE_NUMBER + " contains unsupported characters.");
1142 
1143         // The update didn't actually change the data.
1144         assertThat(mIccPhoneBook.getAllValidRecords())
1145                 .comparingElementsUsing(Correspondence.from(AdnRecord::isEqual, "isEqual"))
1146                 .containsExactly(new AdnRecord(IccConstants.EF_ADN, 1, "Initial", "8005550101"));
1147     }
1148 
1149     @Test
insert_nonAdnDirUris_throwsUnsupportedOperationException()1150     public void insert_nonAdnDirUris_throwsUnsupportedOperationException() {
1151         setupSimsWithSubscriptionIds(1);
1152         mIccPhoneBook.makeAllEfsSupported(1);
1153 
1154         ContentValues values = new ContentValues();
1155         values.put(SimRecords.NAME, "Name");
1156         values.put(SimRecords.PHONE_NUMBER, "8005550101");
1157 
1158         assertThrows(UnsupportedOperationException.class, () ->
1159                 mResolver.insert(ElementaryFiles.CONTENT_URI, values));
1160         assertThrows(UnsupportedOperationException.class,
1161                 () -> mResolver.insert(SimRecords.getContentUri(1, EF_FDN), values));
1162         assertThrows(UnsupportedOperationException.class,
1163                 () -> mResolver.insert(SimRecords.getContentUri(1, EF_SDN), values));
1164         assertThrows(UnsupportedOperationException.class, () ->
1165                 mResolver.insert(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 1), values));
1166         assertThrows(UnsupportedOperationException.class, () ->
1167                 mResolver.insert(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 1), values));
1168     }
1169 
1170     @Test
update_nonAdnDirUris_throwsUnsupportedOperationException()1171     public void update_nonAdnDirUris_throwsUnsupportedOperationException() {
1172         setupSimsWithSubscriptionIds(1);
1173         mIccPhoneBook.makeAllEfsSupported(1);
1174 
1175         ContentValues values = new ContentValues();
1176         values.put(SimRecords.NAME, "Name");
1177         values.put(SimRecords.PHONE_NUMBER, "8005550101");
1178 
1179         assertThrows(UnsupportedOperationException.class, () ->
1180                 mResolver.update(ElementaryFiles.CONTENT_URI, values, null));
1181         assertThrows(UnsupportedOperationException.class,
1182                 () -> mResolver.update(SimRecords.getContentUri(1, EF_FDN), values, null));
1183         assertThrows(UnsupportedOperationException.class,
1184                 () -> mResolver.update(SimRecords.getContentUri(1, EF_SDN), values, null));
1185         assertThrows(UnsupportedOperationException.class,
1186                 () -> mResolver.update(SimRecords.getContentUri(1, EF_SDN), values, null));
1187         assertThrows(UnsupportedOperationException.class, () ->
1188                 mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 1), values,
1189                         null));
1190         assertThrows(UnsupportedOperationException.class, () ->
1191                 mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 1), values,
1192                         null));
1193     }
1194 
1195     @Test
delete_nonAdnDirUris_throwsUnsupportedOperationException()1196     public void delete_nonAdnDirUris_throwsUnsupportedOperationException() {
1197         setupSimsWithSubscriptionIds(1);
1198         mIccPhoneBook.makeAllEfsSupported(1);
1199 
1200         ContentValues values = new ContentValues();
1201         values.put(SimRecords.NAME, "Name");
1202         values.put(SimRecords.PHONE_NUMBER, "8005550101");
1203 
1204         assertThrows(UnsupportedOperationException.class, () ->
1205                 mResolver.delete(ElementaryFiles.CONTENT_URI, null));
1206         assertThrows(UnsupportedOperationException.class,
1207                 () -> mResolver.delete(SimRecords.getContentUri(1, EF_FDN), null));
1208         assertThrows(UnsupportedOperationException.class,
1209                 () -> mResolver.delete(SimRecords.getContentUri(1, EF_SDN), null));
1210         assertThrows(UnsupportedOperationException.class,
1211                 () -> mResolver.delete(SimRecords.getContentUri(1, EF_SDN), null));
1212         assertThrows(UnsupportedOperationException.class, () ->
1213                 mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 1), null));
1214         assertThrows(UnsupportedOperationException.class, () ->
1215                 mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 1), null));
1216     }
1217 
1218     @Test
subscriptionsChange_callsNotifyChange()1219     public void subscriptionsChange_callsNotifyChange() {
1220         // Clear invocations that happened in setUp
1221         Mockito.reset(mMockSubscriptionManager);
1222         // Stubbing this prevents the spied instance from calling the listener when it is added
1223         // which may cause flakiness.
1224         doNothing().when(mMockSubscriptionManager)
1225                 .addOnSubscriptionsChangedListener(any(), any());
1226         setupSimsWithSubscriptionIds(1);
1227         mIccPhoneBook.makeAllEfsSupported(1);
1228         SimPhonebookProvider.ContentNotifier mockNotifier = mock(
1229                 SimPhonebookProvider.ContentNotifier.class);
1230         ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener> listenerCaptor =
1231                 ArgumentCaptor.forClass(SubscriptionManager.OnSubscriptionsChangedListener.class);
1232 
1233         TestableSimPhonebookProvider.setup(
1234                 mResolver, mMockSubscriptionManager, mIccPhoneBook, mockNotifier);
1235         verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
1236                 any(), listenerCaptor.capture());
1237 
1238         // Fake the initial call that is made by SubscriptionManager when a listener is registered
1239         // with addOnSubscriptionsChangedListener
1240         listenerCaptor.getValue().onSubscriptionsChanged();
1241 
1242         // First subscription change
1243         setupSimsWithSubscriptionIds(1, 2);
1244         listenerCaptor.getValue().onSubscriptionsChanged();
1245 
1246         // Second subscription change
1247         setupSimsWithSubscriptionIds(1);
1248         listenerCaptor.getValue().onSubscriptionsChanged();
1249 
1250         // Listener is called but subscriptions didn't change so this won't notify
1251         listenerCaptor.getValue().onSubscriptionsChanged();
1252 
1253         verify(mockNotifier, times(2)).notifyChange(eq(SimPhonebookContract.AUTHORITY_URI));
1254     }
1255 
1256     @Test
insert_callsNotifyChange()1257     public void insert_callsNotifyChange() {
1258         // Clear invocations that happened in setUp
1259         Mockito.reset(mMockSubscriptionManager);
1260         setupSimsWithSubscriptionIds(1);
1261         mIccPhoneBook.makeAllEfsSupported(1);
1262         SimPhonebookProvider.ContentNotifier mockNotifier = mock(
1263                 SimPhonebookProvider.ContentNotifier.class);
1264 
1265         TestableSimPhonebookProvider.setup(
1266                 mResolver, mMockSubscriptionManager, mIccPhoneBook, mockNotifier);
1267 
1268         ContentValues values = new ContentValues();
1269         values.put(SimRecords.NAME, "name");
1270         values.put(SimRecords.PHONE_NUMBER, "5550101");
1271         mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
1272 
1273         verify(mockNotifier).notifyChange(eq(SimPhonebookContract.AUTHORITY_URI));
1274     }
1275 
1276     @Test
update_callsNotifyChange()1277     public void update_callsNotifyChange() {
1278         // Clear invocations that happened in setUp
1279         Mockito.reset(mMockSubscriptionManager);
1280         setupSimsWithSubscriptionIds(1);
1281         mIccPhoneBook.addAdnRecord(1, "Initial", "5550101");
1282         SimPhonebookProvider.ContentNotifier mockNotifier = mock(
1283                 SimPhonebookProvider.ContentNotifier.class);
1284 
1285         TestableSimPhonebookProvider.setup(
1286                 mResolver, mMockSubscriptionManager, mIccPhoneBook, mockNotifier);
1287 
1288         ContentValues values = new ContentValues();
1289         values.put(SimRecords.NAME, "Updated");
1290         values.put(SimRecords.PHONE_NUMBER, "5550102");
1291         mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1), values, null);
1292 
1293         verify(mockNotifier).notifyChange(eq(SimPhonebookContract.AUTHORITY_URI));
1294     }
1295 
1296     @Test
delete_callsNotifyChange()1297     public void delete_callsNotifyChange() {
1298         // Clear invocations that happened in setUp
1299         Mockito.reset(mMockSubscriptionManager);
1300         setupSimsWithSubscriptionIds(1);
1301         mIccPhoneBook.addAdnRecord(1, "Initial", "5550101");
1302         SimPhonebookProvider.ContentNotifier mockNotifier = mock(
1303                 SimPhonebookProvider.ContentNotifier.class);
1304 
1305         TestableSimPhonebookProvider.setup(
1306                 mResolver, mMockSubscriptionManager, mIccPhoneBook, mockNotifier);
1307 
1308         mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1), null);
1309 
1310         verify(mockNotifier).notifyChange(eq(SimPhonebookContract.AUTHORITY_URI));
1311     }
1312 
1313     @Test
getEncodedNameLength_returnsValueIsCorrect()1314     public void getEncodedNameLength_returnsValueIsCorrect() {
1315         String name = "";
1316         int length = SimRecords.getEncodedNameLength(mResolver, name);
1317         assertThat(length).isEqualTo(0);
1318 
1319         name = "First Last";
1320         length = SimRecords.getEncodedNameLength(mResolver, name);
1321         assertThat(length).isEqualTo(name.length());
1322 
1323         name = "日本";
1324         length = SimRecords.getEncodedNameLength(mResolver, name);
1325         assertThat(length).isEqualTo(name.length() * 2 + 1);
1326 
1327         name = EMOJI;
1328         length = SimRecords.getEncodedNameLength(mResolver, name);
1329         assertThat(length).isEqualTo(name.length() * 2 + 1);
1330 
1331         name = "abc日本" + EMOJI;
1332         length = SimRecords.getEncodedNameLength(mResolver, name);
1333         assertThat(length).isEqualTo(name.length() * 2 + 1);
1334     }
1335 
setupSimsWithSubscriptionIds(int... subscriptionIds)1336     private void setupSimsWithSubscriptionIds(int... subscriptionIds) {
1337         when(mMockSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(subscriptionIds);
1338         when(mMockSubscriptionManager.getActiveSubscriptionInfoCount())
1339                 .thenReturn(subscriptionIds.length);
1340         List<SubscriptionInfo> subscriptions = createSubscriptionsWithIds(subscriptionIds);
1341         when(mMockSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(subscriptions);
1342         for (SubscriptionInfo info : subscriptions) {
1343             when(mMockSubscriptionManager.getActiveSubscriptionInfo(info.getSubscriptionId()))
1344                     .thenReturn(info);
1345         }
1346     }
1347 
1348     public static class FakeIccPhoneBook extends IIccPhoneBook.Default {
1349 
1350         private static final int DEFAULT_RECORD_SIZE = 30;
1351         private static final int DEFAULT_RECORDS_COUNT = 100;
1352 
1353         // The key for both maps is the (subscription ID, efid)
1354         private Map<Pair<Integer, Integer>, AdnRecord[]> mRecords = new HashMap<>();
1355         // The value is the single record size
1356         private Map<Pair<Integer, Integer>, int[]> mRecordSizes = new HashMap<>();
1357 
1358         private int mDefaultSubscriptionId = 101;
1359 
addRecord(Pair<Integer, Integer> key, AdnRecord record)1360         private void addRecord(Pair<Integer, Integer> key, AdnRecord record) {
1361             // Assume that if records are being added then the test wants it to be a valid
1362             // elementary file so set sizes as well.
1363             if (!mRecordSizes.containsKey(key)) {
1364                 setupEfWithSizes(key.first, key.second,
1365                         Math.max(record.getRecId(), DEFAULT_RECORDS_COUNT), DEFAULT_RECORD_SIZE);
1366             }
1367             mRecords.get(key)[record.getRecId() - 1] = record;
1368         }
1369 
addRecord(AdnRecord record)1370         public void addRecord(AdnRecord record) {
1371             addRecord(Pair.create(mDefaultSubscriptionId, record.getEfid()), record);
1372         }
1373 
addRecord(int subscriptionId, AdnRecord record)1374         public void addRecord(int subscriptionId, AdnRecord record) {
1375             addRecord(Pair.create(subscriptionId, record.getEfid()), record);
1376         }
1377 
addRecord(int subscriptionId, int efId, String name, String phoneNumber)1378         public void addRecord(int subscriptionId, int efId, String name, String phoneNumber) {
1379             Pair<Integer, Integer> key = Pair.create(subscriptionId, efId);
1380             AdnRecord[] records = mRecords.computeIfAbsent(key, unused ->
1381                     createEmptyRecords(efId, 100));
1382             int recordIndex = -1;
1383             for (int i = 0; i < records.length; i++) {
1384                 if (records[i].isEmpty()) {
1385                     recordIndex = i;
1386                     break;
1387                 }
1388             }
1389             if (recordIndex == -1) {
1390                 throw new IllegalStateException("");
1391             }
1392             addRecord(key, new AdnRecord(efId, recordIndex + 1, name, phoneNumber));
1393         }
1394 
addAdnRecord(int subscriptionId, String name, String phoneNumber)1395         public void addAdnRecord(int subscriptionId, String name, String phoneNumber) {
1396             addRecord(subscriptionId, IccConstants.EF_ADN, name, phoneNumber);
1397         }
1398 
addAdnRecord(String name, String phoneNumber)1399         public void addAdnRecord(String name, String phoneNumber) {
1400             addRecord(mDefaultSubscriptionId, IccConstants.EF_ADN, name, phoneNumber);
1401         }
1402 
getAllValidRecords()1403         public List<AdnRecord> getAllValidRecords() {
1404             List<AdnRecord> result = new ArrayList<>();
1405             for (AdnRecord[] records : mRecords.values()) {
1406                 for (AdnRecord record : records) {
1407                     if (!record.isEmpty()) {
1408                         result.add(record);
1409                     }
1410                 }
1411             }
1412             return result;
1413         }
1414 
makeAllEfsSupported()1415         public void makeAllEfsSupported() {
1416             makeAllEfsSupported(mDefaultSubscriptionId);
1417         }
1418 
1419         /**
1420          * Sets up the fake to return valid records size for all elementary files for the provided
1421          * subscription IDs.
1422          */
makeAllEfsSupported(int... subscriptionIds)1423         public void makeAllEfsSupported(int... subscriptionIds) {
1424             for (int subId : subscriptionIds) {
1425                 makeAllEfsSupported(subId);
1426             }
1427         }
1428 
1429         /**
1430          * Sets up the fake to return valid records size for all elementary files for the provided
1431          * subscription IDs.
1432          */
makeAllEfsSupported(int subscriptionId)1433         public void makeAllEfsSupported(int subscriptionId) {
1434             setupEfWithSizes(subscriptionId, IccConstants.EF_ADN, DEFAULT_RECORDS_COUNT,
1435                     DEFAULT_RECORD_SIZE);
1436             setupEfWithSizes(subscriptionId, IccConstants.EF_FDN, DEFAULT_RECORDS_COUNT,
1437                     DEFAULT_RECORD_SIZE);
1438             setupEfWithSizes(subscriptionId, IccConstants.EF_SDN, DEFAULT_RECORDS_COUNT,
1439                     DEFAULT_RECORD_SIZE);
1440         }
1441 
setRecords(int subscriptionId, int efid, AdnRecord[] records)1442         public void setRecords(int subscriptionId, int efid, AdnRecord[] records) {
1443             mRecords.put(Pair.create(subscriptionId, efid), records);
1444         }
1445 
setRecordsSize(int subscriptionId, int efid, int maxRecordCount, int maxRecordSize)1446         public void setRecordsSize(int subscriptionId, int efid, int maxRecordCount,
1447                 int maxRecordSize) {
1448             setRecordsSize(Pair.create(subscriptionId, efid), maxRecordCount, maxRecordSize);
1449         }
1450 
setRecordsSize(Pair<Integer, Integer> key, int maxRecordCount, int maxRecordSize)1451         private void setRecordsSize(Pair<Integer, Integer> key, int maxRecordCount,
1452                 int maxRecordSize) {
1453             int[] sizes = { maxRecordSize, maxRecordSize * maxRecordCount, maxRecordCount };
1454             mRecordSizes.put(key, sizes);
1455         }
1456 
setupEfWithSizes(int subscriptionId, int efid, int maxRecordCount, int maxRecordSize)1457         public void setupEfWithSizes(int subscriptionId, int efid, int maxRecordCount,
1458                 int maxRecordSize) {
1459             Pair<Integer, Integer> key = Pair.create(subscriptionId, efid);
1460             setRecordsSize(key, maxRecordCount, maxRecordSize);
1461             AdnRecord[] records = mRecords.computeIfAbsent(key, unused ->
1462                     createEmptyRecords(efid, maxRecordCount));
1463             if (records.length < maxRecordCount) {
1464                 throw new IllegalStateException("Records already initialized with a smaller size");
1465             }
1466         }
1467 
createEmptyRecords(int efid, int count)1468         AdnRecord[] createEmptyRecords(int efid, int count) {
1469             AdnRecord[] records = new AdnRecord[count];
1470             for (int i = 0; i < records.length; i++) {
1471                 if (records[i] == null) {
1472                     records[i] = new AdnRecord(efid, i + 1, "", "");
1473                 }
1474             }
1475             return records;
1476         }
1477 
setDefaultSubscriptionId(int defaultSubscriptionId)1478         public void setDefaultSubscriptionId(int defaultSubscriptionId) {
1479             mDefaultSubscriptionId = defaultSubscriptionId;
1480         }
1481 
reset()1482         public void reset() {
1483             mRecords.clear();
1484             mRecordSizes.clear();
1485         }
1486 
1487         @Override
getAdnRecordsInEf(int efid)1488         public List<AdnRecord> getAdnRecordsInEf(int efid) {
1489             return getAdnRecordsInEfForSubscriber(mDefaultSubscriptionId, efid);
1490         }
1491 
1492         @Override
getAdnRecordsInEfForSubscriber(int subId, int efid)1493         public List<AdnRecord> getAdnRecordsInEfForSubscriber(int subId, int efid) {
1494             return Arrays.asList(
1495                     mRecords.getOrDefault(Pair.create(subId, efid), new AdnRecord[0]));
1496         }
1497 
1498         @Override
updateAdnRecordsInEfBySearchForSubscriber(int subId, int efid, ContentValues values, String pin2)1499         public boolean updateAdnRecordsInEfBySearchForSubscriber(int subId, int efid,
1500                 ContentValues values, String pin2) {
1501             final String oldTag = values.getAsString(IccProvider.STR_TAG);
1502             final String oldPhoneNumber = values.getAsString(IccProvider.STR_NUMBER);
1503             final String newTag = values.getAsString(IccProvider.STR_NEW_TAG);
1504             final String newPhoneNumber = values.getAsString(IccProvider.STR_NEW_NUMBER);
1505             return updateAdnRecordsInEfBySearchForSubscriber(subId, efid, oldTag, oldPhoneNumber,
1506                     newTag, newPhoneNumber, pin2);
1507 
1508         }
1509 
updateAdnRecordsInEfBySearchForSubscriber(int subId, int efid, String oldTag, String oldPhoneNumber, String newTag, String newPhoneNumber, String pin2)1510         private boolean updateAdnRecordsInEfBySearchForSubscriber(int subId, int efid, String oldTag,
1511                 String oldPhoneNumber, String newTag, String newPhoneNumber, String pin2) {
1512             if (!oldTag.isEmpty() || !oldPhoneNumber.isEmpty()) {
1513                 throw new IllegalArgumentException(
1514                         "updateAdnRecordsInEfBySearchForSubscriber only supports insert");
1515             }
1516             addRecord(subId, efid, newTag, newPhoneNumber);
1517             return true;
1518         }
1519 
1520         @Override
updateAdnRecordsInEfByIndexForSubscriber(int subId, int efid, ContentValues values, int index, String pin2)1521         public boolean updateAdnRecordsInEfByIndexForSubscriber(int subId, int efid,
1522                 ContentValues values, int index, String pin2) {
1523             final String newTag = values.getAsString(IccProvider.STR_NEW_TAG);
1524             final String newPhoneNumber = values.getAsString(IccProvider.STR_NEW_NUMBER);
1525             return updateAdnRecordsInEfByIndexForSubscriber(subId, efid, newTag, newPhoneNumber,
1526                     index, pin2);
1527 
1528         }
1529 
updateAdnRecordsInEfByIndexForSubscriber(int subId, int efid, String newTag, String newPhoneNumber, int index, String pin2)1530         private boolean updateAdnRecordsInEfByIndexForSubscriber(int subId, int efid, String newTag,
1531                 String newPhoneNumber, int index, String pin2) {
1532             AdnRecord[] records = mRecords.computeIfAbsent(Pair.create(subId, efid), unused ->
1533                     createEmptyRecords(efid, 100));
1534             records[index - 1] = new AdnRecord(efid, index, newTag, newPhoneNumber);
1535             return true;
1536         }
1537 
1538         @Override
getAdnRecordsSize(int efid)1539         public int[] getAdnRecordsSize(int efid) {
1540             return getAdnRecordsSizeForSubscriber(mDefaultSubscriptionId, efid);
1541         }
1542 
1543         @Override
getAdnRecordsSizeForSubscriber(int subId, int efid)1544         public int[] getAdnRecordsSizeForSubscriber(int subId, int efid) {
1545             Pair<Integer, Integer> key = Pair.create(subId, efid);
1546             int[] recordsSize = mRecordSizes.get(key);
1547             if (recordsSize == null) {
1548                 return new int[]{0, 0, 0};
1549             }
1550             return recordsSize;
1551         }
1552 
1553         @Override
getAdnRecordsCapacityForSubscriber(int subId)1554         public AdnCapacity getAdnRecordsCapacityForSubscriber(int subId) {
1555             return new AdnCapacity(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
1556         }
1557     }
1558 
1559     /**
1560      * Implementation of SimPhonebookProvider that allows test-doubles to be injected.
1561      *
1562      * <p>The ProviderTestRule doesn't seem to allow a better way to do this since it just
1563      * invokes the constructor.
1564      */
1565     public static class TestableSimPhonebookProvider extends SimPhonebookProvider {
1566 
setup( ContentResolver resolver, SubscriptionManager subscriptionManager, IIccPhoneBook iccPhoneBook)1567         public static void setup(
1568                 ContentResolver resolver,
1569                 SubscriptionManager subscriptionManager,
1570                 IIccPhoneBook iccPhoneBook) {
1571             setup(resolver, subscriptionManager, iccPhoneBook, uri -> {
1572             });
1573         }
1574 
setup( ContentResolver resolver, SubscriptionManager subscriptionManager, IIccPhoneBook iccPhoneBook, ContentNotifier notifier)1575         public static void setup(
1576                 ContentResolver resolver,
1577                 SubscriptionManager subscriptionManager,
1578                 IIccPhoneBook iccPhoneBook,
1579                 ContentNotifier notifier) {
1580             TestableSimPhonebookProvider provider =
1581                     (TestableSimPhonebookProvider) Objects.requireNonNull(
1582                             resolver.acquireContentProviderClient(
1583                                     SimPhonebookContract.AUTHORITY))
1584                             .getLocalContentProvider();
1585             InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
1586                     provider.onCreate(subscriptionManager, () -> iccPhoneBook, notifier));
1587         }
1588 
1589         @Override
onCreate()1590         public boolean onCreate() {
1591             // We stub super.onCreate because it initializes services which causes an
1592             // IllegalArgumentException because of the context used for the test.
1593             return true;
1594         }
1595     }
1596 }
1597