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.MatrixCursor;
24import android.net.Uri;
25import android.os.RemoteException;
26import android.os.ServiceManager;
27import android.text.TextUtils;
28import android.telephony.Rlog;
29
30import java.util.List;
31
32import com.android.internal.telephony.IIccPhoneBook;
33import com.android.internal.telephony.uicc.AdnRecord;
34import com.android.internal.telephony.uicc.IccConstants;
35
36
37/**
38 * {@hide}
39 */
40public class IccProvider extends ContentProvider {
41    private static final String TAG = "IccProvider";
42    private static final boolean DBG = false;
43
44
45    private static final String[] ADDRESS_BOOK_COLUMN_NAMES = new String[] {
46        "name",
47        "number",
48        "emails",
49        "_id"
50    };
51
52    private static final int ADN = 1;
53    private static final int FDN = 2;
54    private static final int SDN = 3;
55
56    private static final String STR_TAG = "tag";
57    private static final String STR_NUMBER = "number";
58    private static final String STR_EMAILS = "emails";
59    private static final String STR_PIN2 = "pin2";
60
61    private static final UriMatcher URL_MATCHER =
62                            new UriMatcher(UriMatcher.NO_MATCH);
63
64    static {
65        URL_MATCHER.addURI("icc", "adn", ADN);
66        URL_MATCHER.addURI("icc", "fdn", FDN);
67        URL_MATCHER.addURI("icc", "sdn", SDN);
68    }
69
70
71    @Override
72    public boolean onCreate() {
73        return true;
74    }
75
76    @Override
77    public Cursor query(Uri url, String[] projection, String selection,
78            String[] selectionArgs, String sort) {
79        switch (URL_MATCHER.match(url)) {
80            case ADN:
81                return loadFromEf(IccConstants.EF_ADN);
82
83            case FDN:
84                return loadFromEf(IccConstants.EF_FDN);
85
86            case SDN:
87                return loadFromEf(IccConstants.EF_SDN);
88
89            default:
90                throw new IllegalArgumentException("Unknown URL " + url);
91        }
92    }
93
94    @Override
95    public String getType(Uri url) {
96        switch (URL_MATCHER.match(url)) {
97            case ADN:
98            case FDN:
99            case SDN:
100                return "vnd.android.cursor.dir/sim-contact";
101
102            default:
103                throw new IllegalArgumentException("Unknown URL " + url);
104        }
105    }
106
107    @Override
108    public Uri insert(Uri url, ContentValues initialValues) {
109        Uri resultUri;
110        int efType;
111        String pin2 = null;
112
113        if (DBG) log("insert");
114
115        int match = URL_MATCHER.match(url);
116        switch (match) {
117            case ADN:
118                efType = IccConstants.EF_ADN;
119                break;
120
121            case FDN:
122                efType = IccConstants.EF_FDN;
123                pin2 = initialValues.getAsString("pin2");
124                break;
125
126            default:
127                throw new UnsupportedOperationException(
128                        "Cannot insert into URL: " + url);
129        }
130
131        String tag = initialValues.getAsString("tag");
132        String number = initialValues.getAsString("number");
133        // TODO(): Read email instead of sending null.
134        boolean success = addIccRecordToEf(efType, tag, number, null, pin2);
135
136        if (!success) {
137            return null;
138        }
139
140        StringBuilder buf = new StringBuilder("content://icc/");
141        switch (match) {
142            case ADN:
143                buf.append("adn/");
144                break;
145
146            case FDN:
147                buf.append("fdn/");
148                break;
149        }
150
151        // TODO: we need to find out the rowId for the newly added record
152        buf.append(0);
153
154        resultUri = Uri.parse(buf.toString());
155
156        /*
157        // notify interested parties that an insertion happened
158        getContext().getContentResolver().notifyInsert(
159                resultUri, rowID, null);
160        */
161
162        return resultUri;
163    }
164
165    private String normalizeValue(String inVal) {
166        int len = inVal.length();
167        String retVal = inVal;
168
169        if (inVal.charAt(0) == '\'' && inVal.charAt(len-1) == '\'') {
170            retVal = inVal.substring(1, len-1);
171        }
172
173        return retVal;
174    }
175
176    @Override
177    public int delete(Uri url, String where, String[] whereArgs) {
178        int efType;
179
180        if (DBG) log("delete");
181
182        int match = URL_MATCHER.match(url);
183        switch (match) {
184            case ADN:
185                efType = IccConstants.EF_ADN;
186                break;
187
188            case FDN:
189                efType = IccConstants.EF_FDN;
190                break;
191
192            default:
193                throw new UnsupportedOperationException(
194                        "Cannot insert into URL: " + url);
195        }
196
197        // parse where clause
198        String tag = null;
199        String number = null;
200        String[] emails = null;
201        String pin2 = null;
202
203        String[] tokens = where.split("AND");
204        int n = tokens.length;
205
206        while (--n >= 0) {
207            String param = tokens[n];
208            if (DBG) log("parsing '" + param + "'");
209
210            String[] pair = param.split("=", 2);
211
212            String key = pair[0].trim();
213            String val = pair[1].trim();
214
215            if (STR_TAG.equals(key)) {
216                tag = normalizeValue(val);
217            } else if (STR_NUMBER.equals(key)) {
218                number = normalizeValue(val);
219            } else if (STR_EMAILS.equals(key)) {
220                //TODO(): Email is null.
221                emails = null;
222            } else if (STR_PIN2.equals(key)) {
223                pin2 = normalizeValue(val);
224            }
225        }
226
227        if (TextUtils.isEmpty(number)) {
228            return 0;
229        }
230
231        if (efType == IccConstants.EF_FDN && TextUtils.isEmpty(pin2)) {
232            return 0;
233        }
234
235        boolean success = deleteIccRecordFromEf(efType, tag, number, emails, pin2);
236        if (!success) {
237            return 0;
238        }
239
240        return 1;
241    }
242
243    @Override
244    public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
245        int efType;
246        String pin2 = null;
247
248        if (DBG) log("update");
249
250        int match = URL_MATCHER.match(url);
251        switch (match) {
252            case ADN:
253                efType = IccConstants.EF_ADN;
254                break;
255
256            case FDN:
257                efType = IccConstants.EF_FDN;
258                pin2 = values.getAsString("pin2");
259                break;
260
261            default:
262                throw new UnsupportedOperationException(
263                        "Cannot insert into URL: " + url);
264        }
265
266        String tag = values.getAsString("tag");
267        String number = values.getAsString("number");
268        String[] emails = null;
269        String newTag = values.getAsString("newTag");
270        String newNumber = values.getAsString("newNumber");
271        String[] newEmails = null;
272        // TODO(): Update for email.
273        boolean success = updateIccRecordInEf(efType, tag, number,
274                newTag, newNumber, pin2);
275
276        if (!success) {
277            return 0;
278        }
279
280        return 1;
281    }
282
283    private MatrixCursor loadFromEf(int efType) {
284        if (DBG) log("loadFromEf: efType=" + efType);
285
286        List<AdnRecord> adnRecords = null;
287        try {
288            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
289                    ServiceManager.getService("simphonebook"));
290            if (iccIpb != null) {
291                adnRecords = iccIpb.getAdnRecordsInEf(efType);
292            }
293        } catch (RemoteException ex) {
294            // ignore it
295        } catch (SecurityException ex) {
296            if (DBG) log(ex.toString());
297        }
298
299        if (adnRecords != null) {
300            // Load the results
301            final int N = adnRecords.size();
302            final MatrixCursor cursor = new MatrixCursor(ADDRESS_BOOK_COLUMN_NAMES, N);
303            if (DBG) log("adnRecords.size=" + N);
304            for (int i = 0; i < N ; i++) {
305                loadRecord(adnRecords.get(i), cursor, i);
306            }
307            return cursor;
308        } else {
309            // No results to load
310            Rlog.w(TAG, "Cannot load ADN records");
311            return new MatrixCursor(ADDRESS_BOOK_COLUMN_NAMES);
312        }
313    }
314
315    private boolean
316    addIccRecordToEf(int efType, String name, String number, String[] emails, String pin2) {
317        if (DBG) log("addIccRecordToEf: efType=" + efType + ", name=" + name +
318                ", number=" + number + ", emails=" + emails);
319
320        boolean success = false;
321
322        // TODO: do we need to call getAdnRecordsInEf() before calling
323        // updateAdnRecordsInEfBySearch()? In any case, we will leave
324        // the UI level logic to fill that prereq if necessary. But
325        // hopefully, we can remove this requirement.
326
327        try {
328            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
329                    ServiceManager.getService("simphonebook"));
330            if (iccIpb != null) {
331                success = iccIpb.updateAdnRecordsInEfBySearch(efType, "", "",
332                        name, number, pin2);
333            }
334        } catch (RemoteException ex) {
335            // ignore it
336        } catch (SecurityException ex) {
337            if (DBG) log(ex.toString());
338        }
339        if (DBG) log("addIccRecordToEf: " + success);
340        return success;
341    }
342
343    private boolean
344    updateIccRecordInEf(int efType, String oldName, String oldNumber,
345            String newName, String newNumber, String pin2) {
346        if (DBG) log("updateIccRecordInEf: efType=" + efType +
347                ", oldname=" + oldName + ", oldnumber=" + oldNumber +
348                ", newname=" + newName + ", newnumber=" + newNumber);
349        boolean success = false;
350
351        try {
352            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
353                    ServiceManager.getService("simphonebook"));
354            if (iccIpb != null) {
355                success = iccIpb.updateAdnRecordsInEfBySearch(efType,
356                        oldName, oldNumber, newName, newNumber, pin2);
357            }
358        } catch (RemoteException ex) {
359            // ignore it
360        } catch (SecurityException ex) {
361            if (DBG) log(ex.toString());
362        }
363        if (DBG) log("updateIccRecordInEf: " + success);
364        return success;
365    }
366
367
368    private boolean deleteIccRecordFromEf(int efType, String name, String number, String[] emails,
369            String pin2) {
370        if (DBG) log("deleteIccRecordFromEf: efType=" + efType +
371                ", name=" + name + ", number=" + number + ", emails=" + emails + ", pin2=" + pin2);
372
373        boolean success = false;
374
375        try {
376            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
377                    ServiceManager.getService("simphonebook"));
378            if (iccIpb != null) {
379                success = iccIpb.updateAdnRecordsInEfBySearch(efType,
380                        name, number, "", "", pin2);
381            }
382        } catch (RemoteException ex) {
383            // ignore it
384        } catch (SecurityException ex) {
385            if (DBG) log(ex.toString());
386        }
387        if (DBG) log("deleteIccRecordFromEf: " + success);
388        return success;
389    }
390
391    /**
392     * Loads an AdnRecord into a MatrixCursor. Must be called with mLock held.
393     *
394     * @param record the ADN record to load from
395     * @param cursor the cursor to receive the results
396     */
397    private void loadRecord(AdnRecord record, MatrixCursor cursor, int id) {
398        if (!record.isEmpty()) {
399            Object[] contact = new Object[4];
400            String alphaTag = record.getAlphaTag();
401            String number = record.getNumber();
402
403            if (DBG) log("loadRecord: " + alphaTag + ", " + number + ",");
404            contact[0] = alphaTag;
405            contact[1] = number;
406
407            String[] emails = record.getEmails();
408            if (emails != null) {
409                StringBuilder emailString = new StringBuilder();
410                for (String email: emails) {
411                    if (DBG) log("Adding email:" + email);
412                    emailString.append(email);
413                    emailString.append(",");
414                }
415                contact[2] = emailString.toString();
416            }
417            contact[3] = id;
418            cursor.addRow(contact);
419        }
420    }
421
422    private void log(String msg) {
423        Rlog.d(TAG, "[IccProvider] " + msg);
424    }
425
426}
427