🇮🇷蘇萊曼尼遇刺,竟是由於這個....

最近伊朗的少將蘇萊曼尼遇刺,被全網稱爲2020年第一隻黑天鵝事件。人們爲美國沒有奪走其餘無辜生命狀況下,精準打擊目標的能力感到震驚。畢竟上一個有記載能作到「在百萬軍中取上將首級如探囊取物」的人是我們的張飛大哥。java

我也閒來無事,打開萬年不開的微博,看到一條微博內容:android

忽然感受虎軀一震,在現在的科技時代,掌握科技的制高點和核心研發能力是多麼的重要。不過,若是對於這號人物真經過IMEI就能簡單的定位的話,或許他們須要一臺裝了xposed的安卓手機。好像發現了商機?git

在震驚之餘,我拿出恰好這周複習的安卓設備ID信息和Sim卡信息的筆記,認真複習了一番。github

下面是Demo的運行結果:api

IMEI和MEID

IMEI和MEID都是移動設備的身份識別碼。相似於設備的身份證。他們的區別以下:markdown

  • IMEI:國際移動設備識別碼International Mobile Equipment IdentityIMEI),即一般所說的手機「串號」,用於在移動電話網絡中識別每一部獨立的手機等移動通訊設備。序列號共有15位數字。
  • MEID:移動設備識別碼(Mobile Equipment Identifier)是CDMA手機的身份識別碼。序列號共有14位數字。

在安卓8.0如下手機,咱們經過APITelephonyManager.getDeviceId()獲取插有電信運營商SIM卡的卡槽時,默認返回MEID號;移動和聯通運營商SIM卡的卡槽返回IMEI號。網絡

獲取IMEI或MEID

在Android 8.0以上的系統,TelephonyManager廢棄了getDeviceId()方法,提供了兩個獨立的API來準確的獲取IMEI和MEID:getImei()getMeid()。兩個API均可以支持傳入slotIndex來獲取對應位置的IMEI和MEID。jsp

在Android8.0如下的系統,TelephonyManager經過getDeviceId()來獲取IMEI或MEID;且在Android 6.0系統及以上系統,getDeviceId(slotIndex)支持經過傳入slotIndex來獲取對應位置的IMEI和MEID,來適應一機雙卡的狀況。當存在電信SIM卡時,getDeviceId()方法優先返回MEID,當移動和聯通SIM卡時,返回IMEI。ide

卡1 卡2 getDeviceId(0) getDeviceId(1)
無/非電信卡 無/非電信卡 IMEI IMEI
電信卡 無/非電信卡 MEID IMEI
無/非電信卡 電信卡 IMEI MEID
/** * 獲取IMEI或MEID */
    fun getIMEIORMEID(){
        //檢查是否有READ_PHONE_STATE權限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //獲取IMEI和MEID
            val tm = getSystemService<TelephonyManager>()
            //getDeviceId()從API1就已經存在,默認返回卡槽一的IMEI或MEID
            var imei: String? = tm?.getDeviceId()
            //getDeviceId(solotIndex)在API23加入能夠經過指定卡槽位置獲取IMEI或MEID
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                //solotIndex:0 -> 卡槽1 IMEI或MEID
                tvIMEI1.text = "卡槽一 IMEI或MEID:${tm?.getDeviceId(0)}"
                //solotIndex:1 -> 卡槽2 IMEI或MEID
                tvIMEI2.text = "卡槽二 IMEI或MEID:${tm?.getDeviceId(1)}"
            }
        }
    }

    /** * 獲取MEID */
    fun getMEID(){
        //檢查是否有READ_PHONE_STATE權限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //獲取IMEI和MEID
            val tm = getSystemService<TelephonyManager>()
            //獲取MEID在API26加入
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                //返回默承認用卡槽的MEID
                var meid:String? = tm?.meid
                //能夠經過指定卡槽獲取MEID
                tvMEID.text = "MEID:${tm?.getMeid(0)}"
            }
        }
    }
複製代碼

MAC地址

MAC地址(Media Access Control Address),它是一個用來確認網絡設備位置的地址。MAC地址用於在網絡中惟一標示一個網卡,一臺設備若一個或多個網卡,則每一個網卡都存在一個惟一的MAC地址。工具

通常會將IMEI或MEID結合MAC地址做爲設備的惟一標識。

MAC地址在Android中不一樣版本的獲取方式也不太同樣。你們能夠自行Google。這裏就再也不贅述。後續的Demo中也會給出代碼。

