Android运营商名称显示之SPN的读取

来源:互联网 发布:宝宝起名 知乎 编辑:程序博客网 时间:2024/05/17 20:31
上一节介绍了PLMN的读取流程,本节来介绍SPN的读取流程。

        SPN(Service Provider Name)就是当前发行SIM卡的运营商的名称,可以从以下两个路径获取:

        1、从SIM文件系统读取
        2、从配置文件读取

        我们本节就来分析该字串的读取过程。


一、从SIM读取SPN过程


        一般来说,SIM上保存有当前SIM的发行运营商名称,也就是SPN,该字串可以存储在SIM的EF_SPN(0x6F46)、EF_SPN_CPHS(0x6f14)、EF_SPN_SHORT_CPHS(0x6f18)三个地址上,在SIMRecords初始化时通过getSpnFsm()从SIM中读取出来并保存。下面来看读取SPN的过程:
[java] view plain copy
  1. @SIMRecords.java  
  2. protected void fetchSimRecords() {  
  3.     getSpnFsm(truenull);  
  4. }  
        请注意,此时的getSpnFsm()的start参数为true,而且mSpnState为初始化值:GetSpnFsmState.IDLE
[java] view plain copy
  1. private void getSpnFsm(boolean start, AsyncResult ar) {  
  2.     byte[] data;  
  3.     if (start) {  
  4.         if(mSpnState == GetSpnFsmState.READ_SPN_3GPP ||  
  5.                 mSpnState == GetSpnFsmState.READ_SPN_CPHS ||  
  6.                 mSpnState == GetSpnFsmState.READ_SPN_SHORT_CPHS ||  
  7.                 mSpnState == GetSpnFsmState.INIT) {  
  8.             mSpnState = GetSpnFsmState.INIT;  
  9.             return;  
  10.         } else {  
  11.             //mSpnState默认为IDLE,然后修改为INIT  
  12.             mSpnState = GetSpnFsmState.INIT;  
  13.         }  
  14.     }  
  15.     switch(mSpnState){  
  16.         case INIT:  
  17.             //初始化SPN  
  18.             setServiceProviderName(null);  
  19.             //从SIM的EF_SPN读取SPN  
  20.             mFh.loadEFTransparent(EF_SPN, obtainMessage(EVENT_GET_SPN_DONE));  
  21.             mRecordsToLoad++;  
  22.             //mSpnState修改为READ_SPN_3GPP  
  23.             mSpnState = GetSpnFsmState.READ_SPN_3GPP;  
  24.             break;  
  25.         case READ_SPN_3GPP:  
  26.         case READ_SPN_CPHS:  
  27.         case READ_SPN_SHORT_CPHS:  
  28.         default:  
  29.             mSpnState = GetSpnFsmState.IDLE;  
  30.     }  
  31. }  
        在上面的过程中,将会从EF_SPN中读取当前SPN,并且将mSpnState置为READ_SPN_3GPP。
        当读取完毕后,在handleMessage()中读取反馈:
[java] view plain copy
  1. public void handleMessage(Message msg) {  
  2.     try {  
  3.         switch (msg.what) {  
  4.             case EVENT_GET_SPN_DONE:  
  5.                 isRecordLoadResponse = true;  
  6.                 ar = (AsyncResult) msg.obj;  
  7.                 getSpnFsm(false, ar);  
  8.                 break;  
  9.         }  
  10.     } catch (RuntimeException exc) {  
  11.     } finally {  
  12.         if (isRecordLoadResponse) {  
  13.             onRecordLoaded();  
  14.         }  
  15.     }  
  16. }  
        然后再次进入getSpnFsm()中处理,此时的mSpnState状态为READ_SPN_3GPP,而start为false,所以直接进入switch语句判断:
