/* * 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.util.Log; import com.android.internal.telephony.AdnRecord; import com.android.internal.telephony.AdnRecordCache; import com.android.internal.telephony.IccConstants; import com.android.internal.telephony.IccFileHandler; import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.PhoneBase; 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 = "GSM"; 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 mPhoneBookRecords; private boolean mEmailPresentInIap = false; private int mEmailTagNumberInIap = 0; private ArrayList mIapFileRecord; private ArrayList mEmailFileRecord; private Map> 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(); 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 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) { Log.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait"); } } private void readEmailFileAndWait(int recNum) { Map 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) { Log.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) { Log.e(LOG_TAG, "Interrupted Exception in readEmailFileAndWait"); } if (mEmailFileRecord == null) { Log.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) { Log.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) { Log.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 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>(); byte[] emailRec = null; for (int i = 0; i < numRecs; i++) { try { emailRec = mEmailFileRecord.get(i); } catch (IndexOutOfBoundsException e) { Log.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 val = mEmailsForAdnRec.get(adnRecNum - 1); if (val == null) { val = new ArrayList(); } 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 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) { Log.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait"); } } private void createPbrFile(ArrayList 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)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)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)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)ar.result); } synchronized (mLock) { mLock.notify(); } break; } } private class PbrFile { // RecNum HashMap> mFileIds; PbrFile(ArrayList records) { mFileIds = new HashMap>(); 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 val = new HashMap(); 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 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) Log.d(LOG_TAG, msg); } }