MIUI中的权限

概述: MIUI系统在Google Android原生权限体系基础上,对部分权限进行了差异化管理。本文以NFC权限 (官方定义为Normal权限,MIUI中升级为运行时权限)和剪切板权限 (官方无需声明,MIUI中拆分为独立权限)为例,分析MIUI权限管理的特殊行为。由于这类权限可能无法通过标准checkSelfPermission方法检测,本文进一步介绍了如何利用AppOpsManager反射调用,通过查询对应OP值,准确获取权限的真实授权状态,为开发者适配MIUI及HyperOS系统提供参考。

一、背景:Android权限体系与MIUI的差异

在Android开发中,权限管理是保护用户隐私的核心机制。自Android 6.0(API 23)引入运行时权限模型后,权限被大致划分为两类:

  • Normal Permissions(正常权限) :不直接涉及用户隐私,只需在AndroidManifest.xml中声明,系统会自动授予。
  • Dangerous Permissions(危险权限) :涉及用户隐私数据,必须在运行时动态申请并等待用户弹窗授权。

然而,对于深度定制系统(如MIUI、HyperOS)而言,这一规则只是基础。MIUI将Android定义的"正常权限"或未明确界定的操作,纳入了自己的权限管理体系。其中最具代表性的便是NFC权限剪切板权限

官方参考 MIUI官方参考
Android 权限概览(Android Developers) 请求运行时权限(Android Developers) 获取应用列表权限的适配说明 剪切板功能及读写剪切板权限调整说明

二、NFC权限:从"正常"到"运行时"的转变

NFC权限在MIUI12.5.3稳定版\MIUI 13.0.7稳定版未出现运行时请求,之前在MIUI哪个版本出现暂无法追踪,本文只是用此作为一个例子引出MIUI检测特殊的权限方法。

在谷歌原生系统中,NFC权限属于正常权限(Protection level: normal),开发者只需在AndroidManifest.xml中声明即可使用。

官方定义
arduino 复制代码
public static final String NFC
Added in API level 9

Allows applications to perform I/O operations over NFC.
Protection level: normal
Constant Value: "android.permission.NFC"

正常用法:

xml 复制代码
<uses-permission android:name="android.permission.NFC"/>
MIUI运行时用户确认

在MIUI系统中,NFC权限被升级为运行时权限。当App首次调用相关NFC系统API时,系统会弹窗询问用户是否允许。用户也可以在设置 > 应用 > 权限管理中找到独立的"NFC"开关进行控制。

若用户未在倒计时结束前允许,或主动关闭权限,则使用NFC读卡器模式(其他模式推测一致)读卡时会抛出IOException

首次触发NFC 设置关闭打开NFC权限

三、 剪切板权限:官方未定义,MIUI独立拆分

在Android官方权限体系中,读写剪切板甚至不需要声明任何权限,这导致部分应用在后台静默读取剪切板,造成用户隐私泄露。MIUI 12及以上版本引入了"剪切板隐私保护"功能。

官方说明:剪切板功能及读写剪切板权限调整说明

引用MIUI官方说明:

二、剪切板权限调整说明

  • 原读写剪切板权限拆分为"读取剪切板"和"写入剪切板",给予用户更加灵活的隐私权限管控方式;
  • "读取剪切板"权限将默认设置为"智能允许";"写入剪切板"权限将默认设置为"仅在使用中允许";
  • 当应用读取剪切板时,若剪切板中的最新内容符合应用读取规则(例如:淘宝读取剪切板中的淘口令),将智能允许应用读取剪切板;智能允许时,将在屏幕顶部通过提示气泡的方式告知用户(用户可见内容为:已智能允许某某应用读取剪切板);
  • 当应用读取剪切板时,若剪切板中的最新内容不符合应用读取规则,且剪切板中的内容符合其他应用读取规则(例如:非淘宝应用读取剪切板中的淘口令),将智能拒绝应用读取剪切板,且用户无感知;
  • 当应用读取剪切板时,若该应用存在读取规则但剪切板最新内容不符合应用读取规则(例如:淘宝读取剪切板中的一段任意文字),将智能拒绝应用读取剪切板,且用户无感知;
  • 当应用读取剪切板时,若剪切板中的最新内容不符合任何应用读取规则且应用不存在读取规则(例如:某地图应用读取剪切板中的一段任意文字),将智能拒绝应用读取剪切板,并弹出通知、气泡告知用户,用户有权利关闭提示。

