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 }