Android 获取双卡手机IMEI,IMSI,ICCID

一、首先要添加权限

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

二、获取主卡的IMEI,IMSI,ICCID

    /**
     * Author: liuqiang
     * Time: 2017-08-14 15:28
     * Description:
     * <p>
     * IMEI 与你的手机是绑定关系 用于区别移动终端设备
     * IMSI 与你的手机卡是绑定关系 用于区别移动用户的有效信息 IMSI是用户的标识。
     * ICCID ICCID是卡的标识,由20位数字组成
     * ICCID只是用来区别SIM卡,不作接入网络的鉴权认证。而IMSI在接入网络的时候,会到运营商的服务器中进行验证。
     * https://github.com/android/platform_frameworks_base/blob/master/telephony/java/android/telephony/TelephonyManager.java
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public void check(View view) {

        TelephonyManager telephonyManager = (TelephonyManager) this
                .getSystemService(TELEPHONY_SERVICE);// 取得相关系统服务

        String simOperatorName = telephonyManager.getSimOperatorName();
        String imei = telephonyManager.getDeviceId();       //取出 IMEI
        String imeiAPI26 = telephonyManager.getImei();       //取出 IMEI 需要 api26以上
        String tel = telephonyManager.getLine1Number();     //取出 MSISDN,很可能为空
        String imsi = telephonyManager.getSubscriberId();     //取出 IMSI
        String icc = telephonyManager.getSimSerialNumber();  //取出 ICCID

        Log.d("Q_M", "运行商名字--" + simOperatorName);
        Log.d("Q_M", "IMEI--" + imei);
        Log.d("Q_M", "IMEI_API26--" + imeiAPI26);
        Log.d("Q_M", "IMSI--" + imsi);
        Log.d("Q_M", "ICCID--" + icc);
    }

三、如果手机有多张卡

TelephonyManager的官方源码

其实多卡情况下主要要获得的是两个地方:getSubscriberIdgetSimSerialNumber,打开上面的的源码,搜索一下这两个方法,发现这两个方法都有一个带参数(subId)的重载方法,并且这两个方法都是@hide的,hide倒是无所谓,这个可以通过反射调用,主要的问题要弄清楚他的这个参数subId是个什么东西。代码片段如下:

    /**
     * Returns the unique subscriber ID, for example, the IMSI for a GSM phone.
     * Return null if it is unavailable.
     * <p>
     * Requires Permission:
     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
     */
    public String getSubscriberId() {
        return getSubscriberId(getSubId());
    }

    /**
     * Returns the unique subscriber ID, for example, the IMSI for a GSM phone
     * for a subscription.
     * Return null if it is unavailable.
     * <p>
     * Requires Permission:
     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
     *
     * @param subId whose subscriber id is returned
     * @hide
     */
    public String getSubscriberId(int subId) {
        try {
            IPhoneSubInfo info = getSubscriberInfo();
            if (info == null)
                return null;
            return info.getSubscriberIdForSubscriber(subId, mContext.getOpPackageName());
        } catch (RemoteException ex) {
            return null;
        } catch (NullPointerException ex) {
            // This could happen before phone restarts due to crashing
            return null;
        }
    }

从上面的注释来来,这个subId 是subscription id的简写,既然提到subscription id,那不得不说的就是SubscriptionManager,这个东西这里不解释了看这篇文章官方文档。只要知道一点,这个类就是为了5.0之后,为了配合TelephonyProvider操作和处理/data/data/com.android.providers.telephony/databases/telephony.db这个数据库中的表的,就可以了。这个数据在root了的手机上可以直接通过SQLite-Editor-2.1.1.apk这个工具apk查看。当然也可以把他导出到电脑上查看。

0 、不算业余知识的业余知识

既然所有的CURD都是针对这个数据库,那么来研究下这个数据库,
打开后发现这个数据库中有张叫siminfo的表。如下图:

siminfo.png

截图中并没有包含表中所有的列,但是这些是主要的,后面的那些和这篇文章无关。上图这个是在Android模拟器中导出来的。在看一张表的时候,不仅要了解他的字段的含义,而且要了解这种表中数据的插入规则。

