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