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