Android实现将国内卡模拟成任意卡

问题背景

在Android项目的开发过程中,我们常常遇到一些运营商的需求,例如运营商的CarrierConfig需求、APN需求等,我们通常需要使用写卡工具对白卡写参数来验证这些需求,但写卡工具和白卡比较稀缺,可能资源经常被占用,但普通的国内卡或者无效卡我们基本上都有,若可以通过国内卡模拟国外的一些运营商,那此问题就得到迎刃而解。

问题实现

其实在Android源码中,已有这种操作,可实现将国内卡模拟成其他运营商卡,不过此模拟效果仅在AP侧生效,若需要验证CP侧的需求,则仍然需要白卡才可以实现。此处需要使用userdebug软件才可以实现模拟卡的操作。

操作步骤

1、新建carrier_test_conf.xml文件

<carrierTestOverrides>
 
       <carrierTestOverride key="isInTestMode" value="true"/>
 
       <carrierTestOverride key="mccmnc" value="722310" />
 
       <carrierTestOverride key="gid1" value="ffffffffffffffff"/>
 
       <carrierTestOverride key="gid2" value="ffffffffffffffff"/>
 
       <carrierTestOverride key="imsi" value="722310123456789"/>
 
       <carrierTestOverride key="spn" value="Claro AR"/>
 
       <carrierTestOverride key="pnn" value="Claro AR network"/>
 
       <carrierTestOverride key="iccid" value="123456789012345678901"/>
 
</carrierTestOverrides>

其中isInTestMode表示当前是否为测试模式,需配置为true才能实现模拟。

其他的如mccmnc,imsi,iccid等均可以按照个人的要求进行配置。

2、对设备进行root

xjx@xjx-ThinkStation-P340:~$ adb root
adbd is already running as root

 3、重启设备phone进程

使用adb shell ps | grep phone获取到phone进程的pid

xjx@xjx-ThinkStation-P340:~$ adb shell ps | grep phone
radio         1274   473 1123372 107944 do_epoll_wait       0 S com.android.phone

此处获取到com.android.phone进程的pid为1274,使用adb shell kill 1274

xjx@xjx-ThinkStation-P340:~$ adb shell kill 1274

 4、carrier的配置生效

此时我们会发现我们的SIM卡会重新读取,通过查看CarrierConfig配置可以发现生成对应模拟运营商的CarrierConfig配置文件。

xjx@xjx-ThinkStation-P340:~$ adb shell
NVA:/ # cd data/user_de/0/com.android.phone/files/                                                                
NVA:/data/user_de/0/com.android.phone/files # ls
carrier_test_conf.xml
carrierconfig-com.android.carrierconfig-123456789012345678901-1338.xml
carrierconfig-com.android.carrierconfig-89860121801668091974-1436.xml
carrierconfig-com.android.carrierconfig-sub-89860121801668091974-1338.xml
carrierconfig-com.android.carrierconfig-sub-89860121801668091974-1436.xml
carrierconfig-com.android.carrierconfig-vendor---1.xml
persist_atoms.pb
NVA:/data/user_de/0/com.android.phone/files #

 其中可以看到carrierconfig-xxx-1436.xml是我们原始插入的卡,即我们的基础卡是联通卡,后面新生成的carrierconfig-xxx-1338.xml是对应的模拟卡的CarrierConfig文件,对应的1338对应的是722310运营商对应的CarrierId。

 同时查看其APN如上,也是对应运营商722310的APN参数,此时我们已经模拟成功。

5、还原模拟卡的配置

若我们不需要模拟卡,需将原始的联通卡还原,则我们需删除push的文件和新生成的CarrierConfig文件并重启phone进程即可。

