Home | History | Annotate | Download | only in gsm
      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