Android SIM卡识别加载流程
介绍Android系统中SIM卡的识别与信息加载流程
总述
本文基于Android N(Android 7)
首先要知道SIM卡一般是挂载在CP侧(MODEM侧)的,由MODEM给予真正的上电、识别、通信等,然后通过AP侧与CP侧之间的信息交流(Android系统中的逻辑主要集中在RIL层以上部分,RIL层以下为与MODEM通信的RIL库),完成Android侧SIM卡的加载过程。
SIM卡的加载识别流程,作为Telephony系统的一个重要部分,其加载识别流程比较复杂,且需要理解SIM卡的一些相关概念,否则代码难以理解。分析代码时需要结合设备的SIM卡加载日志进行分析。Android系统中SIM卡的加载过程是以消息驱动的,即SIM卡识别加载流程基本就是MODEM与Android侧进行信息交互、消息通知、响应的过程。
代码路径
frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc
UICC框架
实际上我们日常所谓的SIM卡,其名称是UICC(Universal Integrated Circuit Card),卡上包含了多种应用,用户标识模块(SIM)、通用用户标识模块(USIM),CSIM(电信卡接入电信CDMA EVDO的模块)、ISIM(VOLTE使用)及其它应用(电子钱包等)。即我们现在所使用的卡,里面一般可以包含多个模块应用,从而可以适配各种网络。比如移动卡包含USIM(接入LTE和GSM用),电信卡包含USIM和CSIM等(CSIM接入CDMA和EVDO用,所以电信卡经常是双模卡)。
因此我们的SIM卡识别框架就要从UICC说起。我们可以看UiccController的开头说明。
/**
* 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
SIM卡识别加载的核心类,所有的元素都从这里开始逐步创建,保持着与RIL(或者说同MODEM)的通信,将各种信息状态传达出去,然后各个具体的模块会处理处理对应事务。 - UiccCard
作为中间角色,主要是创建、更新UiccCardApplication及CatService - UiccCardApplication
MODEM识别SIM卡后会返回卡中包含哪些模块,比如如果包含了USIM和CSIM,则会创建其usim和csim两个UiccCardApplication,每个UiccCardApplication又会创建自己的IccFileHandler、IccRecords对象, - IccRecords
提供SIM卡常用信息查询,如IMSI、ICCID、Contacts等,UiccController根据SIM卡包含的不同模块,通过创建模块对应的UiccCardApplication,继而创建不同IccRecords对象 - IccFileHandler
负责SIM卡文件系统的读写
IccRecords和IccFileHandler根据当前所属的UiccCardApplication是什么类型的,会被创建出对应的各个子类(SIMRecords、RuimRecords、UsimFileHandler等)
SIM卡识别加载流程
上述框架如果是初次接触的人看完,一定是云里雾里的,因为有太多的专业概念。简单来说,GSM CDMA LTE等不同的网络制式,其伴随着不同的SIM卡类型诞生,SIM卡中存储着对应网络制式需要的一些数据(比如鉴权信息,以及卡的通用属性ICCID等)。现在,UICC卡中可以同时写入这些不同的卡信息,作为独立的模块。所以在卡的识别过程,MODEM返回卡中包含了哪些模块,这样Android系统这边就可以创建出对应的模块(UiccCardApplication)。
下面我们通过具体的代码流程,来进一步学习了解Android系统中是怎么识别加载SIM卡的。先上一张网上看到的画的很好的流程图
下述分析根据上述流程图进行简要说明,完整步骤可参考上图阅读分析源码。
- UiccController创建(流程图step 1、step 3、step4)
在com.android.phone进程启动,创建phone对象之前,会先创建UiccController:
sUiccController = UiccController.make(context, sCommandsInterfaces);
public static void makeDefaultPhone(Context context) {
...
telephonyComponentFactory.initSubscriptionController(
context, sCommandsInterfaces);
// Instantiate UiccController so that all other classes can just
// call getInstance()
//创建UiccController
sUiccController = UiccController.make(context, sCommandsInterfaces);
for (int i = 0; i < numPhones; i++) {
Phone phone = null;
int phoneType = TelephonyManager.getPhoneType(networkModes[i]);
if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
phone = telephonyComponentFactory.makePhone(context,
sCommandsInterfaces[i], sPhoneNotifier, i,
PhoneConstants.PHONE_TYPE_GSM,
telephonyComponentFactory);
} else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
phone = telephonyComponentFactory.makePhone(context,
sCommandsInterfaces[i], sPhoneNotifier, i,
PhoneConstants.PHONE_TYPE_CDMA_LTE,
telephonyComponentFactory);
}
Rlog.i(LOG_TAG, "Creating Phone with type = " + phoneType + " sub = " + i);
sPhones[i] = phone;
}
...
}
UiccController 的创建,会向RIL注册很多监听信息(mCis[i].registerForxxxx),比如EVENT_ICC_
STATUS_CHANGED、EVENT_RADIO_UNAVAILABLE等,尤其是EVENT_ICC_STATUS_CHANGED这个消息,在SIM卡的识别加载中尤其关键。一旦有任何的SIM卡状态变更,RIL就会接收到MODEM发送上来的信息,然后RIL又发送给监听者(比如这里的UiccController)。
private UiccController(Context c, CommandsInterface []ci) {
if (DBG) log("Creating UiccController");
mContext = c;
mCis = ci;
for (int i = 0; i < mCis.length; i++) {
Integer index = new Integer(i);
mCis[i].registerForIccStatusChanged(this, EVENT_ICC_STATUS_CHANGED, index);
// TODO remove this once modem correctly notifies the unsols
// If the device has been decrypted or FBE is supported, read SIM when radio state is
// available.
// Else wait for radio to be on. This is needed for the scenario when SIM is locked --
// to avoid overlap of CryptKeeper and SIM unlock screen.
if (DECRYPT_STATE.equals(SystemProperties.get("vold.decrypt")) ||
StorageManager.isFileEncryptedNativeOrEmulated()) {
mCis[i].registerForAvailable(this, EVENT_ICC_STATUS_CHANGED, index);
} else {
mCis[i].registerForOn(this, EVENT_ICC_STATUS_CHANGED, index);
}
mCis[i].registerForNotAvailable(this, EVENT_RADIO_UNAVAILABLE, index);
mCis[i].registerForIccRefresh(this, EVENT_SIM_REFRESH, index);
}
}
2.获取SIM卡状态(流程图step 5、step 6)
MODEM检测到SIM卡上电后,发送消息给RIL,RIL再发送EVENT_ICC_STATUS_CHANGED给监听者,比如上文的UiccController 。UiccController 是一个handler类,所以,对于RIL发送回来的消息,他在handleMessage 进行处理。
UiccController 通过 mCis[index].getIccCardStatus主动查询SIM卡的状态。mCi即RIL,当RIL获取到SIM卡状态信息后,则继续发送EVENT_GET_ICC_STATUS_DONE消息给UiccController ,UiccController 接着就继续处理获取到的信息(通过onGetIccCardStatusDone)。
public void handleMessage (Message 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;
case EVENT_RADIO_UNAVAILABLE:
if (DBG) log("EVENT_RADIO_UNAVAILABLE, dispose card");
if (mUiccCards[index] != null) {
mUiccCards[index].dispose();
}
mUiccCards[index] = null;
mIccChangedRegistrants.notifyRegistrants(new AsyncResult(null, index, null));
break;
case EVENT_SIM_REFRESH:
if (DBG) log("Received EVENT_SIM_REFRESH");
onSimRefresh(ar, index);
break;
default:
Rlog.e(LOG_TAG, " Unknown Event " + msg.what);
break;
}
}
}
- 获取到SIM卡状态后创建更新UiccCardApplication(流程图step 6)
UiccController在onGetIccCardStatusDone接口中,创建UiccCard(首次创建,后续则是进行更新),UiccCard
则根据SIM卡返回的信息,判断支持几个模块,然后根据支持的模块数,创建/更新对应的应用UiccCardApplication
。随后也创建了CatService。 - 创建IccRecords和IccFileHandler(后续我们以SIMRecord为例)(流程图step 6.1.1.1.1.1)
UiccCardApplication创建/更新的过程,会创建IccRecords和IccFileHandler(根据UiccCardApplication的类型)。根据UiccCardApplication的AppType的类型(APPTYPE_USIM、APPTYPE_CSIM、APPTYPE_ISIM等),会创建对应的IccRecords(SIMRecords、RuimRecords、IsimUiccRecords等)和IccFileHandler(SIMFileHandler、CsimFileHandler、IsimFileHandler等)
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();
queryPin1State();
}
mCi.registerForNotAvailable(mHandler, EVENT_RADIO_UNAVAILABLE, null);
}
这里我们假设创建的是USIM对应的SIMRecords,可以看到SIMRecords又向mParentApp(UiccCardApplication)注册了EVENT_APP_READY事件的监听。所以如果UiccCardApplication获取到EVENT_APP_READY状态后,SIMRecords就收收到监听通知。这样SIM卡状态就绪了,就可以进行下一步的SIM卡信息获取流程了。
public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
...
mParentApp.registerForReady(this, EVENT_APP_READY, null);
mParentApp.registerForLocked(this, EVENT_APP_LOCKED, null);
if (DBG) log("SIMRecords X ctor this=" + this);
...
}
- SIM信息获取(流程图step 11、16、17)
我们在上面第3点分析中提到,UiccController获取到SIM卡状态后,就会通过UiccCard去更新UiccCardApplication。当SIM卡状态是APPSTATE_READY,则UiccCardApplication发送EVENT_APP_READY给SIMRecords,SIMRecords也是一个Handler类,所以他在handleMessage中处理EVENT_APP_READY消息,即这时候,Android侧这边已经知道,SIM卡已经状态就绪,那么可以开始去读取具体的各种信息了。所以调用onReady->fetchSimRecords,fetchSimRecords中便是去主动获取SIM卡的各类信息,包括常见的IMSI、ICCID,以及很多乱七八糟不知道干嘛用的信息。
public void handleMessage(Message msg) {
...
try { switch (msg.what) {
case EVENT_APP_READY:
onReady();
break;
case EVENT_APP_LOCKED:
onLocked();
break;
...
}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();
}
}
}
仍然如上代码流程所示,在最终所有的信息都获取到后,依次执行如下步骤:onRecordLoaded->onAllRecordsLoaded->mRecordsLoadedRegistrants.notifyRegistrants,这里会通知到IccCardProxy。IccCardProxy在创建的时候监听了SIMRecords的信息加载。
至此,实际上SIM卡的识别和加载就已经基本完成了,但是这里还有一个很关键的类没有分析到:IccCardProxy。IccCardProxy看名字是一个代理,他作为中间人,持有我们前面分析过的各个主要类,能够拿到几乎所有的SIM卡状态、信息等,所以IccCardProxy是作为对外(UICC子系统以外)的一个接口类,他会将拿到的状态、信息进行广播,把状态、信息广播出去,或者其他子系统、应用、接口等可以通过IccCardProxy进行主动的信息获取。下面我们对IccCardProxy进行详细的分析。
- IccCardProxy创建
IccCardProxy在创建phone的时候创建,如下所示
//以GsmCdmaPhone.java中的phone初始化为例
private void initOnce(CommandsInterface ci) {
...
mIccCardProxy = mTelephonyComponentFactory.makeIccCardProxy(mContext, mCi, mPhoneId);
mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
mCi.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
mCi.registerForOn(this, EVENT_RADIO_ON, null);
mCi.setOnSuppServiceNotification(this, EVENT_SSN, null);
...
}
具体创建IccCardProxy过程如下,最关键的一点,是向UiccController注册了EVENT_ICC_CHANGED监听消息,则UiccController会有多个时机向IccCardProxy发送EVENT_ICC_CHANGED的通知消息:registerForIccChanged调用时、onGetIccCardStatusDone中(获取到SIM卡状态后)等。
public IccCardProxy(Context context, CommandsInterface ci, int phoneId) {
if (DBG) log("ctor: ci=" + ci + " phoneId=" + phoneId);
...
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);
}
- IccCardProxy中EVENT_ICC_CHANGED消息的处理(流程图step 9)
IccCardProxy接收到EVENT_ICC_CHANGED消息后,就会去获取getUiccCard、getApplication、getIccRecords,把UICC框架中的几个核心成员获取到,然后调用registerUiccCardEvents注册了一系列的监听消息:EVENT_APP_READY、EVENT_RECORDS_LOADED等。
private void updateIccAvailability() {
synchronized (mLock) {
UiccCard newCard = mUiccController.getUiccCard(mPhoneId);
CardState state = CardState.CARDSTATE_ABSENT;
UiccCardApplication newApp = null;
IccRecords newRecords = null;
if (newCard != null) {
state = newCard.getCardState();
newApp = newCard.getApplication(mCurrentAppType);
if (newApp != null) {
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();
}
}
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);
}
}
- SIM卡就绪、信息加载完毕后IccCardProxy的处理步骤
SIM卡状态就绪后,UiccApplication会给IccCardProxy发送EVENT_APP_READY的消息,IccCardProxy则调用setExternalState将APP_READY的状态(或者其他状态,比如ABSENT、NOT_READY等)设置到系统属性“gsm.sim.state”。随后将状态通过广播发送出去。
private void setExternalState(State newState, boolean override) {
...
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));
}
...
}
}
SIM卡信息获取完毕后(SIMRecords发出EVENT_RECORDS_LOADED消息通知IccCardProxy),然后IccCardProxy更新了“gsm.sim.operator.numeric”属性的值,即更新mccmnc,随后调用onRecordsLoaded发送了个参数值为INTENT_VALUE_ICC_LOADED的广播出去。
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
...
case EVENT_ICC_CHANGED:
if (mInitialized) {
updateIccAvailability();
}
break;
...
case EVENT_APP_READY:
setExternalState(State.READY);
break;
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;
...
}
}
至此,SIM卡加载、信息获取流程结束。SIM卡的状态众多、信息类型繁杂,本文抛砖引玉,只从主要流程进行分析,具体更多的细节仍然需要结合实际的日志和代码进行分析梳理,才能够对SIM卡的加载有更深刻的理解。
日志分析举例
很多人对SIM卡中所包含的应用不是很了解,这边通过移动卡和电信卡的日志进行说明。
- 移动卡 UiccCardApplication的创建与更新
如下日志所示,接收到EVENT_GET_ICC_STATUS_DONE 事件后,则UiccCard被创建,紧接着根据返回的值创建UiccCardApplication,创建的参数、类型等,则是一个 IccCardApplicationStatus 类型的状态值({APPTYPE_USIM,
APPSTATE_SUBSCRIPTION_PERSO,PERSOSUBSTATE_READY,pin1=PINSTATE_DISABLED,pin2=PINSTATE_ENABLED_NOT_VERIFIED})
即mAppType=APPTYPE_USIM(这个值表示当前应用是USIM模块,决定了后续创建的IccRecords、IccFileHandler是什么类型的),mAppState=APPSTATE_SUBSCRIPTION_PERSO(决定了该模块应用的状态,后续会变为APPSTATE_READY,READY状态才是可用的)
04-26 10:04:04.696 D/UiccController( 3580): Received EVENT_GET_ICC_STATUS_DONE
04-26 10:04:04.696 D/UiccCard( 3580): 1 applications
04-26 10:04:04.696 D/UiccCardApplication( 3580): Creating UiccApp: {APPTYPE_USIM,APPSTATE_SUBSCRIPTION_PERSO,PERSOSUBSTATE_READY,pin1=PINSTATE_DISABLED,pin2=PINSTATE_ENABLED_NOT_VERIFIED}
MODEM继续更新SIM卡状态后,则如下所示,UiccController通过UiccCard更新UiccCardApplication,这时SIM卡该模块应用的状态已经变成了APPSTATE_READY。此时SIM卡状态就绪了(只有一个应用,其他应用也就不再继续更新了)
04-26 10:04:06.214 D/UiccController( 3580): Received EVENT_GET_ICC_STATUS_DONE
04-26 10:04:06.214 D/UiccCard( 3580): 1 applications
04-26 10:04:06.214 D/UiccCardApplication( 3580): APPTYPE_USIM update. New {APPTYPE_USIM,APPSTATE_READY,pin1=PINSTATE_DISABLED,pin2=PINSTATE_ENABLED_NOT_VERIFIED}
04-26 10:04:06.214 D/UiccCardApplication( 3580): APPTYPE_USIM changed state: APPSTATE_SUBSCRIPTION_PERSO -> APPSTATE_READY
04-26 10:04:06.214 D/RILJ ( 3580): [3758]> QUERY_FACILITY_LOCK [FD 7 a0000000871002ff86ffff89ffffffff] [SUB1]
04-26 10:04:06.215 D/RILJ ( 3580): [3759]> QUERY_FACILITY_LOCK [SC 7 a0000000871002ff86ffff89ffffffff] [SUB1]
04-26 10:04:06.216 D/UiccCardApplication( 3580): Notifying registrants: READY
- 电信卡 UiccCardApplication的创建与更新
这边不再列举完整的创建、更新日志,仅简单看下普通电信卡带有几个应用,如下所示:
2022-05-30 15:54:29.596 com.android.phone D/UiccController( 3111): Received EVENT_GET_ICC_STATUS_DONE
2022-05-30 15:54:29.596 com.android.phone D/UiccCard( 3111): 2 applications
2022-05-30 15:54:29.596 com.android.phone D/UiccCardApplication( 3111): APPTYPE_CSIM update. New {APPTYPE_CSIM,APPSTATE_READY,pin1=PINSTATE_DISABLED,pin2=PINSTATE_ENABLED_NOT_VERIFIED}
2022-05-30 15:54:29.596 com.android.phone D/UiccCardApplication( 3111): APPTYPE_USIM update. New {APPTYPE_USIM,APPSTATE_READY,pin1=PINSTATE_DISABLED,pin2=PINSTATE_ENABLED_NOT_VERIFIED}
我们可以看到,普通电信卡有两个应用,即APPTYPE_CSIM 和APPTYPE_USIM ,其中APPTYPE_USIM 主要是用于4G入网使用,而APPTYPE_CSIM 主要是用于2G/3G入网使用(电信的2G/3G与4G属于两种不同的技术制式,卡中需要通过两种应用来保存不同的参数,以便电信卡可以顺利连接入2G/3G或者4G)
当然,现在电信也推出了单模卡(只有USIM应用),这种卡是无法连接电信2G 3G的,只能连接4G,这里就不展开描述了。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)