(M)SIM卡开机流程分析之SPN加载

本篇仅以USIM卡为例进行代码分析

从之前对于UiccController.java文件进行分析中,我们知道UiccController中创建了UiccCard,而在UiccCard中创建了一个UiccCardAppliaction对象,而在UiccCardApplication的构造方法中,调用了如下方法:

UiccCardApplication(UiccCard uiccCard,
                    IccCardApplicationStatus as,
                    Context c,
                    CommandsInterface ci) {
    ......
    mIccFh = createIccFileHandler(as.app_type);
    mIccRecords = createIccRecords(as.app_type, mContext, mCi);
    ......
}
通过createIccFileHandler和createIccRecords方法,创建了IccFileHandler和IccRecords对象

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;
    }
}
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;
    }
}
由于是USIM卡,因此创建的是SIMRecords和UsimFileHandler对象,接下来我们进入SIMRecords.java的构造方法,传入的参数分别为当前的UiccCardApplication对象,上下文对象,和RIL对象(从之前的分析中我们知道)

public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
    // 调用父类IccRecords的构造方法
    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);
    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);
}
首先其调用的是父类IccRecords的构造方法

public IccRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
    mContext = c;
    mCi = ci;
    // 这个就是刚刚我们在UiccCardApplication中通过createIccFileHandler创建的UsimFileHandler对象
    mFh = app.getIccFileHandler();
    mParentApp = app;
    mTelephonyManager = (TelephonyManager) mContext.getSystemService(
            Context.TELEPHONY_SERVICE);
}
可以看到,从SIMRecords的构造方法中一共做了如下几件事:

1. 首先初始化了一些对象,将上下文对象、RIL对象、UsimFileHandler对象,UiccCardApplication对象、SpnOverride对象等,并将其关联在一起

2. 为RIL对象注册了几个消息,EVENT_SMS_ON_SIM,EVENT_SIM_REFRESH消息,并在SIMRecords中处理这些消息

3. 为UiccCardApplication注册EVENT_APP_READY,EVENT_APP_LOCKED消息,并在SIMRecords中处理这些消息

关于registerForReady和registerForLocked方法,暂不做分析,确认结果是,当SIM卡准备好后,会向上发送EVENT_APP_READY消息,并在SIMRecords中进行处理(不考虑SIMLOCK情况)

查看SIMRecords接收到EVENT_APP_READY方法后的处理

@Override
public void handleMessage(Message msg) {
    ...
    try { switch (msg.what) {
        case EVENT_APP_READY:
            onReady();
            break;
    }
    ......
}
@Override
public void onReady() {
    fetchSimRecords();
}
protected void fetchSimRecords() {
    ......
    getSpnFsm(true, null);
    ......
}
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));

                    if (DBG) log("Load EF_SPN: " + getServiceProviderName()
                            + " spnDisplayCondition: " + mSpnDisplayCondition);
                    mTelephonyManager.setSimOperatorNameForPhone(
                            mParentApp.getPhoneId(), getServiceProviderName());

                    mSpnState = GetSpnFsmState.IDLE;
                } else {
                    mFh.loadEFTransparent( EF_SPN_CPHS,
                            obtainMessage(EVENT_GET_SPN_DONE));
                    mRecordsToLoad++;

                    mSpnState = GetSpnFsmState.READ_SPN_CPHS;

                    // 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));

                    if (DBG) log("Load EF_SPN_CPHS: " + getServiceProviderName());
                    mTelephonyManager.setSimOperatorNameForPhone(
                            mParentApp.getPhoneId(), getServiceProviderName());

                    mSpnState = GetSpnFsmState.IDLE;
                } else {
                    mFh.loadEFTransparent(
                            EF_SPN_SHORT_CPHS, obtainMessage(EVENT_GET_SPN_DONE));
                    mRecordsToLoad++;

                    mSpnState = GetSpnFsmState.READ_SPN_SHORT_CPHS;
                }
                break;
            case READ_SPN_SHORT_CPHS:
                if (ar != null && ar.exception == null) {
                    data = (byte[]) ar.result;
                    setServiceProviderName(IccUtils.adnStringFieldToString(data, 0, data.length));

                    if (DBG) log("Load EF_SPN_SHORT_CPHS: " + getServiceProviderName());
                    mTelephonyManager.setSimOperatorNameForPhone(
                            mParentApp.getPhoneId(), getServiceProviderName());
                }else {
                    if (DBG) log("No SPN loaded in either CHPS or 3GPP");
                }

                mSpnState = GetSpnFsmState.IDLE;
                break;
            default:
                mSpnState = GetSpnFsmState.IDLE;
        }
    }
主要是

1. 在接收到EVENT_APP_READY消息后,调用了onReady方法,在onReady方法中调用了fetchSimRecords(从名字上来看,就知道是获取SIMRecords的数据),然后在fetchSimRecords方法中调用了getSpnFsm方法,并且开始传入的参数为true和null

2. 第一次调用getSpnFsm方法后,由于mSpnState的值为GetSpnFsmState.IDLE,因此首次会将mSpnState的值设置为GetSpnFsmState.INIT,然后进入INIT对应的分支,设置ServiceProviderName为null,后调用UsimFileHandler对象的loadEFTransparent方法,并且将mSpnState设置为GetSpnFsmState.READ_SPN_3GPP

3. UsimFileHandler对象的loadEFTransparent方法主要是通过RIL对象的iccIOForApp方法,最终会返回给SIMRecords,发送EVENT_GET_SPN_DONE消息给SIMRecords对象处理,处理时,第二次调用SIMRecords对象的getSpnFsm方法的,只是传入的第一个参数变为false,而且由于mSpnState之前被设置为GetSpnFsmState.READ_SPN_3GPP,所以会进入getSpnFsm方法的GetSpnFsmState.READ_SPN_3GPP分支

4. 进入GetSpnFsmState.READ_SPN_3GPP分支后,根据RIL层传过来的数据,设置ServiceProviderName,若传上来的数据有异常,则继续将mSpnState的值设置为GetSpnFsmState.READ_SPN_CPHS,继续第3步,若无异常,则将mSpnState设置为IDLE,然后退出

5. 依照4的步骤,getSpnFsm完成ServiceProviderName的设置

最后,返回SIMRecords.java的handleMessage方法,我们看到在default分支中调用了一次onRecordLoaded

@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 (mRecordsToLoad == 0 && mRecordsRequested == true) {
        onAllRecordsLoaded();
    } else if (mRecordsToLoad < 0) {
        loge("recordsToLoad <0, programmer error suspected");
        mRecordsToLoad = 0;
    }
}
当所有的数据加载完成后,会调用onAllRecordsLoaded方法

@Override
protected void onAllRecordsLoaded() {
    ......
    setSpnFromConfig(operator);
    ......
}
调用了setSpnFromConfig方法

private void setSpnFromConfig(String carrier) {
    if (mSpnOverride.containsCarrier(carrier)) {
        setServiceProviderName(mSpnOverride.getSpn(carrier));
        mTelephonyManager.setSimOperatorNameForPhone(
                mParentApp.getPhoneId(), getServiceProviderName());
    }
}
我们通过分析SpnOverride对象,知道,这个类主要是解析项目/system/etc/spn-conf.xml文件,得到默认的SPN名称

因此setSpnFromConfig方法主要是通过确认spn-conf.xml中有没有设置自定义的SPN名称,如果有,则将ServiceProviderName设置为我们自定义的SPN

至此,SPN名称加载完毕

你可能感兴趣的:(SIM卡开机流程分析)