/*
* 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);
}
}