很重要:每次插入一张新SIM卡(这张卡没有插入到过这个手机)的时候,都会在数据库中添加一条记录,插入的时候_id字段从1开始每次增1;每次移除一张SIM卡,sim_id字段设置为-1(但是这条记录不会被删除),表示这张卡曾经插入过,但是又被移除了,用专业术语来说就是这张卡现在不是Active的,处于不可用状态。

这些字段都表示了什么意思,其中最重要的是_idsim_id

_id:从数据库的角度来说,做过sqlite开发的都知道,他是个从1开始自增的主键。但是他在这里还代表了程序中另一个东西subId也就是subscription id

icc_id:不解释,上面说过了

sim_id:这个字段有两层含义,在大于-1,的情况下他表示的是卡槽序号,比如sim_id为0表示卡1,取值为1的时候表示的是卡2,以此类推,但是一般手机不会超过两个卡槽吧?!如果取值为-1,表示这张SIM卡曾经被插入过,但是现在被移除了。

display_name:顾名思义,显示名。这个一般可以改,但是默认的是读取的运营商的名字,比如:中国移动,中国联通,中国电信

carrier_name :恩,运营商名字

number:SIM卡对应的手机号,这个不一定能取到

mcc:Mobile Country Code,移动国家码

mnc:Mobile Network Code,移动网络码

现在的主要问题是如何获取subscription id,对吧,how?

1 、读数据库取subId也即是表的_id 字段

    ///data/data/com.android.providers.telephony/databases/telephony.db
    public void getSimInfo() {

        Uri uri = Uri.parse("content://telephony/siminfo");
        Cursor cursor = null;
        ContentResolver contentResolver = getApplicationContext().getContentResolver();
        cursor = contentResolver.query(uri,
                new String[]{"_id", "sim_id", "icc_id", "display_name"}, "0=0",
                new String[]{}, null);
        if (null != cursor) {
            while (cursor.moveToNext()) {
                String icc_id = cursor.getString(cursor.getColumnIndex("icc_id"));
                String display_name = cursor.getString(cursor.getColumnIndex("display_name"));
                int sim_id = cursor.getInt(cursor.getColumnIndex("sim_id"));
                int _id = cursor.getInt(cursor.getColumnIndex("_id"));

                Log.d("Q_M", "icc_id-->" + icc_id);
                Log.d("Q_M", "sim_id-->" + sim_id);
                Log.d("Q_M", "display_name-->" + display_name);
                Log.d("Q_M", "subId或者说是_id->" + _id);
                Log.d("Q_M", "---------------------------------");
            }
        }
    }

如上代码,我在小米6的测试机上进行测试,插入过3张卡,两张移动,一张联通的,运行结果如下(因为是真是的SIM卡,隐藏了icc_id):

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: icc_id-->898600*************7
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: sim_id-->0
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: display_name-->中国移动
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: subId或者说是_id->1
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: ---------------------------------
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: icc_id-->898601*************6
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: sim_id-->-1
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: display_name-->CARD 2
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: subId或者说是_id->2
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: ---------------------------------
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: icc_id-->898602*************9
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: sim_id-->1
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: display_name-->中国移动
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: subId或者说是_id->3
08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: ---------------------------------

2、通过SubscriptionManager取subId

但是这个api有两个缺陷:必须5.0以上才能用;只能获取active的卡的信息。其实对于第二点缺陷,源码中是能获取所有的卡的信息的,也就是表中所有的数据,通过源码看到这个方法叫做getAllSubscriptionInfoList不过这个方法也被hide了,能用是能用,就是这种@hide的方法,不一定在哪个版本就会被删除掉。需要做很多兼容性上的操作。

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
    public void getSimInfoBySubscriptionManager() {
        List<SubscriptionInfo> list = SubscriptionManager.from(this).getActiveSubscriptionInfoList();
        for (SubscriptionInfo info : list) {
            Log.d("Q_M", "ICCID-->" + info.getIccId());
            Log.d("Q_M", "subId-->" + info.getSubscriptionId());
            Log.d("Q_M", "DisplayName-->" + info.getDisplayName());
            Log.d("Q_M", "CarrierName-->" + info.getCarrierName());
            Log.d("Q_M", "---------------------------------");
        }
    }

上面的SubscriptionInfo源码,就几乎对应了siminfo这个表中的所有的字段。

