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

本文详细解析了从SIM卡及配置文件读取SPN(ServiceProviderName)的过程,包括读取SIM卡上的EF_SPN、EF_SPN_CPHS、EF_SPN_SHORT_CPHS分区,以及从spn-conf.xml配置文件中读取预置SPN,实现运营商名称的获取与覆盖。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://blog.youkuaiyun.com/u010961631/article/details/50682373

    上一节介绍了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的过程:
        @SIMRecords.java
        protected void fetchSimRecords() {
            getSpnFsm(true, null);
        }

 

请注意,此时的getSpnFsm()的start参数为true,而且mSpnState为初始化值:GetSpnFsmState.IDLE

        private void getSpnFsm(boolean start, AsyncResult ar) {
            byte[] data;
            if (start) {
                if(mSpnState == GetSpnFsmState.READ_SPN_3GPP ||
                        mSpnState == GetSpnFsmState.READ_SPN_CPHS ||
                        mSpnState == GetSpnFsmState.READ_SPN_SHORT_CPHS ||
                        mSpnState == GetSpnFsmState.INIT) {
                    mSpnState = GetSpnFsmState.INIT;
                    return;
                } else {
                    //mSpnState默认为IDLE,然后修改为INIT
                    mSpnState = GetSpnFsmState.INIT;
                }
            }
            switch(mSpnState){
                case INIT:
                    //初始化SPN
                    setServiceProviderName(null);
                    //从SIM的EF_SPN读取SPN
                    mFh.loadEFTransparent(EF_SPN, obtainMessage(EVENT_GET_SPN_DONE));
                    mRecordsToLoad++;
                    //mSpnState修改为READ_SPN_3GPP
                    mSpnState = GetSpnFsmState.READ_SPN_3GPP;
                    break;
                case READ_SPN_3GPP:
                case READ_SPN_CPHS:
                case READ_SPN_SHORT_CPHS:
                default:
                    mSpnState = GetSpnFsmState.IDLE;
            }
        }
在上面的过程中,将会从EF_SPN中读取当前SPN,并且将mSpnState置为READ_SPN_3GPP。
 当读取完毕后,在handleMessage()中读取反馈:

        public void handleMessage(Message msg) {
            try {
                switch (msg.what) {
                    case EVENT_GET_SPN_DONE:
                        isRecordLoadResponse = true;
                        ar = (AsyncResult) msg.obj;
                        getSpnFsm(false, ar);
                        break;
                }
            } catch (RuntimeException exc) {
            } finally {
                if (isRecordLoadResponse) {
                    onRecordLoaded();
                }
            }
        }
然后再次进入getSpnFsm()中处理,此时的mSpnState状态为READ_SPN_3GPP,而start为false,所以直接进入switch语句判断:

        private void getSpnFsm(boolean start, AsyncResult ar) {
            byte[] data;
            if (start) {
            }
 
 
            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));
                        //将当前的SPN写入系统属性
                        setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());
                        mSpnState = GetSpnFsmState.IDLE;
                    } else {
                        mFh.loadEFTransparent( EF_SPN_CPHS, obtainMessage(EVENT_GET_SPN_DONE));
                        mRecordsToLoad++;
                        mSpnState = GetSpnFsmState.READ_SPN_CPHS;
                        mSpnDisplayCondition = -1;
                    }
                    break;
                case READ_SPN_CPHS:
                case READ_SPN_SHORT_CPHS:
                default:
                    mSpnState = GetSpnFsmState.IDLE;
            }
        }
 

