如果判斷小米MIUI的NFC權限、後臺彈出界面權限是否禁用

一、遇到奇葩問題

最近的項目需求有個NFC刷卡的功能,按照正常的流程,在AndroidManifest.xml中添加<uses-permission android:name="android.permission.NFC" />,然後在Activity中正常處理NFC邏輯就行了,我也是這麼幹的,我的一加七妥妥的正常運行,但是測試拿着一臺小米9來跟我說有bug,what the fxxx?

如上圖,NFC的權限級別不是normal麼?爲什麼小米會有這個選項?我的一加七就沒有。重點是小米如果把這個NFC權限設爲拒絕,那就不能正常讀卡了。那麼問題來,如何判斷用戶是否已經允許了這個NFC權限呢?

SoEasy啦,很自然的敲下代碼,ContextCompat.checkSelfPermission(this, Manifest.permission.NFC),結果呢?無論我把NFC設置成“允許”還是“拒絕”,這個方法都返回PERMISSION_GRANTED!!!那麼問題又來了,官方的API已經不能滿足了,我們該如何優雅的判斷用戶是否已經允許了NFC權限呢?

二、解決方法

這裏直接上解決方法,後面再一步一步記錄一下是怎麼找到這個方法的,趕進度的你們直接複製這個代碼就行了,後面不用看了。

/**
 * @param op
 * * op=10016 對應 NFC
 * * op=10021 對應 後臺彈出界面
 * * 其它未知,根據博客的方法自己去找你需要的
 * @return true爲允許,false爲詢問或者拒絕。
 */
