NFC权限在MIUI系统中需要运行时权限

在谷歌原生系统中NFC权限属于正常权限,但是在MIUI系统中,NFC权限被声明为运行时权限并且在使用相关功能时,系统会弹窗告知用户是否允许,用户也可以在应用设置界面开启或者关闭NFC权限。(暂未查询到MIUI几开始引入)

NFC

Added in API level 9

public static final String NFC
复制代码

Allows applications to perform I/O operations over NFC.

Protection level: normal

Constant Value: "android.permission.NFC"

正常用法:

AndroidManifest.xml文件中声明权限即可正常使用NFC相关功能

    <uses-permission android:name="android.permission.NFC"/>
复制代码

MIUI运行时用户确认

在App首次调用相关NFC系统API时,会触发系统弹窗让用户授权App是否允许使用NFC。用户也可以直接在应用权限管理界面设置NFC权限的开启与关闭。

如果用户未在倒计时结束前允许、主动关闭权限,那么在使用NFC的读卡器模式(其他模式猜测一致)读卡时会报IOException。

MIUI检查App是否获取了NFC权限

利用反射 AppOpsManager#checkOpNoThrow(@NonNull String op, int uid, @NonNull String packageName)获取op值对应权限的授权状态,NFC的op为10016,返回值0表示允许,1表示禁止,5表示询问

Android权限管理源码解析就能了解权限相关状态的存储位置为/data/system/appOps/xxx.xml
用一台root的MIUI手机查看相关xml文件能找到NFC的op值为10016

//检查NFC权限的核心代码

@IntDef(value = [PERMISSION_GRANTED, PERMISSION_DENIED, PERMISSION_ASK, PERMISSION_UNKNOWN])
@kotlin.annotation.Retention(
    AnnotationRetention.SOURCE
)
annotation class PermissionResult {
    companion object {
        /**
         * Permission check result: this is returned by [.check]
         * if the permission has been granted to the given package.
         */
        const val PERMISSION_GRANTED = 0

        /**
         * Permission check result: this is returned by [.check]
         * if the permission has not been granted to the given package.
         */
        const val PERMISSION_DENIED = -1

        const val PERMISSION_ASK = 1

        const val PERMISSION_UNKNOWN = 2
    }
}

/**
 * 检测NFC权限是否有授权.
 */
@PermissionResult
@RequiresApi(Build.VERSION_CODES.KITKAT)
fun checkNfcPermission(context: Context): Int {
    try {
        val mAppOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
        val pkg = context.applicationContext.packageName
        val uid = context.applicationInfo.uid
        val appOpsClass = Class.forName(AppOpsManager::class.java.name)
        val checkOpNoThrowMethod = appOpsClass.getDeclaredMethod(
            "checkOpNoThrow", Integer.TYPE, Integer.TYPE,
            String::class.java
        )
        //the ops of NFC is 10016,check /data/system/appops/xxx.xml
        val invoke = checkOpNoThrowMethod.invoke(mAppOps, 10016, uid, pkg)
        if (invoke == null) {
            Logger.get().println(
                "MIUI check permission checkOpNoThrowMethod(AppOpsManager) invoke result is null"
            )
            return PERMISSION_UNKNOWN
        }
        val result = invoke.toString()
        Logger.get().println(
            "MIUI check permission checkOpNoThrowMethod(AppOpsManager) invoke result = $result"
        )
        when (result) {
            "0" -> return PERMISSION_GRANTED
            "1" -> return PERMISSION_DENIED
            "5" -> return PERMISSION_ASK
        }
    } catch (e: Exception) {
        Logger.get().println("check nfc permission fail ${e.message}", e)
    }
    return PERMISSION_UNKNOWN
}

复制代码

最佳做法

场景1:用户正常打开NFC读卡界面,等待NFC后续读取到卡标签

graph LR
A[用户] -->B(打开NFC操作界面)-->H{是否支持NFC} -->|是|C{是否有权限}
    H -->|否|Z(结束)
    C -->|permission=0| G{NFC是否打开} -->|否|I[弹窗提示跳转系统NFC设置]-->|确定-返回界面|G
    C -->|permission=5| E[弹窗提示需要的NFC权限说明]-->|确定|J
    C -->|permission=1| F[弹窗提示跳转应用权限设置授权]-->|取消|Z
    F -->|确定-返回界面|C
    E -->|取消|Z
    G -->|是|J(注册NFC)
    I -->|取消|Z
    

相关代码

github地址:NfcSample

猜你喜欢

转载自juejin.im/post/7030819721851174942