| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.telephony.gsm; |
| |
| import android.os.AsyncResult; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.telephony.Rlog; |
| |
| import com.android.internal.telephony.uicc.AdnRecord; |
| import com.android.internal.telephony.uicc.AdnRecordCache; |
| import com.android.internal.telephony.uicc.IccConstants; |
| import com.android.internal.telephony.uicc.IccFileHandler; |
| import com.android.internal.telephony.uicc.IccUtils; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * This class implements reading and parsing USIM records. |
| * Refer to Spec 3GPP TS 31.102 for more details. |
| * |
| * {@hide} |
| */ |
| public class UsimPhoneBookManager extends Handler implements IccConstants { |
| private static final String LOG_TAG = "UsimPhoneBookManager"; |
| private static final boolean DBG = true; |
| private PbrFile mPbrFile; |
| private Boolean mIsPbrPresent; |
| private IccFileHandler mFh; |
| private AdnRecordCache mAdnCache; |
| private Object mLock = new Object(); |
| private ArrayList<AdnRecord> mPhoneBookRecords; |
| private boolean mEmailPresentInIap = false; |
| private int mEmailTagNumberInIap = 0; |
| private ArrayList<byte[]> mIapFileRecord; |
| private ArrayList<byte[]> mEmailFileRecord; |
| private Map<Integer, ArrayList<String>> mEmailsForAdnRec; |
| private boolean mRefreshCache = false; |
| |
| private static final int EVENT_PBR_LOAD_DONE = 1; |
| private static final int EVENT_USIM_ADN_LOAD_DONE = 2; |
| private static final int EVENT_IAP_LOAD_DONE = 3; |
| private static final int EVENT_EMAIL_LOAD_DONE = 4; |
| |
| private static final int USIM_TYPE1_TAG = 0xA8; |
| private static final int USIM_TYPE2_TAG = 0xA9; |
| private static final int USIM_TYPE3_TAG = 0xAA; |
| private static final int USIM_EFADN_TAG = 0xC0; |
| private static final int USIM_EFIAP_TAG = 0xC1; |
| private static final int USIM_EFEXT1_TAG = 0xC2; |
| private static final int USIM_EFSNE_TAG = 0xC3; |
| private static final int USIM_EFANR_TAG = 0xC4; |
| private static final int USIM_EFPBC_TAG = 0xC5; |
| private static final int USIM_EFGRP_TAG = 0xC6; |
| private static final int USIM_EFAAS_TAG = 0xC7; |
| private static final int USIM_EFGSD_TAG = 0xC8; |
| private static final int USIM_EFUID_TAG = 0xC9; |
| private static final int USIM_EFEMAIL_TAG = 0xCA; |
| private static final int USIM_EFCCP1_TAG = 0xCB; |
| |
| public UsimPhoneBookManager(IccFileHandler fh, AdnRecordCache cache) { |
| mFh = fh; |
| mPhoneBookRecords = new ArrayList<AdnRecord>(); |
| mPbrFile = null; |
| // We assume its present, after the first read this is updated. |
| // So we don't have to read from UICC if its not present on subsequent reads. |
| mIsPbrPresent = true; |
| mAdnCache = cache; |
| } |
| |
| public void reset() { |
| mPhoneBookRecords.clear(); |
| mIapFileRecord = null; |
| mEmailFileRecord = null; |
| mPbrFile = null; |
| mIsPbrPresent = true; |
| mRefreshCache = false; |
| } |
| |
| public ArrayList<AdnRecord> loadEfFilesFromUsim() { |
| synchronized (mLock) { |
| if (!mPhoneBookRecords.isEmpty()) { |
| if (mRefreshCache) { |
| mRefreshCache = false; |
| refreshCache(); |
| } |
| return mPhoneBookRecords; |
| } |
| |
| if (!mIsPbrPresent) return null; |
| |
| // Check if the PBR file is present in the cache, if not read it |
| // from the USIM. |
| if (mPbrFile == null) { |
| readPbrFileAndWait(); |
| } |
| |
| if (mPbrFile == null) return null; |
| |
| int numRecs = mPbrFile.mFileIds.size(); |
| for (int i = 0; i < numRecs; i++) { |
| readAdnFileAndWait(i); |
| readEmailFileAndWait(i); |
| } |
| // All EF files are loaded, post the response. |
| } |
| return mPhoneBookRecords; |
| } |
| |
| private void refreshCache() { |
| if (mPbrFile == null) return; |
| mPhoneBookRecords.clear(); |
| |
| int numRecs = mPbrFile.mFileIds.size(); |
| for (int i = 0; i < numRecs; i++) { |
| readAdnFileAndWait(i); |
| } |
| } |
| |
| public void invalidateCache() { |
| mRefreshCache = true; |
| } |
| |
| private void readPbrFileAndWait() { |
| mFh.loadEFLinearFixedAll(EF_PBR, obtainMessage(EVENT_PBR_LOAD_DONE)); |
| try { |
| mLock.wait(); |
| } catch (InterruptedException e) { |
| Rlog.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait"); |
| } |
| } |
| |
| private void readEmailFileAndWait(int recNum) { |
| Map <Integer,Integer> fileIds; |
| fileIds = mPbrFile.mFileIds.get(recNum); |
| if (fileIds == null) return; |
| |
| if (fileIds.containsKey(USIM_EFEMAIL_TAG)) { |
| int efid = fileIds.get(USIM_EFEMAIL_TAG); |
| // Check if the EFEmail is a Type 1 file or a type 2 file. |
| // If mEmailPresentInIap is true, its a type 2 file. |
| // So we read the IAP file and then read the email records. |
| // instead of reading directly. |
| if (mEmailPresentInIap) { |
| readIapFileAndWait(fileIds.get(USIM_EFIAP_TAG)); |
| if (mIapFileRecord == null) { |
| Rlog.e(LOG_TAG, "Error: IAP file is empty"); |
| return; |
| } |
| } |
| // Read the EFEmail file. |
| mFh.loadEFLinearFixedAll(fileIds.get(USIM_EFEMAIL_TAG), |
| obtainMessage(EVENT_EMAIL_LOAD_DONE)); |
| try { |
| mLock.wait(); |
| } catch (InterruptedException e) { |
| Rlog.e(LOG_TAG, "Interrupted Exception in readEmailFileAndWait"); |
| } |
| |
| if (mEmailFileRecord == null) { |
| Rlog.e(LOG_TAG, "Error: Email file is empty"); |
| return; |
| } |
| updatePhoneAdnRecord(); |
| } |
| |
| } |
| |
| private void readIapFileAndWait(int efid) { |
| mFh.loadEFLinearFixedAll(efid, obtainMessage(EVENT_IAP_LOAD_DONE)); |
| try { |
| mLock.wait(); |
| } catch (InterruptedException e) { |
| Rlog.e(LOG_TAG, "Interrupted Exception in readIapFileAndWait"); |
| } |
| } |
| |
| private void updatePhoneAdnRecord() { |
| if (mEmailFileRecord == null) return; |
| int numAdnRecs = mPhoneBookRecords.size(); |
| if (mIapFileRecord != null) { |
| // The number of records in the IAP file is same as the number of records in ADN file. |
| // The order of the pointers in an EFIAP shall be the same as the order of file IDs |
| // that appear in the TLV object indicated by Tag 'A9' in the reference file record. |
| // i.e value of mEmailTagNumberInIap |
| |
| for (int i = 0; i < numAdnRecs; i++) { |
| byte[] record = null; |
| try { |
| record = mIapFileRecord.get(i); |
| } catch (IndexOutOfBoundsException e) { |
| Rlog.e(LOG_TAG, "Error: Improper ICC card: No IAP record for ADN, continuing"); |
| break; |
| } |
| int recNum = record[mEmailTagNumberInIap]; |
| |
| if (recNum != -1) { |
| String[] emails = new String[1]; |
| // SIM record numbers are 1 based |
| emails[0] = readEmailRecord(recNum - 1); |
| AdnRecord rec = mPhoneBookRecords.get(i); |
| if (rec != null) { |
| rec.setEmails(emails); |
| } else { |
| // might be a record with only email |
| rec = new AdnRecord("", "", emails); |
| } |
| mPhoneBookRecords.set(i, rec); |
| } |
| } |
| } |
| |
| // ICC cards can be made such that they have an IAP file but all |
| // records are empty. So we read both type 1 and type 2 file |
| // email records, just to be sure. |
| |
| int len = mPhoneBookRecords.size(); |
| // Type 1 file, the number of records is the same as the number of |
| // records in the ADN file. |
| if (mEmailsForAdnRec == null) { |
| parseType1EmailFile(len); |
| } |
| for (int i = 0; i < numAdnRecs; i++) { |
| ArrayList<String> emailList = null; |
| try { |
| emailList = mEmailsForAdnRec.get(i); |
| } catch (IndexOutOfBoundsException e) { |
| break; |
| } |
| if (emailList == null) continue; |
| |
| AdnRecord rec = mPhoneBookRecords.get(i); |
| |
| String[] emails = new String[emailList.size()]; |
| System.arraycopy(emailList.toArray(), 0, emails, 0, emailList.size()); |
| rec.setEmails(emails); |
| mPhoneBookRecords.set(i, rec); |
| } |
| } |
| |
| void parseType1EmailFile(int numRecs) { |
| mEmailsForAdnRec = new HashMap<Integer, ArrayList<String>>(); |
| byte[] emailRec = null; |
| for (int i = 0; i < numRecs; i++) { |
| try { |
| emailRec = mEmailFileRecord.get(i); |
| } catch (IndexOutOfBoundsException e) { |
| Rlog.e(LOG_TAG, "Error: Improper ICC card: No email record for ADN, continuing"); |
| break; |
| } |
| int adnRecNum = emailRec[emailRec.length - 1]; |
| |
| if (adnRecNum == -1) { |
| continue; |
| } |
| |
| String email = readEmailRecord(i); |
| |
| if (email == null || email.equals("")) { |
| continue; |
| } |
| |
| // SIM record numbers are 1 based. |
| ArrayList<String> val = mEmailsForAdnRec.get(adnRecNum - 1); |
| if (val == null) { |
| val = new ArrayList<String>(); |
| } |
| val.add(email); |
| // SIM record numbers are 1 based. |
| mEmailsForAdnRec.put(adnRecNum - 1, val); |
| } |
| } |
| |
| private String readEmailRecord(int recNum) { |
| byte[] emailRec = null; |
| try { |
| emailRec = mEmailFileRecord.get(recNum); |
| } catch (IndexOutOfBoundsException e) { |
| return null; |
| } |
| |
| // The length of the record is X+2 byte, where X bytes is the email address |
| String email = IccUtils.adnStringFieldToString(emailRec, 0, emailRec.length - 2); |
| return email; |
| } |
| |
| private void readAdnFileAndWait(int recNum) { |
| Map <Integer,Integer> fileIds; |
| fileIds = mPbrFile.mFileIds.get(recNum); |
| if (fileIds == null || fileIds.isEmpty()) return; |
| |
| |
| int extEf = 0; |
| // Only call fileIds.get while EFEXT1_TAG is available |
| if (fileIds.containsKey(USIM_EFEXT1_TAG)) { |
| extEf = fileIds.get(USIM_EFEXT1_TAG); |
| } |
| |
| mAdnCache.requestLoadAllAdnLike(fileIds.get(USIM_EFADN_TAG), |
| extEf, obtainMessage(EVENT_USIM_ADN_LOAD_DONE)); |
| try { |
| mLock.wait(); |
| } catch (InterruptedException e) { |
| Rlog.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait"); |
| } |
| } |
| |
| private void createPbrFile(ArrayList<byte[]> records) { |
| if (records == null) { |
| mPbrFile = null; |
| mIsPbrPresent = false; |
| return; |
| } |
| mPbrFile = new PbrFile(records); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| AsyncResult ar; |
| |
| switch(msg.what) { |
| case EVENT_PBR_LOAD_DONE: |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| createPbrFile((ArrayList<byte[]>)ar.result); |
| } |
| synchronized (mLock) { |
| mLock.notify(); |
| } |
| break; |
| case EVENT_USIM_ADN_LOAD_DONE: |
| log("Loading USIM ADN records done"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| mPhoneBookRecords.addAll((ArrayList<AdnRecord>)ar.result); |
| } |
| synchronized (mLock) { |
| mLock.notify(); |
| } |
| break; |
| case EVENT_IAP_LOAD_DONE: |
| log("Loading USIM IAP records done"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| mIapFileRecord = ((ArrayList<byte[]>)ar.result); |
| } |
| synchronized (mLock) { |
| mLock.notify(); |
| } |
| break; |
| case EVENT_EMAIL_LOAD_DONE: |
| log("Loading USIM Email records done"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| mEmailFileRecord = ((ArrayList<byte[]>)ar.result); |
| } |
| |
| synchronized (mLock) { |
| mLock.notify(); |
| } |
| break; |
| } |
| } |
| |
| private class PbrFile { |
| // RecNum <EF Tag, efid> |
| HashMap<Integer,Map<Integer,Integer>> mFileIds; |
| |
| PbrFile(ArrayList<byte[]> records) { |
| mFileIds = new HashMap<Integer, Map<Integer, Integer>>(); |
| SimTlv recTlv; |
| int recNum = 0; |
| for (byte[] record: records) { |
| recTlv = new SimTlv(record, 0, record.length); |
| parseTag(recTlv, recNum); |
| recNum ++; |
| } |
| } |
| |
| void parseTag(SimTlv tlv, int recNum) { |
| SimTlv tlvEf; |
| int tag; |
| byte[] data; |
| Map<Integer, Integer> val = new HashMap<Integer, Integer>(); |
| do { |
| tag = tlv.getTag(); |
| switch(tag) { |
| case USIM_TYPE1_TAG: // A8 |
| case USIM_TYPE3_TAG: // AA |
| case USIM_TYPE2_TAG: // A9 |
| data = tlv.getData(); |
| tlvEf = new SimTlv(data, 0, data.length); |
| parseEf(tlvEf, val, tag); |
| break; |
| } |
| } while (tlv.nextObject()); |
| mFileIds.put(recNum, val); |
| } |
| |
| void parseEf(SimTlv tlv, Map<Integer, Integer> val, int parentTag) { |
| int tag; |
| byte[] data; |
| int tagNumberWithinParentTag = 0; |
| do { |
| tag = tlv.getTag(); |
| if (parentTag == USIM_TYPE2_TAG && tag == USIM_EFEMAIL_TAG) { |
| mEmailPresentInIap = true; |
| mEmailTagNumberInIap = tagNumberWithinParentTag; |
| } |
| switch(tag) { |
| case USIM_EFEMAIL_TAG: |
| case USIM_EFADN_TAG: |
| case USIM_EFEXT1_TAG: |
| case USIM_EFANR_TAG: |
| case USIM_EFPBC_TAG: |
| case USIM_EFGRP_TAG: |
| case USIM_EFAAS_TAG: |
| case USIM_EFGSD_TAG: |
| case USIM_EFUID_TAG: |
| case USIM_EFCCP1_TAG: |
| case USIM_EFIAP_TAG: |
| case USIM_EFSNE_TAG: |
| data = tlv.getData(); |
| int efid = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF); |
| val.put(tag, efid); |
| break; |
| } |
| tagNumberWithinParentTag ++; |
| } while(tlv.nextObject()); |
| } |
| } |
| |
| private void log(String msg) { |
| if(DBG) Rlog.d(LOG_TAG, msg); |
| } |
| } |