如果此时从SIM读取的SPN不为空,则会通过adnStringFieldToString()将数据转换为字串后,通过setServiceProviderName()保存,同时也要存储在PROPERTY_ICC_OPERATOR_ALPHA的系统属性中,并且重置mSpnState为IDLE;
        另外,这里的mSpnDisplayCondition是SPN的第一位数据,在显示SPN时用来判定显示规则。
        如果SIM中的SPN为空,则再去读取SIM中的EF_SPN_CPHS分区,和上面流程相同,该请求会通过handleMessage()再次发送给getSpnFsm()内部,只不过这次进入READ_SPN_CPHS分支处理:

        private void getSpnFsm(boolean start, AsyncResult ar) {
            byte[] data;
            if (start) {
            }
 
 
            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));
                        setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, 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:
                default:
                    mSpnState = GetSpnFsmState.IDLE;
            }
        }


与上面读取EF_SPN类似,如果读取成功就保存,否则再去读取EF_SPN_SHORT_CPHS,而读取后的结果一样在getSpnFsm()中处理

        private void getSpnFsm(boolean start, AsyncResult ar) {
            byte[] data;
            if (start) {
            }
 
 
            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));
                        setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());
                    }else {
                        if (DBG) log("No SPN loaded in either CHPS or 3GPP");
                    }
                    mSpnState = GetSpnFsmState.IDLE;
                default:
                    mSpnState = GetSpnFsmState.IDLE;
            }
        }

 遇上面流程类似,读取成功就保存,不成功就不再处理。

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

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

Android原始代码中,无论在SIM的三个文件分区有没有查询到SPN,系统都会继续尝试从配置文件中读取SPN,如果读取成功,则覆盖刚才SIM中读取的值,如果配置文件读取失败,就使用上面的SIM中的SPN。
        开发者可以将所有预置的SPN存入spn-conf.xml这个文件中(不同平台该文件的存储位置不同),在编译时候就会将其拷贝到out的system\etc\目录中,以供系统读取。我们来挑选几条该文件中的项来看一下

        @spn-conf.xml
        <spnOverride numeric="46000" spn="CHINA MOBILE"/>
        <spnOverride numeric="46001" spn="CHN-UNICOM"/>
        <spnOverride numeric="46002" spn="CHINA MOBILE"/>
        <spnOverride numeric="46003" spn="CHINA TELECOM"/>
        <spnOverride numeric="46007" spn="CHINA MOBILE"/>
        <spnOverride numeric="46008" spn="CHINA MOBILE"/>
        <spnOverride numeric="46009" spn="CHN-UNICOM"/>

  这些项是针对中国区的SPN,我们看到,每一项都包含两个元素,PLMN和SPN,我们可以用当前SIM所驻留的网络的PLMN号码来匹配查找当前的SPN字串
下面我们来看如何将该文件读取到SPN中。
  SIMRecords对象在初始化时,在构造方法里面创建了一个SpnOverride对象:

        @SIMRecords.java
        public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
            super(app, c, ci);
            mSpnOverride = new SpnOverride();
        }


      这里的SpnOverride作用就是读取系统预置的SPN列表,我们先来看其初始化流程:

         public SpnOverride () {
            mCarrierSpnMap = new HashMap<String, String>();
            loadSpnOverrides();
        }
        private void loadSpnOverrides() {
            FileReader spnReader;
            //PARTNER_SPN_OVERRIDE_PATH ="etc/spn-conf.xml"
            final File spnFile = new File(Environment.getRootDirectory(), PARTNER_SPN_OVERRIDE_PATH);
 
 
            try {
                spnReader = new FileReader(spnFile);
            } catch (FileNotFoundException e) {
                Rlog.w(LOG_TAG, "Can not open " + Environment.getRootDirectory() + "/" + PARTNER_SPN_OVERRIDE_PATH);
                return;
            }
 
 
            try {
                //解析spn-conf.xml文件
                XmlPullParser parser = Xml.newPullParser();
                parser.setInput(spnReader);
                XmlUtils.beginDocument(parser, "spnOverrides");
                while (true) {
                    XmlUtils.nextElement(parser);
                    String name = parser.getName();
                    if (!"spnOverride".equals(name)) {
                        break;
                    }
 
 
                    String numeric = parser.getAttributeValue(null, "numeric");
                    String data    = parser.getAttributeValue(null, "spn");
                    mCarrierSpnMap.put(numeric, data);
                }
                spnReader.close();
            } catch (XmlPullParserException e) {
                Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e);
            } catch (IOException e) {
                Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e);
            }
        }

