1 /* 2 * Copyright (C) 2008 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.cdma; 18 19 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY; 20 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC; 21 import android.os.AsyncResult; 22 import android.os.Handler; 23 import android.os.Message; 24 import android.os.Registrant; 25 import android.os.SystemProperties; 26 import android.util.Log; 27 28 import com.android.internal.telephony.AdnRecord; 29 import com.android.internal.telephony.AdnRecordCache; 30 import com.android.internal.telephony.AdnRecordLoader; 31 import com.android.internal.telephony.CommandsInterface; 32 import com.android.internal.telephony.TelephonyProperties; 33 import com.android.internal.telephony.cdma.RuimCard; 34 import com.android.internal.telephony.MccTable; 35 36 // can't be used since VoiceMailConstants is not public 37 //import com.android.internal.telephony.gsm.VoiceMailConstants; 38 import com.android.internal.telephony.IccException; 39 import com.android.internal.telephony.IccRecords; 40 import com.android.internal.telephony.IccUtils; 41 import com.android.internal.telephony.PhoneProxy; 42 43 44 /** 45 * {@hide} 46 */ 47 public final class RuimRecords extends IccRecords { 48 static final String LOG_TAG = "CDMA"; 49 50 private static final boolean DBG = true; 51 private boolean m_ota_commited=false; 52 53 // ***** Instance Variables 54 55 private String mImsi; 56 private String mMyMobileNumber; 57 private String mMin2Min1; 58 59 private String mPrlVersion; 60 61 // ***** Event Constants 62 63 private static final int EVENT_RUIM_READY = 1; 64 private static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 2; 65 private static final int EVENT_GET_IMSI_DONE = 3; 66 private static final int EVENT_GET_DEVICE_IDENTITY_DONE = 4; 67 private static final int EVENT_GET_ICCID_DONE = 5; 68 private static final int EVENT_GET_CDMA_SUBSCRIPTION_DONE = 10; 69 private static final int EVENT_UPDATE_DONE = 14; 70 private static final int EVENT_GET_SST_DONE = 17; 71 private static final int EVENT_GET_ALL_SMS_DONE = 18; 72 private static final int EVENT_MARK_SMS_READ_DONE = 19; 73 74 private static final int EVENT_SMS_ON_RUIM = 21; 75 private static final int EVENT_GET_SMS_DONE = 22; 76 77 private static final int EVENT_RUIM_REFRESH = 31; 78 79 80 RuimRecords(CDMAPhone p) { 81 super(p); 82 83 adnCache = new AdnRecordCache(phone); 84 85 recordsRequested = false; // No load request is made till SIM ready 86 87 // recordsToLoad is set to 0 because no requests are made yet 88 recordsToLoad = 0; 89 90 91 p.mCM.registerForRUIMReady(this, EVENT_RUIM_READY, null); 92 p.mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null); 93 // NOTE the EVENT_SMS_ON_RUIM is not registered 94 p.mCM.registerForIccRefresh(this, EVENT_RUIM_REFRESH, null); 95 96 // Start off by setting empty state 97 onRadioOffOrNotAvailable(); 98 99 } 100 101 @Override 102 public void dispose() { 103 //Unregister for all events 104 phone.mCM.unregisterForRUIMReady(this); 105 phone.mCM.unregisterForOffOrNotAvailable( this); 106 phone.mCM.unregisterForIccRefresh(this); 107 } 108 109 @Override 110 protected void finalize() { 111 if(DBG) Log.d(LOG_TAG, "RuimRecords finalized"); 112 } 113 114 @Override 115 protected void onRadioOffOrNotAvailable() { 116 countVoiceMessages = 0; 117 mncLength = UNINITIALIZED; 118 iccid = null; 119 120 adnCache.reset(); 121 122 // Don't clean up PROPERTY_ICC_OPERATOR_ISO_COUNTRY and 123 // PROPERTY_ICC_OPERATOR_NUMERIC here. Since not all CDMA 124 // devices have RUIM, these properties should keep the original 125 // values, e.g. build time settings, when there is no RUIM but 126 // set new values when RUIM is available and loaded. 127 128 // recordsRequested is set to false indicating that the SIM 129 // read requests made so far are not valid. This is set to 130 // true only when fresh set of read requests are made. 131 recordsRequested = false; 132 } 133 134 public String getMdnNumber() { 135 return mMyMobileNumber; 136 } 137 138 public String getCdmaMin() { 139 return mMin2Min1; 140 } 141 142 /** Returns null if RUIM is not yet ready */ 143 public String getPrlVersion() { 144 return mPrlVersion; 145 } 146 147 @Override 148 public void setVoiceMailNumber(String alphaTag, String voiceNumber, Message onComplete){ 149 // In CDMA this is Operator/OEM dependent 150 AsyncResult.forMessage((onComplete)).exception = 151 new IccException("setVoiceMailNumber not implemented"); 152 onComplete.sendToTarget(); 153 Log.e(LOG_TAG, "method setVoiceMailNumber is not implemented"); 154 } 155 156 /** 157 * Called by CCAT Service when REFRESH is received. 158 * @param fileChanged indicates whether any files changed 159 * @param fileList if non-null, a list of EF files that changed 160 */ 161 @Override 162 public void onRefresh(boolean fileChanged, int[] fileList) { 163 if (fileChanged) { 164 // A future optimization would be to inspect fileList and 165 // only reload those files that we care about. For now, 166 // just re-fetch all RUIM records that we cache. 167 fetchRuimRecords(); 168 } 169 } 170 171 /** 172 * Returns the 5 or 6 digit MCC/MNC of the operator that 173 * provided the RUIM card. Returns null of RUIM is not yet ready 174 */ 175 public String getRUIMOperatorNumeric() { 176 if (mImsi == null) { 177 return null; 178 } 179 180 if (mncLength != UNINITIALIZED && mncLength != UNKNOWN) { 181 // Length = length of MCC + length of MNC 182 // length of mcc = 3 (3GPP2 C.S0005 - Section 2.3) 183 return mImsi.substring(0, 3 + mncLength); 184 } 185 186 // Guess the MNC length based on the MCC if we don't 187 // have a valid value in ef[ad] 188 189 int mcc = Integer.parseInt(mImsi.substring(0,3)); 190 return mImsi.substring(0, 3 + MccTable.smallestDigitsMccForMnc(mcc)); 191 } 192 193 @Override 194 public void handleMessage(Message msg) { 195 AsyncResult ar; 196 197 byte data[]; 198 199 boolean isRecordLoadResponse = false; 200 201 try { switch (msg.what) { 202 case EVENT_RUIM_READY: 203 onRuimReady(); 204 break; 205 206 case EVENT_RADIO_OFF_OR_NOT_AVAILABLE: 207 onRadioOffOrNotAvailable(); 208 break; 209 210 case EVENT_GET_DEVICE_IDENTITY_DONE: 211 Log.d(LOG_TAG, "Event EVENT_GET_DEVICE_IDENTITY_DONE Received"); 212 break; 213 214 /* IO events */ 215 case EVENT_GET_IMSI_DONE: 216 isRecordLoadResponse = true; 217 218 ar = (AsyncResult)msg.obj; 219 if (ar.exception != null) { 220 Log.e(LOG_TAG, "Exception querying IMSI, Exception:" + ar.exception); 221 break; 222 } 223 224 mImsi = (String) ar.result; 225 226 // IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more 227 // than 15 (and usually 15). 228 if (mImsi != null && (mImsi.length() < 6 || mImsi.length() > 15)) { 229 Log.e(LOG_TAG, "invalid IMSI " + mImsi); 230 mImsi = null; 231 } 232 233 Log.d(LOG_TAG, "IMSI: " + mImsi.substring(0, 6) + "xxxxxxxxx"); 234 235 String operatorNumeric = getRUIMOperatorNumeric(); 236 if (operatorNumeric != null) { 237 if(operatorNumeric.length() <= 6){ 238 MccTable.updateMccMncConfiguration(phone, operatorNumeric); 239 } 240 } 241 break; 242 243 case EVENT_GET_CDMA_SUBSCRIPTION_DONE: 244 ar = (AsyncResult)msg.obj; 245 String localTemp[] = (String[])ar.result; 246 if (ar.exception != null) { 247 break; 248 } 249 250 mMyMobileNumber = localTemp[0]; 251 mMin2Min1 = localTemp[3]; 252 mPrlVersion = localTemp[4]; 253 254 Log.d(LOG_TAG, "MDN: " + mMyMobileNumber + " MIN: " + mMin2Min1); 255 256 break; 257 258 case EVENT_GET_ICCID_DONE: 259 isRecordLoadResponse = true; 260 261 ar = (AsyncResult)msg.obj; 262 data = (byte[])ar.result; 263 264 if (ar.exception != null) { 265 break; 266 } 267 268 iccid = IccUtils.bcdToString(data, 0, data.length); 269 270 Log.d(LOG_TAG, "iccid: " + iccid); 271 272 break; 273 274 case EVENT_UPDATE_DONE: 275 ar = (AsyncResult)msg.obj; 276 if (ar.exception != null) { 277 Log.i(LOG_TAG, "RuimRecords update failed", ar.exception); 278 } 279 break; 280 281 case EVENT_GET_ALL_SMS_DONE: 282 case EVENT_MARK_SMS_READ_DONE: 283 case EVENT_SMS_ON_RUIM: 284 case EVENT_GET_SMS_DONE: 285 Log.w(LOG_TAG, "Event not supported: " + msg.what); 286 break; 287 288 // TODO: probably EF_CST should be read instead 289 case EVENT_GET_SST_DONE: 290 Log.d(LOG_TAG, "Event EVENT_GET_SST_DONE Received"); 291 break; 292 293 case EVENT_RUIM_REFRESH: 294 isRecordLoadResponse = false; 295 ar = (AsyncResult)msg.obj; 296 if (ar.exception == null) { 297 handleRuimRefresh((int[])(ar.result)); 298 } 299 break; 300 301 }}catch (RuntimeException exc) { 302 // I don't want these exceptions to be fatal 303 Log.w(LOG_TAG, "Exception parsing RUIM record", exc); 304 } finally { 305 // Count up record load responses even if they are fails 306 if (isRecordLoadResponse) { 307 onRecordLoaded(); 308 } 309 } 310 } 311 312 @Override 313 protected void onRecordLoaded() { 314 // One record loaded successfully or failed, In either case 315 // we need to update the recordsToLoad count 316 recordsToLoad -= 1; 317 318 if (recordsToLoad == 0 && recordsRequested == true) { 319 onAllRecordsLoaded(); 320 } else if (recordsToLoad < 0) { 321 Log.e(LOG_TAG, "RuimRecords: recordsToLoad <0, programmer error suspected"); 322 recordsToLoad = 0; 323 } 324 } 325 326 @Override 327 protected void onAllRecordsLoaded() { 328 Log.d(LOG_TAG, "RuimRecords: record load complete"); 329 330 // Further records that can be inserted are Operator/OEM dependent 331 332 String operator = getRUIMOperatorNumeric(); 333 SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, operator); 334 335 if (mImsi != null) { 336 SystemProperties.set(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, 337 MccTable.countryCodeForMcc(Integer.parseInt(mImsi.substring(0,3)))); 338 } 339 recordsLoadedRegistrants.notifyRegistrants( 340 new AsyncResult(null, null, null)); 341 phone.mIccCard.broadcastIccStateChangedIntent( 342 RuimCard.INTENT_VALUE_ICC_LOADED, null); 343 } 344 345 private void onRuimReady() { 346 /* broadcast intent ICC_READY here so that we can make sure 347 READY is sent before IMSI ready 348 */ 349 350 phone.mIccCard.broadcastIccStateChangedIntent( 351 RuimCard.INTENT_VALUE_ICC_READY, null); 352 353 fetchRuimRecords(); 354 355 phone.mCM.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE)); 356 357 } 358 359 360 private void fetchRuimRecords() { 361 recordsRequested = true; 362 363 Log.v(LOG_TAG, "RuimRecords:fetchRuimRecords " + recordsToLoad); 364 365 phone.mCM.getIMSI(obtainMessage(EVENT_GET_IMSI_DONE)); 366 recordsToLoad++; 367 368 phone.getIccFileHandler().loadEFTransparent(EF_ICCID, 369 obtainMessage(EVENT_GET_ICCID_DONE)); 370 recordsToLoad++; 371 372 // Further records that can be inserted are Operator/OEM dependent 373 } 374 375 /** 376 * {@inheritDoc} 377 * 378 * No Display rule for RUIMs yet. 379 */ 380 @Override 381 public int getDisplayRule(String plmn) { 382 // TODO together with spn 383 return 0; 384 } 385 386 @Override 387 public void setVoiceMessageWaiting(int line, int countWaiting) { 388 if (line != 1) { 389 // only profile 1 is supported 390 return; 391 } 392 393 // range check 394 if (countWaiting < 0) { 395 countWaiting = -1; 396 } else if (countWaiting > 0xff) { 397 // C.S0015-B v2, 4.5.12 398 // range: 0-99 399 countWaiting = 0xff; 400 } 401 countVoiceMessages = countWaiting; 402 403 ((CDMAPhone) phone).notifyMessageWaitingIndicator(); 404 } 405 406 private void handleRuimRefresh(int[] result) { 407 if (result == null || result.length == 0) { 408 if (DBG) log("handleRuimRefresh without input"); 409 return; 410 } 411 412 switch ((result[0])) { 413 case CommandsInterface.SIM_REFRESH_FILE_UPDATED: 414 if (DBG) log("handleRuimRefresh with SIM_REFRESH_FILE_UPDATED"); 415 adnCache.reset(); 416 fetchRuimRecords(); 417 break; 418 case CommandsInterface.SIM_REFRESH_INIT: 419 if (DBG) log("handleRuimRefresh with SIM_REFRESH_INIT"); 420 // need to reload all files (that we care about) 421 fetchRuimRecords(); 422 break; 423 case CommandsInterface.SIM_REFRESH_RESET: 424 if (DBG) log("handleRuimRefresh with SIM_REFRESH_RESET"); 425 phone.mCM.setRadioPower(false, null); 426 /* Note: no need to call setRadioPower(true). Assuming the desired 427 * radio power state is still ON (as tracked by ServiceStateTracker), 428 * ServiceStateTracker will call setRadioPower when it receives the 429 * RADIO_STATE_CHANGED notification for the power off. And if the 430 * desired power state has changed in the interim, we don't want to 431 * override it with an unconditional power on. 432 */ 433 break; 434 default: 435 // unknown refresh operation 436 if (DBG) log("handleRuimRefresh with unknown operation"); 437 break; 438 } 439 } 440 441 @Override 442 protected void log(String s) { 443 Log.d(LOG_TAG, "[RuimRecords] " + s); 444 } 445 446 @Override 447 protected void loge(String s) { 448 Log.e(LOG_TAG, "[RuimRecords] " + s); 449 } 450 } 451