1 /*
2  * Copyright (C) 2006 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.internal.telephony;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.content.ContentProvider;
21 import android.content.ContentValues;
22 import android.content.UriMatcher;
23 import android.database.Cursor;
24 import android.database.MatrixCursor;
25 import android.database.MergeCursor;
26 import android.net.Uri;
27 import android.os.Build;
28 import android.os.RemoteException;
29 import android.telephony.SubscriptionInfo;
30 import android.telephony.SubscriptionManager;
31 import android.telephony.TelephonyFrameworkInitializer;
32 import android.text.TextUtils;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.telephony.uicc.AdnRecord;
36 import com.android.internal.telephony.uicc.IccConstants;
37 import com.android.telephony.Rlog;
38 
39 import java.util.List;
40 import java.util.Locale;
41 
42 /**
43  * {@hide}
44  */
45 public class IccProvider extends ContentProvider {
46     private static final String TAG = "IccProvider";
47     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
48     private static final boolean DBG = true;
49 
50 
51     @UnsupportedAppUsage
52     private static final String[] ADDRESS_BOOK_COLUMN_NAMES = new String[] {
53         "name",
54         "number",
55         "emails",
56         "anrs",
57         "_id"
58     };
59 
60     protected static final int ADN = 1;
61     protected static final int ADN_SUB = 2;
62     protected static final int FDN = 3;
63     protected static final int FDN_SUB = 4;
64     protected static final int SDN = 5;
65     protected static final int SDN_SUB = 6;
66     protected static final int ADN_ALL = 7;
67 
68     @VisibleForTesting
69     public static final String STR_TAG = "tag";
70     @VisibleForTesting
71     public static final String STR_NUMBER = "number";
72     @VisibleForTesting
73     public static final String STR_EMAILS = "emails";
74     @VisibleForTesting
75     public static final String STR_ANRS = "anrs";
76     @VisibleForTesting
77     public static final String STR_NEW_TAG = "newTag";
78     @VisibleForTesting
79     public static final String STR_NEW_NUMBER = "newNumber";
80     @VisibleForTesting
81     public static final String STR_NEW_EMAILS = "newEmails";
82     @VisibleForTesting
83     public static final String STR_NEW_ANRS = "newAnrs";
84     @VisibleForTesting
85     public static final String STR_PIN2 = "pin2";
86 
87     private static final UriMatcher URL_MATCHER =
88                             new UriMatcher(UriMatcher.NO_MATCH);
89 
90     static {
91         URL_MATCHER.addURI("icc", "adn", ADN);
92         URL_MATCHER.addURI("icc", "adn/subId/#", ADN_SUB);
93         URL_MATCHER.addURI("icc", "fdn", FDN);
94         URL_MATCHER.addURI("icc", "fdn/subId/#", FDN_SUB);
95         URL_MATCHER.addURI("icc", "sdn", SDN);
96         URL_MATCHER.addURI("icc", "sdn/subId/#", SDN_SUB);
97     }
98 
99     private SubscriptionManager mSubscriptionManager;
100 
101     @UnsupportedAppUsage
IccProvider()102     public IccProvider() {
103     }
104 
105     @Override
onCreate()106     public boolean onCreate() {
107         mSubscriptionManager = SubscriptionManager.from(getContext());
108         return true;
109     }
110 
111     @Override
query(Uri url, String[] projection, String selection, String[] selectionArgs, String sort)112     public Cursor query(Uri url, String[] projection, String selection,
113             String[] selectionArgs, String sort) {
114         if (DBG) log("query");
115 
116         switch (URL_MATCHER.match(url)) {
117             case ADN:
118                 return loadFromEf(IccConstants.EF_ADN,
119                         SubscriptionManager.getDefaultSubscriptionId());
120 
121             case ADN_SUB:
122                 return loadFromEf(IccConstants.EF_ADN, getRequestSubId(url));
123 
124             case FDN:
125                 return loadFromEf(IccConstants.EF_FDN,
126                         SubscriptionManager.getDefaultSubscriptionId());
127 
128             case FDN_SUB:
129                 return loadFromEf(IccConstants.EF_FDN, getRequestSubId(url));
130 
131             case SDN:
132                 return loadFromEf(IccConstants.EF_SDN,
133                         SubscriptionManager.getDefaultSubscriptionId());
134 
135             case SDN_SUB:
136                 return loadFromEf(IccConstants.EF_SDN, getRequestSubId(url));
137 
138             case ADN_ALL:
139                 return loadAllSimContacts(IccConstants.EF_ADN);
140 
141             default:
142                 throw new IllegalArgumentException("Unknown URL " + url);
143         }
144     }
145 
loadAllSimContacts(int efType)146     private Cursor loadAllSimContacts(int efType) {
147         Cursor [] result;
148         List<SubscriptionInfo> subInfoList = mSubscriptionManager
149                 .getActiveSubscriptionInfoList(false);
150 
151         if ((subInfoList == null) || (subInfoList.size() == 0)) {
152             result = new Cursor[0];
153         } else {
154             int subIdCount = subInfoList.size();
155             result = new Cursor[subIdCount];
156             int subId;
157 
158             for (int i = 0; i < subIdCount; i++) {
159                 subId = subInfoList.get(i).getSubscriptionId();
160                 result[i] = loadFromEf(efType, subId);
161                 Rlog.i(TAG,"ADN Records loaded for Subscription ::" + subId);
162             }
163         }
164 
165         return new MergeCursor(result);
166     }
167 
168     @Override
getType(Uri url)169     public String getType(Uri url) {
170         switch (URL_MATCHER.match(url)) {
171             case ADN:
172             case ADN_SUB:
173             case FDN:
174             case FDN_SUB:
175             case SDN:
176             case SDN_SUB:
177             case ADN_ALL:
178                 return "vnd.android.cursor.dir/sim-contact";
179 
180             default:
181                 throw new IllegalArgumentException("Unknown URL " + url);
182         }
183     }
184 
185     @Override
insert(Uri url, ContentValues initialValues)186     public Uri insert(Uri url, ContentValues initialValues) {
187         Uri resultUri;
188         int efType;
189         String pin2 = null;
190         int subId;
191 
192         if (DBG) log("insert");
193 
194         int match = URL_MATCHER.match(url);
195         switch (match) {
196             case ADN:
197                 efType = IccConstants.EF_ADN;
198                 subId = SubscriptionManager.getDefaultSubscriptionId();
199                 break;
200 
201             case ADN_SUB:
202                 efType = IccConstants.EF_ADN;
203                 subId = getRequestSubId(url);
204                 break;
205 
206             case FDN:
207                 efType = IccConstants.EF_FDN;
208                 subId = SubscriptionManager.getDefaultSubscriptionId();
209                 pin2 = initialValues.getAsString("pin2");
210                 break;
211 
212             case FDN_SUB:
213                 efType = IccConstants.EF_FDN;
214                 subId = getRequestSubId(url);
215                 pin2 = initialValues.getAsString("pin2");
216                 break;
217 
218             default:
219                 throw new UnsupportedOperationException(
220                         "Cannot insert into URL: " + url);
221         }
222 
223         // We're not using the incoming initialValues
224         // so we can check/gate the arguments.
225         String tag = initialValues.getAsString(STR_TAG);
226         String number = initialValues.getAsString(STR_NUMBER);
227         String emails = initialValues.getAsString(STR_EMAILS);
228         String anrs = initialValues.getAsString(STR_ANRS);
229 
230         ContentValues values = new ContentValues();
231         values.put(STR_NEW_TAG, tag);
232         values.put(STR_NEW_NUMBER, number);
233         values.put(STR_NEW_EMAILS, emails);
234         values.put(STR_NEW_ANRS, anrs);
235         boolean success = updateIccRecordInEf(efType, values, pin2, subId);
236 
237         if (!success) {
238             return null;
239         }
240 
241         StringBuilder buf = new StringBuilder("content://icc/");
242         switch (match) {
243             case ADN:
244                 buf.append("adn/");
245                 break;
246 
247             case ADN_SUB:
248                 buf.append("adn/subId/");
249                 break;
250 
251             case FDN:
252                 buf.append("fdn/");
253                 break;
254 
255             case FDN_SUB:
256                 buf.append("fdn/subId/");
257                 break;
258         }
259 
260         // TODO: we need to find out the rowId for the newly added record
261         buf.append(0);
262 
263         resultUri = Uri.parse(buf.toString());
264 
265         getContext().getContentResolver().notifyChange(url, null);
266         /*
267         // notify interested parties that an insertion happened
268         getContext().getContentResolver().notifyInsert(
269                 resultUri, rowID, null);
270         */
271 
272         return resultUri;
273     }
274 
normalizeValue(String inVal)275     private String normalizeValue(String inVal) {
276         int len = inVal.length();
277         // If name is empty in contact return null to avoid crash.
278         if (len == 0) {
279             if (DBG) log("len of input String is 0");
280             return inVal;
281         }
282         String retVal = inVal;
283 
284         if (inVal.charAt(0) == '\'' && inVal.charAt(len-1) == '\'') {
285             retVal = inVal.substring(1, len-1);
286         }
287 
288         return retVal;
289     }
290 
291     @Override
delete(Uri url, String where, String[] whereArgs)292     public int delete(Uri url, String where, String[] whereArgs) {
293         int efType;
294         int subId;
295 
296         int match = URL_MATCHER.match(url);
297         switch (match) {
298             case ADN:
299                 efType = IccConstants.EF_ADN;
300                 subId = SubscriptionManager.getDefaultSubscriptionId();
301                 break;
302 
303             case ADN_SUB:
304                 efType = IccConstants.EF_ADN;
305                 subId = getRequestSubId(url);
306                 break;
307 
308             case FDN:
309                 efType = IccConstants.EF_FDN;
310                 subId = SubscriptionManager.getDefaultSubscriptionId();
311                 break;
312 
313             case FDN_SUB:
314                 efType = IccConstants.EF_FDN;
315                 subId = getRequestSubId(url);
316                 break;
317 
318             default:
319                 throw new UnsupportedOperationException(
320                         "Cannot insert into URL: " + url);
321         }
322 
323         if (DBG) log("delete");
324 
325         // parse where clause
326         String tag = null;
327         String number = null;
328         String emails = null;
329         String anrs = null;
330         String pin2 = null;
331 
332         String[] tokens = where.split(" AND ");
333         int n = tokens.length;
334 
335         while (--n >= 0) {
336             String param = tokens[n];
337             if (DBG) log("parsing '" + param + "'");
338 
339             String[] pair = param.split("=", 2);
340 
341             if (pair.length != 2) {
342                 Rlog.e(TAG, "resolve: bad whereClause parameter: " + param);
343                 continue;
344             }
345             String key = pair[0].trim();
346             String val = pair[1].trim();
347 
348             if (STR_TAG.equals(key)) {
349                 tag = normalizeValue(val);
350             } else if (STR_NUMBER.equals(key)) {
351                 number = normalizeValue(val);
352             } else if (STR_EMAILS.equals(key)) {
353                 emails = normalizeValue(val);
354             } else if (STR_ANRS.equals(key)) {
355                 anrs = normalizeValue(val);
356             } else if (STR_PIN2.equals(key)) {
357                 pin2 = normalizeValue(val);
358             }
359         }
360 
361         ContentValues values = new ContentValues();
362         values.put(STR_TAG, tag);
363         values.put(STR_NUMBER, number);
364         values.put(STR_EMAILS, emails);
365         values.put(STR_ANRS, anrs);
366         if ((efType == FDN) && TextUtils.isEmpty(pin2)) {
367             return 0;
368         }
369         if (DBG) log("delete mvalues= " + values);
370         boolean success = updateIccRecordInEf(efType, values, pin2, subId);
371         if (!success) {
372             return 0;
373         }
374 
375         getContext().getContentResolver().notifyChange(url, null);
376         return 1;
377     }
378 
379     @Override
update(Uri url, ContentValues values, String where, String[] whereArgs)380     public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
381         String pin2 = null;
382         int efType;
383         int subId;
384 
385         if (DBG) log("update");
386 
387         int match = URL_MATCHER.match(url);
388         switch (match) {
389             case ADN:
390                 efType = IccConstants.EF_ADN;
391                 subId = SubscriptionManager.getDefaultSubscriptionId();
392                 break;
393 
394             case ADN_SUB:
395                 efType = IccConstants.EF_ADN;
396                 subId = getRequestSubId(url);
397                 break;
398 
399             case FDN:
400                 efType = IccConstants.EF_FDN;
401                 subId = SubscriptionManager.getDefaultSubscriptionId();
402                 pin2 = values.getAsString("pin2");
403                 break;
404 
405             case FDN_SUB:
406                 efType = IccConstants.EF_FDN;
407                 subId = getRequestSubId(url);
408                 pin2 = values.getAsString("pin2");
409                 break;
410 
411             default:
412                 throw new UnsupportedOperationException(
413                         "Cannot insert into URL: " + url);
414         }
415 
416         boolean success = updateIccRecordInEf(efType, values, pin2, subId);
417 
418         if (!success) {
419             return 0;
420         }
421 
422         getContext().getContentResolver().notifyChange(url, null);
423         return 1;
424     }
425 
loadFromEf(int efType, int subId)426     private MatrixCursor loadFromEf(int efType, int subId) {
427         if (DBG) log("loadFromEf: efType=0x" +
428                 Integer.toHexString(efType).toUpperCase(Locale.ROOT) + ", subscription=" + subId);
429 
430         List<AdnRecord> adnRecords = null;
431         try {
432             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
433                     TelephonyFrameworkInitializer
434                             .getTelephonyServiceManager()
435                             .getIccPhoneBookServiceRegisterer()
436                             .get());
437             if (iccIpb != null) {
438                 adnRecords = iccIpb.getAdnRecordsInEfForSubscriber(subId, efType);
439             }
440         } catch (RemoteException ex) {
441             // ignore it
442         } catch (SecurityException ex) {
443             if (DBG) log(ex.toString());
444         }
445 
446         if (adnRecords != null) {
447             // Load the results
448             final int N = adnRecords.size();
449             final MatrixCursor cursor = new MatrixCursor(ADDRESS_BOOK_COLUMN_NAMES, N);
450             if (DBG) log("adnRecords.size=" + N);
451             for (int i = 0; i < N ; i++) {
452                 loadRecord(adnRecords.get(i), cursor, i);
453             }
454             return cursor;
455         } else {
456             // No results to load
457             Rlog.w(TAG, "Cannot load ADN records");
458             return new MatrixCursor(ADDRESS_BOOK_COLUMN_NAMES);
459         }
460     }
461 
462     private boolean
updateIccRecordInEf(int efType, ContentValues values, String pin2, int subId)463     updateIccRecordInEf(int efType, ContentValues values, String pin2, int subId) {
464         boolean success = false;
465         if (DBG) log("updateIccRecordInEf: efType=" + efType +
466                 ", values: [ "+ values + "  ], subId:" + subId);
467         try {
468             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
469                     TelephonyFrameworkInitializer
470                             .getTelephonyServiceManager()
471                             .getIccPhoneBookServiceRegisterer()
472                             .get());
473             if (iccIpb != null) {
474                 success = iccIpb
475                         .updateAdnRecordsInEfBySearchForSubscriber(
476                             subId, efType, values, pin2);
477             }
478         } catch (RemoteException ex) {
479             // ignore it
480         } catch (SecurityException ex) {
481             if (DBG) log(ex.toString());
482         }
483         if (DBG) log("updateIccRecordInEf: " + success);
484         return success;
485     }
486 
487     /**
488      * Loads an AdnRecord into a MatrixCursor. Must be called with mLock held.
489      *
490      * @param record the ADN record to load from
491      * @param cursor the cursor to receive the results
492      */
493     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
loadRecord(AdnRecord record, MatrixCursor cursor, int id)494     private void loadRecord(AdnRecord record, MatrixCursor cursor, int id) {
495         if (!record.isEmpty()) {
496             Object[] contact = new Object[5];
497             String alphaTag = record.getAlphaTag();
498             String number = record.getNumber();
499 
500             if (DBG) log("loadRecord: " + alphaTag + ", " + Rlog.pii(TAG, number));
501             contact[0] = alphaTag;
502             contact[1] = number;
503 
504             String[] emails = record.getEmails();
505             if (emails != null) {
506                 StringBuilder emailString = new StringBuilder();
507                 for (String email: emails) {
508                     log("Adding email:" + Rlog.pii(TAG, email));
509                     emailString.append(email);
510                     emailString.append(",");
511                 }
512                 contact[2] = emailString.toString();
513             }
514 
515             String[] anrs = record.getAdditionalNumbers();
516             if (anrs != null) {
517                 StringBuilder anrString = new StringBuilder();
518                 for (String anr : anrs) {
519                     if (DBG) log("Adding anr:" + anr);
520                     anrString.append(anr);
521                     anrString.append(":");
522                 }
523                 contact[3] = anrString.toString();
524             }
525 
526             contact[4] = id;
527             cursor.addRow(contact);
528         }
529     }
530 
531     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
log(String msg)532     private void log(String msg) {
533         Rlog.d(TAG, "[IccProvider] " + msg);
534     }
535 
getRequestSubId(Uri url)536     private int getRequestSubId(Uri url) {
537         if (DBG) log("getRequestSubId url: " + url);
538 
539         try {
540             return Integer.parseInt(url.getLastPathSegment());
541         } catch (NumberFormatException ex) {
542             throw new IllegalArgumentException("Unknown URL " + url);
543         }
544     }
545 }
546