[java] view plain copy
  1. private void getSpnFsm(boolean start, AsyncResult ar) {  
  2.     byte[] data;  
  3.     if (start) {  
  4.     }  
  5.   
  6.   
  7.     switch(mSpnState){  
  8.         case INIT:  
  9.             break;  
  10.         case READ_SPN_3GPP:  
  11.             if (ar != null && ar.exception == null) {  
  12.                 data = (byte[]) ar.result;  
  13.                 //设置mSpnDisplayCondition显示标志位,如果通过EF_SPN没有取到,则认为mSpnDisplayCondition=-1  
  14.                 mSpnDisplayCondition = 0xff & data[0];  
  15.                 setServiceProviderName(IccUtils.adnStringFieldToString( data, 1, data.length - 1));  
  16.                 //将当前的SPN写入系统属性  
  17.                 setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());  
  18.                 mSpnState = GetSpnFsmState.IDLE;  
  19.             } else {  
  20.                 mFh.loadEFTransparent( EF_SPN_CPHS, obtainMessage(EVENT_GET_SPN_DONE));  
  21.                 mRecordsToLoad++;  
  22.                 mSpnState = GetSpnFsmState.READ_SPN_CPHS;  
  23.                 mSpnDisplayCondition = -1;  
  24.             }  
  25.             break;  
  26.         case READ_SPN_CPHS:  
  27.         case READ_SPN_SHORT_CPHS:  
  28.         default:  
  29.             mSpnState = GetSpnFsmState.IDLE;  
  30.     }  
  31. }  
        如果此时从SIM读取的SPN不为空,则会通过adnStringFieldToString()将数据转换为字串后,通过setServiceProviderName()保存,同时也要存储在PROPERTY_ICC_OPERATOR_ALPHA的系统属性中,并且重置mSpnState为IDLE;
        另外,这里的mSpnDisplayCondition是SPN的第一位数据,在显示SPN时用来判定显示规则。
        如果SIM中的SPN为空,则再去读取SIM中的EF_SPN_CPHS分区,和上面流程相同,该请求会通过handleMessage()再次发送给getSpnFsm()内部,只不过这次进入READ_SPN_CPHS分支处理:
[java] view plain copy
  1. private void getSpnFsm(boolean start, AsyncResult ar) {  
  2.     byte[] data;  
  3.     if (start) {  
  4.     }  
  5.   
  6.   
  7.     switch(mSpnState){  
  8.         case INIT:  
  9.             break;  
  10.         case READ_SPN_3GPP:  
  11.             break;  
  12.         case READ_SPN_CPHS:  
  13.             if (ar != null && ar.exception == null) {  
  14.                 data = (byte[]) ar.result;  
  15.                 setServiceProviderName(IccUtils.adnStringFieldToString(data, 0, data.length));  
  16.                 setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());  
  17.                 mSpnState = GetSpnFsmState.IDLE;  
  18.             } else {  
  19.                 mFh.loadEFTransparent( EF_SPN_SHORT_CPHS, obtainMessage(EVENT_GET_SPN_DONE));  
  20.                 mRecordsToLoad++;  
  21.                 mSpnState = GetSpnFsmState.READ_SPN_SHORT_CPHS;  
  22.             }  
  23.             break;  
  24.         case READ_SPN_SHORT_CPHS:  
  25.         default:  
  26.             mSpnState = GetSpnFsmState.IDLE;  
  27.     }  
  28. }  
        与上面读取EF_SPN类似,如果读取成功就保存,否则再去读取EF_SPN_SHORT_CPHS,而读取后的结果一样在getSpnFsm()中处理:
[java] view plain copy
  1. private void getSpnFsm(boolean start, AsyncResult ar) {  
  2.     byte[] data;  
  3.     if (start) {  
  4.     }  
  5.   
  6.   
  7.     switch(mSpnState){  
  8.         case INIT:  
  9.             break;  
  10.         case READ_SPN_3GPP:  
  11.             break;  
  12.         case READ_SPN_CPHS:  
  13.             break;  
  14.         case READ_SPN_SHORT_CPHS:  
  15.             if (ar != null && ar.exception == null) {  
  16.                 data = (byte[]) ar.result;  
  17.                 setServiceProviderName(IccUtils.adnStringFieldToString(data, 0, data.length));  
  18.                 setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());  
  19.             }else {  
  20.                 if (DBG) log("No SPN loaded in either CHPS or 3GPP");  
  21.             }  
  22.             mSpnState = GetSpnFsmState.IDLE;  
  23.         default:  
  24.             mSpnState = GetSpnFsmState.IDLE;  
  25.     }  
  26. }  
        遇上面流程类似,读取成功就保存,不成功就不再处理。

        经过上面的过程,就将SIM中的SPN读取并保存起来了。