IMSI和ICCID

IMSI

國際移動用戶識別碼(International Mobile Subscriber Identity),是用於區分蜂窩網絡中不一樣用戶的、在全部蜂窩網絡中不重複的識別碼。

獲取IMSI

咱們能夠經過TelephonyManager.getSubscriberId()獲取SIM卡的IMSI,該API在SDK API 1就已經存在,但若是在雙卡雙待的手機上有多個卡槽的狀況下,咱們想獲取對應卡槽的IMSI,則須要調用TelephonyManager.getSubscriberId(int subId) 方法,傳入對應卡槽的subId,0表明卡槽1,1表明卡槽2,該方法在SDK API 21加入,但在SDK API 29及以上則沒法再調用,但咱們目前能夠經過反射的方式去調用。

在這裏安利一個工具:對於一個API,在各個Android系統的源碼,咱們能夠在http://androidxref.com/ 這個網站進行查看。

咱們經過這個網站搜索查看,還會發如今SDK API 21加入時,TelephonyManager.getSubscriberId(long subId)它的入參是Long類型,在以後的版本都是int類型。因此在版本兼容的時候,咱們若是是反射調用時,可能須要注意參數類型。

SDK API getSubscriberId()
1~20 getSubscriberId()
21 getSubscriberId()或getSubscriberId(long subId)
22~28 getSubscriberId()或getSubscriberId(int subId)
29及以上 暴露getSubscriberId(),但getSubscriberId(int subId)方法再也不暴露
/** * 經過反射調用 */
    fun getIMSI(){
        //檢查是否有READ_PHONE_STATE權限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //獲取IMSI
            val tm = getSystemService<TelephonyManager>()
            //在SDKAPI 1就存在getSubscriberId()方法,返回默認卡槽IMSI
            var imsi = tm?.subscriberId
            //在SDKAPI 21加入getSubscriberId(subId)來指定返回卡槽位置IMSI
            //在SDKAPI 29以上,指定卡槽位置的方法再也不暴露,但依舊能經過反射來獲取
            tvIMSI1.text = "卡槽一 IMSI:${getReflectMethod(this,"getSubscriberId",0) as CharSequence?}"
            tvIMSI2.text = "卡槽二 IMSI:${getReflectMethod(this,"getSubscriberId",1) as CharSequence?}"

        }
    }


    fun getReflectMethod(context: Context, method: String, param: Int): Any? {
        val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        try {
            val telephonyClass = Class.forName(telephony.javaClass.name)
            val parameter = arrayOfNulls<Class<*>>(1)
            parameter[0] = Int::class.javaPrimitiveType
            val getSimState = telephonyClass.getMethod(method, *parameter)
            val ob_phone = getSimState.invoke(telephony, param)

            if (ob_phone != null) {
                return ob_phone
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return null
    }
複製代碼

上面的代碼展現瞭如何經過反射獲取IMSI。在獲取到IMSI以後,咱們能夠經過IMSI的開頭五位數來區別移動用戶所屬的國家和運營商。

在中國IMSI的開頭三位爲460,以後的兩位表明中國的運營商信息:

運營商 MNC號碼
移動 00, 02,04, 07,08
聯通 01, 06,09
電信 03, 05
電信4G 11
鐵通 20

ICCID

SIM卡卡號,是卡的標識,不做接入網絡的鑑權認證,可在SIM卡卡後查詢到。格式:大多爲19或20位0-9的數字,亦存在6位/12位的狀況。

獲取ICCID

獲取ICCID有兩種方式:

  • 經過TelephonyManager的getSimSerialNumber()方法來獲取ICCID,在SDK API 21加入了getSubscriberId(subId)來指定獲取對應卡槽位置的ICCID;可是和IMSI同樣,在SDK API 29及以上,getSubscriberId(subId)的方法再也不暴露外部調用,但咱們能夠經過反射來調用。
  • 經過SubscriptionManager的getActiveSubscriptionInfoList()方法來獲取SubscriptionInfo列表,這個列表包含了有效的SIM卡的信息,若是有多個SIM卡,則會返回多個SubscriptionInfo對象。每一個SubscriptionInfo對象表明一個有效的SIM卡。經過循環遍歷SubscriptionInfo列表,調用SubscriptionInfogetIccId()的方法,能夠獲取ICCID的值,可是SubscriptionManager在SDk API 22才被加到SDK中。
/** * 經過反射調用 */
    fun getReflectMethod(context: Context, method: String, param: Int): Any? {
        val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        try {
            val telephonyClass = Class.forName(telephony.javaClass.name)
            val parameter = arrayOfNulls<Class<*>>(1)
            parameter[0] = Int::class.javaPrimitiveType
            val getSimState = telephonyClass.getMethod(method, *parameter)
            val ob_phone = getSimState.invoke(telephony, param)

            if (ob_phone != null) {
                return ob_phone
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return null
    }

    /** * 經過TelephonyManager獲取CCID */
    fun getICCIDByTM(){
        //檢查是否有READ_PHONE_STATE權限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //獲取IMSI
            val tm = getSystemService<TelephonyManager>()
            //在SDKAPI 1就存在getSubscriberId()方法,返回默認卡槽IMSI
            var iccid = tm?.simSerialNumber
            //在SDKAPI 21加入getSubscriberId(subId)來指定返回卡槽位置IMSI
            //在SDKAPI 29以上,指定卡槽位置的方法再也不暴露,但依舊能經過反射來獲取
            tvICCID1.text = "卡槽一 ICCID:${getReflectMethod(this,"getSimSerialNumber",0) as CharSequence?}"
            tvICCID2.text = "卡槽二 ICCID:${getReflectMethod(this,"getSimSerialNumber",1) as CharSequence?}"

        }
    }

    /** * 經過SubscriptionManager獲取CCID SDK API 22及以上使用 */
    fun getICCIDBySM(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
            val mSubscriptionManager = getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
            //檢查是否有READ_PHONE_STATE權限
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
                == PackageManager.PERMISSION_GRANTED){
                //獲取當前有效的SIM卡列表
                var subInfos = mSubscriptionManager.activeSubscriptionInfoList
                for ((index,item) in subInfos.withIndex()){
                    when(index){
                        0->tvICCID1.text = "卡槽一 ICCID:${item.iccId}"
                        1->tvICCID2.text = "卡槽二 ICCID:${item.iccId}"
                    }
                }
            }
        }
    }
複製代碼

SN

SN碼是Serial Number的縮寫,是產品的序列號,主要爲了驗證"產品的合法性",主要用來保護用戶的正版權益。通常是不可改變的。咱們能夠經過adb devices命令來查看。

獲取SN

咱們在SDK API 26如下,能夠直接經過Build.SERIAL進行獲取,在SDK API 26以上則須要READ_PHONE_STATE權限,調用Build.getSerial()方法進行獲取。

/** * 獲取SN */
    fun getSN(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
                == PackageManager.PERMISSION_GRANTED){
                // 須要READ_PHONE_STATE權限
                tvSN.text = "SN:${Build.getSerial()}"
            }
        }else{
            tvSN.text = "SN:${Build.SERIAL}"
        }
    }