xjx@xjx-ThinkStation-P340:~$ adb shell
NVA:/ # cd data/user_de/0/com.android.phone/files
NVA:/data/user_de/0/com.android.phone/files # ls
carrier_test_conf.xml                                                      carrierconfig-com.android.carrierconfig-sub-89860121801668091974-1436.xml
carrierconfig-com.android.carrierconfig-123456789012345678901-1338.xml     carrierconfig-com.android.carrierconfig-vendor---1.xml
carrierconfig-com.android.carrierconfig-89860121801668091974-1436.xml      persist_atoms.pb
carrierconfig-com.android.carrierconfig-sub-89860121801668091974-1338.xml
NVA:/data/user_de/0/com.android.phone/files # rm -rf carrier_test_conf.xml
NVA:/data/user_de/0/com.android.phone/files # rm -rf carrierconfig-com.android.carrierconfig-123456789012345678901-1338.xml
NVA:/data/user_de/0/com.android.phone/files # rm -rf carrierconfig-com.android.carrierconfig-sub-89860121801668091974-1338.xml
NVA:/data/user_de/0/com.android.phone/files # ls
carrierconfig-com.android.carrierconfig-89860121801668091974-1436.xml      carrierconfig-com.android.carrierconfig-vendor---1.xml
carrierconfig-com.android.carrierconfig-sub-89860121801668091974-1436.xml  persist_atoms.pb
NVA:/data/user_de/0/com.android.phone/files # 

将其删除后,同步骤3中的操作即可重新加载SIM卡,将SIM卡还原到原始配置。

实现原理

alps/frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/CarrierTestOverride.java

在Android源码中CarrierTestOverride的类可以针对运营商进行模拟,具体代码如下。

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony.uicc;

import android.os.Environment;
import android.util.Xml;

import com.android.internal.telephony.util.XmlUtils;
import com.android.telephony.Rlog;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;

/**
 * Provide a machanism to override MVNO paramteres under CarrierConfig through a config file.
 */
public class CarrierTestOverride {
    static final String LOG_TAG = "CarrierTestOverride";

    /**
     * Config file that can be created and adb-pushed by tester/developer
     *
     * Sample xml:
     * <carrierTestOverrides>
       <carrierTestOverride key="isInTestMode" value="true"/>
       <carrierTestOverride key="mccmnc" value="310010" />
       <carrierTestOverride key="gid1" value="bae0000000000000"/>
       <carrierTestOverride key="gid2" value="ffffffffffffffff"/>
       <carrierTestOverride key="imsi" value="310010123456789"/>
       <carrierTestOverride key="spn" value="Verizon"/>
       <carrierTestOverride key="pnn" value="Verizon network"/>
       <carrierTestOverride key="iccid" value="123456789012345678901"/>
       </carrierTestOverrides>
     */
    static final String DATA_CARRIER_TEST_OVERRIDE_PATH =
            "/user_de/0/com.android.phone/files/carrier_test_conf.xml";
    static final String CARRIER_TEST_XML_HEADER = "carrierTestOverrides";
    static final String CARRIER_TEST_XML_SUBHEADER = "carrierTestOverride";
    static final String CARRIER_TEST_XML_ITEM_KEY = "key";
    static final String CARRIER_TEST_XML_ITEM_VALUE = "value";
    static final String CARRIER_TEST_XML_ITEM_KEY_STRING_ISINTESTMODE = "isInTestMode";
    static final String CARRIER_TEST_XML_ITEM_KEY_STRING_MCCMNC = "mccmnc";
    static final String CARRIER_TEST_XML_ITEM_KEY_STRING_GID1 = "gid1";
    static final String CARRIER_TEST_XML_ITEM_KEY_STRING_GID2 = "gid2";
    static final String CARRIER_TEST_XML_ITEM_KEY_STRING_IMSI = "imsi";
    static final String CARRIER_TEST_XML_ITEM_KEY_STRING_SPN = "spn";
    static final String CARRIER_TEST_XML_ITEM_KEY_STRING_PNN = "pnn";
    static final String CARRIER_TEST_XML_ITEM_KEY_STRING_ICCID = "iccid";

