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