複製代碼

ANDROID ID

設備首次啓動隨機生成的ID,設備還原出廠設置後或系統升級後會從新生成 格式:長度爲16位的字符串,但因爲生產廠商定製系統的Bug,有的設備會生成相同的ID,或者返回null

獲取ANDROID ID

經過Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID便可獲取ANDROID ID

/** * 獲取AndroidId */
    fun getAndroidId(){
        tvAndroidId.text = "Android ID:${Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID)}"
    }

複製代碼

Android10 AndroidId

在Android10已經沒法獲取設備硬件相關的Id,例如IMEI、MAC地址等。若是代碼中獲取這方面的信息,則會給你拋出一個大寫的SecurityException錯誤。

受影響的API爲:

  • Build

    getSerial()

  • TelephonyManager

    getImei()

    getDeviceId()

    getMeid()

    getSimSerialNumber()

    getSubscriberId()

在Android10,除非系統應用或者特殊的應用商應用READ_PRIVILEGED_PHONE_STATE才能訪問以上的API。這就意味這Android ID的許多獲取方法在Android10基本GG。

Google爲咱們提出了Android10獲取軟件範疇的ID的方法。具體參考文章:惟一標識符最佳作法

但這方案不能解決咱們不少場景:須要獲取設備的惟一不可更改的ID,例如:風控、推送和用戶畫像等領域。但不用擔憂,咱們偉大的祖國廠商系統,爲咱們提供了統一的OAID(匿名設備標識符)來替代IMEI的做用。具體參考文檔:OAID文檔

SIM卡相關拓展

