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.util.Log;
23
24import com.android.internal.telephony.AdnRecord;
25import com.android.internal.telephony.AdnRecordCache;
26import com.android.internal.telephony.IccConstants;
27import com.android.internal.telephony.IccFileHandler;
28import com.android.internal.telephony.IccUtils;
29import com.android.internal.telephony.PhoneBase;
30
31import java.util.ArrayList;
32import java.util.HashMap;
33import java.util.Map;
34
35/**
36 * This class implements reading and parsing USIM records.
37 * Refer to Spec 3GPP TS 31.102 for more details.
38 *
39 * {@hide}
40 */
41public class UsimPhoneBookManager extends Handler implements IccConstants {
42    private static final String LOG_TAG = "GSM";
43    private static final boolean DBG = true;
44    private PbrFile mPbrFile;
45    private Boolean mIsPbrPresent;
46    private IccFileHandler mFh;
47    private AdnRecordCache mAdnCache;
48    private Object mLock = new Object();
49    private ArrayList<AdnRecord> mPhoneBookRecords;
50    private boolean mEmailPresentInIap = false;
51    private int mEmailTagNumberInIap = 0;
52    private ArrayList<byte[]> mIapFileRecord;
53    private ArrayList<byte[]> mEmailFileRecord;
54    private Map<Integer, ArrayList<String>> mEmailsForAdnRec;
55    private boolean mRefreshCache = false;
56
57    private static final int EVENT_PBR_LOAD_DONE = 1;
58    private static final int EVENT_USIM_ADN_LOAD_DONE = 2;
59    private static final int EVENT_IAP_LOAD_DONE = 3;
60    private static final int EVENT_EMAIL_LOAD_DONE = 4;
61
62    private static final int USIM_TYPE1_TAG   = 0xA8;
63    private static final int USIM_TYPE2_TAG   = 0xA9;
64    private static final int USIM_TYPE3_TAG   = 0xAA;
65    private static final int USIM_EFADN_TAG   = 0xC0;
66    private static final int USIM_EFIAP_TAG   = 0xC1;
67    private static final int USIM_EFEXT1_TAG  = 0xC2;
68    private static final int USIM_EFSNE_TAG   = 0xC3;
69    private static final int USIM_EFANR_TAG   = 0xC4;
70    private static final int USIM_EFPBC_TAG   = 0xC5;
71    private static final int USIM_EFGRP_TAG   = 0xC6;
72    private static final int USIM_EFAAS_TAG   = 0xC7;
73    private static final int USIM_EFGSD_TAG   = 0xC8;
74    private static final int USIM_EFUID_TAG   = 0xC9;
75    private static final int USIM_EFEMAIL_TAG = 0xCA;
76    private static final int USIM_EFCCP1_TAG  = 0xCB;
77
78    public UsimPhoneBookManager(IccFileHandler fh, AdnRecordCache cache) {
79        mFh = fh;
80        mPhoneBookRecords = new ArrayList<AdnRecord>();
81        mPbrFile = null;
82        // We assume its present, after the first read this is updated.
83        // So we don't have to read from UICC if its not present on subsequent reads.
84        mIsPbrPresent = true;
85        mAdnCache = cache;
86    }
87
88    public void reset() {
89        mPhoneBookRecords.clear();
90        mIapFileRecord = null;
91        mEmailFileRecord = null;
92        mPbrFile = null;
93        mIsPbrPresent = true;
94        mRefreshCache = false;
95    }
96
97    public ArrayList<AdnRecord> loadEfFilesFromUsim() {
98        synchronized (mLock) {
99            if (!mPhoneBookRecords.isEmpty()) {
100                if (mRefreshCache) {
101                    mRefreshCache = false;
102                    refreshCache();
103                }
104                return mPhoneBookRecords;
105            }
106
107            if (!mIsPbrPresent) return null;
108
109            // Check if the PBR file is present in the cache, if not read it
110            // from the USIM.
111            if (mPbrFile == null) {
112                readPbrFileAndWait();
113            }
114
115            if (mPbrFile == null) return null;
116
117            int numRecs = mPbrFile.mFileIds.size();
118            for (int i = 0; i < numRecs; i++) {
119                readAdnFileAndWait(i);
120                readEmailFileAndWait(i);
121            }
122            // All EF files are loaded, post the response.
123        }
124        return mPhoneBookRecords;
125    }
126
127    private void refreshCache() {
128        if (mPbrFile == null) return;
129        mPhoneBookRecords.clear();
130
131        int numRecs = mPbrFile.mFileIds.size();
132        for (int i = 0; i < numRecs; i++) {
133            readAdnFileAndWait(i);
134        }
135    }
136
137    public void invalidateCache() {
138        mRefreshCache = true;
139    }
140
141    private void readPbrFileAndWait() {
142        mFh.loadEFLinearFixedAll(EF_PBR, obtainMessage(EVENT_PBR_LOAD_DONE));
143        try {
144            mLock.wait();
145        } catch (InterruptedException e) {
146            Log.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait");
147        }
148    }
149
150    private void readEmailFileAndWait(int recNum) {
151        Map <Integer,Integer> fileIds;
152        fileIds = mPbrFile.mFileIds.get(recNum);
153        if (fileIds == null) return;
154
155        if (fileIds.containsKey(USIM_EFEMAIL_TAG)) {
156            int efid = fileIds.get(USIM_EFEMAIL_TAG);
157            // Check if the EFEmail is a Type 1 file or a type 2 file.
158            // If mEmailPresentInIap is true, its a type 2 file.
159            // So we read the IAP file and then read the email records.
160            // instead of reading directly.
161            if (mEmailPresentInIap) {
162                readIapFileAndWait(fileIds.get(USIM_EFIAP_TAG));
163                if (mIapFileRecord == null) {
164                    Log.e(LOG_TAG, "Error: IAP file is empty");
165                    return;
166                }
167            }
168            // Read the EFEmail file.
169            mFh.loadEFLinearFixedAll(fileIds.get(USIM_EFEMAIL_TAG),
170                    obtainMessage(EVENT_EMAIL_LOAD_DONE));
171            try {
172                mLock.wait();
173            } catch (InterruptedException e) {
174                Log.e(LOG_TAG, "Interrupted Exception in readEmailFileAndWait");
175            }
176
177            if (mEmailFileRecord == null) {
178                Log.e(LOG_TAG, "Error: Email file is empty");
179                return;
180            }
181            updatePhoneAdnRecord();
182        }
183
184    }
185
186    private void readIapFileAndWait(int efid) {
187        mFh.loadEFLinearFixedAll(efid, obtainMessage(EVENT_IAP_LOAD_DONE));
188        try {
189            mLock.wait();
190        } catch (InterruptedException e) {
191            Log.e(LOG_TAG, "Interrupted Exception in readIapFileAndWait");
192        }
193    }
194
195    private void updatePhoneAdnRecord() {
196        if (mEmailFileRecord == null) return;
197        int numAdnRecs = mPhoneBookRecords.size();
198        if (mIapFileRecord != null) {
199            // The number of records in the IAP file is same as the number of records in ADN file.
200            // The order of the pointers in an EFIAP shall be the same as the order of file IDs
201            // that appear in the TLV object indicated by Tag 'A9' in the reference file record.
202            // i.e value of mEmailTagNumberInIap
203
204            for (int i = 0; i < numAdnRecs; i++) {
205                byte[] record = null;
206                try {
207                    record = mIapFileRecord.get(i);
208                } catch (IndexOutOfBoundsException e) {
209                    Log.e(LOG_TAG, "Error: Improper ICC card: No IAP record for ADN, continuing");
210                    break;
211                }
212                int recNum = record[mEmailTagNumberInIap];
213
214                if (recNum != -1) {
215                    String[] emails = new String[1];
216                    // SIM record numbers are 1 based
217                    emails[0] = readEmailRecord(recNum - 1);
218                    AdnRecord rec = mPhoneBookRecords.get(i);
219                    if (rec != null) {
220                        rec.setEmails(emails);
221                    } else {
222                        // might be a record with only email
223                        rec = new AdnRecord("", "", emails);
224                    }
225                    mPhoneBookRecords.set(i, rec);
226                }
227            }
228        }
229
230        // ICC cards can be made such that they have an IAP file but all
231        // records are empty. So we read both type 1 and type 2 file
232        // email records, just to be sure.
233
234        int len = mPhoneBookRecords.size();
235        // Type 1 file, the number of records is the same as the number of
236        // records in the ADN file.
237        if (mEmailsForAdnRec == null) {
238            parseType1EmailFile(len);
239        }
240        for (int i = 0; i < numAdnRecs; i++) {
241            ArrayList<String> emailList = null;
242            try {
243                emailList = mEmailsForAdnRec.get(i);
244            } catch (IndexOutOfBoundsException e) {
245                break;
246            }
247            if (emailList == null) continue;
248
249            AdnRecord rec = mPhoneBookRecords.get(i);
250
251            String[] emails = new String[emailList.size()];
252            System.arraycopy(emailList.toArray(), 0, emails, 0, emailList.size());
253            rec.setEmails(emails);
254            mPhoneBookRecords.set(i, rec);
255        }
256    }
257
258    void parseType1EmailFile(int numRecs) {
259        mEmailsForAdnRec = new HashMap<Integer, ArrayList<String>>();
260        byte[] emailRec = null;
261        for (int i = 0; i < numRecs; i++) {
262            try {
263                emailRec = mEmailFileRecord.get(i);
264            } catch (IndexOutOfBoundsException e) {
265                Log.e(LOG_TAG, "Error: Improper ICC card: No email record for ADN, continuing");
266                break;
267            }
268            int adnRecNum = emailRec[emailRec.length - 1];
269
270            if (adnRecNum == -1) {
271                continue;
272            }
273
274            String email = readEmailRecord(i);
275
276            if (email == null || email.equals("")) {
277                continue;
278            }
279
280            // SIM record numbers are 1 based.
281            ArrayList<String> val = mEmailsForAdnRec.get(adnRecNum - 1);
282            if (val == null) {
283                val = new ArrayList<String>();
284            }
285            val.add(email);
286            // SIM record numbers are 1 based.
287            mEmailsForAdnRec.put(adnRecNum - 1, val);
288        }
289    }
290
291    private String readEmailRecord(int recNum) {
292        byte[] emailRec = null;
293        try {
294            emailRec = mEmailFileRecord.get(recNum);
295        } catch (IndexOutOfBoundsException e) {
296            return null;
297        }
298
299        // The length of the record is X+2 byte, where X bytes is the email address
300        String email = IccUtils.adnStringFieldToString(emailRec, 0, emailRec.length - 2);
301        return email;
302    }
303
304    private void readAdnFileAndWait(int recNum) {
305        Map <Integer,Integer> fileIds;
306        fileIds = mPbrFile.mFileIds.get(recNum);
307        if (fileIds == null || fileIds.isEmpty()) return;
308
309
310        int extEf = 0;
311        // Only call fileIds.get while EFEXT1_TAG is available
312        if (fileIds.containsKey(USIM_EFEXT1_TAG)) {
313            extEf = fileIds.get(USIM_EFEXT1_TAG);
314        }
315
316        mAdnCache.requestLoadAllAdnLike(fileIds.get(USIM_EFADN_TAG),
317            extEf, obtainMessage(EVENT_USIM_ADN_LOAD_DONE));
318        try {
319            mLock.wait();
320        } catch (InterruptedException e) {
321            Log.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait");
322        }
323    }
324
325    private void createPbrFile(ArrayList<byte[]> records) {
326        if (records == null) {
327            mPbrFile = null;
328            mIsPbrPresent = false;
329            return;
330        }
331        mPbrFile = new PbrFile(records);
332    }
333
334    @Override
335    public void handleMessage(Message msg) {
336        AsyncResult ar;
337
338        switch(msg.what) {
339        case EVENT_PBR_LOAD_DONE:
340            ar = (AsyncResult) msg.obj;
341            if (ar.exception == null) {
342                createPbrFile((ArrayList<byte[]>)ar.result);
343            }
344            synchronized (mLock) {
345                mLock.notify();
346            }
347            break;
348        case EVENT_USIM_ADN_LOAD_DONE:
349            log("Loading USIM ADN records done");
350            ar = (AsyncResult) msg.obj;
351            if (ar.exception == null) {
352                mPhoneBookRecords.addAll((ArrayList<AdnRecord>)ar.result);
353            }
354            synchronized (mLock) {
355                mLock.notify();
356            }
357            break;
358        case EVENT_IAP_LOAD_DONE:
359            log("Loading USIM IAP records done");
360            ar = (AsyncResult) msg.obj;
361            if (ar.exception == null) {
362                mIapFileRecord = ((ArrayList<byte[]>)ar.result);
363            }
364            synchronized (mLock) {
365                mLock.notify();
366            }
367            break;
368        case EVENT_EMAIL_LOAD_DONE:
369            log("Loading USIM Email records done");
370            ar = (AsyncResult) msg.obj;
371            if (ar.exception == null) {
372                mEmailFileRecord = ((ArrayList<byte[]>)ar.result);
373            }
374
375            synchronized (mLock) {
376                mLock.notify();
377            }
378            break;
379        }
380    }
381
382    private class PbrFile {
383        // RecNum <EF Tag, efid>
384        HashMap<Integer,Map<Integer,Integer>> mFileIds;
385
386        PbrFile(ArrayList<byte[]> records) {
387            mFileIds = new HashMap<Integer, Map<Integer, Integer>>();
388            SimTlv recTlv;
389            int recNum = 0;
390            for (byte[] record: records) {
391                recTlv = new SimTlv(record, 0, record.length);
392                parseTag(recTlv, recNum);
393                recNum ++;
394            }
395        }
396
397        void parseTag(SimTlv tlv, int recNum) {
398            SimTlv tlvEf;
399            int tag;
400            byte[] data;
401            Map<Integer, Integer> val = new HashMap<Integer, Integer>();
402            do {
403                tag = tlv.getTag();
404                switch(tag) {
405                case USIM_TYPE1_TAG: // A8
406                case USIM_TYPE3_TAG: // AA
407                case USIM_TYPE2_TAG: // A9
408                    data = tlv.getData();
409                    tlvEf = new SimTlv(data, 0, data.length);
410                    parseEf(tlvEf, val, tag);
411                    break;
412                }
413            } while (tlv.nextObject());
414            mFileIds.put(recNum, val);
415        }
416
417        void parseEf(SimTlv tlv, Map<Integer, Integer> val, int parentTag) {
418            int tag;
419            byte[] data;
420            int tagNumberWithinParentTag = 0;
421            do {
422                tag = tlv.getTag();
423                if (parentTag == USIM_TYPE2_TAG && tag == USIM_EFEMAIL_TAG) {
424                    mEmailPresentInIap = true;
425                    mEmailTagNumberInIap = tagNumberWithinParentTag;
426                }
427                switch(tag) {
428                    case USIM_EFEMAIL_TAG:
429                    case USIM_EFADN_TAG:
430                    case USIM_EFEXT1_TAG:
431                    case USIM_EFANR_TAG:
432                    case USIM_EFPBC_TAG:
433                    case USIM_EFGRP_TAG:
434                    case USIM_EFAAS_TAG:
435                    case USIM_EFGSD_TAG:
436                    case USIM_EFUID_TAG:
437                    case USIM_EFCCP1_TAG:
438                    case USIM_EFIAP_TAG:
439                    case USIM_EFSNE_TAG:
440                        data = tlv.getData();
441                        int efid = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
442                        val.put(tag, efid);
443                        break;
444                }
445                tagNumberWithinParentTag ++;
446            } while(tlv.nextObject());
447        }
448    }
449
450    private void log(String msg) {
451        if(DBG) Log.d(LOG_TAG, msg);
452    }
453}
454