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