2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com
.android
.internal
.telephony
.gsm
;
19 import android
.os
.AsyncResult
;
20 import android
.os
.Handler
;
21 import android
.os
.Message
;
22 import android
.telephony
.Rlog
;
23 import android
.util
.SparseArray
;
24 import android
.util
.SparseIntArray
;
26 import com
.android
.internal
.telephony
.uicc
.AdnRecord
;
27 import com
.android
.internal
.telephony
.uicc
.AdnRecordCache
;
28 import com
.android
.internal
.telephony
.uicc
.IccConstants
;
29 import com
.android
.internal
.telephony
.uicc
.IccFileHandler
;
30 import com
.android
.internal
.telephony
.uicc
.IccUtils
;
31 import java
.util
.ArrayList
;
34 * This class implements reading and parsing USIM records.
35 * Refer to Spec 3GPP TS 31.102 for more details.
39 public class UsimPhoneBookManager
extends Handler
implements IccConstants
{
40 private static final String LOG_TAG
= "UsimPhoneBookManager";
41 private static final boolean DBG
= true;
42 private ArrayList
<PbrRecord
> mPbrRecords
;
43 private Boolean mIsPbrPresent
;
44 private IccFileHandler mFh
;
45 private AdnRecordCache mAdnCache
;
46 private Object mLock
= new Object();
47 private ArrayList
<AdnRecord
> mPhoneBookRecords
;
48 private ArrayList
<byte[]> mIapFileRecord
;
49 private ArrayList
<byte[]> mEmailFileRecord
;
51 // email list for each ADN record. The key would be
52 // ADN's efid << 8 + record #
53 private SparseArray
<ArrayList
<String
>> mEmailsForAdnRec
;
55 // SFI to ADN Efid mapping table
56 private SparseIntArray mSfiEfidTable
;
58 private boolean mRefreshCache
= false;
61 private static final int EVENT_PBR_LOAD_DONE
= 1;
62 private static final int EVENT_USIM_ADN_LOAD_DONE
= 2;
63 private static final int EVENT_IAP_LOAD_DONE
= 3;
64 private static final int EVENT_EMAIL_LOAD_DONE
= 4;
66 private static final int USIM_TYPE1_TAG
= 0xA8;
67 private static final int USIM_TYPE2_TAG
= 0xA9;
68 private static final int USIM_TYPE3_TAG
= 0xAA;
69 private static final int USIM_EFADN_TAG
= 0xC0;
70 private static final int USIM_EFIAP_TAG
= 0xC1;
71 private static final int USIM_EFEXT1_TAG
= 0xC2;
72 private static final int USIM_EFSNE_TAG
= 0xC3;
73 private static final int USIM_EFANR_TAG
= 0xC4;
74 private static final int USIM_EFPBC_TAG
= 0xC5;
75 private static final int USIM_EFGRP_TAG
= 0xC6;
76 private static final int USIM_EFAAS_TAG
= 0xC7;
77 private static final int USIM_EFGSD_TAG
= 0xC8;
78 private static final int USIM_EFUID_TAG
= 0xC9;
79 private static final int USIM_EFEMAIL_TAG
= 0xCA;
80 private static final int USIM_EFCCP1_TAG
= 0xCB;
82 private static final int INVALID_SFI
= -1;
83 private static final byte INVALID_BYTE
= -1;
85 // class File represent a PBR record TLV object which points to the rest of the phonebook EFs
87 // Phonebook reference file constructed tag defined in 3GPP TS 31.102
88 // section 4.4.2.1 table 4.1
89 private final int mParentTag
;
91 private final int mEfid
;
92 // SFI (Short File Identification) of the file. 0xFF indicates invalid SFI.
93 private final int mSfi
;
94 // The order of this tag showing in the PBR record.
95 private final int mIndex
;
97 File(int parentTag
, int efid
, int sfi
, int index
) {
98 mParentTag
= parentTag
;
104 public int getParentTag() { return mParentTag
; }
105 public int getEfid() { return mEfid
; }
106 public int getSfi() { return mSfi
; }
107 public int getIndex() { return mIndex
; }
110 public UsimPhoneBookManager(IccFileHandler fh
, AdnRecordCache cache
) {
112 mPhoneBookRecords
= new ArrayList
<AdnRecord
>();
114 // We assume its present, after the first read this is updated.
115 // So we don't have to read from UICC if its not present on subsequent reads.
116 mIsPbrPresent
= true;
118 mEmailsForAdnRec
= new SparseArray
<ArrayList
<String
>>();
119 mSfiEfidTable
= new SparseIntArray();
122 public void reset() {
123 mPhoneBookRecords
.clear();
124 mIapFileRecord
= null;
125 mEmailFileRecord
= null;
127 mIsPbrPresent
= true;
128 mRefreshCache
= false;
129 mEmailsForAdnRec
.clear();
130 mSfiEfidTable
.clear();
133 // Load all phonebook related EFs from the SIM.
134 public ArrayList
<AdnRecord
> loadEfFilesFromUsim() {
135 synchronized (mLock
) {
136 if (!mPhoneBookRecords
.isEmpty()) {
138 mRefreshCache
= false;
141 return mPhoneBookRecords
;
144 if (!mIsPbrPresent
) return null;
146 // Check if the PBR file is present in the cache, if not read it
148 if (mPbrRecords
== null) {
149 readPbrFileAndWait();
152 if (mPbrRecords
== null)
155 int numRecs
= mPbrRecords
.size();
157 log("loadEfFilesFromUsim: Loading adn and emails");
158 for (int i
= 0; i
< numRecs
; i
++) {
159 readAdnFileAndWait(i
);
160 readEmailFileAndWait(i
);
163 updatePhoneAdnRecord();
164 // All EF files are loaded, return all the records
166 return mPhoneBookRecords
;
169 // Refresh the phonebook cache.
170 private void refreshCache() {
171 if (mPbrRecords
== null) return;
172 mPhoneBookRecords
.clear();
174 int numRecs
= mPbrRecords
.size();
175 for (int i
= 0; i
< numRecs
; i
++) {
176 readAdnFileAndWait(i
);
180 // Invalidate the phonebook cache.
181 public void invalidateCache() {
182 mRefreshCache
= true;
185 // Read the phonebook reference file EF_PBR.
186 private void readPbrFileAndWait() {
187 mFh
.loadEFLinearFixedAll(EF_PBR
, obtainMessage(EVENT_PBR_LOAD_DONE
));
190 } catch (InterruptedException e
) {
191 Rlog
.e(LOG_TAG
, "Interrupted Exception in readAdnFileAndWait");
195 // Read EF_EMAIL which contains the email records.
196 private void readEmailFileAndWait(int recId
) {
197 SparseArray
<File
> files
;
198 files
= mPbrRecords
.get(recId
).mFileIds
;
199 if (files
== null) return;
201 File email
= files
.get(USIM_EFEMAIL_TAG
);
205 * Check if the EF_EMAIL is a Type 1 file or a type 2 file.
206 * If mEmailPresentInIap is true, its a type 2 file.
207 * So we read the IAP file and then read the email records.
208 * instead of reading directly.
210 if (email
.getParentTag() == USIM_TYPE2_TAG
) {
211 if (files
.get(USIM_EFIAP_TAG
) == null) {
212 Rlog
.e(LOG_TAG
, "Can't locate EF_IAP in EF_PBR.");
216 log("EF_IAP exists. Loading EF_IAP to retrieve the index.");
217 readIapFileAndWait(files
.get(USIM_EFIAP_TAG
).getEfid());
218 if (mIapFileRecord
== null) {
219 Rlog
.e(LOG_TAG
, "Error: IAP file is empty");
223 log("EF_EMAIL order in PBR record: " + email
.getIndex());
226 int emailEfid
= email
.getEfid();
227 log("EF_EMAIL exists in PBR. efid = 0x" +
228 Integer
.toHexString(emailEfid
).toUpperCase());
231 * Make sure this EF_EMAIL was never read earlier. Sometimes two PBR record points
233 // to the same EF_EMAIL
234 for (int i
= 0; i
< recId
; i
++) {
235 if (mPbrRecords
.get(i
) != null) {
236 SparseArray
<File
> previousFileIds
= mPbrRecords
.get(i
).mFileIds
;
237 if (previousFileIds
!= null) {
238 File id
= previousFileIds
.get(USIM_EFEMAIL_TAG
);
239 if (id
!= null && id
.getEfid() == emailEfid
) {
240 log("Skipped this EF_EMAIL which was loaded earlier");
247 // Read the EFEmail file.
248 mFh
.loadEFLinearFixedAll(emailEfid
,
249 obtainMessage(EVENT_EMAIL_LOAD_DONE
));
252 } catch (InterruptedException e
) {
253 Rlog
.e(LOG_TAG
, "Interrupted Exception in readEmailFileAndWait");
256 if (mEmailFileRecord
== null) {
257 Rlog
.e(LOG_TAG
, "Error: Email file is empty");
262 if (email
.getParentTag() == USIM_TYPE2_TAG
&& mIapFileRecord
!= null) {
263 // If the tag is type 2 and EF_IAP exists, we need to build tpe 2 email list
264 buildType2EmailList(recId
);
267 // If one the followings is true, we build type 1 email list
268 // 1. EF_IAP does not exist or it is failed to load
269 // 2. ICC cards can be made such that they have an IAP file but all
270 // records are empty. In that case buildType2EmailList will fail and
271 // we need to build type 1 email list.
273 // Build type 1 email list
274 buildType1EmailList(recId
);
279 // Build type 1 email list
280 private void buildType1EmailList(int recId
) {
282 * If this is type 1, the number of records in EF_EMAIL would be same as the record number
283 * in the master/reference file.
285 if (mPbrRecords
.get(recId
) == null)
288 int numRecs
= mPbrRecords
.get(recId
).mMasterFileRecordNum
;
289 log("Building type 1 email list. recId = "
290 + recId
+ ", numRecs = " + numRecs
);
293 for (int i
= 0; i
< numRecs
; i
++) {
295 emailRec
= mEmailFileRecord
.get(i
);
296 } catch (IndexOutOfBoundsException e
) {
297 Rlog
.e(LOG_TAG
, "Error: Improper ICC card: No email record for ADN, continuing");
302 * 3GPP TS 31.102 4.4.2.13 EF_EMAIL (e-mail address)
304 * The fields below are mandatory if and only if the file
305 * is not type 1 (as specified in EF_PBR)
307 * Byte [X + 1]: ADN file SFI (Short File Identification)
308 * Byte [X + 2]: ADN file Record Identifier
310 int sfi
= emailRec
[emailRec
.length
- 2];
311 int adnRecId
= emailRec
[emailRec
.length
- 1];
313 String email
= readEmailRecord(i
);
315 if (email
== null || email
.equals("")) {
319 // Get the associated ADN's efid first.
321 if (sfi
== INVALID_SFI
|| mSfiEfidTable
.get(sfi
) == 0) {
323 // If SFI is invalid or cannot be mapped to any ADN, use the ADN's efid
324 // in the same PBR files.
325 File file
= mPbrRecords
.get(recId
).mFileIds
.get(USIM_EFADN_TAG
);
328 adnEfid
= file
.getEfid();
331 adnEfid
= mSfiEfidTable
.get(sfi
);
334 * SIM record numbers are 1 based.
335 * The key is constructed by efid and record index.
337 int index
= (((adnEfid
& 0xFFFF) << 8) | ((adnRecId
- 1) & 0xFF));
338 ArrayList
<String
> emailList
= mEmailsForAdnRec
.get(index
);
339 if (emailList
== null) {
340 emailList
= new ArrayList
<String
>();
342 log("Adding email #" + i
+ " list to index 0x" +
343 Integer
.toHexString(index
).toUpperCase());
344 emailList
.add(email
);
345 mEmailsForAdnRec
.put(index
, emailList
);
349 // Build type 2 email list
350 private boolean buildType2EmailList(int recId
) {
352 if (mPbrRecords
.get(recId
) == null)
355 int numRecs
= mPbrRecords
.get(recId
).mMasterFileRecordNum
;
356 log("Building type 2 email list. recId = "
357 + recId
+ ", numRecs = " + numRecs
);
360 * 3GPP TS 31.102 4.4.2.1 EF_PBR (Phone Book Reference file) table 4.1
362 * The number of records in the IAP file is same as the number of records in the master
363 * file (e.g EF_ADN). The order of the pointers in an EF_IAP shall be the same as the
364 * order of file IDs that appear in the TLV object indicated by Tag 'A9' in the
365 * reference file record (e.g value of mEmailTagNumberInIap)
368 File adnFile
= mPbrRecords
.get(recId
).mFileIds
.get(USIM_EFADN_TAG
);
369 if (adnFile
== null) {
370 Rlog
.e(LOG_TAG
, "Error: Improper ICC card: EF_ADN does not exist in PBR files");
373 int adnEfid
= adnFile
.getEfid();
375 for (int i
= 0; i
< numRecs
; i
++) {
379 record
= mIapFileRecord
.get(i
);
381 record
[mPbrRecords
.get(recId
).mFileIds
.get(USIM_EFEMAIL_TAG
).getIndex()];
382 } catch (IndexOutOfBoundsException e
) {
383 Rlog
.e(LOG_TAG
, "Error: Improper ICC card: Corrupted EF_IAP");
387 String email
= readEmailRecord(emailRecId
- 1);
388 if (email
!= null && !email
.equals("")) {
389 // The key is constructed by efid and record index.
390 int index
= (((adnEfid
& 0xFFFF) << 8) | (i
& 0xFF));
391 ArrayList
<String
> emailList
= mEmailsForAdnRec
.get(index
);
392 if (emailList
== null) {
393 emailList
= new ArrayList
<String
>();
395 emailList
.add(email
);
396 log("Adding email list to index 0x" +
397 Integer
.toHexString(index
).toUpperCase());
398 mEmailsForAdnRec
.put(index
, emailList
);
404 // Read Phonebook Index Admistration EF_IAP file
405 private void readIapFileAndWait(int efid
) {
406 mFh
.loadEFLinearFixedAll(efid
, obtainMessage(EVENT_IAP_LOAD_DONE
));
409 } catch (InterruptedException e
) {
410 Rlog
.e(LOG_TAG
, "Interrupted Exception in readIapFileAndWait");
414 private void updatePhoneAdnRecord() {
416 int numAdnRecs
= mPhoneBookRecords
.size();
418 for (int i
= 0; i
< numAdnRecs
; i
++) {
420 AdnRecord rec
= mPhoneBookRecords
.get(i
);
422 int adnEfid
= rec
.getEfid();
423 int adnRecId
= rec
.getRecId();
425 int index
= (((adnEfid
& 0xFFFF) << 8) | ((adnRecId
- 1) & 0xFF));
427 ArrayList
<String
> emailList
;
429 emailList
= mEmailsForAdnRec
.get(index
);
430 } catch (IndexOutOfBoundsException e
) {
434 if (emailList
== null)
437 String
[] emails
= new String
[emailList
.size()];
438 System
.arraycopy(emailList
.toArray(), 0, emails
, 0, emailList
.size());
439 rec
.setEmails(emails
);
440 log("Adding email list to ADN (0x" +
441 Integer
.toHexString(mPhoneBookRecords
.get(i
).getEfid()).toUpperCase() +
442 ") record #" + mPhoneBookRecords
.get(i
).getRecId());
443 mPhoneBookRecords
.set(i
, rec
);
447 // Read email from the record of EF_EMAIL
448 private String
readEmailRecord(int recId
) {
451 emailRec
= mEmailFileRecord
.get(recId
);
452 } catch (IndexOutOfBoundsException e
) {
456 // The length of the record is X+2 byte, where X bytes is the email address
457 return IccUtils
.adnStringFieldToString(emailRec
, 0, emailRec
.length
- 2);
461 private void readAdnFileAndWait(int recId
) {
462 SparseArray
<File
> files
;
463 files
= mPbrRecords
.get(recId
).mFileIds
;
464 if (files
== null || files
.size() == 0) return;
467 // Only call fileIds.get while EF_EXT1_TAG is available
468 if (files
.get(USIM_EFEXT1_TAG
) != null) {
469 extEf
= files
.get(USIM_EFEXT1_TAG
).getEfid();
472 if (files
.get(USIM_EFADN_TAG
) == null)
475 int previousSize
= mPhoneBookRecords
.size();
476 mAdnCache
.requestLoadAllAdnLike(files
.get(USIM_EFADN_TAG
).getEfid(),
477 extEf
, obtainMessage(EVENT_USIM_ADN_LOAD_DONE
));
480 } catch (InterruptedException e
) {
481 Rlog
.e(LOG_TAG
, "Interrupted Exception in readAdnFileAndWait");
485 * The recent added ADN record # would be the reference record size
486 * for the rest of EFs associated within this PBR.
488 mPbrRecords
.get(recId
).mMasterFileRecordNum
= mPhoneBookRecords
.size() - previousSize
;
491 // Create the phonebook reference file based on EF_PBR
492 private void createPbrFile(ArrayList
<byte[]> records
) {
493 if (records
== null) {
495 mIsPbrPresent
= false;
499 mPbrRecords
= new ArrayList
<PbrRecord
>();
500 for (int i
= 0; i
< records
.size(); i
++) {
501 // Some cards have two records but the 2nd record is filled with all invalid char 0xff.
502 // So we need to check if the record is valid or not before adding into the PBR records.
503 if (records
.get(i
)[0] != INVALID_BYTE
) {
504 mPbrRecords
.add(new PbrRecord(records
.get(i
)));
508 for (PbrRecord record
: mPbrRecords
) {
509 File file
= record
.mFileIds
.get(USIM_EFADN_TAG
);
510 // If the file does not contain EF_ADN, we'll just skip it.
512 int sfi
= file
.getSfi();
513 if (sfi
!= INVALID_SFI
) {
514 mSfiEfidTable
.put(sfi
, record
.mFileIds
.get(USIM_EFADN_TAG
).getEfid());
521 public void handleMessage(Message msg
) {
525 case EVENT_PBR_LOAD_DONE
:
526 ar
= (AsyncResult
) msg
.obj
;
527 if (ar
.exception
== null) {
528 createPbrFile((ArrayList
<byte[]>)ar
.result
);
530 synchronized (mLock
) {
534 case EVENT_USIM_ADN_LOAD_DONE
:
535 log("Loading USIM ADN records done");
536 ar
= (AsyncResult
) msg
.obj
;
537 if (ar
.exception
== null) {
538 mPhoneBookRecords
.addAll((ArrayList
<AdnRecord
>)ar
.result
);
540 synchronized (mLock
) {
544 case EVENT_IAP_LOAD_DONE
:
545 log("Loading USIM IAP records done");
546 ar
= (AsyncResult
) msg
.obj
;
547 if (ar
.exception
== null) {
548 mIapFileRecord
= ((ArrayList
<byte[]>)ar
.result
);
550 synchronized (mLock
) {
554 case EVENT_EMAIL_LOAD_DONE
:
555 log("Loading USIM Email records done");
556 ar
= (AsyncResult
) msg
.obj
;
557 if (ar
.exception
== null) {
558 mEmailFileRecord
= ((ArrayList
<byte[]>)ar
.result
);
561 synchronized (mLock
) {
568 // PbrRecord represents a record in EF_PBR
569 private class PbrRecord
{
571 private SparseArray
<File
> mFileIds
;
574 * 3GPP TS 31.102 4.4.2.1 EF_PBR (Phone Book Reference file)
575 * If this is type 1 files, files that contain as many records as the
576 * reference/master file (EF_ADN, EF_ADN1) and are linked on record number
577 * bases (Rec1 -> Rec1). The master file record number is the reference.
579 private int mMasterFileRecordNum
;
581 PbrRecord(byte[] record
) {
582 mFileIds
= new SparseArray
<File
>();
584 log("PBR rec: " + IccUtils
.bytesToHexString(record
));
585 recTlv
= new SimTlv(record
, 0, record
.length
);
589 void parseTag(SimTlv tlv
) {
597 case USIM_TYPE1_TAG
: // A8
598 case USIM_TYPE3_TAG
: // AA
599 case USIM_TYPE2_TAG
: // A9
600 data
= tlv
.getData();
601 tlvEfSfi
= new SimTlv(data
, 0, data
.length
);
602 parseEfAndSFI(tlvEfSfi
, tag
);
605 } while (tlv
.nextObject());
608 void parseEfAndSFI(SimTlv tlv
, int parentTag
) {
611 int tagNumberWithinParentTag
= 0;
615 case USIM_EFEMAIL_TAG
:
617 case USIM_EFEXT1_TAG
:
624 case USIM_EFCCP1_TAG
:
627 /** 3GPP TS 31.102, 4.4.2.1 EF_PBR (Phone Book Reference file)
629 * The SFI value assigned to an EF which is indicated in EF_PBR shall
630 * correspond to the SFI indicated in the TLV object in EF_PBR.
632 * The primitive tag identifies clearly the type of data, its value
633 * field indicates the file identifier and, if applicable, the SFI
634 * value of the specified EF. That is, the length value of a primitive
635 * tag indicates if an SFI value is available for the EF or not:
636 * - Length = '02' Value: 'EFID (2 bytes)'
637 * - Length = '03' Value: 'EFID (2 bytes)', 'SFI (1 byte)'
640 int sfi
= INVALID_SFI
;
641 data
= tlv
.getData();
643 if (data
.length
< 2 || data
.length
> 3) {
644 log("Invalid TLV length: " + data
.length
);
648 if (data
.length
== 3) {
649 sfi
= data
[2] & 0xFF;
652 int efid
= ((data
[0] & 0xFF) << 8) | (data
[1] & 0xFF);
654 mFileIds
.put(tag
, new File(parentTag
, efid
, sfi
, tagNumberWithinParentTag
));
657 tagNumberWithinParentTag
++;
658 } while(tlv
.nextObject());
662 private void log(String msg
) {
663 if(DBG
) Rlog
.d(LOG_TAG
, msg
);