其实代码分析到这里,双卡的其他基本信息其实都已经获取到了,无论通过读取数据库的方式,还是通过SubscriptionManager的方式,唯一没有获取到的就是getSubscriberId()来得到的IMSI。

3、通过反射获取调用IMSI

反射调用带有参数的getSubscriberId(subId)是很简单的,但是问题就出在,这个方法是@hide,所以说这个方法可能在不同的Android版本中会出现不同的实现。subId在5.0传入的是long类型的参数,而5.1-7.1.1传入的是int类型的参数。再高的版本我就没看了

5.0.0上的方法签名:

 /**
     * Returns the unique subscriber ID, for example, the IMSI for a GSM phone
     * for a subscription.
     * Return null if it is unavailable.
     * <p>
     * Requires Permission:
     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
     *
     * @param subId whose subscriber id is returned
     */
    /** {@hide} */
    public String getSubscriberId(long subId) {
        try {
            return getSubscriberInfo().getSubscriberIdForSubscriber(subId);
        } catch (RemoteException ex) {
            return null;
        } catch (NullPointerException ex) {
            // This could happen before phone restarts due to crashing
            return null;
        }
    }

5.1.0-5.1.1的方法签名:

    public String getSubscriberId(int subId) {
        try {
            return getSubscriberInfo().getSubscriberIdForSubscriber(subId);
        } catch (RemoteException ex) {
            return null;
        } catch (NullPointerException ex) {
            // This could happen before phone restarts due to crashing
            return null;
        }
    }

6.0.0-7.1.1的方法签名:

public String getSubscriberId(int subId) {
    try {
        IPhoneSubInfo info = getSubscriberInfo();
        if (info == null)
            return null;
        return info.getSubscriberIdForSubscriber(subId, mContext.getOpPackageName());
    } catch (RemoteException ex) {
        return null;
    } catch (NullPointerException ex) {
        // This could happen before phone restarts due to crashing
        return null;
    }
}

以上源码来自http://androidxref.com/

反射调用getSubscriberId的代码如下,其中参数,subId的获取方式,在上面已经有了:

    public String getSubscriberId(int subId) {
        TelephonyManager telephonyManager = (TelephonyManager) this
                .getSystemService(TELEPHONY_SERVICE);// 取得相关系统服务
        Class<?> telephonyManagerClass = null;
        String imsi = null;
        try {
            telephonyManagerClass = Class.forName("android.telephony.TelephonyManager");

            if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP) {
                Method method = telephonyManagerClass.getMethod("getSubscriberId", int.class);
                imsi = (String) method.invoke(telephonyManager, subId);
            } else if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.LOLLIPOP) {
                Method method = telephonyManagerClass.getMethod("getSubscriberId", long.class);
                imsi = (String) method.invoke(telephonyManager, (long) subId);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        Log.d("Q_M", "IMSI--" + imsi);
        return imsi;
    }

最后: 这些操作都是在Android5.0以上版本的,并且第三方厂商没有修改这块的代码的情况下的操作。至于5.0以下的系统,怎么判断,好像要分平台单个处理


Android4.x上处理双卡

高通平台有单独的类android.telephony.MSimTelephonyManager里面有方法,这个可以用反射的方式调用,经过验证在努比亚4.4.2的系统上是可以获取的:

  • getSubscriberId(int simId)
  • getSimSerialNumber(int simId)

测试代码如下:

    /**
     * Author: liuqiang
     * Time: 2017-08-15 10:56
     * Description:
     * <p>
     * 高通的“android.telephony.MSimTelephonyManager”类
     */
    private void getAPI19SimInfo() {
        Class<?> tm = null;
        try {
            tm = Class.forName("android.telephony.MSimTelephonyManager");

            Method getSubscriberIdMethod = tm.getMethod("getSubscriberId", int.class);
            Method getSimSerialNumberMethod = tm.getMethod("getSimSerialNumber", int.class);

            Object service = this.getSystemService("phone_msim");

            //0 代表卡1
            //1 代表卡2
            String s = (String) getSimSerialNumberMethod.invoke(service, 0);
            Log.d("Q_M", "-->" + s);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

至于MTK平台,据说有个aidl"com.mediatek.common.telephony.ITelephonyEx.aidl"类或者用Manager类com.mediatek.telephony.TelephonyManagerEx,我这没有MTK平台的测试机,没法测。

【参考文章】

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容