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.telephony.Rlog;
23 
24 import com.android.internal.telephony.uicc.AdnRecord;
25 import com.android.internal.telephony.uicc.AdnRecordCache;
26 import com.android.internal.telephony.uicc.IccConstants;
27 import com.android.internal.telephony.uicc.IccFileHandler;
28 import com.android.internal.telephony.uicc.IccUtils;
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 = "UsimPhoneBookManager";
42     private static final boolean DBG = true;
43     private PbrFile mPbrFile;
44     private Boolean mIsPbrPresent;
45     private IccFileHandler mFh;
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 
UsimPhoneBookManager(IccFileHandler fh, AdnRecordCache cache)77     public UsimPhoneBookManager(IccFileHandler fh, AdnRecordCache cache) {
78         mFh = fh;
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 
reset()87     public void reset() {
88         mPhoneBookRecords.clear();
89         mIapFileRecord = null;
90         mEmailFileRecord = null;
91         mPbrFile = null;
92         mIsPbrPresent = true;
93         mRefreshCache = false;
94     }
95 
loadEfFilesFromUsim()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 
refreshCache()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 
invalidateCache()136     public void invalidateCache() {
137         mRefreshCache = true;
138     }
139 
readPbrFileAndWait()140     private void readPbrFileAndWait() {
141         mFh.loadEFLinearFixedAll(EF_PBR, obtainMessage(EVENT_PBR_LOAD_DONE));
142         try {
143             mLock.wait();
144         } catch (InterruptedException e) {
145             Rlog.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait");
146         }
147     }
148 
readEmailFileAndWait(int recNum)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                     Rlog.e(LOG_TAG, "Error: IAP file is empty");
164                     return;
165                 }
166             }
167             // Read the EFEmail file.
168             mFh.loadEFLinearFixedAll(fileIds.get(USIM_EFEMAIL_TAG),
169                     obtainMessage(EVENT_EMAIL_LOAD_DONE));
170             try {
171                 mLock.wait();
172             } catch (InterruptedException e) {
173                 Rlog.e(LOG_TAG, "Interrupted Exception in readEmailFileAndWait");
174             }
175 
176             if (mEmailFileRecord == null) {
177                 Rlog.e(LOG_TAG, "Error: Email file is empty");
178                 return;
179             }
180             updatePhoneAdnRecord();
181         }
182 
183     }
184 
readIapFileAndWait(int efid)185     private void readIapFileAndWait(int efid) {
186         mFh.loadEFLinearFixedAll(efid, obtainMessage(EVENT_IAP_LOAD_DONE));
187         try {
188             mLock.wait();
189         } catch (InterruptedException e) {
190             Rlog.e(LOG_TAG, "Interrupted Exception in readIapFileAndWait");
191         }
192     }
193 
updatePhoneAdnRecord()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                     Rlog.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 
parseType1EmailFile(int numRecs)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                 Rlog.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 
readEmailRecord(int recNum)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 
readAdnFileAndWait(int recNum)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             Rlog.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait");
321         }
322     }
323 
createPbrFile(ArrayList<byte[]> records)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
handleMessage(Message msg)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 
PbrFile(ArrayList<byte[]> records)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 
parseTag(SimTlv tlv, int recNum)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 
parseEf(SimTlv tlv, Map<Integer, Integer> val, int parentTag)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 
log(String msg)449     private void log(String msg) {
450         if(DBG) Rlog.d(LOG_TAG, msg);
451     }
452 }
453