这个对象在初始化时就将"etc/spn-conf.xml"文件加载进来,并进行XML解析,把每一项存入mCarrierSpnMap的HashMap中。然后该对象提供了两个查询SPN的方法:

        public boolean containsCarrier(String carrier) {
            //查询是否包含某个运营商的SPN
            return mCarrierSpnMap.containsKey(carrier);
        }
        public String getSpn(String carrier) {
            //获取某个运营商的SPN
            return mCarrierSpnMap.get(carrier);
        }


   然后我们接着上一节的介绍,当SIM中的SPN被读取之后,就会在SIMRecords中的handleMessage()消息中收到EVENT_GET_SPN_DONE的消息:

        public void handleMessage(Message msg) {
            try {
                switch (msg.what) {
                    case EVENT_GET_SPN_DONE:
                        isRecordLoadResponse = true;
                        ar = (AsyncResult) msg.obj;
                        getSpnFsm(false, ar);
                        break;
                }
            } catch (RuntimeException exc) {
            } finally {
                if (isRecordLoadResponse) {
                    onRecordLoaded();
                }
            }
        }
        public void handleMessage(Message msg) {
            try {
                switch (msg.what) {
                    case EVENT_GET_SPN_DONE:
                        isRecordLoadResponse = true;
                        ar = (AsyncResult) msg.obj;
                        getSpnFsm(false, ar);
                        break;
                }
            } catch (RuntimeException exc) {
            } finally {
                if (isRecordLoadResponse) {
                    onRecordLoaded();
                }
            }
        }

前面我们分析过,在getSpnFsm()中将会对Modem的返回结果进行解析,如果读取成功,就会将SPN字串保存起来,现在我们继续来看如果保存之后,将会进入finally的处理当中,也就是onRecordLoaded()方法:

        protected void onRecordLoaded() {
            mRecordsToLoad -= 1;
            if (mRecordsToLoad == 0 && mRecordsRequested == true) {
                onAllRecordsLoaded();
            } else if (mRecordsToLoad < 0) {
                mRecordsToLoad = 0;
            }
        }
 这里的mRecordsToLoad表明当前需要读取的SIM信息条数(SIMRecords初始化过程中需要读取大量的SIM数据),每向Modem发送一条读取的指令,该计数就会加1,当一条记录读取完毕后该计数就会减1,当所有记录全部读取完毕,就会进入onAllRecordsLoaded()的处理:

        protected void onAllRecordsLoaded() {
            setLocaleFromUsim();
            if (mParentApp.getState() == AppState.APPSTATE_PIN || mParentApp.getState() == AppState.APPSTATE_PUK) {
                mRecordsRequested = false;
                return ;
            }
 
 
            String operator = getOperatorNumeric();
            if (!TextUtils.isEmpty(operator)) {
                //保存当前的PLMN
                setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, operator);
                final SubscriptionController subController = SubscriptionController.getInstance();
                subController.setMccMnc(operator, subController.getDefaultSmsSubId());
            } else {
            }
 
 
            if (!TextUtils.isEmpty(mImsi)) {
                setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, MccTable.countryCodeForMcc(Integer.parseInt(mImsi.substring(0,3))));
            } else {
                log("onAllRecordsLoaded empty imsi skipping setting mcc");
            }
            //设置当前的语音信箱
            setVoiceMailByCountry(operator);
            //读取配置文件中的SPN
            setSpnFromConfig(operator);
            //将通知发送出来
            mRecordsLoadedRegistrants.notifyRegistrants( new AsyncResult(null, null, null));
        }
 我们来看setSpnFromConfig()的方法:

        private void setSpnFromConfig(String carrier) {
            if (mSpnOverride.containsCarrier(carrier)) {
                //用配置文件中的SPN来作为最终的SPN
                setServiceProviderName(mSpnOverride.getSpn(carrier));
                SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());
            }
        }


