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