SIM上运营商SPN(Service Provider Name)的读取

来源:互联网 发布:windows镜像下载网站 编辑:程序博客网 时间:2024/06/04 18:53

SIM上运营商SPN(Service Provider Name)的读取

在手机上开机的时候会显示运营商名字;在SIM设置里面同样有关于运营商名字的显示需求,然而问题是,运营商的名字是从那里获取的?是如何获取的?又是如何显示的呢?

上一节已经讲过系统开机后如何开始实例化SIMRecords对象的。今天的问题主要是和这个类相关,这个类是ICCRecords的子类,ICCRecords又继承了Handler。
首先看一下SIMRecords的构造方法,毕竟在构造方法中初始化了这个对象的一些重要参数和一些初始化的处理过程。

    // ***** Constructor    public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {        super(app, c, ci);        //ADN  : Abbreviated Dialing Numbers 的缩写 : 快速拨号        mAdnCache = new AdnRecordCache(mFh);        //语音信箱配置        mVmConfig = new VoiceMailConstants();        //SPN Service Provider Name,运营商名称覆盖,本地文件中读取        mSpnOverride = new SpnOverride();        mRecordsRequested = false;  // No load request is made till SIM ready        // recordsToLoad is set to 0 because no requests are made yet,没有加载请求,所以加载记录为0        mRecordsToLoad = 0;        mCi.setOnSmsOnSim(this, EVENT_SMS_ON_SIM, 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);    }

首先super(app, c, ci);则调用父类的构造方法,初始化了UiccCardApplication的对象的引用–>mParentApp;通过这个常量把EVENT_APP_READY和EVENT_APP_LOCKED直接注册到UiccCardApplication中,如果PhoneApp执行完成后,
就会发送这个异步消息给Handler处理,刚好SIMRecords也继承了Handler类,而且覆写了Handler的handlerMessage()方法。也就是说当PhoneApp初始化完毕后就会发送异步消息,SIMRecords的消息处理机制就开始处理SIM卡信息
加载的内容。 还有这一句:mSpnOverride = new SpnOverride();//SPN Service Provider Name,运营商名称覆盖,本地文件中读取。

下面进入到handlerMessage()方法中看看,它是如何处理EVENT_APP_READY的消息的。

      @Override    public void handleMessage(Message msg) {        AsyncResult ar;        AdnRecord adn;        ...        try { switch (msg.what) {            case EVENT_APP_READY:                onReady();                break;            ...            }            ...        }        ....    }

这里的switch语句中直接转向了onReady()方法,那就继续往下看。该方法如下,它来源于对父类方法的覆写:

    @Override    public void onReady() {        fetchSimRecords();    }

通过这个方法名,我们可以猜到它表示开始取SIM卡中的相关信息记录。这里使用fetch:表示读取SIM卡信息是一个主动请求的过程,而事实上就是主动向modem(调制解调器)请求读取SIM卡信息的过程。那么久进入fetchSimRecords()方法。
我们的目的暂时只是读取运营商的名称信息。那我们就暂时只关注和运营商信息相关的内容。

 protected void fetchSimRecords() {        mRecordsRequested = true;        ....        getSpnFsm(true, null);        ....    }