fun checkOpPermission(context: Context, op: Int): Boolean {
    return try {
        val manager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
        val method = manager.javaClass.getMethod("checkOpNoThrow", Int::class.java, Int::class.java, String::class.java)
        val result = method.invoke(manager, op, Process.myUid(), context.packageName)
        AppOpsManager.MODE_ALLOWED == result
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

三、尋找解決方法的過程

碰到問題肯定先百度啦,百度了各種方法都不起作用,各種被虐啊,後來經同事提醒,找到了這個博客https://blog.csdn.net/GuangkuoDing/article/details/100324162,這種方法很類似啊,但是它是判斷“後臺彈出界面”權限的,op=10021,一個op對應這一個權限選項,那麼問題來了,我怎麼知道“NFC”權限的op是多少呢?

經過各種摸索,終於讓我找到了思路,理論上不僅可以找到NFC的,還可以找到所有你看得到的,你需要的。
其實這之前我還嘗試了從源碼中尋找啊,查看系統LOG啊等等方法,但是都走不通,纔想到這個的,本來想全都寫下來的,想想還是算了,簡單粗暴點,直接給結果。

思路:
1、遍歷一個範圍內的op,看看那些沒有拋異常,就證明那些op有對應的選項,這樣就可以知道一個大概範圍了。
2、手動把全部權限都關了,只允許NFC權限,遍歷一下剛纔那個範圍的op,會得到一堆已允許的op,其中必定有一個是NFC。
3、把NFC權限也拒絕了,遍歷一下上面得到的那一堆已允許的op,其中必定有一個是拒絕的,這個就是NFC了。

1、遍歷一定範圍op
for (op in 10000..10030) {
    try {
        val manager = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
        val method = manager.javaClass.getMethod("checkOpNoThrow", Int::class.java, Int::class.java, String::class.java)
        method.invoke(manager, op, Process.myUid(), packageName)
        Log.i("hello", "op=$op 正常op")
    } catch (e: Exception) {
        Log.i("hello", "op=$op 拋異常,無意義op")
    }
}

這裏只是遍歷了10000…10030,是我隨機寫的,沒想到剛剛好,下面是打印結果,從結果來看op的範圍就是10001-10027。

2019-11-28 16:37:47.779 11103-11103/com.audient.androidall I/hello: op=10000 拋異常,無意義op
2019-11-28 16:37:47.780 11103-11103/com.audient.androidall I/hello: op=10001 正常op
2019-11-28 16:37:47.780 11103-11103/com.audient.androidall I/hello: op=10002 正常op
2019-11-28 16:37:47.780 11103-11103/com.audient.androidall I/hello: op=10003 正常op
2019-11-28 16:37:47.781 11103-11103/com.audient.androidall I/hello: op=10004 正常op
2019-11-28 16:37:47.781 11103-11103/com.audient.androidall I/hello: op=10005 正常op
2019-11-28 16:37:47.781 11103-11103/com.audient.androidall I/hello: op=10006 正常op
2019-11-28 16:37:47.781 11103-11103/com.audient.androidall I/hello: op=10007 正常op
2019-11-28 16:37:47.782 11103-11103/com.audient.androidall I/hello: op=10008 正常op
2019-11-28 16:37:47.782 11103-11103/com.audient.androidall I/hello: op=10009 正常op
2019-11-28 16:37:47.782 11103-11103/com.audient.androidall I/hello: op=10010 正常op
2019-11-28 16:37:47.782 11103-11103/com.audient.androidall I/hello: op=10011 正常op
2019-11-28 16:37:47.782 11103-11103/com.audient.androidall I/hello: op=10012 正常op
2019-11-28 16:37:47.783 11103-11103/com.audient.androidall I/hello: op=10013 正常op
2019-11-28 16:37:47.783 11103-11103/com.audient.androidall I/hello: op=10014 正常op
2019-11-28 16:37:47.783 11103-11103/com.audient.androidall I/hello: op=10015 正常op
2019-11-28 16:37:47.784 11103-11103/com.audient.androidall I/hello: op=10016 正常op
2019-11-28 16:37:47.784 11103-11103/com.audient.androidall I/hello: op=10017 正常op
2019-11-28 16:37:47.784 11103-11103/com.audient.androidall I/hello: op=10018 正常op
2019-11-28 16:37:47.784 11103-11103/com.audient.androidall I/hello: op=10019 正常op
2019-11-28 16:37:47.785 11103-11103/com.audient.androidall I/hello: op=10020 正常op
2019-11-28 16:37:47.785 11103-11103/com.audient.androidall I/hello: op=10021 正常op
2019-11-28 16:37:47.785 11103-11103/com.audient.androidall I/hello: op=10022 正常op
2019-11-28 16:37:47.785 11103-11103/com.audient.androidall I/hello: op=10023 正常op
2019-11-28 16:37:47.786 11103-11103/com.audient.androidall I/hello: op=10024 正常op
2019-11-28 16:37:47.786 11103-11103/com.audient.androidall I/hello: op=10025 正常op
2019-11-28 16:37:47.786 11103-11103/com.audient.androidall I/hello: op=10026 正常op
2019-11-28 16:37:47.786 11103-11103/com.audient.androidall I/hello: op=10027 正常op
2019-11-28 16:37:47.787 11103-11103/com.audient.androidall I/hello: op=10028 拋異常,無意義op
2019-11-28 16:37:47.787 11103-11103/com.audient.androidall I/hello: op=10029 拋異常,無意義op
2019-11-28 16:37:47.787 11103-11103/com.audient.androidall I/hello: op=10030 拋異常,無意義op
2、權限全關,只允許NFC權限,繼續遍歷

val allowedList = ArrayList<Int>()
for (op in 10001..10027) {
    val allowed = checkOpPermission(this, op)
    if (allowed) {
        allowedList.add(op)
    }
}
LogUtils.e("hello", allowedList)

allowedList代表當前所有已允許的權限op列表。
checkOpPermission在文章開頭有。
輸出結果爲:[10002, 10003, 10005, 10006, 10007, 10009, 10010, 10011, 10012, 10013, 10014, 10015, 10016, 10018, 10022, 10023, 10024, 10027]
證明NFC權限必然在這個列表裏面,繼續往下走。

3、拒絕NFC權限,繼續遍歷

for (op in arrayOf(10002, 10003, 10005, 10006, 10007, 10009, 10010, 10011, 10012, 10013, 10014, 10015, 10016, 10018, 10022, 10023, 10024, 10027)) {
    val allowed = checkOpPermission(this, op)
    if (!allowed) {
        LogUtils.e("hello", "NFC權限就是這貨了,op=$op")
    }
}

上面代碼輸出:NFC權限就是這貨了,op=10016,大功告成!

四、總結

功夫不負有心人。

權限動態申請已經封裝成了一個kotlin文件了,拿來就用。傳送:https://blog.csdn.net/qiantujava/article/details/103402239

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章