| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.telephony.uicc; |
| |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.res.Resources; |
| import android.os.AsyncResult; |
| import android.os.Message; |
| import android.os.PersistableBundle; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.PhoneNumberUtils; |
| import android.telephony.Rlog; |
| import android.telephony.ServiceState; |
| import android.telephony.SmsMessage; |
| import android.telephony.SubscriptionInfo; |
| import android.telephony.SubscriptionManager; |
| import android.text.TextUtils; |
| |
| import com.android.internal.telephony.CommandsInterface; |
| import com.android.internal.telephony.MccTable; |
| import com.android.internal.telephony.SmsConstants; |
| import com.android.internal.telephony.SubscriptionController; |
| import com.android.internal.telephony.gsm.SimTlv; |
| import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| |
| /** |
| * {@hide} |
| */ |
| public class SIMRecords extends IccRecords { |
| protected static final String LOG_TAG = "SIMRecords"; |
| |
| private static final boolean CRASH_RIL = false; |
| |
| private static final boolean VDBG = false; |
| |
| // ***** Instance Variables |
| |
| VoiceMailConstants mVmConfig; |
| |
| // ***** Cached SIM State; cleared on channel close |
| |
| private int mCallForwardingStatus; |
| |
| |
| /** |
| * States only used by getSpnFsm FSM |
| */ |
| private GetSpnFsmState mSpnState; |
| |
| /** CPHS service information (See CPHS 4.2 B.3.1.1) |
| * It will be set in onSimReady if reading GET_CPHS_INFO successfully |
| * mCphsInfo[0] is CPHS Phase |
| * mCphsInfo[1] and mCphsInfo[2] is CPHS Service Table |
| */ |
| private byte[] mCphsInfo = null; |
| boolean mCspPlmnEnabled = true; |
| |
| byte[] mEfMWIS = null; |
| byte[] mEfCPHS_MWI =null; |
| byte[] mEfCff = null; |
| byte[] mEfCfis = null; |
| |
| byte[] mEfLi = null; |
| byte[] mEfPl = null; |
| |
| int mSpnDisplayCondition; |
| // Numeric network codes listed in TS 51.011 EF[SPDI] |
| ArrayList<String> mSpdiNetworks = null; |
| |
| UsimServiceTable mUsimServiceTable; |
| |
| @Override |
| public String toString() { |
| return "SimRecords: " + super.toString() |
| + " mVmConfig" + mVmConfig |
| + " callForwardingEnabled=" + mCallForwardingStatus |
| + " spnState=" + mSpnState |
| + " mCphsInfo=" + mCphsInfo |
| + " mCspPlmnEnabled=" + mCspPlmnEnabled |
| + " efMWIS=" + mEfMWIS |
| + " efCPHS_MWI=" + mEfCPHS_MWI |
| + " mEfCff=" + mEfCff |
| + " mEfCfis=" + mEfCfis |
| + " getOperatorNumeric=" + getOperatorNumeric(); |
| } |
| |
| // ***** Constants |
| |
| // From TS 51.011 EF[SPDI] section |
| static final int TAG_SPDI = 0xA3; |
| static final int TAG_SPDI_PLMN_LIST = 0x80; |
| |
| // Full Name IEI from TS 24.008 |
| static final int TAG_FULL_NETWORK_NAME = 0x43; |
| |
| // Short Name IEI from TS 24.008 |
| static final int TAG_SHORT_NETWORK_NAME = 0x45; |
| |
| // active CFF from CPHS 4.2 B.4.5 |
| static final int CFF_UNCONDITIONAL_ACTIVE = 0x0a; |
| static final int CFF_UNCONDITIONAL_DEACTIVE = 0x05; |
| static final int CFF_LINE1_MASK = 0x0f; |
| static final int CFF_LINE1_RESET = 0xf0; |
| |
| // CPHS Service Table (See CPHS 4.2 B.3.1) |
| private static final int CPHS_SST_MBN_MASK = 0x30; |
| private static final int CPHS_SST_MBN_ENABLED = 0x30; |
| |
| // EF_CFIS related constants |
| // Spec reference TS 51.011 section 10.3.46. |
| private static final int CFIS_BCD_NUMBER_LENGTH_OFFSET = 2; |
| private static final int CFIS_TON_NPI_OFFSET = 3; |
| private static final int CFIS_ADN_CAPABILITY_ID_OFFSET = 14; |
| private static final int CFIS_ADN_EXTENSION_ID_OFFSET = 15; |
| |
| // ***** Event Constants |
| private static final int SIM_RECORD_EVENT_BASE = 0x00; |
| private static final int EVENT_GET_IMSI_DONE = 3 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_ICCID_DONE = 4 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_MBI_DONE = 5 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_MBDN_DONE = 6 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_MWIS_DONE = 7 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE = 8 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_AD_DONE = 9 + SIM_RECORD_EVENT_BASE; // Admin data on SIM |
| private static final int EVENT_GET_MSISDN_DONE = 10 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_CPHS_MAILBOX_DONE = 11 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_SPN_DONE = 12 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_SPDI_DONE = 13 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_UPDATE_DONE = 14 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_PNN_DONE = 15 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_SST_DONE = 17 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_ALL_SMS_DONE = 18 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_MARK_SMS_READ_DONE = 19 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_SET_MBDN_DONE = 20 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_SMS_ON_SIM = 21 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_SMS_DONE = 22 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_CFF_DONE = 24 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_SET_CPHS_MAILBOX_DONE = 25 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_INFO_CPHS_DONE = 26 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_SET_MSISDN_DONE = 30 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_CFIS_DONE = 32 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_CSP_CPHS_DONE = 33 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_GID1_DONE = 34 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_GID2_DONE = 36 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_PLMN_W_ACT_DONE = 37 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_OPLMN_W_ACT_DONE = 38 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_HPLMN_W_ACT_DONE = 39 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_EHPLMN_DONE = 40 + SIM_RECORD_EVENT_BASE; |
| private static final int EVENT_GET_FPLMN_DONE = 41 + SIM_RECORD_EVENT_BASE; |
| |
| // TODO: Possibly move these to IccRecords.java |
| private static final int SYSTEM_EVENT_BASE = 0x100; |
| private static final int EVENT_CARRIER_CONFIG_CHANGED = 1 + SYSTEM_EVENT_BASE; |
| private static final int EVENT_APP_LOCKED = 2 + SYSTEM_EVENT_BASE; |
| private static final int EVENT_SIM_REFRESH = 3 + SYSTEM_EVENT_BASE; |
| |
| // Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length. |
| |
| private static final String[] MCCMNC_CODES_HAVING_3DIGITS_MNC = { |
| "302370", "302720", "310260", |
| "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032", |
| "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040", |
| "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750", |
| "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800", |
| "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808", |
| "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816", |
| "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824", |
| "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832", |
| "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840", |
| "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848", |
| "405849", "405850", "405851", "405852", "405853", "405854", "405855", "405856", |
| "405857", "405858", "405859", "405860", "405861", "405862", "405863", "405864", |
| "405865", "405866", "405867", "405868", "405869", "405870", "405871", "405872", |
| "405873", "405874", "405875", "405876", "405877", "405878", "405879", "405880", |
| "405881", "405882", "405883", "405884", "405885", "405886", "405908", "405909", |
| "405910", "405911", "405912", "405913", "405914", "405915", "405916", "405917", |
| "405918", "405919", "405920", "405921", "405922", "405923", "405924", "405925", |
| "405926", "405927", "405928", "405929", "405930", "405931", "405932", "502142", |
| "502143", "502145", "502146", "502147", "502148" |
| }; |
| |
| // ***** Constructor |
| |
| public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) { |
| super(app, c, ci); |
| |
| mAdnCache = new AdnRecordCache(mFh); |
| |
| mVmConfig = new VoiceMailConstants(); |
| |
| mRecordsRequested = false; // No load request is made till SIM ready |
| mLockedRecordsRequested = false; |
| |
| // recordsToLoad is set to 0 because no requests are made yet |
| mRecordsToLoad = 0; |
| |
| mCi.setOnSmsOnSim(this, EVENT_SMS_ON_SIM, null); |
| mCi.registerForIccRefresh(this, EVENT_SIM_REFRESH, null); |
| |
| // Start off by setting empty state |
| resetRecords(); |
| mParentApp.registerForReady(this, EVENT_APP_READY, null); |
| mParentApp.registerForLocked(this, EVENT_APP_LOCKED, null); |
| if (DBG) log("SIMRecords X ctor this=" + this); |
| |
| IntentFilter intentfilter = new IntentFilter(); |
| intentfilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); |
| c.registerReceiver(mReceiver, intentfilter); |
| } |
| |
| private final BroadcastReceiver mReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) { |
| sendMessage(obtainMessage(EVENT_CARRIER_CONFIG_CHANGED)); |
| } |
| } |
| }; |
| |
| @Override |
| public void dispose() { |
| if (DBG) log("Disposing SIMRecords this=" + this); |
| //Unregister for all events |
| mCi.unregisterForIccRefresh(this); |
| mCi.unSetOnSmsOnSim(this); |
| mParentApp.unregisterForReady(this); |
| mParentApp.unregisterForLocked(this); |
| mContext.unregisterReceiver(mReceiver); |
| resetRecords(); |
| super.dispose(); |
| } |
| |
| @Override |
| protected void finalize() { |
| if (DBG) log("finalized"); |
| } |
| |
| protected void resetRecords() { |
| mImsi = null; |
| mMsisdn = null; |
| mVoiceMailNum = null; |
| mMncLength = UNINITIALIZED; |
| log("setting0 mMncLength" + mMncLength); |
| mIccId = null; |
| mFullIccId = null; |
| // -1 means no EF_SPN found; treat accordingly. |
| mSpnDisplayCondition = -1; |
| mEfMWIS = null; |
| mEfCPHS_MWI = null; |
| mSpdiNetworks = null; |
| mPnnHomeName = null; |
| mGid1 = null; |
| mGid2 = null; |
| mPlmnActRecords = null; |
| mOplmnActRecords = null; |
| mHplmnActRecords = null; |
| mFplmns = null; |
| mEhplmns = null; |
| |
| mAdnCache.reset(); |
| |
| log("SIMRecords: onRadioOffOrNotAvailable set 'gsm.sim.operator.numeric' to operator=null"); |
| log("update icc_operator_numeric=" + null); |
| mTelephonyManager.setSimOperatorNumericForPhone(mParentApp.getPhoneId(), ""); |
| mTelephonyManager.setSimOperatorNameForPhone(mParentApp.getPhoneId(), ""); |
| mTelephonyManager.setSimCountryIsoForPhone(mParentApp.getPhoneId(), ""); |
| |
| // recordsRequested is set to false indicating that the SIM |
| // read requests made so far are not valid. This is set to |
| // true only when fresh set of read requests are made. |
| mRecordsRequested = false; |
| mLockedRecordsRequested = false; |
| } |
| |
| //***** Public Methods |
| |
| @Override |
| public String getMsisdnNumber() { |
| return mMsisdn; |
| } |
| |
| @Override |
| public UsimServiceTable getUsimServiceTable() { |
| return mUsimServiceTable; |
| } |
| |
| private int getExtFromEf(int ef) { |
| int ext; |
| switch (ef) { |
| case EF_MSISDN: |
| /* For USIM apps use EXT5. (TS 31.102 Section 4.2.37) */ |
| if (mParentApp.getType() == AppType.APPTYPE_USIM) { |
| ext = EF_EXT5; |
| } else { |
| ext = EF_EXT1; |
| } |
| break; |
| default: |
| ext = EF_EXT1; |
| } |
| return ext; |
| } |
| |
| /** |
| * Set subscriber number to SIM record |
| * |
| * The subscriber number is stored in EF_MSISDN (TS 51.011) |
| * |
| * When the operation is complete, onComplete will be sent to its handler |
| * |
| * @param alphaTag alpha-tagging of the dailing nubmer (up to 10 characters) |
| * @param number dialing number (up to 20 digits) |
| * if the number starts with '+', then set to international TOA |
| * @param onComplete |
| * onComplete.obj will be an AsyncResult |
| * ((AsyncResult)onComplete.obj).exception == null on success |
| * ((AsyncResult)onComplete.obj).exception != null on fail |
| */ |
| @Override |
| public void setMsisdnNumber(String alphaTag, String number, |
| Message onComplete) { |
| |
| // If the SIM card is locked by PIN, we will set EF_MSISDN fail. |
| // In that case, msisdn and msisdnTag should not be update. |
| mNewMsisdn = number; |
| mNewMsisdnTag = alphaTag; |
| |
| if(DBG) log("Set MSISDN: " + mNewMsisdnTag + " " + /*mNewMsisdn*/ |
| Rlog.pii(LOG_TAG, mNewMsisdn)); |
| |
| AdnRecord adn = new AdnRecord(mNewMsisdnTag, mNewMsisdn); |
| |
| new AdnRecordLoader(mFh).updateEF(adn, EF_MSISDN, getExtFromEf(EF_MSISDN), 1, null, |
| obtainMessage(EVENT_SET_MSISDN_DONE, onComplete)); |
| } |
| |
| @Override |
| public String getMsisdnAlphaTag() { |
| return mMsisdnTag; |
| } |
| |
| @Override |
| public String getVoiceMailNumber() { |
| return mVoiceMailNum; |
| } |
| |
| /** |
| * Set voice mail number to SIM record |
| * |
| * The voice mail number can be stored either in EF_MBDN (TS 51.011) or |
| * EF_MAILBOX_CPHS (CPHS 4.2) |
| * |
| * If EF_MBDN is available, store the voice mail number to EF_MBDN |
| * |
| * If EF_MAILBOX_CPHS is enabled, store the voice mail number to EF_CHPS |
| * |
| * So the voice mail number will be stored in both EFs if both are available |
| * |
| * Return error only if both EF_MBDN and EF_MAILBOX_CPHS fail. |
| * |
| * When the operation is complete, onComplete will be sent to its handler |
| * |
| * @param alphaTag alpha-tagging of the dailing nubmer (upto 10 characters) |
| * @param voiceNumber dailing nubmer (upto 20 digits) |
| * if the number is start with '+', then set to international TOA |
| * @param onComplete |
| * onComplete.obj will be an AsyncResult |
| * ((AsyncResult)onComplete.obj).exception == null on success |
| * ((AsyncResult)onComplete.obj).exception != null on fail |
| */ |
| @Override |
| public void setVoiceMailNumber(String alphaTag, String voiceNumber, |
| Message onComplete) { |
| if (mIsVoiceMailFixed) { |
| AsyncResult.forMessage((onComplete)).exception = |
| new IccVmFixedException("Voicemail number is fixed by operator"); |
| onComplete.sendToTarget(); |
| return; |
| } |
| |
| mNewVoiceMailNum = voiceNumber; |
| mNewVoiceMailTag = alphaTag; |
| |
| AdnRecord adn = new AdnRecord(mNewVoiceMailTag, mNewVoiceMailNum); |
| |
| if (mMailboxIndex != 0 && mMailboxIndex != 0xff) { |
| |
| new AdnRecordLoader(mFh).updateEF(adn, EF_MBDN, EF_EXT6, |
| mMailboxIndex, null, |
| obtainMessage(EVENT_SET_MBDN_DONE, onComplete)); |
| |
| } else if (isCphsMailboxEnabled()) { |
| |
| new AdnRecordLoader(mFh).updateEF(adn, EF_MAILBOX_CPHS, |
| EF_EXT1, 1, null, |
| obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, onComplete)); |
| |
| } else { |
| AsyncResult.forMessage((onComplete)).exception = |
| new IccVmNotSupportedException("Update SIM voice mailbox error"); |
| onComplete.sendToTarget(); |
| } |
| } |
| |
| @Override |
| public String getVoiceMailAlphaTag() |
| { |
| return mVoiceMailTag; |
| } |
| |
| /** |
| * Sets the SIM voice message waiting indicator records |
| * @param line GSM Subscriber Profile Number, one-based. Only '1' is supported |
| * @param countWaiting The number of messages waiting, if known. Use |
| * -1 to indicate that an unknown number of |
| * messages are waiting |
| */ |
| @Override |
| public void |
| setVoiceMessageWaiting(int line, int countWaiting) { |
| if (line != 1) { |
| // only profile 1 is supported |
| return; |
| } |
| |
| try { |
| if (mEfMWIS != null) { |
| // TS 51.011 10.3.45 |
| |
| // lsb of byte 0 is 'voicemail' status |
| mEfMWIS[0] = (byte)((mEfMWIS[0] & 0xfe) |
| | (countWaiting == 0 ? 0 : 1)); |
| |
| // byte 1 is the number of voice messages waiting |
| if (countWaiting < 0) { |
| // The spec does not define what this should be |
| // if we don't know the count |
| mEfMWIS[1] = 0; |
| } else { |
| mEfMWIS[1] = (byte) countWaiting; |
| } |
| |
| mFh.updateEFLinearFixed( |
| EF_MWIS, 1, mEfMWIS, null, |
| obtainMessage (EVENT_UPDATE_DONE, EF_MWIS, 0)); |
| } |
| |
| if (mEfCPHS_MWI != null) { |
| // Refer CPHS4_2.WW6 B4.2.3 |
| mEfCPHS_MWI[0] = (byte)((mEfCPHS_MWI[0] & 0xf0) |
| | (countWaiting == 0 ? 0x5 : 0xa)); |
| mFh.updateEFTransparent( |
| EF_VOICE_MAIL_INDICATOR_CPHS, mEfCPHS_MWI, |
| obtainMessage (EVENT_UPDATE_DONE, EF_VOICE_MAIL_INDICATOR_CPHS)); |
| } |
| } catch (ArrayIndexOutOfBoundsException ex) { |
| logw("Error saving voice mail state to SIM. Probably malformed SIM record", ex); |
| } |
| } |
| |
| // Validate data is not null and not empty. |
| private boolean validEfCfis(byte[] data) { |
| if (data != null) { |
| if (data[0] < 1 || data[0] > 4) { |
| // The MSP (Multiple Subscriber Profile) byte should be between |
| // 1 and 4 according to ETSI TS 131 102 v11.3.0 section 4.2.64. |
| logw("MSP byte: " + data[0] + " is not between 1 and 4", null); |
| } |
| // empty EF_CFIS should be considered as call forward disabled |
| for (byte b : data) { |
| if (b != (byte) 0xFF) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public int getVoiceMessageCount() { |
| boolean voiceMailWaiting = false; |
| int countVoiceMessages = DEFAULT_VOICE_MESSAGE_COUNT; |
| if (mEfMWIS != null) { |
| // Use this data if the EF[MWIS] exists and |
| // has been loaded |
| // Refer TS 51.011 Section 10.3.45 for the content description |
| voiceMailWaiting = ((mEfMWIS[0] & 0x01) != 0); |
| countVoiceMessages = mEfMWIS[1] & 0xff; |
| |
| if (voiceMailWaiting && (countVoiceMessages == 0 || countVoiceMessages == 0xff)) { |
| // Unknown count = -1 |
| countVoiceMessages = UNKNOWN_VOICE_MESSAGE_COUNT; |
| } |
| if (DBG) log(" VoiceMessageCount from SIM MWIS = " + countVoiceMessages); |
| } else if (mEfCPHS_MWI != null) { |
| // use voice mail count from CPHS |
| int indicator = (int) (mEfCPHS_MWI[0] & 0xf); |
| |
| // Refer CPHS4_2.WW6 B4.2.3 |
| if (indicator == 0xA) { |
| // Unknown count = -1 |
| countVoiceMessages = UNKNOWN_VOICE_MESSAGE_COUNT; |
| } else if (indicator == 0x5) { |
| countVoiceMessages = 0; |
| } |
| if (DBG) log(" VoiceMessageCount from SIM CPHS = " + countVoiceMessages); |
| } |
| return countVoiceMessages; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int getVoiceCallForwardingFlag() { |
| return mCallForwardingStatus; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setVoiceCallForwardingFlag(int line, boolean enable, String dialNumber) { |
| |
| if (line != 1) return; // only line 1 is supported |
| |
| mCallForwardingStatus = enable ? CALL_FORWARDING_STATUS_ENABLED : |
| CALL_FORWARDING_STATUS_DISABLED; |
| |
| mRecordsEventsRegistrants.notifyResult(EVENT_CFI); |
| |
| try { |
| if (validEfCfis(mEfCfis)) { |
| // lsb is of byte f1 is voice status |
| if (enable) { |
| mEfCfis[1] |= 1; |
| } else { |
| mEfCfis[1] &= 0xfe; |
| } |
| |
| log("setVoiceCallForwardingFlag: enable=" + enable |
| + " mEfCfis=" + IccUtils.bytesToHexString(mEfCfis)); |
| |
| // Update dialNumber if not empty and CFU is enabled. |
| // Spec reference for EF_CFIS contents, TS 51.011 section 10.3.46. |
| if (enable && !TextUtils.isEmpty(dialNumber)) { |
| logv("EF_CFIS: updating cf number, " + Rlog.pii(LOG_TAG, dialNumber)); |
| byte[] bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD( |
| dialNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN); |
| |
| System.arraycopy(bcdNumber, 0, mEfCfis, CFIS_TON_NPI_OFFSET, bcdNumber.length); |
| |
| mEfCfis[CFIS_BCD_NUMBER_LENGTH_OFFSET] = (byte) (bcdNumber.length); |
| mEfCfis[CFIS_ADN_CAPABILITY_ID_OFFSET] = (byte) 0xFF; |
| mEfCfis[CFIS_ADN_EXTENSION_ID_OFFSET] = (byte) 0xFF; |
| } |
| |
| mFh.updateEFLinearFixed( |
| EF_CFIS, 1, mEfCfis, null, |
| obtainMessage (EVENT_UPDATE_DONE, EF_CFIS)); |
| } else { |
| log("setVoiceCallForwardingFlag: ignoring enable=" + enable |
| + " invalid mEfCfis=" + IccUtils.bytesToHexString(mEfCfis)); |
| } |
| |
| if (mEfCff != null) { |
| if (enable) { |
| mEfCff[0] = (byte) ((mEfCff[0] & CFF_LINE1_RESET) |
| | CFF_UNCONDITIONAL_ACTIVE); |
| } else { |
| mEfCff[0] = (byte) ((mEfCff[0] & CFF_LINE1_RESET) |
| | CFF_UNCONDITIONAL_DEACTIVE); |
| } |
| |
| mFh.updateEFTransparent( |
| EF_CFF_CPHS, mEfCff, |
| obtainMessage (EVENT_UPDATE_DONE, EF_CFF_CPHS)); |
| } |
| } catch (ArrayIndexOutOfBoundsException ex) { |
| logw("Error saving call forwarding flag to SIM. " |
| + "Probably malformed SIM record", ex); |
| |
| } |
| } |
| |
| /** |
| * Called by STK Service when REFRESH is received. |
| * @param fileChanged indicates whether any files changed |
| * @param fileList if non-null, a list of EF files that changed |
| */ |
| @Override |
| public void onRefresh(boolean fileChanged, int[] fileList) { |
| if (fileChanged) { |
| // A future optimization would be to inspect fileList and |
| // only reload those files that we care about. For now, |
| // just re-fetch all SIM records that we cache. |
| fetchSimRecords(); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String getOperatorNumeric() { |
| String imsi = getIMSI(); |
| if (imsi == null) { |
| log("getOperatorNumeric: IMSI == null"); |
| return null; |
| } |
| if (mMncLength == UNINITIALIZED || mMncLength == UNKNOWN) { |
| log("getSIMOperatorNumeric: bad mncLength"); |
| return null; |
| } |
| |
| // Length = length of MCC + length of MNC |
| // length of mcc = 3 (TS 23.003 Section 2.2) |
| if (imsi.length() >= 3 + mMncLength) { |
| return imsi.substring(0, 3 + mMncLength); |
| } else { |
| return null; |
| } |
| } |
| |
| // ***** Overridden from Handler |
| @Override |
| public void handleMessage(Message msg) { |
| AsyncResult ar; |
| AdnRecord adn; |
| |
| byte data[]; |
| |
| boolean isRecordLoadResponse = false; |
| |
| if (mDestroyed.get()) { |
| loge("Received message " + msg + "[" + msg.what + "] " + |
| " while being destroyed. Ignoring."); |
| return; |
| } |
| |
| try { |
| switch (msg.what) { |
| case EVENT_APP_READY: |
| onReady(); |
| break; |
| |
| case EVENT_APP_LOCKED: |
| onLocked(); |
| break; |
| |
| /* IO events */ |
| case EVENT_GET_IMSI_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| |
| if (ar.exception != null) { |
| loge("Exception querying IMSI, Exception:" + ar.exception); |
| break; |
| } |
| |
| mImsi = (String) ar.result; |
| |
| // IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more |
| // than 15 (and usually 15). |
| if (mImsi != null && (mImsi.length() < 6 || mImsi.length() > 15)) { |
| loge("invalid IMSI " + mImsi); |
| mImsi = null; |
| } |
| |
| log("IMSI: mMncLength=" + mMncLength); |
| |
| if (mImsi != null && mImsi.length() >= 6) { |
| log("IMSI: " + mImsi.substring(0, 6) |
| + Rlog.pii(LOG_TAG, mImsi.substring(6))); |
| } |
| |
| String imsi = getIMSI(); |
| |
| if (((mMncLength == UNKNOWN) || (mMncLength == 2)) |
| && ((imsi != null) && (imsi.length() >= 6))) { |
| String mccmncCode = imsi.substring(0, 6); |
| for (String mccmnc : MCCMNC_CODES_HAVING_3DIGITS_MNC) { |
| if (mccmnc.equals(mccmncCode)) { |
| mMncLength = 3; |
| log("IMSI: setting1 mMncLength=" + mMncLength); |
| break; |
| } |
| } |
| } |
| |
| if (mMncLength == UNKNOWN) { |
| // the SIM has told us all it knows, but it didn't know the mnc length. |
| // guess using the mcc |
| try { |
| int mcc = Integer.parseInt(imsi.substring(0, 3)); |
| mMncLength = MccTable.smallestDigitsMccForMnc(mcc); |
| log("setting2 mMncLength=" + mMncLength); |
| } catch (NumberFormatException e) { |
| mMncLength = UNKNOWN; |
| loge("Corrupt IMSI! setting3 mMncLength=" + mMncLength); |
| } |
| } |
| |
| if (mMncLength != UNKNOWN && mMncLength != UNINITIALIZED |
| && imsi.length() >= 3 + mMncLength) { |
| log("update mccmnc=" + imsi.substring(0, 3 + mMncLength)); |
| // finally have both the imsi and the mncLength and |
| // can parse the imsi properly |
| MccTable.updateMccMncConfiguration(mContext, |
| imsi.substring(0, 3 + mMncLength), false); |
| } |
| mImsiReadyRegistrants.notifyRegistrants(); |
| break; |
| |
| case EVENT_GET_MBI_DONE: |
| boolean isValidMbdn; |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| isValidMbdn = false; |
| if (ar.exception == null) { |
| // Refer TS 51.011 Section 10.3.44 for content details |
| log("EF_MBI: " + IccUtils.bytesToHexString(data)); |
| |
| // Voice mail record number stored first |
| mMailboxIndex = data[0] & 0xff; |
| |
| // check if dailing numbe id valid |
| if (mMailboxIndex != 0 && mMailboxIndex != 0xff) { |
| log("Got valid mailbox number for MBDN"); |
| isValidMbdn = true; |
| } |
| } |
| |
| // one more record to load |
| mRecordsToLoad += 1; |
| |
| if (isValidMbdn) { |
| // Note: MBDN was not included in NUM_OF_SIM_RECORDS_LOADED |
| new AdnRecordLoader(mFh).loadFromEF(EF_MBDN, EF_EXT6, |
| mMailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE)); |
| } else { |
| // If this EF not present, try mailbox as in CPHS standard |
| // CPHS (CPHS4_2.WW6) is a european standard. |
| new AdnRecordLoader(mFh).loadFromEF(EF_MAILBOX_CPHS, |
| EF_EXT1, 1, |
| obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE)); |
| } |
| |
| break; |
| case EVENT_GET_CPHS_MAILBOX_DONE: |
| case EVENT_GET_MBDN_DONE: |
| //Resetting the voice mail number and voice mail tag to null |
| //as these should be updated from the data read from EF_MBDN. |
| //If they are not reset, incase of invalid data/exception these |
| //variables are retaining their previous values and are |
| //causing invalid voice mailbox info display to user. |
| mVoiceMailNum = null; |
| mVoiceMailTag = null; |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| |
| if (ar.exception != null) { |
| |
| log("Invalid or missing EF" |
| + ((msg.what == EVENT_GET_CPHS_MAILBOX_DONE) |
| ? "[MAILBOX]" : "[MBDN]")); |
| |
| // Bug #645770 fall back to CPHS |
| // FIXME should use SST to decide |
| |
| if (msg.what == EVENT_GET_MBDN_DONE) { |
| //load CPHS on fail... |
| // FIXME right now, only load line1's CPHS voice mail entry |
| |
| mRecordsToLoad += 1; |
| new AdnRecordLoader(mFh).loadFromEF( |
| EF_MAILBOX_CPHS, EF_EXT1, 1, |
| obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE)); |
| } |
| break; |
| } |
| |
| adn = (AdnRecord) ar.result; |
| |
| log("VM: " + adn |
| + ((msg.what == EVENT_GET_CPHS_MAILBOX_DONE) |
| ? " EF[MAILBOX]" : " EF[MBDN]")); |
| |
| if (adn.isEmpty() && msg.what == EVENT_GET_MBDN_DONE) { |
| // Bug #645770 fall back to CPHS |
| // FIXME should use SST to decide |
| // FIXME right now, only load line1's CPHS voice mail entry |
| mRecordsToLoad += 1; |
| new AdnRecordLoader(mFh).loadFromEF( |
| EF_MAILBOX_CPHS, EF_EXT1, 1, |
| obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE)); |
| |
| break; |
| } |
| |
| mVoiceMailNum = adn.getNumber(); |
| mVoiceMailTag = adn.getAlphaTag(); |
| break; |
| |
| case EVENT_GET_MSISDN_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| |
| if (ar.exception != null) { |
| log("Invalid or missing EF[MSISDN]"); |
| break; |
| } |
| |
| adn = (AdnRecord) ar.result; |
| |
| mMsisdn = adn.getNumber(); |
| mMsisdnTag = adn.getAlphaTag(); |
| |
| log("MSISDN: " + /*mMsisdn*/ Rlog.pii(LOG_TAG, mMsisdn)); |
| break; |
| |
| case EVENT_SET_MSISDN_DONE: |
| isRecordLoadResponse = false; |
| ar = (AsyncResult) msg.obj; |
| |
| if (ar.exception == null) { |
| mMsisdn = mNewMsisdn; |
| mMsisdnTag = mNewMsisdnTag; |
| log("Success to update EF[MSISDN]"); |
| } |
| |
| if (ar.userObj != null) { |
| AsyncResult.forMessage(((Message) ar.userObj)).exception = ar.exception; |
| ((Message) ar.userObj).sendToTarget(); |
| } |
| break; |
| |
| case EVENT_GET_MWIS_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (DBG) log("EF_MWIS : " + IccUtils.bytesToHexString(data)); |
| |
| if (ar.exception != null) { |
| if (DBG) log("EVENT_GET_MWIS_DONE exception = " + ar.exception); |
| break; |
| } |
| |
| if ((data[0] & 0xff) == 0xff) { |
| if (DBG) log("SIMRecords: Uninitialized record MWIS"); |
| break; |
| } |
| |
| mEfMWIS = data; |
| break; |
| |
| case EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (DBG) log("EF_CPHS_MWI: " + IccUtils.bytesToHexString(data)); |
| |
| if (ar.exception != null) { |
| if (DBG) { |
| log("EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE exception = " |
| + ar.exception); |
| } |
| break; |
| } |
| |
| mEfCPHS_MWI = data; |
| break; |
| |
| case EVENT_GET_ICCID_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null) { |
| break; |
| } |
| |
| mIccId = IccUtils.bcdToString(data, 0, data.length); |
| mFullIccId = IccUtils.bchToString(data, 0, data.length); |
| |
| log("iccid: " + SubscriptionInfo.givePrintableIccid(mFullIccId)); |
| break; |
| |
| case EVENT_GET_AD_DONE: |
| try { |
| isRecordLoadResponse = true; |
| |
| if (mCarrierTestOverride.isInTestMode() && getIMSI() != null) { |
| imsi = getIMSI(); |
| try { |
| int mcc = Integer.parseInt(imsi.substring(0, 3)); |
| mMncLength = MccTable.smallestDigitsMccForMnc(mcc); |
| log("[TestMode] mMncLength=" + mMncLength); |
| } catch (NumberFormatException e) { |
| mMncLength = UNKNOWN; |
| loge("[TestMode] Corrupt IMSI! mMncLength=" + mMncLength); |
| } |
| } else { |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null) { |
| break; |
| } |
| |
| log("EF_AD: " + IccUtils.bytesToHexString(data)); |
| |
| if (data.length < 3) { |
| log("Corrupt AD data on SIM"); |
| break; |
| } |
| |
| if (data.length == 3) { |
| log("MNC length not present in EF_AD"); |
| break; |
| } |
| |
| mMncLength = data[3] & 0xf; |
| log("setting4 mMncLength=" + mMncLength); |
| } |
| |
| if (mMncLength == 0xf) { |
| mMncLength = UNKNOWN; |
| log("setting5 mMncLength=" + mMncLength); |
| } else if (mMncLength != 2 && mMncLength != 3) { |
| mMncLength = UNINITIALIZED; |
| log("setting5 mMncLength=" + mMncLength); |
| } |
| } finally { |
| |
| // IMSI could be a value reading from Sim or a fake IMSI if in the test mode |
| imsi = getIMSI(); |
| |
| if (((mMncLength == UNINITIALIZED) || (mMncLength == UNKNOWN) |
| || (mMncLength == 2)) && ((imsi != null) |
| && (imsi.length() >= 6))) { |
| String mccmncCode = imsi.substring(0, 6); |
| log("mccmncCode=" + mccmncCode); |
| for (String mccmnc : MCCMNC_CODES_HAVING_3DIGITS_MNC) { |
| if (mccmnc.equals(mccmncCode)) { |
| mMncLength = 3; |
| log("setting6 mMncLength=" + mMncLength); |
| break; |
| } |
| } |
| } |
| |
| if (mMncLength == UNKNOWN || mMncLength == UNINITIALIZED) { |
| if (imsi != null) { |
| try { |
| int mcc = Integer.parseInt(imsi.substring(0, 3)); |
| |
| mMncLength = MccTable.smallestDigitsMccForMnc(mcc); |
| log("setting7 mMncLength=" + mMncLength); |
| } catch (NumberFormatException e) { |
| mMncLength = UNKNOWN; |
| loge("Corrupt IMSI! setting8 mMncLength=" + mMncLength); |
| } |
| } else { |
| // Indicate we got this info, but it didn't contain the length. |
| mMncLength = UNKNOWN; |
| log("MNC length not present in EF_AD setting9 " |
| + "mMncLength=" + mMncLength); |
| } |
| } |
| if (imsi != null && mMncLength != UNKNOWN |
| && imsi.length() >= 3 + mMncLength) { |
| // finally have both imsi and the length of the mnc and can parse |
| // the imsi properly |
| log("update mccmnc=" + imsi.substring(0, 3 + mMncLength)); |
| MccTable.updateMccMncConfiguration(mContext, |
| imsi.substring(0, 3 + mMncLength), false); |
| } |
| } |
| break; |
| |
| case EVENT_GET_SPN_DONE: |
| isRecordLoadResponse = true; |
| ar = (AsyncResult) msg.obj; |
| getSpnFsm(false, ar); |
| break; |
| |
| case EVENT_GET_CFF_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null) { |
| mEfCff = null; |
| } else { |
| log("EF_CFF_CPHS: " + IccUtils.bytesToHexString(data)); |
| mEfCff = data; |
| } |
| |
| break; |
| |
| case EVENT_GET_SPDI_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null) { |
| break; |
| } |
| |
| parseEfSpdi(data); |
| break; |
| |
| case EVENT_UPDATE_DONE: |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception != null) { |
| logw("update failed. ", ar.exception); |
| } |
| break; |
| |
| case EVENT_GET_PNN_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null) { |
| break; |
| } |
| |
| SimTlv tlv = new SimTlv(data, 0, data.length); |
| |
| for (; tlv.isValidObject(); tlv.nextObject()) { |
| if (tlv.getTag() == TAG_FULL_NETWORK_NAME) { |
| mPnnHomeName = IccUtils.networkNameToString( |
| tlv.getData(), 0, tlv.getData().length); |
| log("PNN: " + mPnnHomeName); |
| break; |
| } |
| } |
| break; |
| |
| case EVENT_GET_ALL_SMS_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception != null) { |
| break; |
| } |
| |
| handleSmses((ArrayList<byte []>) ar.result); |
| break; |
| |
| case EVENT_MARK_SMS_READ_DONE: |
| Rlog.i("ENF", "marked read: sms " + msg.arg1); |
| break; |
| |
| |
| case EVENT_SMS_ON_SIM: |
| isRecordLoadResponse = false; |
| |
| ar = (AsyncResult) msg.obj; |
| |
| Integer index = (Integer) ar.result; |
| |
| if (ar.exception != null || index == null) { |
| loge("Error on SMS_ON_SIM with exp " |
| + ar.exception + " index " + index); |
| } else { |
| log("READ EF_SMS RECORD index=" + index); |
| mFh.loadEFLinearFixed(EF_SMS, index, obtainMessage(EVENT_GET_SMS_DONE)); |
| } |
| break; |
| |
| case EVENT_GET_SMS_DONE: |
| isRecordLoadResponse = false; |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| handleSms((byte[]) ar.result); |
| } else { |
| loge("Error on GET_SMS with exp " + ar.exception); |
| } |
| break; |
| case EVENT_GET_SST_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null) { |
| break; |
| } |
| |
| mUsimServiceTable = new UsimServiceTable(data); |
| if (DBG) log("SST: " + mUsimServiceTable); |
| break; |
| |
| case EVENT_GET_INFO_CPHS_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| |
| if (ar.exception != null) { |
| break; |
| } |
| |
| mCphsInfo = (byte[]) ar.result; |
| |
| if (DBG) log("iCPHS: " + IccUtils.bytesToHexString(mCphsInfo)); |
| break; |
| |
| case EVENT_SET_MBDN_DONE: |
| isRecordLoadResponse = false; |
| ar = (AsyncResult) msg.obj; |
| |
| if (DBG) log("EVENT_SET_MBDN_DONE ex:" + ar.exception); |
| if (ar.exception == null) { |
| mVoiceMailNum = mNewVoiceMailNum; |
| mVoiceMailTag = mNewVoiceMailTag; |
| } |
| |
| if (isCphsMailboxEnabled()) { |
| adn = new AdnRecord(mVoiceMailTag, mVoiceMailNum); |
| Message onCphsCompleted = (Message) ar.userObj; |
| |
| /* write to cphs mailbox whenever it is available but |
| * we only need notify caller once if both updating are |
| * successful. |
| * |
| * so if set_mbdn successful, notify caller here and set |
| * onCphsCompleted to null |
| */ |
| if (ar.exception == null && ar.userObj != null) { |
| AsyncResult.forMessage(((Message) ar.userObj)).exception = null; |
| ((Message) ar.userObj).sendToTarget(); |
| |
| if (DBG) log("Callback with MBDN successful."); |
| |
| onCphsCompleted = null; |
| } |
| |
| new AdnRecordLoader(mFh) |
| .updateEF(adn, EF_MAILBOX_CPHS, EF_EXT1, 1, null, |
| obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, |
| onCphsCompleted)); |
| } else { |
| if (ar.userObj != null) { |
| CarrierConfigManager configLoader = (CarrierConfigManager) |
| mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); |
| if (ar.exception != null && configLoader != null |
| && configLoader.getConfig().getBoolean( |
| CarrierConfigManager.KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL)) { |
| // GsmCdmaPhone will store vm number on device |
| // when IccVmNotSupportedException occurred |
| AsyncResult.forMessage(((Message) ar.userObj)).exception = |
| new IccVmNotSupportedException( |
| "Update SIM voice mailbox error"); |
| } else { |
| AsyncResult.forMessage(((Message) ar.userObj)) |
| .exception = ar.exception; |
| } |
| ((Message) ar.userObj).sendToTarget(); |
| } |
| } |
| break; |
| case EVENT_SET_CPHS_MAILBOX_DONE: |
| isRecordLoadResponse = false; |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| mVoiceMailNum = mNewVoiceMailNum; |
| mVoiceMailTag = mNewVoiceMailTag; |
| } else { |
| if (DBG) log("Set CPHS MailBox with exception: " + ar.exception); |
| } |
| if (ar.userObj != null) { |
| if (DBG) log("Callback with CPHS MB successful."); |
| AsyncResult.forMessage(((Message) ar.userObj)).exception |
| = ar.exception; |
| ((Message) ar.userObj).sendToTarget(); |
| } |
| break; |
| case EVENT_SIM_REFRESH: |
| isRecordLoadResponse = false; |
| ar = (AsyncResult) msg.obj; |
| if (DBG) log("Sim REFRESH with exception: " + ar.exception); |
| if (ar.exception == null) { |
| handleSimRefresh((IccRefreshResponse) ar.result); |
| } |
| break; |
| case EVENT_GET_CFIS_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null) { |
| mEfCfis = null; |
| } else { |
| log("EF_CFIS: " + IccUtils.bytesToHexString(data)); |
| mEfCfis = data; |
| } |
| |
| break; |
| |
| case EVENT_GET_CSP_CPHS_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| |
| if (ar.exception != null) { |
| loge("Exception in fetching EF_CSP data " + ar.exception); |
| break; |
| } |
| |
| data = (byte[]) ar.result; |
| |
| log("EF_CSP: " + IccUtils.bytesToHexString(data)); |
| handleEfCspData(data); |
| break; |
| |
| case EVENT_GET_GID1_DONE: |
| isRecordLoadResponse = true; |
| |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null) { |
| loge("Exception in get GID1 " + ar.exception); |
| mGid1 = null; |
| break; |
| } |
| |
| mGid1 = IccUtils.bytesToHexString(data); |
| |
| log("GID1: " + mGid1); |
| |
| break; |
| |
| case EVENT_GET_GID2_DONE: |
| isRecordLoadResponse = true; |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null) { |
| loge("Exception in get GID2 " + ar.exception); |
| mGid2 = null; |
| break; |
| } |
| |
| mGid2 = IccUtils.bytesToHexString(data); |
| |
| log("GID2: " + mGid2); |
| |
| break; |
| |
| case EVENT_GET_PLMN_W_ACT_DONE: |
| isRecordLoadResponse = true; |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null || data == null) { |
| loge("Failed getting User PLMN with Access Tech Records: " + ar.exception); |
| break; |
| } else { |
| log("Received a PlmnActRecord, raw=" + IccUtils.bytesToHexString(data)); |
| mPlmnActRecords = PlmnActRecord.getRecords(data); |
| if (VDBG) log("PlmnActRecords=" + Arrays.toString(mPlmnActRecords)); |
| } |
| break; |
| |
| case EVENT_GET_OPLMN_W_ACT_DONE: |
| isRecordLoadResponse = true; |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null || data == null) { |
| loge("Failed getting Operator PLMN with Access Tech Records: " |
| + ar.exception); |
| break; |
| } else { |
| log("Received a PlmnActRecord, raw=" + IccUtils.bytesToHexString(data)); |
| mOplmnActRecords = PlmnActRecord.getRecords(data); |
| if (VDBG) log("OplmnActRecord[]=" + Arrays.toString(mOplmnActRecords)); |
| } |
| break; |
| |
| case EVENT_GET_HPLMN_W_ACT_DONE: |
| isRecordLoadResponse = true; |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| |
| if (ar.exception != null || data == null) { |
| loge("Failed getting Home PLMN with Access Tech Records: " + ar.exception); |
| break; |
| } else { |
| log("Received a PlmnActRecord, raw=" + IccUtils.bytesToHexString(data)); |
| mHplmnActRecords = PlmnActRecord.getRecords(data); |
| log("HplmnActRecord[]=" + Arrays.toString(mHplmnActRecords)); |
| } |
| break; |
| |
| case EVENT_GET_EHPLMN_DONE: |
| isRecordLoadResponse = true; |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| if (ar.exception != null || data == null) { |
| loge("Failed getting Equivalent Home PLMNs: " + ar.exception); |
| break; |
| } else { |
| mEhplmns = parseBcdPlmnList(data, "Equivalent Home"); |
| } |
| break; |
| |
| case EVENT_GET_FPLMN_DONE: |
| isRecordLoadResponse = true; |
| ar = (AsyncResult) msg.obj; |
| data = (byte[]) ar.result; |
| if (ar.exception != null || data == null) { |
| loge("Failed getting Forbidden PLMNs: " + ar.exception); |
| break; |
| } else { |
| mFplmns = parseBcdPlmnList(data, "Forbidden"); |
| } |
| if (msg.arg1 == HANDLER_ACTION_SEND_RESPONSE) { |
| if (VDBG) logv("getForbiddenPlmns(): send async response"); |
| isRecordLoadResponse = false; |
| Message response = retrievePendingResponseMessage(msg.arg2); |
| if (response != null) { |
| AsyncResult.forMessage( |
| response, Arrays.copyOf(mFplmns, mFplmns.length), null); |
| response.sendToTarget(); |
| } else { |
| loge("Failed to retrieve a response message for FPLMN"); |
| break; |
| } |
| } |
| break; |
| |
| case EVENT_CARRIER_CONFIG_CHANGED: |
| handleCarrierNameOverride(); |
| break; |
| |
| default: |
| super.handleMessage(msg); // IccRecords handles generic record load responses |
| } |
| } catch (RuntimeException exc) { |
| // I don't want these exceptions to be fatal |
| logw("Exception parsing SIM record", exc); |
| } finally { |
| // Count up record load responses even if they are fails |
| if (isRecordLoadResponse) { |
| onRecordLoaded(); |
| } |
| } |
| } |
| |
| private class EfPlLoaded implements IccRecordLoaded { |
| public String getEfName() { |
| return "EF_PL"; |
| } |
| |
| public void onRecordLoaded(AsyncResult ar) { |
| mEfPl = (byte[]) ar.result; |
| if (DBG) log("EF_PL=" + IccUtils.bytesToHexString(mEfPl)); |
| } |
| } |
| |
| private class EfUsimLiLoaded implements IccRecordLoaded { |
| public String getEfName() { |
| return "EF_LI"; |
| } |
| |
| public void onRecordLoaded(AsyncResult ar) { |
| mEfLi = (byte[]) ar.result; |
| if (DBG) log("EF_LI=" + IccUtils.bytesToHexString(mEfLi)); |
| } |
| } |
| |
| private void handleFileUpdate(int efid) { |
| switch(efid) { |
| case EF_MBDN: |
| mRecordsToLoad++; |
| new AdnRecordLoader(mFh).loadFromEF(EF_MBDN, EF_EXT6, |
| mMailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE)); |
| break; |
| case EF_MAILBOX_CPHS: |
| mRecordsToLoad++; |
| new AdnRecordLoader(mFh).loadFromEF(EF_MAILBOX_CPHS, EF_EXT1, |
| 1, obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE)); |
| break; |
| case EF_CSP_CPHS: |
| mRecordsToLoad++; |
| log("[CSP] SIM Refresh for EF_CSP_CPHS"); |
| mFh.loadEFTransparent(EF_CSP_CPHS, |
| obtainMessage(EVENT_GET_CSP_CPHS_DONE)); |
| break; |
| case EF_FDN: |
| if (DBG) log("SIM Refresh called for EF_FDN"); |
| mParentApp.queryFdn(); |
| mAdnCache.reset(); |
| break; |
| case EF_MSISDN: |
| mRecordsToLoad++; |
| log("SIM Refresh called for EF_MSISDN"); |
| new AdnRecordLoader(mFh).loadFromEF(EF_MSISDN, getExtFromEf(EF_MSISDN), 1, |
| obtainMessage(EVENT_GET_MSISDN_DONE)); |
| break; |
| case EF_CFIS: |
| case EF_CFF_CPHS: |
| log("SIM Refresh called for EF_CFIS or EF_CFF_CPHS"); |
| loadCallForwardingRecords(); |
| break; |
| default: |
| // For now, fetch all records if this is not a |
| // voicemail number. |
| // TODO: Handle other cases, instead of fetching all. |
| mAdnCache.reset(); |
| fetchSimRecords(); |
| break; |
| } |
| } |
| |
| private void handleSimRefresh(IccRefreshResponse refreshResponse){ |
| if (refreshResponse == null) { |
| if (DBG) log("handleSimRefresh received without input"); |
| return; |
| } |
| |
| if (!TextUtils.isEmpty(refreshResponse.aid) |
| && !refreshResponse.aid.equals(mParentApp.getAid())) { |
| // This is for different app. Ignore. |
| return; |
| } |
| |
| switch (refreshResponse.refreshResult) { |
| case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE: |
| if (DBG) log("handleSimRefresh with SIM_FILE_UPDATED"); |
| handleFileUpdate(refreshResponse.efId); |
| break; |
| case IccRefreshResponse.REFRESH_RESULT_INIT: |
| if (DBG) log("handleSimRefresh with SIM_REFRESH_INIT"); |
| // need to reload all files (that we care about) |
| onIccRefreshInit(); |
| break; |
| case IccRefreshResponse.REFRESH_RESULT_RESET: |
| // Refresh reset is handled by the UiccCard object. |
| if (DBG) log("handleSimRefresh with SIM_REFRESH_RESET"); |
| break; |
| default: |
| // unknown refresh operation |
| if (DBG) log("handleSimRefresh with unknown operation"); |
| break; |
| } |
| } |
| |
| /** |
| * Dispatch 3GPP format message to registrant ({@code GsmCdmaPhone}) to pass to the 3GPP SMS |
| * dispatcher for delivery. |
| */ |
| private int dispatchGsmMessage(SmsMessage message) { |
| mNewSmsRegistrants.notifyResult(message); |
| return 0; |
| } |
| |
| private void handleSms(byte[] ba) { |
| if (ba[0] != 0) |
| Rlog.d("ENF", "status : " + ba[0]); |
| |
| // 3GPP TS 51.011 v5.0.0 (20011-12) 10.5.3 |
| // 3 == "received by MS from network; message to be read" |
| if (ba[0] == 3) { |
| int n = ba.length; |
| |
| // Note: Data may include trailing FF's. That's OK; message |
| // should still parse correctly. |
| byte[] pdu = new byte[n - 1]; |
| System.arraycopy(ba, 1, pdu, 0, n - 1); |
| SmsMessage message = SmsMessage.createFromPdu(pdu, SmsConstants.FORMAT_3GPP); |
| |
| dispatchGsmMessage(message); |
| } |
| } |
| |
| |
| private void handleSmses(ArrayList<byte[]> messages) { |
| int count = messages.size(); |
| |
| for (int i = 0; i < count; i++) { |
| byte[] ba = messages.get(i); |
| |
| if (ba[0] != 0) |
| Rlog.i("ENF", "status " + i + ": " + ba[0]); |
| |
| // 3GPP TS 51.011 v5.0.0 (20011-12) 10.5.3 |
| // 3 == "received by MS from network; message to be read" |
| |
| if (ba[0] == 3) { |
| int n = ba.length; |
| |
| // Note: Data may include trailing FF's. That's OK; message |
| // should still parse correctly. |
| byte[] pdu = new byte[n - 1]; |
| System.arraycopy(ba, 1, pdu, 0, n - 1); |
| SmsMessage message = SmsMessage.createFromPdu(pdu, SmsConstants.FORMAT_3GPP); |
| |
| dispatchGsmMessage(message); |
| |
| // 3GPP TS 51.011 v5.0.0 (20011-12) 10.5.3 |
| // 1 == "received by MS from network; message read" |
| |
| ba[0] = 1; |
| |
| if (false) { // FIXME: writing seems to crash RdoServD |
| mFh.updateEFLinearFixed(EF_SMS, |
| i, ba, null, obtainMessage(EVENT_MARK_SMS_READ_DONE, i)); |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void onRecordLoaded() { |
| // One record loaded successfully or failed, In either case |
| // we need to update the recordsToLoad count |
| mRecordsToLoad -= 1; |
| if (DBG) log("onRecordLoaded " + mRecordsToLoad + " requested: " + mRecordsRequested); |
| |
| if (getRecordsLoaded()) { |
| onAllRecordsLoaded(); |
| } else if (getLockedRecordsLoaded()) { |
| onLockedAllRecordsLoaded(); |
| } else if (mRecordsToLoad < 0) { |
| loge("recordsToLoad <0, programmer error suspected"); |
| mRecordsToLoad = 0; |
| } |
| } |
| |
| private void setVoiceCallForwardingFlagFromSimRecords() { |
| if (validEfCfis(mEfCfis)) { |
| // Refer TS 51.011 Section 10.3.46 for the content description |
| mCallForwardingStatus = (mEfCfis[1] & 0x01); |
| log("EF_CFIS: callForwardingEnabled=" + mCallForwardingStatus); |
| } else if (mEfCff != null) { |
| mCallForwardingStatus = |
| ((mEfCff[0] & CFF_LINE1_MASK) == CFF_UNCONDITIONAL_ACTIVE) ? |
| CALL_FORWARDING_STATUS_ENABLED : CALL_FORWARDING_STATUS_DISABLED; |
| log("EF_CFF: callForwardingEnabled=" + mCallForwardingStatus); |
| } else { |
| mCallForwardingStatus = CALL_FORWARDING_STATUS_UNKNOWN; |
| log("EF_CFIS and EF_CFF not valid. callForwardingEnabled=" + mCallForwardingStatus); |
| } |
| } |
| |
| private void setSimLanguageFromEF() { |
| Resources resource = Resources.getSystem(); |
| if (resource.getBoolean(com.android.internal.R.bool.config_use_sim_language_file)) { |
| setSimLanguage(mEfLi, mEfPl); |
| } else { |
| if (DBG) log ("Not using EF LI/EF PL"); |
| } |
| } |
| |
| private void onLockedAllRecordsLoaded() { |
| setSimLanguageFromEF(); |
| mLockedRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null)); |
| } |
| |
| @Override |
| protected void onAllRecordsLoaded() { |
| if (DBG) log("record load complete"); |
| |
| setSimLanguageFromEF(); |
| setVoiceCallForwardingFlagFromSimRecords(); |
| |
| // Some fields require more than one SIM record to set |
| |
| String operator = getOperatorNumeric(); |
| if (!TextUtils.isEmpty(operator)) { |
| log("onAllRecordsLoaded set 'gsm.sim.operator.numeric' to operator='" + |
| operator + "'"); |
| mTelephonyManager.setSimOperatorNumericForPhone( |
| mParentApp.getPhoneId(), operator); |
| } else { |
| log("onAllRecordsLoaded empty 'gsm.sim.operator.numeric' skipping"); |
| } |
| |
| String imsi = getIMSI(); |
| |
| if (!TextUtils.isEmpty(imsi) && imsi.length() >= 3) { |
| log("onAllRecordsLoaded set mcc imsi" + (VDBG ? ("=" + imsi) : "")); |
| mTelephonyManager.setSimCountryIsoForPhone( |
| mParentApp.getPhoneId(), MccTable.countryCodeForMcc( |
| Integer.parseInt(imsi.substring(0, 3)))); |
| } else { |
| log("onAllRecordsLoaded empty imsi skipping setting mcc"); |
| } |
| |
| setVoiceMailByCountry(operator); |
| |
| mRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null)); |
| } |
| |
| //***** Private methods |
| |
| /** |
| * Override the carrier name with either carrier config or SPN |
| * if an override is provided. |
| */ |
| private void handleCarrierNameOverride() { |
| final int phoneId = mParentApp.getPhoneId(); |
| SubscriptionController subCon = SubscriptionController.getInstance(); |
| final int subId = subCon.getSubIdUsingPhoneId(phoneId); |
| if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { |
| loge("subId not valid for Phone " + phoneId); |
| return; |
| } |
| |
| CarrierConfigManager configLoader = (CarrierConfigManager) |
| mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); |
| if (configLoader == null) { |
| loge("Failed to load a Carrier Config"); |
| return; |
| } |
| |
| PersistableBundle config = configLoader.getConfigForSubId(subId); |
| boolean preferCcName = config.getBoolean( |
| CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false); |
| String ccName = config.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING); |
| // If carrier config is priority, use it regardless - the preference |
| // and the name were both set by the carrier, so this is safe; |
| // otherwise, if the SPN is priority but we don't have one *and* we have |
| // a name in carrier config, use the carrier config name as a backup. |
| if (preferCcName || (TextUtils.isEmpty(getServiceProviderName()) |
| && !TextUtils.isEmpty(ccName))) { |
| setServiceProviderName(ccName); |
| mTelephonyManager.setSimOperatorNameForPhone(phoneId, ccName); |
| } |
| |
| updateCarrierNameForSubscription(subCon, subId); |
| } |
| |
| private void updateCarrierNameForSubscription(SubscriptionController subCon, int subId) { |
| /* update display name with carrier override */ |
| SubscriptionInfo subInfo = subCon.getActiveSubscriptionInfo( |
| subId, mContext.getOpPackageName()); |
| |
| if (subInfo == null || subInfo.getNameSource() |
| == SubscriptionManager.NAME_SOURCE_USER_INPUT) { |
| // either way, there is no subinfo to update |
| return; |
| } |
| |
| CharSequence oldSubName = subInfo.getDisplayName(); |
| String newCarrierName = mTelephonyManager.getSimOperatorName(subId); |
| |
| if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) { |
| log("sim name[" + mParentApp.getPhoneId() + "] = " + newCarrierName); |
| subCon.setDisplayName(newCarrierName, subId); |
| } |
| } |
| |
| private void setVoiceMailByCountry (String spn) { |
| if (mVmConfig.containsCarrier(spn)) { |
| mIsVoiceMailFixed = true; |
| mVoiceMailNum = mVmConfig.getVoiceMailNumber(spn); |
| mVoiceMailTag = mVmConfig.getVoiceMailTag(spn); |
| } |
| } |
| |
| /** |
| * String[] of forbidden PLMNs will be sent to the Message's handler |
| * in the result field of an AsyncResult in the response.obj. |
| */ |
| public void getForbiddenPlmns(Message response) { |
| int key = storePendingResponseMessage(response); |
| mFh.loadEFTransparent(EF_FPLMN, obtainMessage( |
| EVENT_GET_FPLMN_DONE, HANDLER_ACTION_SEND_RESPONSE, key)); |
| } |
| |
| @Override |
| public void onReady() { |
| fetchSimRecords(); |
| } |
| |
| private void onLocked() { |
| if (DBG) log("only fetch EF_LI, EF_PL and EF_ICCID in locked state"); |
| mLockedRecordsRequested = true; |
| |
| loadEfLiAndEfPl(); |
| |
| mFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE)); |
| mRecordsToLoad++; |
| } |
| |
| private void loadEfLiAndEfPl() { |
| if (mParentApp.getType() == AppType.APPTYPE_USIM) { |
| mFh.loadEFTransparent(EF_LI, |
| obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfUsimLiLoaded())); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_PL, |
| obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfPlLoaded())); |
| mRecordsToLoad++; |
| } |
| } |
| |
| private void loadCallForwardingRecords() { |
| mRecordsRequested = true; |
| mFh.loadEFLinearFixed(EF_CFIS, 1, obtainMessage(EVENT_GET_CFIS_DONE)); |
| mRecordsToLoad++; |
| mFh.loadEFTransparent(EF_CFF_CPHS, obtainMessage(EVENT_GET_CFF_DONE)); |
| mRecordsToLoad++; |
| } |
| |
| protected void fetchSimRecords() { |
| mRecordsRequested = true; |
| |
| if (DBG) log("fetchSimRecords " + mRecordsToLoad); |
| |
| mCi.getIMSIForApp(mParentApp.getAid(), obtainMessage(EVENT_GET_IMSI_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE)); |
| mRecordsToLoad++; |
| |
| // FIXME should examine EF[MSISDN]'s capability configuration |
| // to determine which is the voice/data/fax line |
| new AdnRecordLoader(mFh).loadFromEF(EF_MSISDN, getExtFromEf(EF_MSISDN), 1, |
| obtainMessage(EVENT_GET_MSISDN_DONE)); |
| mRecordsToLoad++; |
| |
| // Record number is subscriber profile |
| mFh.loadEFLinearFixed(EF_MBI, 1, obtainMessage(EVENT_GET_MBI_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE)); |
| mRecordsToLoad++; |
| |
| // Record number is subscriber profile |
| mFh.loadEFLinearFixed(EF_MWIS, 1, obtainMessage(EVENT_GET_MWIS_DONE)); |
| mRecordsToLoad++; |
| |
| |
| // Also load CPHS-style voice mail indicator, which stores |
| // the same info as EF[MWIS]. If both exist, both are updated |
| // but the EF[MWIS] data is preferred |
| // Please note this must be loaded after EF[MWIS] |
| mFh.loadEFTransparent( |
| EF_VOICE_MAIL_INDICATOR_CPHS, |
| obtainMessage(EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE)); |
| mRecordsToLoad++; |
| |
| // Same goes for Call Forward Status indicator: fetch both |
| // EF[CFIS] and CPHS-EF, with EF[CFIS] preferred. |
| loadCallForwardingRecords(); |
| |
| getSpnFsm(true, null); |
| |
| mFh.loadEFTransparent(EF_SPDI, obtainMessage(EVENT_GET_SPDI_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFLinearFixed(EF_PNN, 1, obtainMessage(EVENT_GET_PNN_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_INFO_CPHS, obtainMessage(EVENT_GET_INFO_CPHS_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_CSP_CPHS,obtainMessage(EVENT_GET_CSP_CPHS_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_GID1, obtainMessage(EVENT_GET_GID1_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_GID2, obtainMessage(EVENT_GET_GID2_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_PLMN_W_ACT, obtainMessage(EVENT_GET_PLMN_W_ACT_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_OPLMN_W_ACT, obtainMessage(EVENT_GET_OPLMN_W_ACT_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_HPLMN_W_ACT, obtainMessage(EVENT_GET_HPLMN_W_ACT_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_EHPLMN, obtainMessage(EVENT_GET_EHPLMN_DONE)); |
| mRecordsToLoad++; |
| |
| mFh.loadEFTransparent(EF_FPLMN, obtainMessage( |
| EVENT_GET_FPLMN_DONE, HANDLER_ACTION_NONE, -1)); |
| mRecordsToLoad++; |
| |
| loadEfLiAndEfPl(); |
| |
| // XXX should seek instead of examining them all |
| if (false) { // XXX |
| mFh.loadEFLinearFixedAll(EF_SMS, obtainMessage(EVENT_GET_ALL_SMS_DONE)); |
| mRecordsToLoad++; |
| } |
| |
| if (CRASH_RIL) { |
| String sms = "0107912160130310f20404d0110041007030208054832b0120" |
| + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" |
| + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" |
| + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" |
| + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" |
| + "ffffffffffffffffffffffffffffff"; |
| byte[] ba = IccUtils.hexStringToBytes(sms); |
| |
| mFh.updateEFLinearFixed(EF_SMS, 1, ba, null, |
| obtainMessage(EVENT_MARK_SMS_READ_DONE, 1)); |
| } |
| if (DBG) log("fetchSimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested); |
| } |
| |
| /** |
| * Returns the SpnDisplayRule based on settings on the SIM and the |
| * current service state. See TS 22.101 Annex A and TS 51.011 10.3.11 |
| * for details. |
| * |
| * If the SPN is not found on the SIM or is empty, the rule is |
| * always PLMN_ONLY. |
| * |
| * @param serviceState Service state |
| * @return the display rule |
| * |
| * @see #SPN_RULE_SHOW_SPN |
| * @see #SPN_RULE_SHOW_PLMN |
| */ |
| @Override |
| public int getDisplayRule(ServiceState serviceState) { |
| int rule; |
| |
| if (mParentApp != null && mParentApp.getUiccCard() != null && |
| mParentApp.getUiccCard().getOperatorBrandOverride() != null) { |
| // If the operator has been overridden, treat it as the SPN file on the SIM did not exist. |
| rule = SPN_RULE_SHOW_PLMN; |
| } else if (TextUtils.isEmpty(getServiceProviderName()) || mSpnDisplayCondition == -1) { |
| // No EF_SPN content was found on the SIM, or not yet loaded. Just show ONS. |
| rule = SPN_RULE_SHOW_PLMN; |
| } else if (useRoamingFromServiceState() ? !serviceState.getRoaming() |
| : isOnMatchingPlmn(serviceState.getOperatorNumeric())) { |
| rule = SPN_RULE_SHOW_SPN; |
| if ((mSpnDisplayCondition & 0x01) == 0x01) { |
| // ONS required when registered to HPLMN or PLMN in EF_SPDI |
| rule |= SPN_RULE_SHOW_PLMN; |
| } |
| } else { |
| rule = SPN_RULE_SHOW_PLMN; |
| if ((mSpnDisplayCondition & 0x02) == 0x00) { |
| // SPN required if not registered to HPLMN or PLMN in EF_SPDI |
| rule |= SPN_RULE_SHOW_SPN; |
| } |
| } |
| return rule; |
| } |
| |
| private boolean useRoamingFromServiceState() { |
| CarrierConfigManager configManager = (CarrierConfigManager) |
| mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); |
| if (configManager != null) { |
| PersistableBundle b = configManager.getConfigForSubId( |
| SubscriptionController.getInstance().getSubIdUsingPhoneId( |
| mParentApp.getPhoneId())); |
| if (b != null && b.getBoolean(CarrierConfigManager |
| .KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Checks if plmn is HPLMN or on the spdiNetworks list. |
| */ |
| private boolean isOnMatchingPlmn(String plmn) { |
| if (plmn == null) return false; |
| |
| if (plmn.equals(getOperatorNumeric())) { |
| return true; |
| } |
| |
| if (mSpdiNetworks != null) { |
| for (String spdiNet : mSpdiNetworks) { |
| if (plmn.equals(spdiNet)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * States of Get SPN Finite State Machine which only used by getSpnFsm() |
| */ |
| private enum GetSpnFsmState { |
| IDLE, // No initialized |
| INIT, // Start FSM |
| READ_SPN_3GPP, // Load EF_SPN firstly |
| READ_SPN_CPHS, // Load EF_SPN_CPHS secondly |
| READ_SPN_SHORT_CPHS // Load EF_SPN_SHORT_CPHS last |
| } |
| |
| /** |
| * Finite State Machine to load Service Provider Name , which can be stored |
| * in either EF_SPN (3GPP), EF_SPN_CPHS, or EF_SPN_SHORT_CPHS (CPHS4.2) |
| * |
| * After starting, FSM will search SPN EFs in order and stop after finding |
| * the first valid SPN |
| * |
| * If the FSM gets restart while waiting for one of |
| * SPN EFs results (i.e. a SIM refresh occurs after issuing |
| * read EF_CPHS_SPN), it will re-initialize only after |
| * receiving and discarding the unfinished SPN EF result. |
| * |
| * @param start set true only for initialize loading |
| * @param ar the AsyncResult from loadEFTransparent |
| * ar.exception holds exception in error |
| * ar.result is byte[] for data in success |
| */ |
| private void getSpnFsm(boolean start, AsyncResult ar) { |
| byte[] data; |
| |
| if (start) { |
| // Check previous state to see if there is outstanding |
| // SPN read |
| if (mSpnState == GetSpnFsmState.READ_SPN_3GPP |
| || mSpnState == GetSpnFsmState.READ_SPN_CPHS |
| || mSpnState == GetSpnFsmState.READ_SPN_SHORT_CPHS |
| || mSpnState == GetSpnFsmState.INIT) { |
| // Set INIT then return so the INIT code |
| // will run when the outstanding read done. |
| mSpnState = GetSpnFsmState.INIT; |
| return; |
| } else { |
| mSpnState = GetSpnFsmState.INIT; |
| } |
| } |
| |
| switch(mSpnState){ |
| case INIT: |
| setServiceProviderName(null); |
| |
| mFh.loadEFTransparent(EF_SPN, |
| obtainMessage(EVENT_GET_SPN_DONE)); |
| mRecordsToLoad++; |
| |
| mSpnState = GetSpnFsmState.READ_SPN_3GPP; |
| break; |
| case READ_SPN_3GPP: |
| if (ar != null && ar.exception == null) { |
| data = (byte[]) ar.result; |
| mSpnDisplayCondition = 0xff & data[0]; |
| |
| setServiceProviderName(IccUtils.adnStringFieldToString( |
| data, 1, data.length - 1)); |
| // for card double-check and brand override |
| // we have to do this: |
| final String spn = getServiceProviderName(); |
| |
| if (spn == null || spn.length() == 0) { |
| mSpnState = GetSpnFsmState.READ_SPN_CPHS; |
| } else { |
| if (DBG) log("Load EF_SPN: " + spn |
| + " spnDisplayCondition: " + mSpnDisplayCondition); |
| mTelephonyManager.setSimOperatorNameForPhone( |
| mParentApp.getPhoneId(), spn); |
| |
| mSpnState = GetSpnFsmState.IDLE; |
| } |
| } else { |
| mSpnState = GetSpnFsmState.READ_SPN_CPHS; |
| } |
| |
| if (mSpnState == GetSpnFsmState.READ_SPN_CPHS) { |
| mFh.loadEFTransparent( EF_SPN_CPHS, |
| obtainMessage(EVENT_GET_SPN_DONE)); |
| mRecordsToLoad++; |
| |
| // See TS 51.011 10.3.11. Basically, default to |
| // show PLMN always, and SPN also if roaming. |
| mSpnDisplayCondition = -1; |
| } |
| break; |
| case READ_SPN_CPHS: |
| if (ar != null && ar.exception == null) { |
| data = (byte[]) ar.result; |
| |
| setServiceProviderName(IccUtils.adnStringFieldToString( |
| data, 0, data.length)); |
| // for card double-check and brand override |
| // we have to do this: |
| final String spn = getServiceProviderName(); |
| |
| if (spn == null || spn.length() == 0) { |
| mSpnState = GetSpnFsmState.READ_SPN_SHORT_CPHS; |
| } else { |
| // Display CPHS Operator Name only when not roaming |
| mSpnDisplayCondition = 2; |
| |
| if (DBG) log("Load EF_SPN_CPHS: " + spn); |
| mTelephonyManager.setSimOperatorNameForPhone( |
| mParentApp.getPhoneId(), spn); |
| |
| mSpnState = GetSpnFsmState.IDLE; |
| } |
| } else { |
| mSpnState = GetSpnFsmState.READ_SPN_SHORT_CPHS; |
| } |
| |
| if (mSpnState == GetSpnFsmState.READ_SPN_SHORT_CPHS) { |
| mFh.loadEFTransparent( |
| EF_SPN_SHORT_CPHS, obtainMessage(EVENT_GET_SPN_DONE)); |
| mRecordsToLoad++; |
| } |
| break; |
| case READ_SPN_SHORT_CPHS: |
| if (ar != null && ar.exception == null) { |
| data = (byte[]) ar.result; |
| |
| setServiceProviderName(IccUtils.adnStringFieldToString( |
| data, 0, data.length)); |
| // for card double-check and brand override |
| // we have to do this: |
| final String spn = getServiceProviderName(); |
| |
| if (spn == null || spn.length() == 0) { |
| if (DBG) log("No SPN loaded in either CHPS or 3GPP"); |
| } else { |
| // Display CPHS Operator Name only when not roaming |
| mSpnDisplayCondition = 2; |
| |
| if (DBG) log("Load EF_SPN_SHORT_CPHS: " + spn); |
| mTelephonyManager.setSimOperatorNameForPhone( |
| mParentApp.getPhoneId(), spn); |
| } |
| } else { |
| setServiceProviderName(null); |
| if (DBG) log("No SPN loaded in either CHPS or 3GPP"); |
| } |
| |
| mSpnState = GetSpnFsmState.IDLE; |
| break; |
| default: |
| mSpnState = GetSpnFsmState.IDLE; |
| } |
| } |
| |
| /** |
| * Parse TS 51.011 EF[SPDI] record |
| * This record contains the list of numeric network IDs that |
| * are treated specially when determining SPN display |
| */ |
| private void |
| parseEfSpdi(byte[] data) { |
| SimTlv tlv = new SimTlv(data, 0, data.length); |
| |
| byte[] plmnEntries = null; |
| |
| for ( ; tlv.isValidObject() ; tlv.nextObject()) { |
| // Skip SPDI tag, if existant |
| if (tlv.getTag() == TAG_SPDI) { |
| tlv = new SimTlv(tlv.getData(), 0, tlv.getData().length); |
| } |
| // There should only be one TAG_SPDI_PLMN_LIST |
| if (tlv.getTag() == TAG_SPDI_PLMN_LIST) { |
| plmnEntries = tlv.getData(); |
| break; |
| } |
| } |
| |
| if (plmnEntries == null) { |
| return; |
| } |
| |
| mSpdiNetworks = new ArrayList<String>(plmnEntries.length / 3); |
| |
| for (int i = 0 ; i + 2 < plmnEntries.length ; i += 3) { |
| String plmnCode; |
| plmnCode = IccUtils.bcdPlmnToString(plmnEntries, i); |
| |
| // Valid operator codes are 5 or 6 digits |
| if (plmnCode != null && plmnCode.length() >= 5) { |
| log("EF_SPDI network: " + plmnCode); |
| mSpdiNetworks.add(plmnCode); |
| } |
| } |
| } |
| |
| /** |
| * convert a byte array of packed plmns to an array of strings |
| */ |
| private String[] parseBcdPlmnList(byte[] data, String description) { |
| final int packedBcdPlmnLenBytes = 3; |
| log("Received " + description + " PLMNs, raw=" + IccUtils.bytesToHexString(data)); |
| if (data.length == 0 || (data.length % packedBcdPlmnLenBytes) != 0) { |
| loge("Received invalid " + description + " PLMN list"); |
| return null; |
| } |
| int numPlmns = data.length / packedBcdPlmnLenBytes; |
| int numValidPlmns = 0; |
| String[] parsed = new String[numPlmns]; |
| for (int i = 0; i < numPlmns; i++) { |
| parsed[numValidPlmns] = IccUtils.bcdPlmnToString(data, i * packedBcdPlmnLenBytes); |
| // we count the valid (non empty) records and only increment if valid |
| if (!TextUtils.isEmpty(parsed[numValidPlmns])) numValidPlmns++; |
| } |
| String[] ret = Arrays.copyOf(parsed, numValidPlmns); |
| if (VDBG) logv(description + " PLMNs: " + Arrays.toString(ret)); |
| return ret; |
| } |
| |
| /** |
| * check to see if Mailbox Number is allocated and activated in CPHS SST |
| */ |
| private boolean isCphsMailboxEnabled() { |
| if (mCphsInfo == null) return false; |
| return ((mCphsInfo[1] & CPHS_SST_MBN_MASK) == CPHS_SST_MBN_ENABLED ); |
| } |
| |
| @Override |
| protected void log(String s) { |
| Rlog.d(LOG_TAG, "[SIMRecords] " + s); |
| } |
| |
| @Override |
| protected void loge(String s) { |
| Rlog.e(LOG_TAG, "[SIMRecords] " + s); |
| } |
| |
| protected void logw(String s, Throwable tr) { |
| Rlog.w(LOG_TAG, "[SIMRecords] " + s, tr); |
| } |
| |
| protected void logv(String s) { |
| Rlog.v(LOG_TAG, "[SIMRecords] " + s); |
| } |
| |
| /** |
| * Return true if "Restriction of menu options for manual PLMN selection" |
| * bit is set or EF_CSP data is unavailable, return false otherwise. |
| */ |
| @Override |
| public boolean isCspPlmnEnabled() { |
| return mCspPlmnEnabled; |
| } |
| |
| /** |
| * Parse EF_CSP data and check if |
| * "Restriction of menu options for manual PLMN selection" is |
| * Enabled/Disabled |
| * |
| * @param data EF_CSP hex data. |
| */ |
| private void handleEfCspData(byte[] data) { |
| // As per spec CPHS4_2.WW6, CPHS B.4.7.1, EF_CSP contains CPHS defined |
| // 18 bytes (i.e 9 service groups info) and additional data specific to |
| // operator. The valueAddedServicesGroup is not part of standard |
| // services. This is operator specific and can be programmed any where. |
| // Normally this is programmed as 10th service after the standard |
| // services. |
| int usedCspGroups = data.length / 2; |
| // This is the "Service Group Number" of "Value Added Services Group". |
| byte valueAddedServicesGroup = (byte)0xC0; |
| |
| mCspPlmnEnabled = true; |
| for (int i = 0; i < usedCspGroups; i++) { |
| if (data[2 * i] == valueAddedServicesGroup) { |
| log("[CSP] found ValueAddedServicesGroup, value " + data[(2 * i) + 1]); |
| if ((data[(2 * i) + 1] & 0x80) == 0x80) { |
| // Bit 8 is for |
| // "Restriction of menu options for manual PLMN selection". |
| // Operator Selection menu should be enabled. |
| mCspPlmnEnabled = true; |
| } else { |
| mCspPlmnEnabled = false; |
| // Operator Selection menu should be disabled. |
| // Operator Selection Mode should be set to Automatic. |
| log("[CSP] Set Automatic Network Selection"); |
| mNetworkSelectionModeAutomaticRegistrants.notifyRegistrants(); |
| } |
| return; |
| } |
| } |
| |
| log("[CSP] Value Added Service Group (0xC0), not found!"); |
| } |
| |
| @Override |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("SIMRecords: " + this); |
| pw.println(" extends:"); |
| super.dump(fd, pw, args); |
| pw.println(" mVmConfig=" + mVmConfig); |
| pw.println(" mCallForwardingStatus=" + mCallForwardingStatus); |
| pw.println(" mSpnState=" + mSpnState); |
| pw.println(" mCphsInfo=" + mCphsInfo); |
| pw.println(" mCspPlmnEnabled=" + mCspPlmnEnabled); |
| pw.println(" mEfMWIS[]=" + Arrays.toString(mEfMWIS)); |
| pw.println(" mEfCPHS_MWI[]=" + Arrays.toString(mEfCPHS_MWI)); |
| pw.println(" mEfCff[]=" + Arrays.toString(mEfCff)); |
| pw.println(" mEfCfis[]=" + Arrays.toString(mEfCfis)); |
| pw.println(" mSpnDisplayCondition=" + mSpnDisplayCondition); |
| pw.println(" mSpdiNetworks[]=" + mSpdiNetworks); |
| pw.println(" mUsimServiceTable=" + mUsimServiceTable); |
| pw.println(" mGid1=" + mGid1); |
| if (mCarrierTestOverride.isInTestMode()) { |
| pw.println(" mFakeGid1=" + mFakeGid1); |
| } |
| pw.println(" mGid2=" + mGid2); |
| if (mCarrierTestOverride.isInTestMode()) { |
| pw.println(" mFakeGid2=" + mFakeGid2); |
| } |
| pw.println(" mPnnHomeName=" + mPnnHomeName); |
| if (mCarrierTestOverride.isInTestMode()) { |
| pw.println(" mFakePnnHomeName=" + mFakePnnHomeName); |
| } |
| pw.println(" mPlmnActRecords[]=" + Arrays.toString(mPlmnActRecords)); |
| pw.println(" mOplmnActRecords[]=" + Arrays.toString(mOplmnActRecords)); |
| pw.println(" mHplmnActRecords[]=" + Arrays.toString(mHplmnActRecords)); |
| pw.println(" mFplmns[]=" + Arrays.toString(mFplmns)); |
| pw.println(" mEhplmns[]=" + Arrays.toString(mEhplmns)); |
| pw.flush(); |
| } |
| } |