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