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 
17 package com.android.internal.telephony.gsm;
18 
19 import android.os.AsyncResult;
20 import android.os.Handler;
21 import android.os.Message;
22 import android.telephony.Rlog;
23 import android.util.SparseArray;
24 import android.util.SparseIntArray;
25 
26 import com.android.internal.telephony.uicc.AdnRecord;
27 import com.android.internal.telephony.uicc.AdnRecordCache;
28 import com.android.internal.telephony.uicc.IccConstants;
29 import com.android.internal.telephony.uicc.IccFileHandler;
30 import com.android.internal.telephony.uicc.IccUtils;
31 import 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  */
39 public 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 
File(int parentTag, int efid, int sfi, int index)97         File(int parentTag, int efid, int sfi, int index) {
98             mParentTag = parentTag;
99             mEfid = efid;
100             mSfi = sfi;
101             mIndex = index;
102         }
103 
getParentTag()104         public int getParentTag() { return mParentTag; }
getEfid()105         public int getEfid() { return mEfid; }
getSfi()106         public int getSfi() { return mSfi; }
getIndex()107         public int getIndex() { return mIndex; }
108     }
109 
UsimPhoneBookManager(IccFileHandler fh, AdnRecordCache cache)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 
reset()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.
loadEfFilesFromUsim()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.
refreshCache()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.
invalidateCache()181     public void invalidateCache() {
182         mRefreshCache = true;
183     }
184 
185     // Read the phonebook reference file EF_PBR.
readPbrFileAndWait()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.
readEmailFileAndWait(int recId)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
buildType1EmailList(int recId)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
buildType2EmailList(int recId)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
readIapFileAndWait(int efid)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 
updatePhoneAdnRecord()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
readEmailRecord(int recId)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
readAdnFileAndWait(int recId)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
createPbrFile(ArrayList<byte[]> records)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
handleMessage(Message msg)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 
PbrRecord(byte[] record)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 
parseTag(SimTlv tlv)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 
parseEfAndSFI(SimTlv tlv, int parentTag)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 
log(String msg)663     private void log(String msg) {
664         if(DBG) Rlog.d(LOG_TAG, msg);
665     }
666 }