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