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