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