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