其中省略号部分表示读取SIM卡中信息的请求。和运营商名称相关的方法是:getSpnFsm(true, null);这个方法名什么意思FSM表示:Finite State Machine 有限状态机(一种在有限的状态范围内自动转换的机制)。该方法名的含义
就是获取运营商名称状态机,感觉有点奇怪,那我们继续往下看。我们先来看下一个枚举类:

     /**     * 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 ,首先加载处于EF_SPN地址的信息        READ_SPN_CPHS,      // Load EF_SPN_CPHS secondly,其次加载EF_SPN_CPHS地址下的信息        READ_SPN_SHORT_CPHS // Load EF_SPN_SHORT_CPHS last,最后加载EF_SPN_SHORT_CPHS地址下的信息    }

该枚举类只用于getSpnFsm()方法,这就是SPN FSM状态机的有限个状态。含义见注释。表明加载运营商名称的过程中是需要读取这三个地址下的信息。那我们来看看getSpnFsm()方法。

 /**     * 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:                。。。。                break;            case READ_SPN_CPHS:                。。。。                break;            case READ_SPN_SHORT_CPHS:                。。。。                break;            default:                mSpnState = GetSpnFsmState.IDLE;        }    }
EF : Elementary File 基本文件

读这个方法的时候,我们顺便看一下官方的描述:状态机加载可能被存储于这三个地址下的运营商名称(EF_SPN (3GPP), EF_SPN_CPHS, or EF_SPN_SHORT_CPHS (CPHS4.2)),开启状态机后,按照顺序查找各个地址,当查询到有效的
运营商名称后,则停止状态机查找。在状态机查找的过程中,如果SIM卡状态更新,则会丢弃之前的信息,然后重新初始化状态机进行有效运营商名称的查找。
参数一 boolean start,当允许初始化加载运营商信息时就会被设置为true,一旦加载完成就会被重新设置为false
参数二 AsyncResult ar,通过loadEFTransparent来获取包含有数据的二进制字节数组,该数组就是运营商的名称。

来认真分析一下:
开始有一个if的判断,就是start为true时,无论当前的状态机处于什么状态都会被重新设置为初始状态。默认情况下,状态为:mSpnState = GetSpnFsmState.IDLE。下面进入switch中:
在INIT中:首先将通过该方法setServiceProviderName(null);将现有的信息设置为空,然后通过通过loadEFTransparent()请求EF文件,文件获取后就发送EVENT_GET_SPN_DONE信息到handleMessage()中。此时mRecordsToLoad自加1,
表示一次请求,当请求被返回时则会减1,接下来状态被设置为GetSpnFsmState.READ_SPN_3GPP。那我们就要看看handleMessage()中对EVENT_GET_SPN_DONE的处理。

 // ***** Overridden from Handler    @Override    public void handleMessage(Message msg) {        AsyncResult ar;        AdnRecord adn;        byte data[];        boolean isRecordLoadResponse = false;        。。。。        try { switch (msg.what) {            。。。。            case EVENT_GET_SPN_DONE:                isRecordLoadResponse = true;                ar = (AsyncResult) msg.obj;                getSpnFsm(false, ar);            break;            。。。。            }            。。。。        }        。。。。    }

从这里可以看出这里有接收AsyncResult对象,接下来再次执行getSpnFsm(false, ar);,而此时的状态mSpnState = GetSpnFsmState.READ_SPN_3GPP。

     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:                。。。                break;            case READ_SPN_3GPP:                if (ar != null && ar.exception == null) {                    data = (byte[]) ar.result;                    //设置mSpnDisplayCondition显示标志位,如果通过EF_SPN没有取到,则认为mSpnDisplayCondition=-1 【参考】                    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:                。。。                break;            case READ_SPN_SHORT_CPHS:                。。。                break;            default:                mSpnState = GetSpnFsmState.IDLE;        }    }

首先检测ar是否为空,是否结果异常,如果都不是,则进入下一步,获取其中的字节数组。然后将设置这个变量mSpnDisplayCondition = 0xff & data[0]。接下来设置 通过这个setServiceProviderName()方法设置运营商的名字,
其中参数表示从读取的数据中的第二位开始到最后。接下里又进行获取运营商名称的一个操作,被解释为二次检查,如果获取的结果为null或者长度为0,则将 mSpnState = GetSpnFsmState.READ_SPN_CPHS;,然后继续从第二个地址位
读取运营商名称数据。如果二次获取的结果正常,则将该结果设置给TelephonyManager的.setSimOperatorNameForPhone(mParentApp.getPhoneId(), spn);便于提供给应用层调用。
如果没有获取成功,则按照次序仓下一个地址位(READ_SPN_CPHS)读取数据:经过handleMessage()方法后再次进入getSpnFsm(。。。)方法:

 private void getSpnFsm(boolean start, AsyncResult ar) {        byte[] data;        。。。        switch(mSpnState){            case INIT:                。。。                break;            case READ_SPN_3GPP:                。。。                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:                。。。                break;            default:                mSpnState = GetSpnFsmState.IDLE;        }    }

很显然,这个处理过程和上面的基本一致。如果还是没有查找到运营商的名称则会继续讲状态设置为 mSpnState = GetSpnFsmState.READ_SPN_SHORT_CPHS;再次通过handleMessage()发送消息。

 private void getSpnFsm(boolean start, AsyncResult ar) {        byte[] data;        。。。        switch(mSpnState){            case INIT:                。。。                break;            case READ_SPN_3GPP:                。。。                break;            case READ_SPN_CPHS:                。。。                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;        }    }

基本上重复上面的操作,如果获取的运营商名称数据则保存,否则将运营商名称设置为null,退出流程。至此从三个地址读取信息的流程已经完成,那就再来看看handleMessage()方法。

参考文章
【1】对UiccController 的认识
http://blog.csdn.net/lsdmx2016/article/details/9065759

【2】Android N SIM 卡信息加载流程
http://blog.csdn.net/u012724237/article/details/73497775

【3】Android运营商名称显示之PLMN与SPN显示规则
http://blog.csdn.net/u014386544/article/details/52593982

【4】Android N数据业务总结
http://blog.csdn.net/gaugamela/article/details/54406821

【5】Android运营商名称显示之SPN的读取
http://blog.csdn.net/u014386544/article/details/52593978

【6】ADN实现
http://blog.sina.com.cn/s/blog_6d0823a50100w7sp.html

【7】读取SIM卡中的EF文件流程
http://blog.csdn.net/snail_coder/article/details/48576585

【8】android面试题–sim卡的EF文件有何作用 ?????
http://blog.csdn.net/surehao/article/details/11579005

【9】Android 7.0 源码分析
http://blog.csdn.net/column/details/13723.html?&page=3