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