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