这里就用上mSpnOverride这个对象了,前面我们分析过,他的作用就是把spn-conf.xml文件中的SPN信息解析出来,保存到HashMap中,现在我们需要根据当前的MCC/MNC去该HashMap中寻找匹配的SPN值,并把其作为最终的SPN保存起来。
  以上就是整个SPN的读取流程,下面用一张逻辑图来展示上述过程:

 

<think>我们之前讨论的GID和SPN是在操作系统和Kerberos认证环境中的概念。但用户现在的问题是关于SIM卡中虚拟运营商的GID和SPN,这属于移动通信领域的概念。因此,我们需要重新定义这两个术语在移动通信上下文中的含义: 在SIM卡和移动网络领域: - **GID**(Group Identifier):在SIM卡规范中,GID是组标识符,用于标识一组SIM卡(例如,同一虚拟运营商的一组卡)。它通常由两个字节组成(十六进制表示),存储在SIM卡的文件系统中。 - **SPN**(Service Provider Name):服务提供商名称,即显示在手机屏幕上的运营商名称(如“中国移动”、“中国联通”或虚拟运营商的品牌名称)。它存储在SIM卡的特定文件中(通常是EF_SPN)。 因此,用户的问题是如何查询SIM卡中虚拟运营商的GID和SPN信息。 ### 查询方法: #### 1. **通过手机设置界面查看SPN** - 大部分手机可以在设置中直接查看运营商名称SPN): - 进入“设置”>“关于手机”>“SIM卡状态”或类似菜单。 - 在部分安卓手机上,路径可能是“设置”>“网络和互联网”>“移动网络”>“高级”>“运营商设置”。 - 注意:虚拟运营商SPN通常显示为其品牌名称(如“京东通信”、“阿里通信”)。 #### 2. **使用AT命令查询(需要专业工具或APP)** - 通过发送AT命令到调制解调器(Modem)可以读取SIM卡文件。但需要设备支持(如root过的安卓手机或专用读卡器): - 步骤1:连接手机到电脑(开启USB调试模式),或使用串口工具。 - 步骤2:使用终端软件(如PuTTY)发送AT命令。 - 常用命令: - 查询SPN:发送 `AT+CRSM=176,12258,0,0,15`(12258对应EF_SPN文件)[^1]。 - 查询GID:发送 `AT+CRSM=176,12259,0,0,15`(12259对应EF_GID文件)[^1]。 - 注意:命令格式可能因设备而异,需参考3GPP TS 27.007规范。 #### 3. **使用专用APP读取** - 安卓应用(如“SIM Card Info”“SIM Tool”等)可读取SIM卡文件: 1. 安装APP并授予读取SIM卡权限。 2. 在APP内查找“EF_SPN”或“EF_GID”字段。 - 示例:在“SIM Card Info”中,SPN显示在“Service Provider Name”条目下。 #### 4. **通过SIM卡读卡器读取(最直接)** - 使用PC/SC读卡器和软件(如PySIM): - 步骤1:将SIM卡插入读卡器连接电脑。 - 步骤2:运行软件读取文件: - EF_SPN路径:`7F20` > `6F46`(SPN文件)。 - EF_GID路径:`7F20` > `6F3E`(GID文件)[^1]。 - 工具推荐:`pySim-read`(开源工具)或`CardReader`(商业软件)。 ### 注意事项: - **虚拟运营商信息**:虚拟运营商(MVNO)通常租用基础运营商(如中国移动)的网络,其SPN和GID由基础运营商分配。 - **数据格式**:SPN和GID以二进制格式存储,读取后需解码(如SPN可能是ASCII或UCS2编码)。 ### 示例输出: - **SPN**:`"China Mobile"`(中国移动)或`"Virtual Carrier"`(虚拟运营商品牌)。 - **GID**:十六进制值如`01 02`(标识组别)。 ### 总结 查询SIM卡中的GID和SPN需要特定工具(APP/读卡器)或技术命令。普通用户建议使用手机设置或APP查看SPN;开发人员可通过AT命令或读卡器获取完整数据[^1]。 §§相关问题§§ 1. 如何区分SIM卡中的基础运营商和虚拟运营商? 2. 修改SIM卡中的SPN显示是否合法? 3. 虚拟运营商的GID分配规则是什么? [^1]: 参考3GPP TS 31.102(SIM卡文件系统规范)和移动设备AT命令手册。</think>### SIM卡中虚拟运营商的GID和SPN查询指南 在移动通信领域,SIM卡中的 **GID(Group Identifier)** 和 **SPN(Service Provider Name)** 是标识运营商的关键参数: - **GID**:2字节的组标识符,用于区分SIM卡所属的虚拟运营商组别(如物联网卡分组) - **SPN**:服务提供商名称,即手机上显示运营商名称(如"中国移动"或虚拟运营商品牌名) 以下是查询方法(需设备支持): --- ### 一、通过手机系统查询 #### 1. **Android设备** - **SPN查询**: 进入 `设置 > 关于手机 > SIM卡状态`,直接查看"运营商名称"(即SPN) - **GID查询(需开发者模式)**: 1. 启用开发者选项(连续点击"设置>关于手机>版本号"7次) 2. 使用ADB命令: ```bash adb shell service call iphonesubinfo 13 ``` 输出中的 `intValue` 字段即为GID(十六进制)[^1] #### 2. **iOS设备** - **SPN查询**: `设置 > 蜂窝网络 > SIM卡` 查看运营商名称 - **GID查询限制**: iOS系统封闭,需通过运营商后台或专业读卡器获取 --- ### 二、通过专业工具读取SIM文件 #### 1. **使用SIM卡读卡器+软件** 工具推荐: - 硬件:PC/SC兼容读卡器(如ACR38U) - 软件:`pySim-read`(开源)或 `CardReader`(商业) **操作步骤**: 1. 将SIM卡插入读卡器 2. 读取关键文件: - **SPN**:文件路径 `7F20` > `6F46` (EF_SPN) - **GID**:文件路径 `7F20` > `6F3E` (EF_GID) 3. 解码二进制数据: - SPN:ASCII或UCS2编码的字符串 - GID:2字节十六进制值(如 `01 02`) #### 2. **AT指令查询(需root安卓或模块化设备)** 发送GSM 07.07标准指令: ```at AT+CRSM=176,12258,0,0,15 // 读取EF_SPN文件 AT+CRSM=176,12259,0,0,15 // 读取EF_GID文件 ``` 输出示例(十六进制): ``` +CRSM: 144,0,"FFFE 0043 004E 004D 0043" // CNMC(中国移动)的SPN编码 ``` --- ### 三、虚拟运营商特殊说明 1. **GID作用**: - 标识MVNO(虚拟运营商)的分组,例如: - `01 01`: 物联网设备组 - `01 02`: 企业用户组 - 基础运营商(如中国移动)通过GID控制虚拟运营商的网络权限 2. **SPN显示规则**: - 虚拟运营商SPN通常继承自基础运营商(如"中国移动-京东通信") - 可通过修改EF_SPN文件自定义显示(需SIM卡支持OTA更新) --- ### 四、注意事项 1. **数据安全**: - 直接读取SIM文件可能触发安全锁(PIN/PUK验证) 2. **兼容性**: - 老式SIM卡(2G)可能无GID字段 3. **虚拟运营商限制**: - MVNO的GID/SPN通常由基础运营商分配,无法自主修改 --- ### 总结查询路径 | 参数 | 普通用户 | 技术人员 | |------|-------------------|-----------------------| | SPN | 手机设置直接查看 | 读卡器读取EF_SPN文件 | | GID | 联系运营商获取 | AT命令或读卡器读取EF_GID |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值