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.AbstractCursor;
23import android.database.Cursor;
24import android.database.CursorWindow;
25import android.net.Uri;
26import android.os.SystemProperties;
27import android.os.RemoteException;
28import android.os.ServiceManager;
29import android.text.TextUtils;
30import android.util.Log;
31
32import java.util.ArrayList;
33import java.util.List;
34
35import com.android.internal.telephony.IccConstants;
36import com.android.internal.telephony.AdnRecord;
37import com.android.internal.telephony.IIccPhoneBook;
38
39/**
40 * XXX old code -- should be replaced with MatrixCursor.
41 * @deprecated This is has been replaced by MatrixCursor.
42*/
43class ArrayListCursor extends AbstractCursor {
44    private String[] mColumnNames;
45    private ArrayList<Object>[] mRows;
46
47    @SuppressWarnings({"unchecked"})
48    public ArrayListCursor(String[] columnNames, ArrayList<ArrayList> rows) {
49        int colCount = columnNames.length;
50        boolean foundID = false;
51        // Add an _id column if not in columnNames
52        for (int i = 0; i < colCount; ++i) {
53            if (columnNames[i].compareToIgnoreCase("_id") == 0) {
54                mColumnNames = columnNames;
55                foundID = true;
56                break;
57            }
58        }
59
60        if (!foundID) {
61            mColumnNames = new String[colCount + 1];
62            System.arraycopy(columnNames, 0, mColumnNames, 0, columnNames.length);
63            mColumnNames[colCount] = "_id";
64        }
65
66        int rowCount = rows.size();
67        mRows = new ArrayList[rowCount];
68
69        for (int i = 0; i < rowCount; ++i) {
70            mRows[i] = rows.get(i);
71            if (!foundID) {
72                mRows[i].add(i);
73            }
74        }
75    }
76
77    @Override
78    public void fillWindow(int position, CursorWindow window) {
79        if (position < 0 || position > getCount()) {
80            return;
81        }
82
83        window.acquireReference();
84        try {
85            int oldpos = mPos;
86            mPos = position - 1;
87            window.clear();
88            window.setStartPosition(position);
89            int columnNum = getColumnCount();
90            window.setNumColumns(columnNum);
91            while (moveToNext() && window.allocRow()) {
92                for (int i = 0; i < columnNum; i++) {
93                    final Object data = mRows[mPos].get(i);
94                    if (data != null) {
95                        if (data instanceof byte[]) {
96                            byte[] field = (byte[]) data;
97                            if (!window.putBlob(field, mPos, i)) {
98                                window.freeLastRow();
99                                break;
100                            }
101                        } else {
102                            String field = data.toString();
103                            if (!window.putString(field, mPos, i)) {
104                                window.freeLastRow();
105                                break;
106                            }
107                        }
108                    } else {
109                        if (!window.putNull(mPos, i)) {
110                            window.freeLastRow();
111                            break;
112                        }
113                    }
114                }
115            }
116
117            mPos = oldpos;
118        } catch (IllegalStateException e){
119            // simply ignore it
120        } finally {
121            window.releaseReference();
122        }
123    }
124
125    @Override
126    public int getCount() {
127        return mRows.length;
128    }
129
130    @Override
131    public String[] getColumnNames() {
132        return mColumnNames;
133    }
134
135    @Override
136    public byte[] getBlob(int columnIndex) {
137        return (byte[]) mRows[mPos].get(columnIndex);
138    }
139
140    @Override
141    public String getString(int columnIndex) {
142        Object cell = mRows[mPos].get(columnIndex);
143        return (cell == null) ? null : cell.toString();
144    }
145
146    @Override
147    public short getShort(int columnIndex) {
148        Number num = (Number) mRows[mPos].get(columnIndex);
149        return num.shortValue();
150    }
151
152    @Override
153    public int getInt(int columnIndex) {
154        Number num = (Number) mRows[mPos].get(columnIndex);
155        return num.intValue();
156    }
157
158    @Override
159    public long getLong(int columnIndex) {
160        Number num = (Number) mRows[mPos].get(columnIndex);
161        return num.longValue();
162    }
163
164    @Override
165    public float getFloat(int columnIndex) {
166        Number num = (Number) mRows[mPos].get(columnIndex);
167        return num.floatValue();
168    }
169
170    @Override
171    public double getDouble(int columnIndex) {
172        Number num = (Number) mRows[mPos].get(columnIndex);
173        return num.doubleValue();
174    }
175
176    @Override
177    public boolean isNull(int columnIndex) {
178        return mRows[mPos].get(columnIndex) == null;
179    }
180}
181
182
183/**
184 * {@hide}
185 */
186public class IccProvider extends ContentProvider {
187    private static final String TAG = "IccProvider";
188    private static final boolean DBG = false;
189
190
191    private static final String[] ADDRESS_BOOK_COLUMN_NAMES = new String[] {
192        "name",
193        "number",
194        "emails"
195    };
196
197    private static final int ADN = 1;
198    private static final int FDN = 2;
199    private static final int SDN = 3;
200
201    private static final String STR_TAG = "tag";
202    private static final String STR_NUMBER = "number";
203    private static final String STR_EMAILS = "emails";
204    private static final String STR_PIN2 = "pin2";
205
206    private static final UriMatcher URL_MATCHER =
207                            new UriMatcher(UriMatcher.NO_MATCH);
208
209    static {
210        URL_MATCHER.addURI("icc", "adn", ADN);
211        URL_MATCHER.addURI("icc", "fdn", FDN);
212        URL_MATCHER.addURI("icc", "sdn", SDN);
213    }
214
215
216    private boolean mSimulator;
217
218    @Override
219    public boolean onCreate() {
220        String device = SystemProperties.get("ro.product.device");
221        if (!TextUtils.isEmpty(device)) {
222            mSimulator = false;
223        } else {
224            // simulator
225            mSimulator = true;
226        }
227
228        return true;
229    }
230
231    @Override
232    public Cursor query(Uri url, String[] projection, String selection,
233            String[] selectionArgs, String sort) {
234        ArrayList<ArrayList> results;
235
236        if (!mSimulator) {
237            switch (URL_MATCHER.match(url)) {
238                case ADN:
239                    results = loadFromEf(IccConstants.EF_ADN);
240                    break;
241
242                case FDN:
243                    results = loadFromEf(IccConstants.EF_FDN);
244                    break;
245
246                case SDN:
247                    results = loadFromEf(IccConstants.EF_SDN);
248                    break;
249
250                default:
251                    throw new IllegalArgumentException("Unknown URL " + url);
252            }
253        } else {
254            // Fake up some data for the simulator
255            results = new ArrayList<ArrayList>(4);
256            ArrayList<String> contact;
257
258            contact = new ArrayList<String>();
259            contact.add("Ron Stevens/H");
260            contact.add("512-555-5038");
261            results.add(contact);
262
263            contact = new ArrayList<String>();
264            contact.add("Ron Stevens/M");
265            contact.add("512-555-8305");
266            results.add(contact);
267
268            contact = new ArrayList<String>();
269            contact.add("Melissa Owens");
270            contact.add("512-555-8305");
271            results.add(contact);
272
273            contact = new ArrayList<String>();
274            contact.add("Directory Assistence");
275            contact.add("411");
276            results.add(contact);
277        }
278
279        return new ArrayListCursor(ADDRESS_BOOK_COLUMN_NAMES, results);
280    }
281
282    @Override
283    public String getType(Uri url) {
284        switch (URL_MATCHER.match(url)) {
285            case ADN:
286            case FDN:
287            case SDN:
288                return "vnd.android.cursor.dir/sim-contact";
289
290            default:
291                throw new IllegalArgumentException("Unknown URL " + url);
292        }
293    }
294
295    @Override
296    public Uri insert(Uri url, ContentValues initialValues) {
297        Uri resultUri;
298        int efType;
299        String pin2 = null;
300
301        if (DBG) log("insert");
302
303        int match = URL_MATCHER.match(url);
304        switch (match) {
305            case ADN:
306                efType = IccConstants.EF_ADN;
307                break;
308
309            case FDN:
310                efType = IccConstants.EF_FDN;
311                pin2 = initialValues.getAsString("pin2");
312                break;
313
314            default:
315                throw new UnsupportedOperationException(
316                        "Cannot insert into URL: " + url);
317        }
318
319        String tag = initialValues.getAsString("tag");
320        String number = initialValues.getAsString("number");
321        // TODO(): Read email instead of sending null.
322        boolean success = addIccRecordToEf(efType, tag, number, null, pin2);
323
324        if (!success) {
325            return null;
326        }
327
328        StringBuilder buf = new StringBuilder("content://icc/");
329        switch (match) {
330            case ADN:
331                buf.append("adn/");
332                break;
333
334            case FDN:
335                buf.append("fdn/");
336                break;
337        }
338
339        // TODO: we need to find out the rowId for the newly added record
340        buf.append(0);
341
342        resultUri = Uri.parse(buf.toString());
343
344        /*
345        // notify interested parties that an insertion happened
346        getContext().getContentResolver().notifyInsert(
347                resultUri, rowID, null);
348        */
349
350        return resultUri;
351    }
352
353    private String normalizeValue(String inVal) {
354        int len = inVal.length();
355        String retVal = inVal;
356
357        if (inVal.charAt(0) == '\'' && inVal.charAt(len-1) == '\'') {
358            retVal = inVal.substring(1, len-1);
359        }
360
361        return retVal;
362    }
363
364    @Override
365    public int delete(Uri url, String where, String[] whereArgs) {
366        int efType;
367
368        if (DBG) log("delete");
369
370        int match = URL_MATCHER.match(url);
371        switch (match) {
372            case ADN:
373                efType = IccConstants.EF_ADN;
374                break;
375
376            case FDN:
377                efType = IccConstants.EF_FDN;
378                break;
379
380            default:
381                throw new UnsupportedOperationException(
382                        "Cannot insert into URL: " + url);
383        }
384
385        // parse where clause
386        String tag = null;
387        String number = null;
388        String[] emails = null;
389        String pin2 = null;
390
391        String[] tokens = where.split("AND");
392        int n = tokens.length;
393
394        while (--n >= 0) {
395            String param = tokens[n];
396            if (DBG) log("parsing '" + param + "'");
397
398            String[] pair = param.split("=");
399
400            if (pair.length != 2) {
401                Log.e(TAG, "resolve: bad whereClause parameter: " + param);
402                continue;
403            }
404
405            String key = pair[0].trim();
406            String val = pair[1].trim();
407
408            if (STR_TAG.equals(key)) {
409                tag = normalizeValue(val);
410            } else if (STR_NUMBER.equals(key)) {
411                number = normalizeValue(val);
412            } else if (STR_EMAILS.equals(key)) {
413                //TODO(): Email is null.
414                emails = null;
415            } else if (STR_PIN2.equals(key)) {
416                pin2 = normalizeValue(val);
417            }
418        }
419
420        if (TextUtils.isEmpty(tag)) {
421            return 0;
422        }
423
424        if (efType == FDN && TextUtils.isEmpty(pin2)) {
425            return 0;
426        }
427
428        boolean success = deleteIccRecordFromEf(efType, tag, number, emails, pin2);
429        if (!success) {
430            return 0;
431        }
432
433        return 1;
434    }
435
436    @Override
437    public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
438        int efType;
439        String pin2 = null;
440
441        if (DBG) log("update");
442
443        int match = URL_MATCHER.match(url);
444        switch (match) {
445            case ADN:
446                efType = IccConstants.EF_ADN;
447                break;
448
449            case FDN:
450                efType = IccConstants.EF_FDN;
451                pin2 = values.getAsString("pin2");
452                break;
453
454            default:
455                throw new UnsupportedOperationException(
456                        "Cannot insert into URL: " + url);
457        }
458
459        String tag = values.getAsString("tag");
460        String number = values.getAsString("number");
461        String[] emails = null;
462        String newTag = values.getAsString("newTag");
463        String newNumber = values.getAsString("newNumber");
464        String[] newEmails = null;
465        // TODO(): Update for email.
466        boolean success = updateIccRecordInEf(efType, tag, number,
467                newTag, newNumber, pin2);
468
469        if (!success) {
470            return 0;
471        }
472
473        return 1;
474    }
475
476    private ArrayList<ArrayList> loadFromEf(int efType) {
477        ArrayList<ArrayList> results = new ArrayList<ArrayList>();
478        List<AdnRecord> adnRecords = null;
479
480        if (DBG) log("loadFromEf: efType=" + efType);
481
482        try {
483            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
484                    ServiceManager.getService("simphonebook"));
485            if (iccIpb != null) {
486                adnRecords = iccIpb.getAdnRecordsInEf(efType);
487            }
488        } catch (RemoteException ex) {
489            // ignore it
490        } catch (SecurityException ex) {
491            if (DBG) log(ex.toString());
492        }
493        if (adnRecords != null) {
494            // Load the results
495
496            int N = adnRecords.size();
497            if (DBG) log("adnRecords.size=" + N);
498            for (int i = 0; i < N ; i++) {
499                loadRecord(adnRecords.get(i), results);
500            }
501        } else {
502            // No results to load
503            Log.w(TAG, "Cannot load ADN records");
504            results.clear();
505        }
506        if (DBG) log("loadFromEf: return results");
507        return results;
508    }
509
510    private boolean
511    addIccRecordToEf(int efType, String name, String number, String[] emails, String pin2) {
512        if (DBG) log("addIccRecordToEf: efType=" + efType + ", name=" + name +
513                ", number=" + number + ", emails=" + emails);
514
515        boolean success = false;
516
517        // TODO: do we need to call getAdnRecordsInEf() before calling
518        // updateAdnRecordsInEfBySearch()? In any case, we will leave
519        // the UI level logic to fill that prereq if necessary. But
520        // hopefully, we can remove this requirement.
521
522        try {
523            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
524                    ServiceManager.getService("simphonebook"));
525            if (iccIpb != null) {
526                success = iccIpb.updateAdnRecordsInEfBySearch(efType, "", "",
527                        name, number, pin2);
528            }
529        } catch (RemoteException ex) {
530            // ignore it
531        } catch (SecurityException ex) {
532            if (DBG) log(ex.toString());
533        }
534        if (DBG) log("addIccRecordToEf: " + success);
535        return success;
536    }
537
538    private boolean
539    updateIccRecordInEf(int efType, String oldName, String oldNumber,
540            String newName, String newNumber, String pin2) {
541        if (DBG) log("updateIccRecordInEf: efType=" + efType +
542                ", oldname=" + oldName + ", oldnumber=" + oldNumber +
543                ", newname=" + newName + ", newnumber=" + newNumber);
544        boolean success = false;
545
546        try {
547            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
548                    ServiceManager.getService("simphonebook"));
549            if (iccIpb != null) {
550                success = iccIpb.updateAdnRecordsInEfBySearch(efType,
551                        oldName, oldNumber, newName, newNumber, pin2);
552            }
553        } catch (RemoteException ex) {
554            // ignore it
555        } catch (SecurityException ex) {
556            if (DBG) log(ex.toString());
557        }
558        if (DBG) log("updateIccRecordInEf: " + success);
559        return success;
560    }
561
562
563    private boolean deleteIccRecordFromEf(int efType, String name, String number, String[] emails,
564            String pin2) {
565        if (DBG) log("deleteIccRecordFromEf: efType=" + efType +
566                ", name=" + name + ", number=" + number + ", emails=" + emails + ", pin2=" + pin2);
567
568        boolean success = false;
569
570        try {
571            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
572                    ServiceManager.getService("simphonebook"));
573            if (iccIpb != null) {
574                success = iccIpb.updateAdnRecordsInEfBySearch(efType,
575                        name, number, "", "", pin2);
576            }
577        } catch (RemoteException ex) {
578            // ignore it
579        } catch (SecurityException ex) {
580            if (DBG) log(ex.toString());
581        }
582        if (DBG) log("deleteIccRecordFromEf: " + success);
583        return success;
584    }
585
586    /**
587     * Loads an AdnRecord into an ArrayList. Must be called with mLock held.
588     *
589     * @param record the ADN record to load from
590     * @param results the array list to put the results in
591     */
592    private void loadRecord(AdnRecord record,
593            ArrayList<ArrayList> results) {
594        if (!record.isEmpty()) {
595            ArrayList<String> contact = new ArrayList<String>();
596            String alphaTag = record.getAlphaTag();
597            String number = record.getNumber();
598            String[] emails = record.getEmails();
599
600            if (DBG) log("loadRecord: " + alphaTag + ", " + number + ",");
601            contact.add(alphaTag);
602            contact.add(number);
603            StringBuilder emailString = new StringBuilder();
604
605            if (emails != null) {
606                for (String email: emails) {
607                    if (DBG) log("Adding email:" + email);
608                    emailString.append(email);
609                    emailString.append(",");
610                }
611                contact.add(emailString.toString());
612            } else {
613                contact.add(null);
614            }
615            results.add(contact);
616        }
617    }
618
619    private void log(String msg) {
620        Log.d(TAG, "[IccProvider] " + msg);
621    }
622
623}
624