在 PhoneAPP 启动关系类初始化中,我们提到监听处理SIM卡状态的两个关键类 UiccController 和 IccCardProxy,那么他们与SIM卡信息究竟是如何交互的呢?在UiccController 的 Android 源码中有这样一个 SIM卡相关类关系说明,这里我们先整体上看一下
/** * This class is responsible for keeping all knowledge about * Universal Integrated Circuit Card (UICC), also know as SIM's, * in the system. It is also used as API to get appropriate * applications to pass them to phone and service trackers. * * UiccController is created with the call to make() function. * UiccController is a singleton and make() must only be called once * and throws an exception if called multiple times. * * Once created UiccController registers with RIL for "on" and "unsol_sim_status_changed" * notifications. When such notification arrives UiccController will call * getIccCardStatus (GET_SIM_STATUS). Based on the response of GET_SIM_STATUS * request appropriate tree of uicc objects will be created. * * Following is class diagram for uicc classes: * * UiccController * # * | * UiccCard * # # * | ------------------ * UiccCardApplication CatService * # # * | | * IccRecords IccFileHandler * ^ ^ ^ ^ ^ ^ ^ ^ * SIMRecords---- | | | | | | ---SIMFileHandler * RuimRecords----- | | | | ----RuimFileHandler * IsimUiccRecords--- | | -----UsimFileHandler * | ------CsimFileHandler * ----IsimFileHandler * * Legend: # stands for Composition * ^ stands for Generalization * * See also {@link com.android.internal.telephony.IccCard} * and {@link com.android.internal.telephony.uicc.IccCardProxy} */
UiccController:整个UICC事务处理的入口,负责对外提供IccRecords、IccFileHandler、UiccCardApplication等对象,并完成整个UICC系统的初始化工作。
UiccCard: 本身并不实现具体的功能,只负责UiccCardApplication及CatService对象的创建更新工作,以及当SIM卡被插入或者拔出时弹出提示框是否需要重启设备。
UiccCardApplication:主要任务包括创建并向外提供IccFileHandler、IccRecords对象、提供对SIM卡状态的监听等
IccRecords:提供SIM卡常用信息查询,如IMSI、ICCID、VoiceMail、Contacts等,UiccController根据SIM类型不同,创建不同IccRecords对象
IccFileHandler:负责SIM卡文件系统的读写,会在UiccCardApplication中根据SIM类型不同,创建不同IccFileHandler对象
CatService:负责 STK 相关业务,接收解析 modem 上传的相关数据,并回传给RIL及StkAppService
1、SIM卡信息加载流程图
2、SIM卡状态监听及创建
在 PhoneAPP 初始化时,PhoneFactory 创建了 UiccController 和 GsmCdmaPhone,前者以RIL为观察对象直接注册监听卡的状态变化,后者构造出 IccCardProxy 间接向UiccController 注册监听卡状态变化,进行相关事件处理。
在插卡开机或热插拔时,当 Modem 检卡成功后会主动上报 RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGE 消息,通过 RIL 通知上层监听者 UiccController,后者在收到通知后会调用 getIccCardStatus 通过 RIL 向 Modem 主动获取SIM卡相关信息,在得到 RIL_REQUEST_GET_SIM_STATUS 返回后,调用 onGetIccCardStatusDone 进行后续处理。
frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/UiccController.java
@Override public void handleMessage (Message msg) { synchronized (mLock) { Integer index = getCiIndex(msg); AsyncResult ar = (AsyncResult)msg.obj; switch (msg.what) { case EVENT_ICC_STATUS_CHANGED: if (DBG) log("Received EVENT_ICC_STATUS_CHANGED, calling getIccCardStatus"); mCis[index].getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE, index)); break; case EVENT_GET_ICC_STATUS_DONE: if (DBG) log("Received EVENT_GET_ICC_STATUS_DONE"); onGetIccCardStatusDone(ar, index); break; ......
在onGetIccCardStatusDone函数中,UiccController将判断是否存在对应的UiccCard,如果没有将根据卡信息创建出对应的UiccCard对象;否则,仅进行UiccCard的更新操作。实际上,从代码逻辑看,即使新建UiccCard对象,最终也会调用到 Update 函数。
private synchronized void onGetIccCardStatusDone(AsyncResult ar, Integer index) { if (ar.exception != null) { return; } if (!isValidCardIndex(index)) { Rlog.e(LOG_TAG,"onGetIccCardStatusDone: invalid index : " + index); return; } IccCardStatus status = (IccCardStatus)ar.result; if (mUiccCards[index] == null) { //Create new card mUiccCards[index] = new UiccCard(mContext, mCis[index], status, index); } else { //Update already existing card mUiccCards[index].update(mContext, mCis[index] , status); } if (DBG) log("Notifying IccChangedRegistrants"); mIccChangedRegistrants.notifyRegistrants(new AsyncResult(null, index, null)); }
查看代码,UiccCard.update 最终完成了UiccCardApplication 和 CatService 的创建和更新
frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/UiccCard.java
public void update(Context c, CommandsInterface ci, IccCardStatus ics) { ...... //创建/更新UiccCardApplication for ( int i = 0; i < mUiccApplications.length; i++) { if (mUiccApplications[i] == null) { //Create newly added Applications if (i < ics.mApplications.length) { mUiccApplications[i] = new UiccCardApplication(this, ics.mApplications[i], mContext, mCi); } } else if (i >= ics.mApplications.length) { //Delete removed applications mUiccApplications[i].dispose(); mUiccApplications[i] = null; } else { //Update the rest mUiccApplications[i].update(ics.mApplications[i], mContext, mCi); } } // 创建/更新 CatService createAndUpdateCatService(); ...... }
UiccCardApplication 的创建和更新逻辑主要包含了 IccFileHandler 和 IccRecords 对象的创建, 而实际获取的对象都是根 Modem 返回的卡类型有关,具体代码如下。至此,我们就创建了与Modem返回类型匹配的 SIM卡对象,当IccRecords被创建后,同样将作为RIL的观察者,进行 registerForIccRefresh ,监听处理 EVENT_REFRESH 消息。
PS: IccRecords与IccFileHandler的区别在于,前者以SIM存储内容为操作对象,偏重上层应用,后者以SIM文件系统为操作对象,偏重底层实现。
frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
public UiccCardApplication(UiccCard uiccCard, IccCardApplicationStatus as, Context c, CommandsInterface ci) { ...... mIccFh = createIccFileHandler(as.app_type); mIccRecords = createIccRecords(as.app_type, mContext, mCi); if (mAppState == AppState.APPSTATE_READY) { queryFdn(); // 读取FDN数据 queryPin1State(); // 查询PIN状态 } mCi.registerForNotAvailable(mHandler, EVENT_RADIO_UNAVAILABLE, null); } // 根据modem返回的不同卡类型,创建不同 IccRecords 对象 private IccRecords createIccRecords(AppType type, Context c, CommandsInterface ci) { if (type == AppType.APPTYPE_USIM || type == AppType.APPTYPE_SIM) { return new SIMRecords(this, c, ci); } else if (type == AppType.APPTYPE_RUIM || type == AppType.APPTYPE_CSIM){ return new RuimRecords(this, c, ci); } else if (type == AppType.APPTYPE_ISIM) { return new IsimUiccRecords(this, c, ci); } else { // Unknown app type (maybe detection is still in progress) return null; } } // 根据modem返回的不同卡类型,创建不同 IccFileHandler 对象 private IccFileHandler createIccFileHandler(AppType type) { switch (type) { case APPTYPE_SIM: return new SIMFileHandler(this, mAid, mCi); case APPTYPE_RUIM: return new RuimFileHandler(this, mAid, mCi); case APPTYPE_USIM: return new UsimFileHandler(this, mAid, mCi); case APPTYPE_CSIM: return new CsimFileHandler(this, mAid, mCi); case APPTYPE_ISIM: return new IsimFileHandler(this, mAid, mCi); default: return null; } }
3、SIM卡信息加载
在 SIM 卡对象创建时,会进行系列状态及广播的监听,便于在合适的实际进行具体信息的加载等处理,以 SIMRecords 为例
frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/SIMRecords.java
public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) { super(app, c, ci); mAdnCache = new AdnRecordCache(mFh); mVmConfig = new VoiceMailConstants(); mSpnOverride = new SpnOverride(); mRecordsRequested = false; // No load request is made till SIM ready // recordsToLoad is set to 0 because no requests are made yet mRecordsToLoad = 0; mCi.setOnSmsOnSim(this, EVENT_SMS_ON_SIM, null); // Start off by setting empty state resetRecords(); // 向UiccCardApplication 注册监听 READY 和 LOCKED状态变化 mParentApp.registerForReady(this, EVENT_APP_READY, null); mParentApp.registerForLocked(this, EVENT_APP_LOCKED, null); IntentFilter intentfilter = new IntentFilter(); intentfilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); c.registerReceiver(mReceiver, intentfilter); }
SIMRecords 在收到 UiccCardApplication 通知的 EVENT_APP_READY 消息后,会调用 onReady 方法,进而调用实际 SIM卡信息的加载函数 fetchSimRecords。此外,RIL 返回的 EVENT_REFRESH 消息也会触发 fetchSimRecords ,进行 SIM卡信息的加载,下面来具体看下这个函数
protected void fetchSimRecords() { mRecordsRequested = true; 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); // 获取SIM卡SPN信息 ....... if (DBG) log("fetchSimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested); }
从代码看,fetchSimRecords 调用后会通过 IccFileHandler 对 IMSI、ICCID、MSISDN等信息进行逐条 load,并利用 mRecordsToLoad++ 计数。同时在每次load信息返回后,在 handleMessage 进行处理时,调用 onRecordLoaded 对 mRecordsToLoad计数 -1,待得 mRecordsToLoad == 0 && mRecordsRequested == true 时调用 onAllRecordsLoaded 进行VoiceCall、MCCMNC、SimOperatorNumeric、SimCountry等的设置。
@Override protected void onRecordLoaded() { // One record loaded successfully or failed, In either case // we need to update the recordsToLoad count mRecordsToLoad -= 1; if (mRecordsToLoad == 0 && mRecordsRequested == true) { onAllRecordsLoaded(); } else if (mRecordsToLoad < 0) { loge("recordsToLoad <0, programmer error suspected"); mRecordsToLoad = 0; } } @Override protected void onAllRecordsLoaded() { if (DBG) log("record load complete"); 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"); } setVoiceCallForwardingFlagFromSimRecords(); if (mParentApp.getState() == AppState.APPSTATE_PIN || mParentApp.getState() == AppState.APPSTATE_PUK) { // reset recordsRequested, since sim is not loaded really mRecordsRequested = false; // lock state, only update language return ; } // 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); final SubscriptionController subController = SubscriptionController.getInstance(); int subId = subController.getSubIdUsingPhoneId(mParentApp.getPhoneId()); if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { subController.setMccMnc(operator, subId); log("update icc_operator_numeric = " + operator + " subId = " + subId); } } else { log("onAllRecordsLoaded empty 'gsm.sim.operator.numeric' skipping"); } if (!TextUtils.isEmpty(mImsi)) { log("onAllRecordsLoaded set mcc imsi" + (VDBG ? ("=" + mImsi) : "")); mTelephonyManager.setSimCountryIsoForPhone( mParentApp.getPhoneId(), MccTable.countryCodeForMcc( Integer.parseInt(mImsi.substring(0,3)))); } else { log("onAllRecordsLoaded empty imsi skipping setting mcc"); } setVoiceMailByCountry(operator); mRecordsLoadedRegistrants.notifyRegistrants( new AsyncResult(null, null, null)); }
4、IccCardProxy 及对外广播
前面已经说过,UiccController 是整个UICC框架的入口与控制者,对于卡状态的维护和更新它都会做第一手的处理。那么,IccCardProxy 又是负责什么呢?从代码看,IccCardProxy 会从UiccController 获取UiccCard、UiccCardApplication、IccRecords实例,并注册系列SIM卡状态变化的监听,从这点说,和UiccController 功能没多少区别。当然,IccCardProxy同时还负责了对SIM卡状态向外广播及部分属性值的设置更新工作。看下IccCardProxy 的创建流程:与Phone实例个数相对应,在 Phone 对象创建时被构造。考虑到,UiccController里的UiccCard对象是跟卡动态关联的,所以, 通过phone.getIccCard获取 IccCardProxy对象,来进行卡状态查询等操作会更方便些,直接操作UiccController带来一些隐患,如获取不到卡状态等问题。实际上,IccCardProxy 就是一个用来提供给外部使用的接口类,可由Phone直接调用进行相关卡信息查询等,一般外部APP要获取卡信息,都会通过 Phone 相关服务对外提供的接口间接操作,如 TelephonyManager。
下面我们详细看下 IccCardProxy 构造函数,不难发现 IccCardProxy 在通过UiccController 注册 ICC_CARD_STATUS_CHANGED 消息时,没有关联 phoneId 信息,这里UiccController虽是单例,但其内部的UiccCard却可能会是多个,那么无论哪个卡有更新,UiccController在更新完自己内部的UiccCard之后,都会通知所有 IccCardProxy 来更新各自内部的UiccCard实例等。
frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
public IccCardProxy(Context context, CommandsInterface ci, int phoneId) { if (DBG) log("ctor: ci=" + ci + " phoneId=" + phoneId); mContext = context; mCi = ci; mPhoneId = phoneId; mTelephonyManager = (TelephonyManager) mContext.getSystemService( Context.TELEPHONY_SERVICE); mCdmaSSM = CdmaSubscriptionSourceManager.getInstance(context, ci, this, EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null); mUiccController = UiccController.getInstance(); // 单例模式 mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null); ci.registerForOn(this,EVENT_RADIO_ON, null); ci.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_UNAVAILABLE, null); resetProperties(); setExternalState(State.NOT_READY, false); }
顺便提一下这里的 resetProperties,它在调用 TelephonyManager 接口设置属性时,支持双卡的方法:使用同一个key,同时保存两个卡的属性值,值之间使用","分隔,顺序以phoneId 从小到大排序。使用时取出后将","分隔转换为数组直接取下标即可。
void resetProperties() { if (mCurrentAppType == UiccController.APP_FAM_3GPP) { log("update icc_operator_numeric=" + ""); mTelephonyManager.setSimOperatorNumericForPhone(mPhoneId, ""); mTelephonyManager.setSimCountryIsoForPhone(mPhoneId, ""); mTelephonyManager.setSimOperatorNameForPhone(mPhoneId, ""); } }
接着看 IccCardProxy对 ICC_CARD_STATUS_CHANGED 消息返回事件 EVENT_ICC_CHANGED 的处理,这里主要调用了updateIccAvailability 函数,获取/更新 UiccCard、UiccCardApplication、IccRecords实例,并向其注册 SIM 状态变化的各类监听。
private void updateIccAvailability() { synchronized (mLock) { // 获取UiccCard UiccCard newCard = mUiccController.getUiccCard(mPhoneId); CardState state = CardState.CARDSTATE_ABSENT; UiccCardApplication newApp = null; IccRecords newRecords = null; if (newCard != null) { state = newCard.getCardState(); // 获取 UiccCardApplication newApp = newCard.getApplication(mCurrentAppType); if (newApp != null) { // 获取IccRecords newRecords = newApp.getIccRecords(); } } if (mIccRecords != newRecords || mUiccApplication != newApp || mUiccCard != newCard) { if (DBG) log("Icc changed. Reregestering."); unregisterUiccCardEvents(); mUiccCard = newCard; mUiccApplication = newApp; mIccRecords = newRecords; registerUiccCardEvents(); } updateExternalState(); } } // 向各实例注册 SIM 状态变化的监听 private void registerUiccCardEvents() { if (mUiccCard != null) { mUiccCard.registerForAbsent(this, EVENT_ICC_ABSENT, null); } if (mUiccApplication != null) { mUiccApplication.registerForReady(this, EVENT_APP_READY, null); mUiccApplication.registerForLocked(this, EVENT_ICC_LOCKED, null); mUiccApplication.registerForNetworkLocked(this, EVENT_NETWORK_LOCKED, null); } if (mIccRecords != null) { mIccRecords.registerForImsiReady(this, EVENT_IMSI_READY, null); mIccRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null); mIccRecords.registerForRecordsEvents(this, EVENT_ICC_RECORD_EVENTS, null); } }
下面我们简单看下几个注册事件返回消息的处理,在 UiccCardApplication 的注册上,在卡状态变成APPSTATE_READY/APPSTATE_PIN/APPSTATE_PUK/APPSTATE_SUBSCRIPTION_PERSO 等时会发出不同通知,让 IccCardProxy进行相关状态处理并发出广播。以 READY 为例,IccCardProxy 在收到 EVENT_APP_READY 消息后,会逐级调用到 setExternalState 发出广播:TelephonyIntents.ACTION_SIM_STATE_CHANGED。
private void setExternalState(State newState, boolean override) { synchronized (mLock) { if (mPhoneId == null || !SubscriptionManager.isValidSlotId(mPhoneId)) { loge("setExternalState: mPhoneId=" + mPhoneId + " is invalid; Return!!"); return; } if (!override && newState == mExternalState) { loge("setExternalState: !override and newstate unchanged from " + newState); return; } mExternalState = newState; loge("setExternalState: set mPhoneId=" + mPhoneId + " mExternalState=" + mExternalState); mTelephonyManager.setSimStateForPhone(mPhoneId, getState().toString()); // For locked states, we should be sending internal broadcast. if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(getIccStateIntentString(mExternalState))) { broadcastInternalIccStateChangedIntent(getIccStateIntentString(mExternalState), getIccStateReason(mExternalState)); } else { broadcastIccStateChangedIntent(getIccStateIntentString(mExternalState), getIccStateReason(mExternalState)); } // TODO: Need to notify registrants for other states as well. if ( State.ABSENT == mExternalState) { mAbsentRegistrants.notifyRegistrants(); } } }
仔细看代码,我们发现 setExternalState 中除了broadcastIccStateChangedIntent,还有一个broadcastIccStateChangedIntent广播方式,那么二者有什么不同呢?比较代码,首先,从对外广播看,二者发出的 Intent 有所差异,这直接影响接受者的注册监听。其次,二者 Intent 标志位也有所不同,broadcastInternalIccStateChangedIntent 多了一个 FLAG_RECEIVER_REPLACE_PENDING。再看 broadcastIccStateChangedIntent,这里多了个 mQuietMode 判断(In case of 3gpp2 we need to find out if subscription used is coming from NV in which case we shouldn't broadcast any sim states changes)。
private void broadcastIccStateChangedIntent(String value, String reason) { synchronized (mLock) { if (mPhoneId == null || !SubscriptionManager.isValidSlotId(mPhoneId)) { loge("broadcastIccStateChangedIntent: mPhoneId=" + mPhoneId + " is invalid; Return!!"); return; } if (mQuietMode) { // 特殊情况,不发送广播,具体见分析 log("broadcastIccStateChangedIntent: QuietMode" + " NOT Broadcasting intent ACTION_SIM_STATE_CHANGED " + " value=" + value + " reason=" + reason); return; } //LAFITEN-153 //If card was pulled right after fetch sim began, simRecord whould send loaded. { if ((mUiccApplication == null || mUiccApplication.getState() == AppState.APPSTATE_PIN || mUiccApplication.getState() == AppState.APPSTATE_PUK) && (TextUtils.equals(IccCardConstants.INTENT_VALUE_ICC_LOADED, value) || TextUtils.equals(IccCardConstants.INTENT_VALUE_ICC_IMSI, value) || TextUtils.equals(IccCardConstants.INTENT_VALUE_ICC_READY, value))) { loge("Prevent Broadcasting intent ACTION_SIM_STATE_CHANGED " + value + " reason " + reason); return ; } //LAFITEN-153 Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED); // TODO - we'd like this intent to have a single snapshot of all sim state, // but until then this should not use REPLACE_PENDING or we may lose // information // intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone"); intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, value); intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason); SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhoneId); log("broadcastIccStateChangedIntent intent ACTION_SIM_STATE_CHANGED value=" + value + " reason=" + reason + " for mPhoneId=" + mPhoneId); ActivityManagerNative.broadcastStickyIntent(intent, READ_PHONE_STATE, UserHandle.USER_ALL); } } private void broadcastInternalIccStateChangedIntent(String value, String reason) { synchronized (mLock) { if (mPhoneId == null) { loge("broadcastInternalIccStateChangedIntent: Card Index is not set; Return!!"); return; } Intent intent = new Intent(ACTION_INTERNAL_SIM_STATE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING // 新广播发出后会替换被挂起的匹配的旧广播 | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone"); intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, value); intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason); intent.putExtra(PhoneConstants.PHONE_KEY, mPhoneId); // SubId may not be valid. log("Sending intent ACTION_INTERNAL_SIM_STATE_CHANGED" + " for mPhoneId : " + mPhoneId); ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL); } }
在使用场景上 broadcastInternalIccStateChangedIntent 主要用在 INTENT_VALUE_ICC_LOCKED 和 INTENT_VALUE_ICC_LOADED 状态,其他状态使用 broadcastIccStateChangedIntent,主要包括 READY、IMSI、LOADED。
/* INTERNAL LOCKED means ICC is locked by pin or by network */ public static final String INTENT_VALUE_ICC_INTERNAL_LOCKED = "INTERNAL_LOCKED"; /* READY means ICC is ready to access */ public static final String INTENT_VALUE_ICC_READY = "READY"; /* IMSI means ICC IMSI is ready in property */ public static final String INTENT_VALUE_ICC_IMSI = "IMSI"; /* LOADED means all ICC records, including IMSI, are loaded */ public static final String INTENT_VALUE_ICC_LOADED = "LOADED"; /* The extra data for broadcasting intent INTENT_ICC_STATE_CHANGE */ public static final String INTENT_KEY_LOCKED_REASON = "reason"; /* PIN means ICC is locked on PIN1 */ public static final String INTENT_VALUE_LOCKED_ON_PIN = "PIN"; /* PUK means ICC is locked on PUK1 */ public static final String INTENT_VALUE_LOCKED_ON_PUK = "PUK"; /* NETWORK means ICC is locked on NETWORK PERSONALIZATION */ public static final String INTENT_VALUE_LOCKED_NETWORK = "NETWORK"; /* PERM_DISABLED means ICC is permanently disabled due to puk fails */ public static final String INTENT_VALUE_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
到了这里,关于 IccCardProxy 及对外广播 我们就说的差不多了,IccCardProxy 对于所注册 SIM 状态的返回事件处理主要就是一些属性的设置及相应广播的发出。最后,我们再看一下 mIccRecords.registerForRecordsLoaded 返回事件的处理,这里主要更新了 MCC/MNC。
case EVENT_RECORDS_LOADED: // Update the MCC/MNC. if (mIccRecords != null) { Phone currentPhone = PhoneFactory.getPhone(mPhoneId); String operator = currentPhone.getOperatorNumeric(); log("operator=" + operator + " mPhoneId=" + mPhoneId); if (!TextUtils.isEmpty(operator)) { mTelephonyManager.setSimOperatorNumericForPhone(mPhoneId, operator); String countryCode = operator.substring(0,3); if (countryCode != null) { mTelephonyManager.setSimCountryIsoForPhone(mPhoneId, MccTable.countryCodeForMcc(Integer.parseInt(countryCode))); } else { loge("EVENT_RECORDS_LOADED Country code is null"); } } else { loge("EVENT_RECORDS_LOADED Operator name is null"); } } if (mUiccCard != null && !mUiccCard.areCarrierPriviligeRulesLoaded()) { mUiccCard.registerForCarrierPrivilegeRulesLoaded( this, EVENT_CARRIER_PRIVILIGES_LOADED, null); } else { onRecordsLoaded(); } break;