最後再補充幾點與SIM卡相關,但和Android設備信息無關的內容。主要是整理了一下如何檢測一臺設備當前插入了幾張SIM卡、當前手機最多支持幾張Sim卡和獲取當前使用蜂窩網絡的SIM卡。

檢測當前手機插入幾張SIM卡

獲取當前手機插入幾張SIM卡也有兩個途徑,一個是經過TelephonyManager的getSimState()方法獲取,另外一個是經過SubscriptionManager獲取

  • TelephonyManager在SDK API 1就加入了``getSimState()方法獲取默認的Sim卡狀態;在SDK API 21加入了getSimState(int slotId)方法,獲取對應卡槽的Sim卡狀態,可是該方法倒是經過註解@hide`修飾的,因此沒有對外暴露方法,外部沒法調用,但能夠經過反射進行調用;在SDK API 26纔對外暴露。
SDK API getSubscriberId()
1~20 getSimState()
21~26 getSimState()或反射調用getSimState(int slotId)
26以上 getSimState()或getSimState(int slotId)

Sim卡狀態表:

狀態 狀態碼 解釋
SIM_STATE_UNKNOWN 0 SIM卡狀態:未知
SIM_STATE_ABSENT 1 SIM卡狀態:設備中沒有SIM卡
SIM_STATE_PIN_REQUIRED 2 SIM卡狀態:鎖定:要求用戶的SIM卡PIN解鎖
SIM_STATE_PUK_REQUIRED 3 SIM卡狀態:鎖定:要求用戶的SIM PUK解鎖
SIM_STATE_NETWORK_LOCKED 4 SIM卡狀態:鎖定:須要網絡PIN才能解鎖
SIM_STATE_READY 5 SIM卡狀態:就緒
SIM_STATE_NOT_READY 6 SIM卡狀態:SIM卡還沒有就緒
SIM_STATE_PERM_DISABLED 7 SIM卡狀態:SIM卡錯誤,已永久禁用
SIM_STATE_CARD_IO_ERROR 8 SIM卡狀態:SIM卡錯誤,存在但有故障
SIM_STATE_CARD_RESTRICTED 9 SIM卡狀態:SIM卡受限,存在,但因爲運營商的限制而沒法使用
  • SubscriptionManager在SDK API 22才被加入SDK API中。咱們在SDK API 22及以上能夠經過SubscriptionManager.getActiveSubscriptionInfoCount()方法獲取有效的Sim卡個數。但這個方法須要READ_PHONE_STATE用戶權限。

所以結合TelephonyManager.getSimState()SubscriptionManager.getActiveSubscriptionInfoCount()方法,我的整理了一個獲取當前手機SIM卡的方法。

/** * 獲取Sim卡個數 * @param context * @return */
    fun checkSimCount(context: Context): Int {
        var simCount: Int
        try {
            //若是SDK Version >=22 && 擁有Manifest.permission.READ_PHONE_STATE 權限
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1 && ContextCompat.checkSelfPermission(
                    context,
                    Manifest.permission.READ_PHONE_STATE
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                simCount = getSimCountBySubscriptionManager(context)
                if (simCount == -1) {
                    //若是SubscriptionManager獲取失敗,則TelephonyManager嘗試獲取
                    simCount = getSimCountByTelephonyManager(context)
                }
            } else {
                simCount = getSimCountByTelephonyManager(context)
            }

        } catch (e: Exception) {
            simCount = getSimCountByTelephonyManager(context)
        }

        return simCount
    }


    /** * 經過SubscriptionManager獲取Sim卡張數,若是獲取失敗返回-1 * @param context * @return */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
    private fun getSimCountBySubscriptionManager(context: Context): Int {
        try {
            val subscriptionManager =
                context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
            return subscriptionManager?.activeSubscriptionInfoCount ?: -1
        } catch (e: Throwable) {
            return -1
        }

    }

    /** * 經過TelephonyManager反射獲取Sim卡張數 * @param context * @return */
    private fun getSimCountByTelephonyManager(context: Context): Int {
        val slotOne = getSimStateBySlotIdx(context, 0)
        val slotTwo = getSimStateBySlotIdx(context, 1)
        if (slotOne && slotTwo) {
            return 2
        } else if (slotOne || slotTwo) {
            return 1
        } else {
            val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
            val simState = telephony.simState
            return if (simState != TelephonyManager.SIM_STATE_ABSENT && simState != TelephonyManager.SIM_STATE_UNKNOWN) {
                1
            } else 0
        }
    }


    /** * 經過反射去調用TelephonyManager.getSimState(int slotIdx)方法,獲取sim卡狀態 * SIM_STATE_UNKNOWN 0 SIM卡狀態:未知 * SIM_STATE_ABSENT 1 SIM卡狀態:設備中沒有SIM卡 * SIM_STATE_PIN_REQUIRED 2 SIM卡狀態:鎖定:要求用戶的SIM卡PIN解鎖 * SIM_STATE_PUK_REQUIRED 3 SIM卡狀態:鎖定:要求用戶的SIM PUK解鎖 * SIM_STATE_NETWORK_LOCKED 4 SIM卡狀態:鎖定:須要網絡PIN才能解鎖 * SIM_STATE_READY 5 SIM卡狀態:就緒 * SIM_STATE_NOT_READY 6 SIM卡狀態:SIM卡還沒有就緒 * SIM_STATE_PERM_DISABLED 7 SIM卡狀態:SIM卡錯誤,已永久禁用 * SIM_STATE_CARD_IO_ERROR 8 SIM卡狀態:SIM卡錯誤,存在但有故障 * SIM_STATE_CARD_RESTRICTED 9 SIM卡狀態:SIM卡受限,存在,但因爲運營商的限制而沒法使用。 * @param context * @param slotIdx:0(sim1),1(sim2) * @return */
    fun getSimStateBySlotIdx(context: Context, slotIdx: Int): Boolean {
        var isReady = false
        try {
            val getSimState = getSimByMethod(context, "getSimState", slotIdx)
            if (getSimState != null) {
                val simState = Integer.parseInt(getSimState.toString())
                if (simState != TelephonyManager.SIM_STATE_ABSENT && simState != TelephonyManager.SIM_STATE_UNKNOWN) {
                    isReady = true
                }
            }
        } catch (e: Throwable) {
        }

        return isReady
    }


    /** * 經過反射獲取Sim卡狀態 * @param context * @param method * @param param * @return */
    private fun getSimByMethod(context: Context, method: String, param: Int): Any? {
        val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        try {
            val telephonyClass = Class.forName(telephony.javaClass.name)
            val parameter = arrayOfNulls<Class<*>>(1)
            parameter[0] = Int::class.javaPrimitiveType
            val getSimState = telephonyClass.getMethod(method, *parameter)
            val obParameter = arrayOfNulls<Any>(1)
            obParameter[0] = param
            val result = getSimState.invoke(telephony, *obParameter)

            if (result != null) {
                return result
            }
        } catch (e: Throwable) {
            e.printStackTrace()
        }

        return null
    }

複製代碼

當前可支持最大Sim卡張數

SubscriptionManager咱們能夠經過getActiveSubscriptionInfoCountMax()方法來獲取當前手機可支持的Sim卡最大數量。

/** * 獲取當前支持最多sim卡數量 */
    fun getMaxSimCount(context: Context){
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
                val subscriptionManager = context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
                tvSimMaxCount.text =  "當前支持最多Sim卡數量:${subscriptionManager?.activeSubscriptionInfoCountMax}"
            }

        } catch (e: Throwable) {
        }
    }
複製代碼

當前蜂窩網絡Sim卡信息

咱們能夠經過TelephonyManager的getSimOperator()方法獲取當前網絡的SIM卡的MCC+MNC(mobile country code + mobile network code)其實就是IMSI的前五位,這個API在SDK API 1就已經加入。而後根據返回的信息根據運營商的MNC去判斷對應的運營商信息。

/** * 經過TelephonyManager當前蜂窩網絡運營商信息 */
    fun getNetType(){
        //檢查是否有READ_PHONE_STATE權限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //獲取TelephonyManager
            val tm = getSystemService<TelephonyManager>()
            //在SDK API 1 getSimOperator()方法
            when(tm?.simOperator){
                "46000","46002","46004","46007","46008"->tvSimNetType.text = "當前上網SIM卡運營商:移動"
                "46001","46006","46009"->tvSimNetType.text = "當前上網SIM卡運營商:聯通"
                "46003","46005","46011"->tvSimNetType.text = "當前上網SIM卡運營商:電信"
                "46020"->tvSimNetType.text = "當前上網SIM卡運營商:鐵通"
            }

        }
    }
複製代碼

源碼:github.com/DaMaiGit/An…

相關文章
相關標籤/搜索