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