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 ar = (AsyncResult) msg.obj; 527 if (ar.exception == null) { 528 createPbrFile((ArrayList<byte[]>)ar.result); 529 } 530 synchronized (mLock) { 531 mLock.notify(); 532 } 533 break; 534 case EVENT_USIM_ADN_LOAD_DONE: 535 log("Loading USIM ADN records done"); 536 ar = (AsyncResult) msg.obj; 537 if (ar.exception == null) { 538 mPhoneBookRecords.addAll((ArrayList<AdnRecord>)ar.result); 539 } 540 synchronized (mLock) { 541 mLock.notify(); 542 } 543 break; 544 case EVENT_IAP_LOAD_DONE: 545 log("Loading USIM IAP records done"); 546 ar = (AsyncResult) msg.obj; 547 if (ar.exception == null) { 548 mIapFileRecord = ((ArrayList<byte[]>)ar.result); 549 } 550 synchronized (mLock) { 551 mLock.notify(); 552 } 553 break; 554 case EVENT_EMAIL_LOAD_DONE: 555 log("Loading USIM Email records done"); 556 ar = (AsyncResult) msg.obj; 557 if (ar.exception == null) { 558 mEmailFileRecord = ((ArrayList<byte[]>)ar.result); 559 } 560 561 synchronized (mLock) { 562 mLock.notify(); 563 } 564 break; 565 } 566 } 567 568 // PbrRecord represents a record in EF_PBR 569 private class PbrRecord { 570 // TLV tags 571 private SparseArray<File> mFileIds; 572 573 /** 574 * 3GPP TS 31.102 4.4.2.1 EF_PBR (Phone Book Reference file) 575 * If this is type 1 files, files that contain as many records as the 576 * reference/master file (EF_ADN, EF_ADN1) and are linked on record number 577 * bases (Rec1 -> Rec1). The master file record number is the reference. 578 */ 579 private int mMasterFileRecordNum; 580 PbrRecord(byte[] record)581 PbrRecord(byte[] record) { 582 mFileIds = new SparseArray<File>(); 583 SimTlv recTlv; 584 log("PBR rec: " + IccUtils.bytesToHexString(record)); 585 recTlv = new SimTlv(record, 0, record.length); 586 parseTag(recTlv); 587 } 588 parseTag(SimTlv tlv)589 void parseTag(SimTlv tlv) { 590 SimTlv tlvEfSfi; 591 int tag; 592 byte[] data; 593 594 do { 595 tag = tlv.getTag(); 596 switch(tag) { 597 case USIM_TYPE1_TAG: // A8 598 case USIM_TYPE3_TAG: // AA 599 case USIM_TYPE2_TAG: // A9 600 data = tlv.getData(); 601 tlvEfSfi = new SimTlv(data, 0, data.length); 602 parseEfAndSFI(tlvEfSfi, tag); 603 break; 604 } 605 } while (tlv.nextObject()); 606 } 607 parseEfAndSFI(SimTlv tlv, int parentTag)608 void parseEfAndSFI(SimTlv tlv, int parentTag) { 609 int tag; 610 byte[] data; 611 int tagNumberWithinParentTag = 0; 612 do { 613 tag = tlv.getTag(); 614 switch(tag) { 615 case USIM_EFEMAIL_TAG: 616 case USIM_EFADN_TAG: 617 case USIM_EFEXT1_TAG: 618 case USIM_EFANR_TAG: 619 case USIM_EFPBC_TAG: 620 case USIM_EFGRP_TAG: 621 case USIM_EFAAS_TAG: 622 case USIM_EFGSD_TAG: 623 case USIM_EFUID_TAG: 624 case USIM_EFCCP1_TAG: 625 case USIM_EFIAP_TAG: 626 case USIM_EFSNE_TAG: 627 /** 3GPP TS 31.102, 4.4.2.1 EF_PBR (Phone Book Reference file) 628 * 629 * The SFI value assigned to an EF which is indicated in EF_PBR shall 630 * correspond to the SFI indicated in the TLV object in EF_PBR. 631 632 * The primitive tag identifies clearly the type of data, its value 633 * field indicates the file identifier and, if applicable, the SFI 634 * value of the specified EF. That is, the length value of a primitive 635 * tag indicates if an SFI value is available for the EF or not: 636 * - Length = '02' Value: 'EFID (2 bytes)' 637 * - Length = '03' Value: 'EFID (2 bytes)', 'SFI (1 byte)' 638 */ 639 640 int sfi = INVALID_SFI; 641 data = tlv.getData(); 642 643 if (data.length < 2 || data.length > 3) { 644 log("Invalid TLV length: " + data.length); 645 break; 646 } 647 648 if (data.length == 3) { 649 sfi = data[2] & 0xFF; 650 } 651 652 int efid = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF); 653 654 mFileIds.put(tag, new File(parentTag, efid, sfi, tagNumberWithinParentTag)); 655 break; 656 } 657 tagNumberWithinParentTag++; 658 } while(tlv.nextObject()); 659 } 660 } 661 log(String msg)662 private void log(String msg) { 663 if(DBG) Rlog.d(LOG_TAG, msg); 664 } 665 }