总述

本文基于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卡的。先上一张网上看到的画的很好的流程图
Android系统SIM卡识别加载流程
下述分析根据上述流程图进行简要说明,完整步骤可参考上图阅读分析源码。

  1. 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;
            }
        }
    }
  1. 获取到SIM卡状态后创建更新UiccCardApplication(流程图step 6)
    UiccController在onGetIccCardStatusDone接口中,创建UiccCard(首次创建,后续则是进行更新),UiccCard
    则根据SIM卡返回的信息,判断支持几个模块,然后根据支持的模块数,创建/更新对应的应用UiccCardApplication
    。随后也创建了CatService。
  2. 创建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);
		...
    }

  1. 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进行详细的分析。

  1. 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);
    }
  1. 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);
        }
    }

  1. 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卡中所包含的应用不是很了解,这边通过移动卡和电信卡的日志进行说明。

  1. 移动卡 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
  1. 电信卡 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,这里就不展开描述了。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