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