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 17package com.android.internal.telephony.gsm; 18 19import android.os.AsyncResult; 20import android.os.Handler; 21import android.os.Message; 22import android.util.Log; 23 24import com.android.internal.telephony.AdnRecord; 25import com.android.internal.telephony.AdnRecordCache; 26import com.android.internal.telephony.IccConstants; 27import com.android.internal.telephony.IccFileHandler; 28import com.android.internal.telephony.IccUtils; 29import com.android.internal.telephony.PhoneBase; 30 31import java.util.ArrayList; 32import java.util.HashMap; 33import java.util.Map; 34 35/** 36 * This class implements reading and parsing USIM records. 37 * Refer to Spec 3GPP TS 31.102 for more details. 38 * 39 * {@hide} 40 */ 41public class UsimPhoneBookManager extends Handler implements IccConstants { 42 private static final String LOG_TAG = "GSM"; 43 private static final boolean DBG = true; 44 private PbrFile mPbrFile; 45 private Boolean mIsPbrPresent; 46 private IccFileHandler mFh; 47 private AdnRecordCache mAdnCache; 48 private Object mLock = new Object(); 49 private ArrayList<AdnRecord> mPhoneBookRecords; 50 private boolean mEmailPresentInIap = false; 51 private int mEmailTagNumberInIap = 0; 52 private ArrayList<byte[]> mIapFileRecord; 53 private ArrayList<byte[]> mEmailFileRecord; 54 private Map<Integer, ArrayList<String>> mEmailsForAdnRec; 55 private boolean mRefreshCache = false; 56 57 private static final int EVENT_PBR_LOAD_DONE = 1; 58 private static final int EVENT_USIM_ADN_LOAD_DONE = 2; 59 private static final int EVENT_IAP_LOAD_DONE = 3; 60 private static final int EVENT_EMAIL_LOAD_DONE = 4; 61 62 private static final int USIM_TYPE1_TAG = 0xA8; 63 private static final int USIM_TYPE2_TAG = 0xA9; 64 private static final int USIM_TYPE3_TAG = 0xAA; 65 private static final int USIM_EFADN_TAG = 0xC0; 66 private static final int USIM_EFIAP_TAG = 0xC1; 67 private static final int USIM_EFEXT1_TAG = 0xC2; 68 private static final int USIM_EFSNE_TAG = 0xC3; 69 private static final int USIM_EFANR_TAG = 0xC4; 70 private static final int USIM_EFPBC_TAG = 0xC5; 71 private static final int USIM_EFGRP_TAG = 0xC6; 72 private static final int USIM_EFAAS_TAG = 0xC7; 73 private static final int USIM_EFGSD_TAG = 0xC8; 74 private static final int USIM_EFUID_TAG = 0xC9; 75 private static final int USIM_EFEMAIL_TAG = 0xCA; 76 private static final int USIM_EFCCP1_TAG = 0xCB; 77 78 public UsimPhoneBookManager(IccFileHandler fh, AdnRecordCache cache) { 79 mFh = fh; 80 mPhoneBookRecords = new ArrayList<AdnRecord>(); 81 mPbrFile = null; 82 // We assume its present, after the first read this is updated. 83 // So we don't have to read from UICC if its not present on subsequent reads. 84 mIsPbrPresent = true; 85 mAdnCache = cache; 86 } 87 88 public void reset() { 89 mPhoneBookRecords.clear(); 90 mIapFileRecord = null; 91 mEmailFileRecord = null; 92 mPbrFile = null; 93 mIsPbrPresent = true; 94 mRefreshCache = false; 95 } 96 97 public ArrayList<AdnRecord> loadEfFilesFromUsim() { 98 synchronized (mLock) { 99 if (!mPhoneBookRecords.isEmpty()) { 100 if (mRefreshCache) { 101 mRefreshCache = false; 102 refreshCache(); 103 } 104 return mPhoneBookRecords; 105 } 106 107 if (!mIsPbrPresent) return null; 108 109 // Check if the PBR file is present in the cache, if not read it 110 // from the USIM. 111 if (mPbrFile == null) { 112 readPbrFileAndWait(); 113 } 114 115 if (mPbrFile == null) return null; 116 117 int numRecs = mPbrFile.mFileIds.size(); 118 for (int i = 0; i < numRecs; i++) { 119 readAdnFileAndWait(i); 120 readEmailFileAndWait(i); 121 } 122 // All EF files are loaded, post the response. 123 } 124 return mPhoneBookRecords; 125 } 126 127 private void refreshCache() { 128 if (mPbrFile == null) return; 129 mPhoneBookRecords.clear(); 130 131 int numRecs = mPbrFile.mFileIds.size(); 132 for (int i = 0; i < numRecs; i++) { 133 readAdnFileAndWait(i); 134 } 135 } 136 137 public void invalidateCache() { 138 mRefreshCache = true; 139 } 140 141 private void readPbrFileAndWait() { 142 mFh.loadEFLinearFixedAll(EF_PBR, obtainMessage(EVENT_PBR_LOAD_DONE)); 143 try { 144 mLock.wait(); 145 } catch (InterruptedException e) { 146 Log.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait"); 147 } 148 } 149 150 private void readEmailFileAndWait(int recNum) { 151 Map <Integer,Integer> fileIds; 152 fileIds = mPbrFile.mFileIds.get(recNum); 153 if (fileIds == null) return; 154 155 if (fileIds.containsKey(USIM_EFEMAIL_TAG)) { 156 int efid = fileIds.get(USIM_EFEMAIL_TAG); 157 // Check if the EFEmail is a Type 1 file or a type 2 file. 158 // If mEmailPresentInIap is true, its a type 2 file. 159 // So we read the IAP file and then read the email records. 160 // instead of reading directly. 161 if (mEmailPresentInIap) { 162 readIapFileAndWait(fileIds.get(USIM_EFIAP_TAG)); 163 if (mIapFileRecord == null) { 164 Log.e(LOG_TAG, "Error: IAP file is empty"); 165 return; 166 } 167 } 168 // Read the EFEmail file. 169 mFh.loadEFLinearFixedAll(fileIds.get(USIM_EFEMAIL_TAG), 170 obtainMessage(EVENT_EMAIL_LOAD_DONE)); 171 try { 172 mLock.wait(); 173 } catch (InterruptedException e) { 174 Log.e(LOG_TAG, "Interrupted Exception in readEmailFileAndWait"); 175 } 176 177 if (mEmailFileRecord == null) { 178 Log.e(LOG_TAG, "Error: Email file is empty"); 179 return; 180 } 181 updatePhoneAdnRecord(); 182 } 183 184 } 185 186 private void readIapFileAndWait(int efid) { 187 mFh.loadEFLinearFixedAll(efid, obtainMessage(EVENT_IAP_LOAD_DONE)); 188 try { 189 mLock.wait(); 190 } catch (InterruptedException e) { 191 Log.e(LOG_TAG, "Interrupted Exception in readIapFileAndWait"); 192 } 193 } 194 195 private void updatePhoneAdnRecord() { 196 if (mEmailFileRecord == null) return; 197 int numAdnRecs = mPhoneBookRecords.size(); 198 if (mIapFileRecord != null) { 199 // The number of records in the IAP file is same as the number of records in ADN file. 200 // The order of the pointers in an EFIAP shall be the same as the order of file IDs 201 // that appear in the TLV object indicated by Tag 'A9' in the reference file record. 202 // i.e value of mEmailTagNumberInIap 203 204 for (int i = 0; i < numAdnRecs; i++) { 205 byte[] record = null; 206 try { 207 record = mIapFileRecord.get(i); 208 } catch (IndexOutOfBoundsException e) { 209 Log.e(LOG_TAG, "Error: Improper ICC card: No IAP record for ADN, continuing"); 210 break; 211 } 212 int recNum = record[mEmailTagNumberInIap]; 213 214 if (recNum != -1) { 215 String[] emails = new String[1]; 216 // SIM record numbers are 1 based 217 emails[0] = readEmailRecord(recNum - 1); 218 AdnRecord rec = mPhoneBookRecords.get(i); 219 if (rec != null) { 220 rec.setEmails(emails); 221 } else { 222 // might be a record with only email 223 rec = new AdnRecord("", "", emails); 224 } 225 mPhoneBookRecords.set(i, rec); 226 } 227 } 228 } 229 230 // ICC cards can be made such that they have an IAP file but all 231 // records are empty. So we read both type 1 and type 2 file 232 // email records, just to be sure. 233 234 int len = mPhoneBookRecords.size(); 235 // Type 1 file, the number of records is the same as the number of 236 // records in the ADN file. 237 if (mEmailsForAdnRec == null) { 238 parseType1EmailFile(len); 239 } 240 for (int i = 0; i < numAdnRecs; i++) { 241 ArrayList<String> emailList = null; 242 try { 243 emailList = mEmailsForAdnRec.get(i); 244 } catch (IndexOutOfBoundsException e) { 245 break; 246 } 247 if (emailList == null) continue; 248 249 AdnRecord rec = mPhoneBookRecords.get(i); 250 251 String[] emails = new String[emailList.size()]; 252 System.arraycopy(emailList.toArray(), 0, emails, 0, emailList.size()); 253 rec.setEmails(emails); 254 mPhoneBookRecords.set(i, rec); 255 } 256 } 257 258 void parseType1EmailFile(int numRecs) { 259 mEmailsForAdnRec = new HashMap<Integer, ArrayList<String>>(); 260 byte[] emailRec = null; 261 for (int i = 0; i < numRecs; i++) { 262 try { 263 emailRec = mEmailFileRecord.get(i); 264 } catch (IndexOutOfBoundsException e) { 265 Log.e(LOG_TAG, "Error: Improper ICC card: No email record for ADN, continuing"); 266 break; 267 } 268 int adnRecNum = emailRec[emailRec.length - 1]; 269 270 if (adnRecNum == -1) { 271 continue; 272 } 273 274 String email = readEmailRecord(i); 275 276 if (email == null || email.equals("")) { 277 continue; 278 } 279 280 // SIM record numbers are 1 based. 281 ArrayList<String> val = mEmailsForAdnRec.get(adnRecNum - 1); 282 if (val == null) { 283 val = new ArrayList<String>(); 284 } 285 val.add(email); 286 // SIM record numbers are 1 based. 287 mEmailsForAdnRec.put(adnRecNum - 1, val); 288 } 289 } 290 291 private String readEmailRecord(int recNum) { 292 byte[] emailRec = null; 293 try { 294 emailRec = mEmailFileRecord.get(recNum); 295 } catch (IndexOutOfBoundsException e) { 296 return null; 297 } 298 299 // The length of the record is X+2 byte, where X bytes is the email address 300 String email = IccUtils.adnStringFieldToString(emailRec, 0, emailRec.length - 2); 301 return email; 302 } 303 304 private void readAdnFileAndWait(int recNum) { 305 Map <Integer,Integer> fileIds; 306 fileIds = mPbrFile.mFileIds.get(recNum); 307 if (fileIds == null || fileIds.isEmpty()) return; 308 309 310 int extEf = 0; 311 // Only call fileIds.get while EFEXT1_TAG is available 312 if (fileIds.containsKey(USIM_EFEXT1_TAG)) { 313 extEf = fileIds.get(USIM_EFEXT1_TAG); 314 } 315 316 mAdnCache.requestLoadAllAdnLike(fileIds.get(USIM_EFADN_TAG), 317 extEf, obtainMessage(EVENT_USIM_ADN_LOAD_DONE)); 318 try { 319 mLock.wait(); 320 } catch (InterruptedException e) { 321 Log.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait"); 322 } 323 } 324 325 private void createPbrFile(ArrayList<byte[]> records) { 326 if (records == null) { 327 mPbrFile = null; 328 mIsPbrPresent = false; 329 return; 330 } 331 mPbrFile = new PbrFile(records); 332 } 333 334 @Override 335 public void handleMessage(Message msg) { 336 AsyncResult ar; 337 338 switch(msg.what) { 339 case EVENT_PBR_LOAD_DONE: 340 ar = (AsyncResult) msg.obj; 341 if (ar.exception == null) { 342 createPbrFile((ArrayList<byte[]>)ar.result); 343 } 344 synchronized (mLock) { 345 mLock.notify(); 346 } 347 break; 348 case EVENT_USIM_ADN_LOAD_DONE: 349 log("Loading USIM ADN records done"); 350 ar = (AsyncResult) msg.obj; 351 if (ar.exception == null) { 352 mPhoneBookRecords.addAll((ArrayList<AdnRecord>)ar.result); 353 } 354 synchronized (mLock) { 355 mLock.notify(); 356 } 357 break; 358 case EVENT_IAP_LOAD_DONE: 359 log("Loading USIM IAP records done"); 360 ar = (AsyncResult) msg.obj; 361 if (ar.exception == null) { 362 mIapFileRecord = ((ArrayList<byte[]>)ar.result); 363 } 364 synchronized (mLock) { 365 mLock.notify(); 366 } 367 break; 368 case EVENT_EMAIL_LOAD_DONE: 369 log("Loading USIM Email records done"); 370 ar = (AsyncResult) msg.obj; 371 if (ar.exception == null) { 372 mEmailFileRecord = ((ArrayList<byte[]>)ar.result); 373 } 374 375 synchronized (mLock) { 376 mLock.notify(); 377 } 378 break; 379 } 380 } 381 382 private class PbrFile { 383 // RecNum <EF Tag, efid> 384 HashMap<Integer,Map<Integer,Integer>> mFileIds; 385 386 PbrFile(ArrayList<byte[]> records) { 387 mFileIds = new HashMap<Integer, Map<Integer, Integer>>(); 388 SimTlv recTlv; 389 int recNum = 0; 390 for (byte[] record: records) { 391 recTlv = new SimTlv(record, 0, record.length); 392 parseTag(recTlv, recNum); 393 recNum ++; 394 } 395 } 396 397 void parseTag(SimTlv tlv, int recNum) { 398 SimTlv tlvEf; 399 int tag; 400 byte[] data; 401 Map<Integer, Integer> val = new HashMap<Integer, Integer>(); 402 do { 403 tag = tlv.getTag(); 404 switch(tag) { 405 case USIM_TYPE1_TAG: // A8 406 case USIM_TYPE3_TAG: // AA 407 case USIM_TYPE2_TAG: // A9 408 data = tlv.getData(); 409 tlvEf = new SimTlv(data, 0, data.length); 410 parseEf(tlvEf, val, tag); 411 break; 412 } 413 } while (tlv.nextObject()); 414 mFileIds.put(recNum, val); 415 } 416 417 void parseEf(SimTlv tlv, Map<Integer, Integer> val, int parentTag) { 418 int tag; 419 byte[] data; 420 int tagNumberWithinParentTag = 0; 421 do { 422 tag = tlv.getTag(); 423 if (parentTag == USIM_TYPE2_TAG && tag == USIM_EFEMAIL_TAG) { 424 mEmailPresentInIap = true; 425 mEmailTagNumberInIap = tagNumberWithinParentTag; 426 } 427 switch(tag) { 428 case USIM_EFEMAIL_TAG: 429 case USIM_EFADN_TAG: 430 case USIM_EFEXT1_TAG: 431 case USIM_EFANR_TAG: 432 case USIM_EFPBC_TAG: 433 case USIM_EFGRP_TAG: 434 case USIM_EFAAS_TAG: 435 case USIM_EFGSD_TAG: 436 case USIM_EFUID_TAG: 437 case USIM_EFCCP1_TAG: 438 case USIM_EFIAP_TAG: 439 case USIM_EFSNE_TAG: 440 data = tlv.getData(); 441 int efid = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF); 442 val.put(tag, efid); 443 break; 444 } 445 tagNumberWithinParentTag ++; 446 } while(tlv.nextObject()); 447 } 448 } 449 450 private void log(String msg) { 451 if(DBG) Log.d(LOG_TAG, msg); 452 } 453} 454