二、从配置文件读取SPN过程


        Android原始代码中,无论在SIM的三个文件分区有没有查询到SPN,系统都会继续尝试从配置文件中读取SPN,如果读取成功,则覆盖刚才SIM中读取的值,如果配置文件读取失败,就使用上面的SIM中的SPN。
        开发者可以将所有预置的SPN存入spn-conf.xml这个文件中(不同平台该文件的存储位置不同),在编译时候就会将其拷贝到out的system\etc\目录中,以供系统读取。我们来挑选几条该文件中的项来看一下:
[html] view plain copy
  1. @spn-conf.xml  
  2. <spnOverride numeric="46000" spn="CHINA MOBILE"/>  
  3. <spnOverride numeric="46001" spn="CHN-UNICOM"/>  
  4. <spnOverride numeric="46002" spn="CHINA MOBILE"/>  
  5. <spnOverride numeric="46003" spn="CHINA TELECOM"/>  
  6. <spnOverride numeric="46007" spn="CHINA MOBILE"/>  
  7. <spnOverride numeric="46008" spn="CHINA MOBILE"/>  
  8. <spnOverride numeric="46009" spn="CHN-UNICOM"/>  
        这些项是针对中国区的SPN,我们看到,每一项都包含两个元素,PLMN和SPN,我们可以用当前SIM所驻留的网络的PLMN号码来匹配查找当前的SPN字串
        下面我们来看如何将该文件读取到SPN中。
        SIMRecords对象在初始化时,在构造方法里面创建了一个SpnOverride对象:
[java] view plain copy
  1. @SIMRecords.java  
  2. public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {  
  3.     super(app, c, ci);  
  4.     mSpnOverride = new SpnOverride();  
  5. }  
        这里的SpnOverride作用就是读取系统预置的SPN列表,我们先来看其初始化流程:
[java] view plain copy
  1. public SpnOverride () {  
  2.     mCarrierSpnMap = new HashMap<String, String>();  
  3.     loadSpnOverrides();  
  4. }  
  5. private void loadSpnOverrides() {  
  6.     FileReader spnReader;  
  7.     //PARTNER_SPN_OVERRIDE_PATH ="etc/spn-conf.xml"  
  8.     final File spnFile = new File(Environment.getRootDirectory(), PARTNER_SPN_OVERRIDE_PATH);  
  9.   
  10.   
  11.     try {  
  12.         spnReader = new FileReader(spnFile);  
  13.     } catch (FileNotFoundException e) {  
  14.         Rlog.w(LOG_TAG, "Can not open " + Environment.getRootDirectory() + "/" + PARTNER_SPN_OVERRIDE_PATH);  
  15.         return;  
  16.     }  
  17.   
  18.   
  19.     try {  
  20.         //解析spn-conf.xml文件  
  21.         XmlPullParser parser = Xml.newPullParser();  
  22.         parser.setInput(spnReader);  
  23.         XmlUtils.beginDocument(parser, "spnOverrides");  
  24.         while (true) {  
  25.             XmlUtils.nextElement(parser);  
  26.             String name = parser.getName();  
  27.             if (!"spnOverride".equals(name)) {  
  28.                 break;  
  29.             }  
  30.   
  31.   
  32.             String numeric = parser.getAttributeValue(null"numeric");  
  33.             String data    = parser.getAttributeValue(null"spn");  
  34.             mCarrierSpnMap.put(numeric, data);  
  35.         }  
  36.         spnReader.close();  
  37.     } catch (XmlPullParserException e) {  
  38.         Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e);  
  39.     } catch (IOException e) {  
  40.         Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e);  
  41.     }  
  42. }  
        这个对象在初始化时就将"etc/spn-conf.xml"文件加载进来,并进行XML解析,把每一项存入mCarrierSpnMap的HashMap中。
        然后该对象提供了两个查询SPN的方法:
