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