    private HashMap<String, String> mCarrierTestParamMap;

    CarrierTestOverride() {
        mCarrierTestParamMap = new HashMap<String, String>();
        loadCarrierTestOverrides();
    }

    boolean isInTestMode() {
        return mCarrierTestParamMap.containsKey(CARRIER_TEST_XML_ITEM_KEY_STRING_ISINTESTMODE)
                && mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_ISINTESTMODE)
                .equals("true");
    }

    String getFakeSpn() {
        try {
            String spn = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_SPN);
            Rlog.d(LOG_TAG, "reading spn from CarrierTestConfig file: " + spn);
            return spn;
        } catch (NullPointerException e) {
            Rlog.w(LOG_TAG, "No spn in CarrierTestConfig file ");
            return null;
        }
    }

    String getFakeIMSI() {
        try {
            String imsi = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_IMSI);
            Rlog.d(LOG_TAG, "reading imsi from CarrierTestConfig file: " + imsi);
            return imsi;
        } catch (NullPointerException e) {
            Rlog.w(LOG_TAG, "No imsi in CarrierTestConfig file ");
            return null;
        }
    }

    String getFakeGid1() {
        try {
            String gid1 = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_GID1);
            Rlog.d(LOG_TAG, "reading gid1 from CarrierTestConfig file: " + gid1);
            return gid1;
        } catch (NullPointerException e) {
            Rlog.w(LOG_TAG, "No gid1 in CarrierTestConfig file ");
            return null;
        }
    }

    String getFakeGid2() {
        try {
            String gid2 = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_GID2);
            Rlog.d(LOG_TAG, "reading gid2 from CarrierTestConfig file: " + gid2);
            return gid2;
        } catch (NullPointerException e) {
            Rlog.w(LOG_TAG, "No gid2 in CarrierTestConfig file ");
            return null;
        }
    }

    String getFakePnnHomeName() {
        try {
            String pnn = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_PNN);
            Rlog.d(LOG_TAG, "reading pnn from CarrierTestConfig file: " + pnn);
            return pnn;
        } catch (NullPointerException e) {
            Rlog.w(LOG_TAG, "No pnn in CarrierTestConfig file ");
            return null;
        }
    }

    String getFakeIccid() {
        try {
            String iccid = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_ICCID);
            Rlog.d(LOG_TAG, "reading iccid from CarrierTestConfig file: " + iccid);
            return iccid;
        } catch (NullPointerException e) {
            Rlog.w(LOG_TAG, "No iccid in CarrierTestConfig file ");
            return null;
        }
    }

    String getFakeMccMnc() {
        try {
            String mccmnc = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_MCCMNC);
            Rlog.d(LOG_TAG, "reading mccmnc from CarrierTestConfig file: " + mccmnc);
            return mccmnc;
        } catch (NullPointerException e) {
            Rlog.w(LOG_TAG, "No mccmnc in CarrierTestConfig file ");
            return null;
        }
    }

    void override(String mccmnc, String imsi, String iccid, String gid1, String gid2, String pnn,
            String spn) {
        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_ISINTESTMODE, "true");
        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_MCCMNC, mccmnc);
        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_IMSI, imsi);
        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_ICCID, iccid);
        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_GID1, gid1);
        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_GID2, gid2);
        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_PNN, pnn);
        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_SPN, spn);
    }

    private void loadCarrierTestOverrides() {

        FileReader carrierTestConfigReader;

        File carrierTestConfigFile = new File(Environment.getDataDirectory(),
                DATA_CARRIER_TEST_OVERRIDE_PATH);

        try {
            carrierTestConfigReader = new FileReader(carrierTestConfigFile);
            Rlog.d(LOG_TAG, "CarrierTestConfig file Modified Timestamp: "
                    + carrierTestConfigFile.lastModified());
        } catch (FileNotFoundException e) {
            Rlog.w(LOG_TAG, "Can not open " + carrierTestConfigFile.getAbsolutePath());
            return;
        }

        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(carrierTestConfigReader);

            XmlUtils.beginDocument(parser, CARRIER_TEST_XML_HEADER);

            while (true) {
                XmlUtils.nextElement(parser);

                String name = parser.getName();
                if (!CARRIER_TEST_XML_SUBHEADER.equals(name)) {
                    break;
                }

                String key = parser.getAttributeValue(null, CARRIER_TEST_XML_ITEM_KEY);
                String value = parser.getAttributeValue(null, CARRIER_TEST_XML_ITEM_VALUE);

                Rlog.d(LOG_TAG,
                        "extracting key-values from CarrierTestConfig file: " + key + "|" + value);
                mCarrierTestParamMap.put(key, value);
            }
            carrierTestConfigReader.close();
        } catch (XmlPullParserException e) {
            Rlog.w(LOG_TAG, "Exception in carrier_test_conf parser " + e);
        } catch (IOException e) {
            Rlog.w(LOG_TAG, "Exception in carrier_test_conf parser " + e);
        }
    }
}

