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 }