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