其中loadCarrierTestOverrides方法会加载相关的override的文件,即DATA_CARRIER_TEST_OVERRIDE_PATH

    static final String DATA_CARRIER_TEST_OVERRIDE_PATH =
            "/user_de/0/com.android.phone/files/carrier_test_conf.xml";

在上述的模拟步骤中的文件名来源于此,且其提供了该文件的样例代码。

其中可以看到一些常用的获取gid1以及mccmnc的方法,在系统中会先获取TestMode再决定是否要获取FakeXXX的内容,即我们模拟卡的信息。

    String getFakeGid1() {
        try {
            String gid1 = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_GID1);
            Rlog.d(LOG_TAG, "reading gid1 from CarrierTestConfig file: " + gid1);
            return gid1;
        } catch (NullPointerException e) {
            Rlog.w(LOG_TAG, "No gid1 in CarrierTestConfig file ");
            return null;
        }
    }

    String getFakeMccMnc() {
        try {
            String mccmnc = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_MCCMNC);
            Rlog.d(LOG_TAG, "reading mccmnc from CarrierTestConfig file: " + mccmnc);
            return mccmnc;
        } catch (NullPointerException e) {
            Rlog.w(LOG_TAG, "No mccmnc in CarrierTestConfig file ");
            return null;
        }
    }

alps/frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/IccRecords.java

其中在IccRecords中可以看到在判断isInTestMode后会获取相应的FakeXXX的信息,以此来实现将运营商信息进行Override的需求,我们也是通过系统的此机制来进行模拟卡。

    /**
     * Get the Group Identifier Level 1 (GID1) on a SIM for GSM.
     * @return null if SIM is not yet ready
     */
    public String getGid1() {
        if (mCarrierTestOverride.isInTestMode()) {
            String fakeGid1 = mCarrierTestOverride.getFakeGid1();
            if (fakeGid1 != null) {
                return fakeGid1;
            }
        }
        return mGid1;
    }

    /**
     * Get the International Mobile Subscriber ID (IMSI) on a SIM
     * for GSM, UMTS and like networks. Default is null if IMSI is
     * not supported or unavailable.
     *
     * @return null if SIM is not yet ready or unavailable
     */
    public String getIMSI() {
        if (mCarrierTestOverride.isInTestMode()) {
            String fakeImsi = mCarrierTestOverride.getFakeIMSI();
            if (fakeImsi != null) {
                return fakeImsi;
            }
        }
        return mImsi;
    }

总结 

在没有白卡进行模拟验证问题时,可使用上述方法进行验证可以提高工作的效率,但针对Modem侧的修改无法验证,是因为此Override只在AP侧进行,适用面较小,但在没有白卡时非常好用,另外不同平台的路径和文件名可能有所不同,具体根据平台进行配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值