三、FAQ

1、此功能适配哪些Android版本

此功能适配Android的版本:Android 10、 Android 11。

2、开发者在哪个MIUI版本可以体验测试

三方开发者可安装MIUI 12开发版20.11.16之后的版本调试。

3、智能拒绝的逻辑是否会影响用户主动粘贴剪切板中的内容

不会,只要用户不手动将应用读取剪切板的权限状态手动调整为"拒绝",用户都可正常手动长按粘贴。此次剪切板隐私保护的功能仅针对应用主动请求读取剪切板的情况。

4、读取剪切板权限是否可以使用权限弹窗引导用户授权

读取剪切板权限目前未支持权限询问弹窗,所有应用默认权限状态为"智能允许"。

MIUI运行时用户确认

若权限是询问,在App调用写入剪切板系统API时,会触发系统弹窗让用户授权App是否允许应用向剪切板写入任何内容。用户也可以直接在应用权限管理界面设置剪切板权限的开启与关闭。

触发写入剪切板 设置关闭打开写入剪切板权限

四、如何正确检测MIUI特殊权限状态

原理说明

在Android源码中,AppOpsManager负责细粒度的应用操作跟踪。权限状态的最终存储位置为/data/system/appOps/xxx.xml

MIUI系统为NFC、剪切板等特殊权限分配了独立的OP(Operation)值。这些权限无法通过标准的checkSelfPermission检测,必须通过反射调用AppOpsManager.checkOpNoThrow来获取真实状态。

验证方法 :使用root后的MIUI手机,查看/data/system/appOps/xxx.xml文件,可找到NFC对应的OP值为10016

OP值与返回值说明
OP值 说明
24 悬浮窗权限
29 读取剪切板
30 写入剪切板
36 媒体音量控制
10016 NFC
10017 创建桌面快捷方式
10020 锁屏显示
10021 后台弹窗
10022 读取应用列表
10026 常驻通知

返回值含义:

  • 0:允许(PERMISSION_GRANTED)
  • 1:禁止(PERMISSION_DENIED)
  • 5:询问(PERMISSION_ASK)

检查MIUI权限的核心代码片段:

kotlin 复制代码
fun checkMIUI(context: Context, op: Int): PermissionResult {
    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
        )
        val invoke = checkOpNoThrowMethod.invoke(mAppOps, op, uid, pkg)
        if (invoke == null) {
            return PermissionResult(
                code = PERMISSION_UNKNOWN,
                msg = "MIUI check permission checkOpNoThrowMethod(AppOpsManager) invoke result is null"
            )
        }
        val result = invoke.toString()
        Log.d(TAG, "Checked op=$op for package=$pkg, uid=$uid, result=$result")
        return when (result) {
            "0" -> PermissionResult(PERMISSION_GRANTED)
            "1" -> PermissionResult(PERMISSION_DENIED)
            "5" -> PermissionResult(PERMISSION_ASK)
            else -> PermissionResult(code = PERMISSION_UNKNOWN)
        }
    } catch (e: Exception) {
        Log.e(TAG, "Failed to check permission for op=$op: ${e.message}", e)
        return PermissionResult(
            code = PERMISSION_UNKNOWN,
            msg = e.message ?: "Unknown error",
            exception = e
        )
    }
}

五、相关代码,供参考调试

github地址:PermissionCheck

相关推荐
OpenTiny社区2 小时前
GenUI SDK 生成式UI:六大开发特性详解,适配多种业务场景
前端·github·ai编程
Eloudy3 小时前
迁移带有 git lfs 功能的 github 仓库
git·github
我命由我123453 小时前
Java 开发 - CountDownLatch 不需要手动关闭
android·java·开发语言·jvm·kotlin·android studio·android-studio
众少成多积小致巨3 小时前
GNU Make 核心指南
android·c++
凛_Lin~~3 小时前
安卓进程保活方案记录(双重fork+文件锁+手搓parcel)
android·安卓
海天鹰3 小时前
安卓相机:获取最近拍摄的照片缩略图做相册按钮图标
android
tongyiixiaohuang3 小时前
技术案例分享:金蝶云星空客户数据同步到MySQL的实现
android·数据库·mysql
小羊子说4 小时前
Android ANR 原理浅析
android·性能优化·车载系统
忡黑梨4 小时前
eNSP_ACL原理及应用
运维·服务器·网络·tcp/ip·github·负载均衡