• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.IccFileHandler;
28 import com.android.internal.telephony.IccUtils;
29 import com.android.internal.telephony.PhoneBase;
30 
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import 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  */
41 public 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 
UsimPhoneBookManager(IccFileHandler fh, AdnRecordCache cache)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 
reset()88     public void reset() {
89         mPhoneBookRecords.clear();
90         mIapFileRecord = null;
91         mEmailFileRecord = null;
92         mPbrFile = null;
93         mIsPbrPresent = true;
94         mRefreshCache = false;
95     }
96 
loadEfFilesFromUsim()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 
refreshCache()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 
invalidateCache()137     public void invalidateCache() {
138         mRefreshCache = true;
139     }
140 
readPbrFileAndWait()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 
readEmailFileAndWait(int recNum)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 
readIapFileAndWait(int efid)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 
updatePhoneAdnRecord()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 
parseType1EmailFile(int numRecs)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 
readEmailRecord(int recNum)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 
readAdnFileAndWait(int recNum)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 
createPbrFile(ArrayList<byte[]> records)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
handleMessage(Message msg)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 
PbrFile(ArrayList<byte[]> records)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 
parseTag(SimTlv tlv, int recNum)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 
parseEf(SimTlv tlv, Map<Integer, Integer> val, int parentTag)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 
log(String msg)450     private void log(String msg) {
451         if(DBG) Log.d(LOG_TAG, msg);
452     }
453 }
454