[java] view plain copy
  1. public boolean containsCarrier(String carrier) {  
  2.     //查询是否包含某个运营商的SPN  
  3.     return mCarrierSpnMap.containsKey(carrier);  
  4. }  
  5. public String getSpn(String carrier) {  
  6.     //获取某个运营商的SPN  
  7.     return mCarrierSpnMap.get(carrier);  
  8. }  
        然后我们接着上一节的介绍,当SIM中的SPN被读取之后,就会在SIMRecords中的handleMessage()消息中收到EVENT_GET_SPN_DONE的消息:
[java] view plain copy
  1. public void handleMessage(Message msg) {  
  2.     try {  
  3.         switch (msg.what) {  
  4.             case EVENT_GET_SPN_DONE:  
  5.                 isRecordLoadResponse = true;  
  6.                 ar = (AsyncResult) msg.obj;  
  7.                 getSpnFsm(false, ar);  
  8.                 break;  
  9.         }  
  10.     } catch (RuntimeException exc) {  
  11.     } finally {  
  12.         if (isRecordLoadResponse) {  
  13.             onRecordLoaded();  
  14.         }  
  15.     }  
  16. }  
        前面我们分析过,在getSpnFsm()中将会对Modem的返回结果进行解析,如果读取成功,就会将SPN字串保存起来,现在我们继续来看如果保存之后,将会进入finally的处理当中,也就是onRecordLoaded()方法:
[java] view plain copy
  1. protected void onRecordLoaded() {  
  2.     mRecordsToLoad -= 1;  
  3.     if (mRecordsToLoad == 0 && mRecordsRequested == true) {  
  4.         onAllRecordsLoaded();  
  5.     } else if (mRecordsToLoad < 0) {  
  6.         mRecordsToLoad = 0;  
  7.     }  
  8. }  
        这里的mRecordsToLoad表明当前需要读取的SIM信息条数(SIMRecords初始化过程中需要读取大量的SIM数据),每向Modem发送一条读取的指令,该计数就会加1,当一条记录读取完毕后该计数就会减1,当所有记录全部读取完毕,就会进入onAllRecordsLoaded()的处理:
[java] view plain copy
  1. protected void onAllRecordsLoaded() {  
  2.     setLocaleFromUsim();  
  3.     if (mParentApp.getState() == AppState.APPSTATE_PIN || mParentApp.getState() == AppState.APPSTATE_PUK) {  
  4.         mRecordsRequested = false;  
  5.         return ;  
  6.     }  
  7.   
  8.   
  9.     String operator = getOperatorNumeric();  
  10.     if (!TextUtils.isEmpty(operator)) {  
  11.         //保存当前的PLMN  
  12.         setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, operator);  
  13.         final SubscriptionController subController = SubscriptionController.getInstance();  
  14.         subController.setMccMnc(operator, subController.getDefaultSmsSubId());  
  15.     } else {  
  16.     }  
  17.   
  18.   
  19.     if (!TextUtils.isEmpty(mImsi)) {  
  20.         setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, MccTable.countryCodeForMcc(Integer.parseInt(mImsi.substring(0,3))));  
  21.     } else {  
  22.         log("onAllRecordsLoaded empty imsi skipping setting mcc");  
  23.     }  
  24.     //设置当前的语音信箱  
  25.     setVoiceMailByCountry(operator);  
  26.     //读取配置文件中的SPN  
  27.     setSpnFromConfig(operator);  
  28.     //将通知发送出来  
  29.     mRecordsLoadedRegistrants.notifyRegistrants( new AsyncResult(nullnullnull));  
  30. }  
        我们来看setSpnFromConfig()的方法:
[java] view plain copy
  1. private void setSpnFromConfig(String carrier) {  
  2.     if (mSpnOverride.containsCarrier(carrier)) {  
  3.         //用配置文件中的SPN来作为最终的SPN  
  4.         setServiceProviderName(mSpnOverride.getSpn(carrier));  
  5.         SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());  
  6.     }  
  7. }  
        这里就用上mSpnOverride这个对象了,前面我们分析过,他的作用就是把spn-conf.xml文件中的SPN信息解析出来,保存到HashMap中,现在我们需要根据当前的MCC/MNC去该HashMap中寻找匹配的SPN值,并把其作为最终的SPN保存起来。
        以上就是整个SPN的读取流程,下面用一张逻辑图来展示上述过程:
        
原创粉丝点击