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