1/*
2 * Copyright (C) 2009 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.gsm;
18
19import android.os.AsyncResult;
20import android.os.Handler;
21import android.os.Message;
22import android.telephony.Rlog;
23import android.util.SparseArray;
24import android.util.SparseIntArray;
25
26import com.android.internal.telephony.uicc.AdnRecord;
27import com.android.internal.telephony.uicc.AdnRecordCache;
28import com.android.internal.telephony.uicc.IccConstants;
29import com.android.internal.telephony.uicc.IccFileHandler;
30import com.android.internal.telephony.uicc.IccUtils;
31import java.util.ArrayList;
32
33/**
34 * This class implements reading and parsing USIM records.
35 * Refer to Spec 3GPP TS 31.102 for more details.
36 *
37 * {@hide}
38 */
39public class UsimPhoneBookManager extends Handler implements IccConstants {
40    private static final String LOG_TAG = "UsimPhoneBookManager";
41    private static final boolean DBG = true;
42    private ArrayList<PbrRecord> mPbrRecords;
43    private Boolean mIsPbrPresent;
44    private IccFileHandler mFh;
45    private AdnRecordCache mAdnCache;
46    private Object mLock = new Object();
47    private ArrayList<AdnRecord> mPhoneBookRecords;
48    private ArrayList<byte[]> mIapFileRecord;
49    private ArrayList<byte[]> mEmailFileRecord;
50
51    // email list for each ADN record. The key would be
52    // ADN's efid << 8 + record #
53    private SparseArray<ArrayList<String>> mEmailsForAdnRec;
54
55    // SFI to ADN Efid mapping table
56    private SparseIntArray mSfiEfidTable;
57
58    private boolean mRefreshCache = false;
59
60
61    private static final int EVENT_PBR_LOAD_DONE = 1;
62    private static final int EVENT_USIM_ADN_LOAD_DONE = 2;
63    private static final int EVENT_IAP_LOAD_DONE = 3;
64    private static final int EVENT_EMAIL_LOAD_DONE = 4;
65
66    private static final int USIM_TYPE1_TAG   = 0xA8;
67    private static final int USIM_TYPE2_TAG   = 0xA9;
68    private static final int USIM_TYPE3_TAG   = 0xAA;
69    private static final int USIM_EFADN_TAG   = 0xC0;
70    private static final int USIM_EFIAP_TAG   = 0xC1;
71    private static final int USIM_EFEXT1_TAG  = 0xC2;
72    private static final int USIM_EFSNE_TAG   = 0xC3;
73    private static final int USIM_EFANR_TAG   = 0xC4;
74    private static final int USIM_EFPBC_TAG   = 0xC5;
75    private static final int USIM_EFGRP_TAG   = 0xC6;
76    private static final int USIM_EFAAS_TAG   = 0xC7;
77    private static final int USIM_EFGSD_TAG   = 0xC8;
78    private static final int USIM_EFUID_TAG   = 0xC9;
79    private static final int USIM_EFEMAIL_TAG = 0xCA;
80    private static final int USIM_EFCCP1_TAG  = 0xCB;
81
82    private static final int INVALID_SFI = -1;
83    private static final byte INVALID_BYTE = -1;
84
85    // class File represent a PBR record TLV object which points to the rest of the phonebook EFs
86    private class File {
87        // Phonebook reference file constructed tag defined in 3GPP TS 31.102
88        // section 4.4.2.1 table 4.1
89        private final int mParentTag;
90        // EFID of the file
91        private final int mEfid;
92        // SFI (Short File Identification) of the file. 0xFF indicates invalid SFI.
93        private final int mSfi;
94        // The order of this tag showing in the PBR record.
95        private final int mIndex;
96
97        File(int parentTag, int efid, int sfi, int index) {
98            mParentTag = parentTag;
99            mEfid = efid;
100            mSfi = sfi;
101            mIndex = index;
102        }
103
104        public int getParentTag() { return mParentTag; }
105        public int getEfid() { return mEfid; }
106        public int getSfi() { return mSfi; }
107        public int getIndex() { return mIndex; }
108    }
109
110    public UsimPhoneBookManager(IccFileHandler fh, AdnRecordCache cache) {
111        mFh = fh;
112        mPhoneBookRecords = new ArrayList<AdnRecord>();
113        mPbrRecords = null;
114        // We assume its present, after the first read this is updated.
115        // So we don't have to read from UICC if its not present on subsequent reads.
116        mIsPbrPresent = true;
117        mAdnCache = cache;
118        mEmailsForAdnRec = new SparseArray<ArrayList<String>>();
119        mSfiEfidTable = new SparseIntArray();
120    }
121
122    public void reset() {
123        mPhoneBookRecords.clear();
124        mIapFileRecord = null;
125        mEmailFileRecord = null;
126        mPbrRecords = null;
127        mIsPbrPresent = true;
128        mRefreshCache = false;
129        mEmailsForAdnRec.clear();
130        mSfiEfidTable.clear();
131    }
132
133    // Load all phonebook related EFs from the SIM.
134    public ArrayList<AdnRecord> loadEfFilesFromUsim() {
135        synchronized (mLock) {
136            if (!mPhoneBookRecords.isEmpty()) {
137                if (mRefreshCache) {
138                    mRefreshCache = false;
139                    refreshCache();
140                }
141                return mPhoneBookRecords;
142            }
143
144            if (!mIsPbrPresent) return null;
145
146            // Check if the PBR file is present in the cache, if not read it
147            // from the USIM.
148            if (mPbrRecords == null) {
149                readPbrFileAndWait();
150            }
151
152            if (mPbrRecords == null)
153                return null;
154
155            int numRecs = mPbrRecords.size();
156
157            log("loadEfFilesFromUsim: Loading adn and emails");
158            for (int i = 0; i < numRecs; i++) {
159                readAdnFileAndWait(i);
160                readEmailFileAndWait(i);
161            }
162
163            updatePhoneAdnRecord();
164            // All EF files are loaded, return all the records
165        }
166        return mPhoneBookRecords;
167    }
168
169    // Refresh the phonebook cache.
170    private void refreshCache() {
171        if (mPbrRecords == null) return;
172        mPhoneBookRecords.clear();
173
174        int numRecs = mPbrRecords.size();
175        for (int i = 0; i < numRecs; i++) {
176            readAdnFileAndWait(i);
177        }
178    }
179
180    // Invalidate the phonebook cache.
181    public void invalidateCache() {
182        mRefreshCache = true;
183    }
184
185    // Read the phonebook reference file EF_PBR.
186    private void readPbrFileAndWait() {
187        mFh.loadEFLinearFixedAll(EF_PBR, obtainMessage(EVENT_PBR_LOAD_DONE));
188        try {
189            mLock.wait();
190        } catch (InterruptedException e) {
191            Rlog.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait");
192        }
193    }
194
195    // Read EF_EMAIL which contains the email records.
196    private void readEmailFileAndWait(int recId) {
197        SparseArray<File> files;
198        files = mPbrRecords.get(recId).mFileIds;
199        if (files == null) return;
200
201        File email = files.get(USIM_EFEMAIL_TAG);
202        if (email != null) {
203
204            /**
205             * Check if the EF_EMAIL is a Type 1 file or a type 2 file.
206             * If mEmailPresentInIap is true, its a type 2 file.
207             * So we read the IAP file and then read the email records.
208             * instead of reading directly.
209             */
210            if (email.getParentTag() == USIM_TYPE2_TAG) {
211                if (files.get(USIM_EFIAP_TAG) == null) {
212                    Rlog.e(LOG_TAG, "Can't locate EF_IAP in EF_PBR.");
213                    return;
214                }
215
216                log("EF_IAP exists. Loading EF_IAP to retrieve the index.");
217                readIapFileAndWait(files.get(USIM_EFIAP_TAG).getEfid());
218                if (mIapFileRecord == null) {
219                    Rlog.e(LOG_TAG, "Error: IAP file is empty");
220                    return;
221                }
222
223                log("EF_EMAIL order in PBR record: " + email.getIndex());
224            }
225
226            int emailEfid = email.getEfid();
227            log("EF_EMAIL exists in PBR. efid = 0x" +
228                    Integer.toHexString(emailEfid).toUpperCase());
229
230            /**
231             * Make sure this EF_EMAIL was never read earlier. Sometimes two PBR record points
232             */
233            // to the same EF_EMAIL
234            for (int i = 0; i < recId; i++) {
235                if (mPbrRecords.get(i) != null) {
236                    SparseArray<File> previousFileIds = mPbrRecords.get(i).mFileIds;
237                    if (previousFileIds != null) {
238                        File id = previousFileIds.get(USIM_EFEMAIL_TAG);
239                        if (id != null && id.getEfid() == emailEfid) {
240                            log("Skipped this EF_EMAIL which was loaded earlier");
241                            return;
242                        }
243                    }
244                }
245            }
246
247            // Read the EFEmail file.
248            mFh.loadEFLinearFixedAll(emailEfid,
249                    obtainMessage(EVENT_EMAIL_LOAD_DONE));
250            try {
251                mLock.wait();
252            } catch (InterruptedException e) {
253                Rlog.e(LOG_TAG, "Interrupted Exception in readEmailFileAndWait");
254            }
255
256            if (mEmailFileRecord == null) {
257                Rlog.e(LOG_TAG, "Error: Email file is empty");
258                return;
259            }
260
261            // Build email list
262            if (email.getParentTag() == USIM_TYPE2_TAG && mIapFileRecord != null) {
263                // If the tag is type 2 and EF_IAP exists, we need to build tpe 2 email list
264                buildType2EmailList(recId);
265            }
266            else {
267                // If one the followings is true, we build type 1 email list
268                // 1. EF_IAP does not exist or it is failed to load
269                // 2. ICC cards can be made such that they have an IAP file but all
270                //    records are empty. In that case buildType2EmailList will fail and
271                //    we need to build type 1 email list.
272
273                // Build type 1 email list
274                buildType1EmailList(recId);
275            }
276        }
277    }
278
279    // Build type 1 email list
280    private void buildType1EmailList(int recId) {
281        /**
282         * If this is type 1, the number of records in EF_EMAIL would be same as the record number
283         * in the master/reference file.
284         */
285        if (mPbrRecords.get(recId) == null)
286            return;
287
288        int numRecs = mPbrRecords.get(recId).mMasterFileRecordNum;
289        log("Building type 1 email list. recId = "
290                + recId + ", numRecs = " + numRecs);
291
292        byte[] emailRec;
293        for (int i = 0; i < numRecs; i++) {
294            try {
295                emailRec = mEmailFileRecord.get(i);
296            } catch (IndexOutOfBoundsException e) {
297                Rlog.e(LOG_TAG, "Error: Improper ICC card: No email record for ADN, continuing");
298                break;
299            }
300
301            /**
302             *  3GPP TS 31.102 4.4.2.13 EF_EMAIL (e-mail address)
303             *
304             *  The fields below are mandatory if and only if the file
305             *  is not type 1 (as specified in EF_PBR)
306             *
307             *  Byte [X + 1]: ADN file SFI (Short File Identification)
308             *  Byte [X + 2]: ADN file Record Identifier
309             */
310            int sfi = emailRec[emailRec.length - 2];
311            int adnRecId = emailRec[emailRec.length - 1];
312
313            String email = readEmailRecord(i);
314
315            if (email == null || email.equals("")) {
316                continue;
317            }
318
319            // Get the associated ADN's efid first.
320            int adnEfid = 0;
321            if (sfi == INVALID_SFI || mSfiEfidTable.get(sfi) == 0) {
322
323                // If SFI is invalid or cannot be mapped to any ADN, use the ADN's efid
324                // in the same PBR files.
325                File file = mPbrRecords.get(recId).mFileIds.get(USIM_EFADN_TAG);
326                if (file == null)
327                    continue;
328                adnEfid = file.getEfid();
329            }
330            else {
331                adnEfid = mSfiEfidTable.get(sfi);
332            }
333            /**
334             * SIM record numbers are 1 based.
335             * The key is constructed by efid and record index.
336             */
337            int index = (((adnEfid & 0xFFFF) << 8) | ((adnRecId - 1) & 0xFF));
338            ArrayList<String> emailList = mEmailsForAdnRec.get(index);
339            if (emailList == null) {
340                emailList = new ArrayList<String>();
341            }
342            log("Adding email #" + i + " list to index 0x" +
343                    Integer.toHexString(index).toUpperCase());
344            emailList.add(email);
345            mEmailsForAdnRec.put(index, emailList);
346        }
347    }
348
349    // Build type 2 email list
350    private boolean buildType2EmailList(int recId) {
351
352        if (mPbrRecords.get(recId) == null)
353            return false;
354
355        int numRecs = mPbrRecords.get(recId).mMasterFileRecordNum;
356        log("Building type 2 email list. recId = "
357                + recId + ", numRecs = " + numRecs);
358
359        /**
360         * 3GPP TS 31.102 4.4.2.1 EF_PBR (Phone Book Reference file) table 4.1
361
362         * The number of records in the IAP file is same as the number of records in the master
363         * file (e.g EF_ADN). The order of the pointers in an EF_IAP shall be the same as the
364         * order of file IDs that appear in the TLV object indicated by Tag 'A9' in the
365         * reference file record (e.g value of mEmailTagNumberInIap)
366         */
367
368        File adnFile = mPbrRecords.get(recId).mFileIds.get(USIM_EFADN_TAG);
369        if (adnFile == null) {
370            Rlog.e(LOG_TAG, "Error: Improper ICC card: EF_ADN does not exist in PBR files");
371            return false;
372        }
373        int adnEfid = adnFile.getEfid();
374
375        for (int i = 0; i < numRecs; i++) {
376            byte[] record;
377            int emailRecId;
378            try {
379                record = mIapFileRecord.get(i);
380                emailRecId =
381                        record[mPbrRecords.get(recId).mFileIds.get(USIM_EFEMAIL_TAG).getIndex()];
382            } catch (IndexOutOfBoundsException e) {
383                Rlog.e(LOG_TAG, "Error: Improper ICC card: Corrupted EF_IAP");
384                continue;
385            }
386
387            String email = readEmailRecord(emailRecId - 1);
388            if (email != null && !email.equals("")) {
389                // The key is constructed by efid and record index.
390                int index = (((adnEfid & 0xFFFF) << 8) | (i & 0xFF));
391                ArrayList<String> emailList = mEmailsForAdnRec.get(index);
392                if (emailList == null) {
393                    emailList = new ArrayList<String>();
394                }
395                emailList.add(email);
396                log("Adding email list to index 0x" +
397                        Integer.toHexString(index).toUpperCase());
398                mEmailsForAdnRec.put(index, emailList);
399            }
400        }
401        return true;
402    }
403
404    // Read Phonebook Index Admistration EF_IAP file
405    private void readIapFileAndWait(int efid) {
406        mFh.loadEFLinearFixedAll(efid, obtainMessage(EVENT_IAP_LOAD_DONE));
407        try {
408            mLock.wait();
409        } catch (InterruptedException e) {
410            Rlog.e(LOG_TAG, "Interrupted Exception in readIapFileAndWait");
411        }
412    }
413
414    private void updatePhoneAdnRecord() {
415
416        int numAdnRecs = mPhoneBookRecords.size();
417
418        for (int i = 0; i < numAdnRecs; i++) {
419
420            AdnRecord rec = mPhoneBookRecords.get(i);
421
422            int adnEfid = rec.getEfid();
423            int adnRecId = rec.getRecId();
424
425            int index = (((adnEfid & 0xFFFF) << 8) | ((adnRecId - 1) & 0xFF));
426
427            ArrayList<String> emailList;
428            try {
429                emailList = mEmailsForAdnRec.get(index);
430            } catch (IndexOutOfBoundsException e) {
431                continue;
432            }
433
434            if (emailList == null)
435                continue;
436
437            String[] emails = new String[emailList.size()];
438            System.arraycopy(emailList.toArray(), 0, emails, 0, emailList.size());
439            rec.setEmails(emails);
440            log("Adding email list to ADN (0x" +
441                    Integer.toHexString(mPhoneBookRecords.get(i).getEfid()).toUpperCase() +
442                    ") record #" + mPhoneBookRecords.get(i).getRecId());
443            mPhoneBookRecords.set(i, rec);
444        }
445    }
446
447    // Read email from the record of EF_EMAIL
448    private String readEmailRecord(int recId) {
449        byte[] emailRec;
450        try {
451            emailRec = mEmailFileRecord.get(recId);
452        } catch (IndexOutOfBoundsException e) {
453            return null;
454        }
455
456        // The length of the record is X+2 byte, where X bytes is the email address
457        return IccUtils.adnStringFieldToString(emailRec, 0, emailRec.length - 2);
458    }
459
460    // Read EF_ADN file
461    private void readAdnFileAndWait(int recId) {
462        SparseArray<File> files;
463        files = mPbrRecords.get(recId).mFileIds;
464        if (files == null || files.size() == 0) return;
465
466        int extEf = 0;
467        // Only call fileIds.get while EF_EXT1_TAG is available
468        if (files.get(USIM_EFEXT1_TAG) != null) {
469            extEf = files.get(USIM_EFEXT1_TAG).getEfid();
470        }
471
472        if (files.get(USIM_EFADN_TAG) == null)
473            return;
474
475        int previousSize = mPhoneBookRecords.size();
476        mAdnCache.requestLoadAllAdnLike(files.get(USIM_EFADN_TAG).getEfid(),
477            extEf, obtainMessage(EVENT_USIM_ADN_LOAD_DONE));
478        try {
479            mLock.wait();
480        } catch (InterruptedException e) {
481            Rlog.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait");
482        }
483
484        /**
485         * The recent added ADN record # would be the reference record size
486         * for the rest of EFs associated within this PBR.
487         */
488        mPbrRecords.get(recId).mMasterFileRecordNum = mPhoneBookRecords.size() - previousSize;
489    }
490
491    // Create the phonebook reference file based on EF_PBR
492    private void createPbrFile(ArrayList<byte[]> records) {
493        if (records == null) {
494            mPbrRecords = null;
495            mIsPbrPresent = false;
496            return;
497        }
498
499        mPbrRecords = new ArrayList<PbrRecord>();
500        for (int i = 0; i < records.size(); i++) {
501            // Some cards have two records but the 2nd record is filled with all invalid char 0xff.
502            // So we need to check if the record is valid or not before adding into the PBR records.
503            if (records.get(i)[0] != INVALID_BYTE) {
504                mPbrRecords.add(new PbrRecord(records.get(i)));
505            }
506        }
507
508        for (PbrRecord record : mPbrRecords) {
509            File file = record.mFileIds.get(USIM_EFADN_TAG);
510            // If the file does not contain EF_ADN, we'll just skip it.
511            if (file != null) {
512                int sfi = file.getSfi();
513                if (sfi != INVALID_SFI) {
514                    mSfiEfidTable.put(sfi, record.mFileIds.get(USIM_EFADN_TAG).getEfid());
515                }
516            }
517        }
518    }
519
520    @Override
521    public void handleMessage(Message msg) {
522        AsyncResult ar;
523
524        switch(msg.what) {
525        case EVENT_PBR_LOAD_DONE:
526            log("Loading PBR records done");
527            ar = (AsyncResult) msg.obj;
528            if (ar.exception == null) {
529                createPbrFile((ArrayList<byte[]>)ar.result);
530            }
531            synchronized (mLock) {
532                mLock.notify();
533            }
534            break;
535        case EVENT_USIM_ADN_LOAD_DONE:
536            log("Loading USIM ADN records done");
537            ar = (AsyncResult) msg.obj;
538            if (ar.exception == null) {
539                mPhoneBookRecords.addAll((ArrayList<AdnRecord>)ar.result);
540            }
541            synchronized (mLock) {
542                mLock.notify();
543            }
544            break;
545        case EVENT_IAP_LOAD_DONE:
546            log("Loading USIM IAP records done");
547            ar = (AsyncResult) msg.obj;
548            if (ar.exception == null) {
549                mIapFileRecord = ((ArrayList<byte[]>)ar.result);
550            }
551            synchronized (mLock) {
552                mLock.notify();
553            }
554            break;
555        case EVENT_EMAIL_LOAD_DONE:
556            log("Loading USIM Email records done");
557            ar = (AsyncResult) msg.obj;
558            if (ar.exception == null) {
559                mEmailFileRecord = ((ArrayList<byte[]>)ar.result);
560            }
561
562            synchronized (mLock) {
563                mLock.notify();
564            }
565            break;
566        }
567    }
568
569    // PbrRecord represents a record in EF_PBR
570    private class PbrRecord {
571        // TLV tags
572        private SparseArray<File> mFileIds;
573
574        /**
575         * 3GPP TS 31.102 4.4.2.1 EF_PBR (Phone Book Reference file)
576         * If this is type 1 files, files that contain as many records as the
577         * reference/master file (EF_ADN, EF_ADN1) and are linked on record number
578         * bases (Rec1 -> Rec1). The master file record number is the reference.
579         */
580        private int mMasterFileRecordNum;
581
582        PbrRecord(byte[] record) {
583            mFileIds = new SparseArray<File>();
584            SimTlv recTlv;
585            log("PBR rec: " + IccUtils.bytesToHexString(record));
586            recTlv = new SimTlv(record, 0, record.length);
587            parseTag(recTlv);
588        }
589
590        void parseTag(SimTlv tlv) {
591            SimTlv tlvEfSfi;
592            int tag;
593            byte[] data;
594
595            do {
596                tag = tlv.getTag();
597                switch(tag) {
598                case USIM_TYPE1_TAG: // A8
599                case USIM_TYPE3_TAG: // AA
600                case USIM_TYPE2_TAG: // A9
601                    data = tlv.getData();
602                    tlvEfSfi = new SimTlv(data, 0, data.length);
603                    parseEfAndSFI(tlvEfSfi, tag);
604                    break;
605                }
606            } while (tlv.nextObject());
607        }
608
609        void parseEfAndSFI(SimTlv tlv, int parentTag) {
610            int tag;
611            byte[] data;
612            int tagNumberWithinParentTag = 0;
613            do {
614                tag = tlv.getTag();
615                switch(tag) {
616                    case USIM_EFEMAIL_TAG:
617                    case USIM_EFADN_TAG:
618                    case USIM_EFEXT1_TAG:
619                    case USIM_EFANR_TAG:
620                    case USIM_EFPBC_TAG:
621                    case USIM_EFGRP_TAG:
622                    case USIM_EFAAS_TAG:
623                    case USIM_EFGSD_TAG:
624                    case USIM_EFUID_TAG:
625                    case USIM_EFCCP1_TAG:
626                    case USIM_EFIAP_TAG:
627                    case USIM_EFSNE_TAG:
628                        /** 3GPP TS 31.102, 4.4.2.1 EF_PBR (Phone Book Reference file)
629                         *
630                         * The SFI value assigned to an EF which is indicated in EF_PBR shall
631                         * correspond to the SFI indicated in the TLV object in EF_PBR.
632
633                         * The primitive tag identifies clearly the type of data, its value
634                         * field indicates the file identifier and, if applicable, the SFI
635                         * value of the specified EF. That is, the length value of a primitive
636                         * tag indicates if an SFI value is available for the EF or not:
637                         * - Length = '02' Value: 'EFID (2 bytes)'
638                         * - Length = '03' Value: 'EFID (2 bytes)', 'SFI (1 byte)'
639                         */
640
641                        int sfi = INVALID_SFI;
642                        data = tlv.getData();
643
644                        if (data.length < 2 || data.length > 3) {
645                            log("Invalid TLV length: " + data.length);
646                            break;
647                        }
648
649                        if (data.length == 3) {
650                            sfi = data[2] & 0xFF;
651                        }
652
653                        int efid = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
654
655                        mFileIds.put(tag, new File(parentTag, efid, sfi, tagNumberWithinParentTag));
656                        break;
657                }
658                tagNumberWithinParentTag++;
659            } while(tlv.nextObject());
660        }
661    }
662
663    private void log(String msg) {
664        if(DBG) Rlog.d(LOG_TAG, msg);
665    }
666}