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