1 /*
2  * Copyright (C) 2011 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.uicc;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.os.AsyncResult;
23 import android.os.Message;
24 import android.telephony.SubscriptionManager;
25 
26 import com.android.internal.telephony.CommandsInterface;
27 import com.android.internal.telephony.gsm.SimTlv;
28 import com.android.telephony.Rlog;
29 
30 import java.io.FileDescriptor;
31 import java.io.PrintWriter;
32 import java.nio.charset.Charset;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 
36 /**
37  * {@hide}
38  */
39 public class IsimUiccRecords extends IccRecords implements IsimRecords {
40     protected static final String LOG_TAG = "IsimUiccRecords";
41 
42     private static final boolean DBG = true;
43     private static final boolean VDBG = false; // STOPSHIP if true
44     private static final boolean DUMP_RECORDS = false;  // Note: PII is logged when this is true
45                                                         // STOPSHIP if true
46     public static final String INTENT_ISIM_REFRESH = "com.android.intent.isim_refresh";
47 
48     // ISIM EF records (see 3GPP TS 31.103)
49     @UnsupportedAppUsage
50     private String mIsimImpi;               // IMS private user identity
51     @UnsupportedAppUsage
52     private String mIsimDomain;             // IMS home network domain name
53     @UnsupportedAppUsage
54     private String[] mIsimImpu;             // IMS public user identity(s)
55     @UnsupportedAppUsage
56     private String mIsimIst;                // IMS Service Table
57     @UnsupportedAppUsage
58     private String[] mIsimPcscf;            // IMS Proxy Call Session Control Function
59     @UnsupportedAppUsage
60     private String auth_rsp;
61 
62     private static final int TAG_ISIM_VALUE = 0x80;     // From 3GPP TS 31.103
63 
64     @Override
toString()65     public String toString() {
66         return "IsimUiccRecords: " + super.toString()
67                 + (DUMP_RECORDS ? (" mIsimImpi=" + mIsimImpi
68                 + " mIsimDomain=" + mIsimDomain
69                 + " mIsimImpu=" + mIsimImpu
70                 + " mIsimIst=" + mIsimIst
71                 + " mIsimPcscf=" + mIsimPcscf) : "");
72     }
73 
IsimUiccRecords(UiccCardApplication app, Context c, CommandsInterface ci)74     public IsimUiccRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
75         super(app, c, ci);
76 
77         mRecordsRequested = false;  // No load request is made till SIM ready
78         //todo: currently locked state for ISIM is not handled well and may cause app state to not
79         //be broadcast
80         mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_NONE;
81 
82         // recordsToLoad is set to 0 because no requests are made yet
83         mRecordsToLoad = 0;
84         // Start off by setting empty state
85         resetRecords();
86         if (DBG) log("IsimUiccRecords X ctor this=" + this);
87     }
88 
89     @Override
dispose()90     public void dispose() {
91         log("Disposing " + this);
92         resetRecords();
93         super.dispose();
94     }
95 
96     // ***** Overridden from Handler
handleMessage(Message msg)97     public void handleMessage(Message msg) {
98         AsyncResult ar;
99 
100         if (mDestroyed.get()) {
101             Rlog.e(LOG_TAG, "Received message " + msg +
102                     "[" + msg.what + "] while being destroyed. Ignoring.");
103             return;
104         }
105         loge("IsimUiccRecords: handleMessage " + msg + "[" + msg.what + "] ");
106 
107         try {
108             switch (msg.what) {
109                 case EVENT_REFRESH:
110                     broadcastRefresh();
111                     super.handleMessage(msg);
112                     break;
113 
114                 default:
115                     super.handleMessage(msg);   // IccRecords handles generic record load responses
116 
117             }
118         } catch (RuntimeException exc) {
119             // I don't want these exceptions to be fatal
120             Rlog.w(LOG_TAG, "Exception parsing SIM record", exc);
121         }
122     }
123 
124     @UnsupportedAppUsage
fetchIsimRecords()125     protected void fetchIsimRecords() {
126         mRecordsRequested = true;
127 
128         mFh.loadEFTransparent(EF_IMPI, obtainMessage(
129                 IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpiLoaded()));
130         mRecordsToLoad++;
131 
132         mFh.loadEFLinearFixedAll(EF_IMPU, obtainMessage(
133                 IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpuLoaded()));
134         mRecordsToLoad++;
135 
136         mFh.loadEFTransparent(EF_DOMAIN, obtainMessage(
137                 IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimDomainLoaded()));
138         mRecordsToLoad++;
139         mFh.loadEFTransparent(EF_IST, obtainMessage(
140                     IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimIstLoaded()));
141         mRecordsToLoad++;
142         mFh.loadEFLinearFixedAll(EF_PCSCF, obtainMessage(
143                     IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimPcscfLoaded()));
144         mRecordsToLoad++;
145 
146         if (DBG) log("fetchIsimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested);
147     }
148 
resetRecords()149     protected void resetRecords() {
150         // recordsRequested is set to false indicating that the SIM
151         // read requests made so far are not valid. This is set to
152         // true only when fresh set of read requests are made.
153         mIsimImpi = null;
154         mIsimDomain = null;
155         mIsimImpu = null;
156         mIsimIst = null;
157         mIsimPcscf = null;
158         auth_rsp = null;
159 
160         mRecordsRequested = false;
161         mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_NONE;
162         mLoaded.set(false);
163     }
164 
165     private class EfIsimImpiLoaded implements IccRecords.IccRecordLoaded {
getEfName()166         public String getEfName() {
167             return "EF_ISIM_IMPI";
168         }
onRecordLoaded(AsyncResult ar)169         public void onRecordLoaded(AsyncResult ar) {
170             byte[] data = (byte[]) ar.result;
171             mIsimImpi = isimTlvToString(data);
172             if (DUMP_RECORDS) log("EF_IMPI=" + mIsimImpi);
173         }
174     }
175 
176     private class EfIsimImpuLoaded implements IccRecords.IccRecordLoaded {
getEfName()177         public String getEfName() {
178             return "EF_ISIM_IMPU";
179         }
onRecordLoaded(AsyncResult ar)180         public void onRecordLoaded(AsyncResult ar) {
181             ArrayList<byte[]> impuList = (ArrayList<byte[]>) ar.result;
182             if (DBG) log("EF_IMPU record count: " + impuList.size());
183             mIsimImpu = new String[impuList.size()];
184             int i = 0;
185             for (byte[] identity : impuList) {
186                 String impu = isimTlvToString(identity);
187                 if (DUMP_RECORDS) log("EF_IMPU[" + i + "]=" + impu);
188                 mIsimImpu[i++] = impu;
189             }
190         }
191     }
192 
193     private class EfIsimDomainLoaded implements IccRecords.IccRecordLoaded {
getEfName()194         public String getEfName() {
195             return "EF_ISIM_DOMAIN";
196         }
onRecordLoaded(AsyncResult ar)197         public void onRecordLoaded(AsyncResult ar) {
198             byte[] data = (byte[]) ar.result;
199             mIsimDomain = isimTlvToString(data);
200             if (DUMP_RECORDS) log("EF_DOMAIN=" + mIsimDomain);
201         }
202     }
203 
204     private class EfIsimIstLoaded implements IccRecords.IccRecordLoaded {
getEfName()205         public String getEfName() {
206             return "EF_ISIM_IST";
207         }
onRecordLoaded(AsyncResult ar)208         public void onRecordLoaded(AsyncResult ar) {
209             byte[] data = (byte[]) ar.result;
210             mIsimIst = IccUtils.bytesToHexString(data);
211             if (DUMP_RECORDS) log("EF_IST=" + mIsimIst);
212         }
213     }
214     private class EfIsimPcscfLoaded implements IccRecords.IccRecordLoaded {
getEfName()215         public String getEfName() {
216             return "EF_ISIM_PCSCF";
217         }
onRecordLoaded(AsyncResult ar)218         public void onRecordLoaded(AsyncResult ar) {
219             ArrayList<byte[]> pcscflist = (ArrayList<byte[]>) ar.result;
220             if (DBG) log("EF_PCSCF record count: " + pcscflist.size());
221             mIsimPcscf = new String[pcscflist.size()];
222             int i = 0;
223             for (byte[] identity : pcscflist) {
224                 String pcscf = isimTlvToString(identity);
225                 if (DUMP_RECORDS) log("EF_PCSCF[" + i + "]=" + pcscf);
226                 mIsimPcscf[i++] = pcscf;
227             }
228         }
229     }
230 
231     /**
232      * ISIM records for IMS are stored inside a Tag-Length-Value record as a UTF-8 string
233      * with tag value 0x80.
234      * @param record the byte array containing the IMS data string
235      * @return the decoded String value, or null if the record can't be decoded
236      */
237     @UnsupportedAppUsage
isimTlvToString(byte[] record)238     private static String isimTlvToString(byte[] record) {
239         SimTlv tlv = new SimTlv(record, 0, record.length);
240         do {
241             if (tlv.getTag() == TAG_ISIM_VALUE) {
242                 return new String(tlv.getData(), Charset.forName("UTF-8"));
243             }
244         } while (tlv.nextObject());
245 
246         if (VDBG) {
247             Rlog.d(LOG_TAG, "[ISIM] can't find TLV. record = " + IccUtils.bytesToHexString(record));
248         }
249         return null;
250     }
251 
252     @Override
onRecordLoaded()253     protected void onRecordLoaded() {
254         // One record loaded successfully or failed, In either case
255         // we need to update the recordsToLoad count
256         mRecordsToLoad -= 1;
257         if (DBG) log("onRecordLoaded " + mRecordsToLoad + " requested: " + mRecordsRequested);
258 
259         if (getRecordsLoaded()) {
260             onAllRecordsLoaded();
261         } else if (getLockedRecordsLoaded() || getNetworkLockedRecordsLoaded()) {
262             onLockedAllRecordsLoaded();
263         } else if (mRecordsToLoad < 0) {
264             loge("recordsToLoad <0, programmer error suspected");
265             mRecordsToLoad = 0;
266         }
267     }
268 
onLockedAllRecordsLoaded()269     private void onLockedAllRecordsLoaded() {
270         if (DBG) log("SIM locked; record load complete");
271         if (mLockedRecordsReqReason == LOCKED_RECORDS_REQ_REASON_LOCKED) {
272             mLockedRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null));
273         } else if (mLockedRecordsReqReason == LOCKED_RECORDS_REQ_REASON_NETWORK_LOCKED) {
274             mNetworkLockedRecordsLoadedRegistrants.notifyRegistrants(
275                     new AsyncResult(null, null, null));
276         } else {
277             loge("onLockedAllRecordsLoaded: unexpected mLockedRecordsReqReason "
278                     + mLockedRecordsReqReason);
279         }
280     }
281 
282     @Override
onAllRecordsLoaded()283     protected void onAllRecordsLoaded() {
284        if (DBG) log("record load complete");
285         mLoaded.set(true);
286         mRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null));
287     }
288 
289     @Override
handleFileUpdate(int efid)290     protected void handleFileUpdate(int efid) {
291         switch (efid) {
292             case EF_IMPI:
293                 mFh.loadEFTransparent(EF_IMPI, obtainMessage(
294                             IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpiLoaded()));
295                 mRecordsToLoad++;
296                 break;
297 
298             case EF_IMPU:
299                 mFh.loadEFLinearFixedAll(EF_IMPU, obtainMessage(
300                             IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpuLoaded()));
301                 mRecordsToLoad++;
302             break;
303 
304             case EF_DOMAIN:
305                 mFh.loadEFTransparent(EF_DOMAIN, obtainMessage(
306                             IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimDomainLoaded()));
307                 mRecordsToLoad++;
308             break;
309 
310             case EF_IST:
311                 mFh.loadEFTransparent(EF_IST, obtainMessage(
312                             IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimIstLoaded()));
313                 mRecordsToLoad++;
314             break;
315 
316             case EF_PCSCF:
317                 mFh.loadEFLinearFixedAll(EF_PCSCF, obtainMessage(
318                             IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimPcscfLoaded()));
319                 mRecordsToLoad++;
320 
321             default:
322                 mLoaded.set(false);
323                 fetchIsimRecords();
324                 break;
325         }
326     }
327 
broadcastRefresh()328     private void broadcastRefresh() {
329         Intent intent = new Intent(INTENT_ISIM_REFRESH);
330         log("send ISim REFRESH: " + INTENT_ISIM_REFRESH);
331         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mParentApp.getPhoneId());
332         mContext.sendBroadcast(intent);
333     }
334 
335     /**
336      * Return the IMS private user identity (IMPI).
337      * Returns null if the IMPI hasn't been loaded or isn't present on the ISIM.
338      * @return the IMS private user identity string, or null if not available
339      */
340     @Override
getIsimImpi()341     public String getIsimImpi() {
342         return mIsimImpi;
343     }
344 
345     /**
346      * Return the IMS home network domain name.
347      * Returns null if the IMS domain hasn't been loaded or isn't present on the ISIM.
348      * @return the IMS home network domain name, or null if not available
349      */
350     @Override
getIsimDomain()351     public String getIsimDomain() {
352         return mIsimDomain;
353     }
354 
355     /**
356      * Return an array of IMS public user identities (IMPU).
357      * Returns null if the IMPU hasn't been loaded or isn't present on the ISIM.
358      * @return an array of IMS public user identity strings, or null if not available
359      */
360     @Override
getIsimImpu()361     public String[] getIsimImpu() {
362         return (mIsimImpu != null) ? mIsimImpu.clone() : null;
363     }
364 
365     /**
366      * Returns the IMS Service Table (IST) that was loaded from the ISIM.
367      * @return IMS Service Table or null if not present or not loaded
368      */
369     @Override
getIsimIst()370     public String getIsimIst() {
371         return mIsimIst;
372     }
373 
374     /**
375      * Returns the IMS Proxy Call Session Control Function(PCSCF) that were loaded from the ISIM.
376      * @return an array of  PCSCF strings with one PCSCF per string, or null if
377      *      not present or not loaded
378      */
379     @Override
getIsimPcscf()380     public String[] getIsimPcscf() {
381         return (mIsimPcscf != null) ? mIsimPcscf.clone() : null;
382     }
383 
384     @Override
onReady()385     public void onReady() {
386         fetchIsimRecords();
387     }
388 
389     @Override
onRefresh(boolean fileChanged, int[] fileList)390     public void onRefresh(boolean fileChanged, int[] fileList) {
391         if (fileChanged) {
392             // A future optimization would be to inspect fileList and
393             // only reload those files that we care about.  For now,
394             // just re-fetch all SIM records that we cache.
395             fetchIsimRecords();
396         }
397     }
398 
399     @Override
setVoiceMailNumber(String alphaTag, String voiceNumber, Message onComplete)400     public void setVoiceMailNumber(String alphaTag, String voiceNumber,
401             Message onComplete) {
402         // Not applicable to Isim
403     }
404 
405     @Override
setVoiceMessageWaiting(int line, int countWaiting)406     public void setVoiceMessageWaiting(int line, int countWaiting) {
407         // Not applicable to Isim
408     }
409 
410     @UnsupportedAppUsage
411     @Override
log(String s)412     protected void log(String s) {
413         if (mParentApp != null) {
414             Rlog.d(LOG_TAG, "[ISIM-" + mParentApp.getPhoneId() + "] " + s);
415         } else {
416             Rlog.d(LOG_TAG, "[ISIM] " + s);
417         }
418     }
419 
420     @Override
loge(String s)421     protected void loge(String s) {
422         if (mParentApp != null) {
423             Rlog.e(LOG_TAG, "[ISIM-" + mParentApp.getPhoneId() + "] " + s);
424         } else {
425             Rlog.e(LOG_TAG, "[ISIM] " + s);
426         }
427     }
428 
429     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)430     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
431         pw.println("IsimRecords: " + this);
432         pw.println(" extends:");
433         super.dump(fd, pw, args);
434         if (DUMP_RECORDS) {
435             pw.println(" mIsimImpi=" + mIsimImpi);
436             pw.println(" mIsimDomain=" + mIsimDomain);
437             pw.println(" mIsimImpu[]=" + Arrays.toString(mIsimImpu));
438             pw.println(" mIsimIst" + mIsimIst);
439             pw.println(" mIsimPcscf" + mIsimPcscf);
440         }
441         pw.flush();
442     }
443 
444     @Override
getVoiceMessageCount()445     public int getVoiceMessageCount() {
446         return 0; // Not applicable to Isim
447     }
448 
449 }
450