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