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.uicc.AdnRecord;
36import com.android.internal.telephony.uicc.IccConstants;
37
38
39/**
40 * {@hide}
41 */
42public 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
82    public boolean onCreate() {
83        mSubscriptionManager = SubscriptionManager.from(getContext());
84        return true;
85    }
86
87    @Override
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
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
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
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
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
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
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
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
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
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
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     */
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
539    private void log(String msg) {
540        Rlog.d(TAG, "[IccProvider] " + msg);
541    }
542
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