Android 16 的 GTS 包出来之后,GtsPermissionTestcases 这个模块可以说是刷存在感最强的模块之一。我在日常处理机型认证时,十个项目里有七八个都会卡在权限用例上,报错还五花八门:有的日志提示 default-permissions.xml 被改过,有的预置应用多拿了权限,还有的是开机向导逻辑和 AOSP 默认行为不一致,甚至有的用例直接因为 appops 状态没同步而挂掉。这篇文章就把我处理这类问题的完整思路写下来,包括这个模块到底在查什么、怎么定位失败用例、以及从测试执行到系统配置各个层面“跳过权限检查”的实际操作,方便正在跑 Android 16 GTS 的同行直接抄作业。
顺便提两个热词相关的背景:Android 16 内部代号是 Baklava,对应的字母是 B,API level 是 36,所以 GTS 包版本也升级到了 gts_16 系。跑之前最好先确认手里的包和系统版本对得上,不然权限枚举规则都对不齐,后面排查全是白忙活。文章适合三类人:做 Android 系统定制的工程师、做 GMS/GTS 认证集成的人、以及被 permission 相关测试用例折磨到准备放弃的测试开发。
1. 先搞明白 GtsPermissionTestcases 到底在查什么
1.1 权限测试模块的职责边界
GTS 全称 Google Test Suite,是 GMS 认证前必须跑过的一套测试,GtsPermissionTestcases 是里面专门负责权限体系校验的模块。它不像是普通单元测试那样只验证某个 API 能调通,而是从系统全局角度检查权限授予规则、appops 运行状态、特殊权限管理逻辑是否符合 AOSP 预期。换句话说,它看的不是“这个 app 能不能用相机”,而是“你 ROM 里的权限系统是不是跟 Google 钦定的那套规则一致”。
这个模块在 Android 16 上尤为重要,因为这一代 GTS 对权限枚举粒度、运行时权限迁移、部分受限权限(比如照片选择器相关权限)的检查都更细了。如果你在定制 ROM 时改过权限默认授予策略,或者把某个系统应用的权限保护级别调整过,很容易在权限模块里一次性引爆三四组测试。我见过最夸张的一个项目,GtsPermissionTestcases 跑出来 27 个失败,最后定位到根因就是厂商在 default-permissions.xml 里给某个输入法预置了本来不该授予的 READ_CONTACTS 权限。
1.2 和开机向导、系统属性之间的隐性关联
很多刚开始跑 GTS 的人不理解,为什么权限测试会和开机向导有关系。这里头有个很关键的逻辑:AOSP 在开机向导完成后,会把一部分默认权限通过 permission controller 写入到用户 0 的运行时权限库中。如果你的设备在测试前压根没走完引导流程,或者定制 ROM 直接把 SetupWizard 跳过了,那么权限初始化数据就是不完整的。GtsPermissionTestcases 里有些用例会去校验这种“开机后默认状态”,一旦系统里缺少了原本应该由向导触发写入的权限记录,结果自然是一路红。
另外 Android 16 里权限系统还沿用了按“角色”来分派权限的能力,比如默认拨号应用、默认短信应用、默认相册应用这些角色对应的权限集合,也会被 GtsPermissionTestcases 覆盖。所以排查失败时不要只盯着某个单独权限,要从系统角色分配、权限授予来源、用户空间状态三个维度一起看。我自己的习惯是,跑 GTS 之前先把设备恢复出厂,然后把开机向导完整走一遍,最后关闭屏幕超时和分析弹窗,再开始执行测试,这样能避开非常多“历史残留”导致的假失败。
2. 跑测试前的准备工作和结果定位方法
2.1 环境、版本和基础命令
跑 Android 16 GTS 第一件事是确认包版本。不同厂商拿到的 GTS 包可能是 gts_16_r1、gts_16_r2 这类小版本,一定要选择与系统版本匹配的那一版,否则 permission policy 的 baseline 对不上。解压后进入目录,执行:
./gts-tradefed run gts -m GtsPermissionTestcases这是跑全模块的命令。机器上只预置了必要工具的 Linux 环境就行,设备通过 adb 连接,确认设备已经解锁、ADB 授权正常、没有多余弹窗干扰。整个跑完大概需要半小时到一个小时,具体取决于设备性能和用例数量。
如果只想先跑一部分用例快速验证,可以用:
./gts-tradefed run gts -m GtsPermissionTestcases --test-filter <测试类名>测试类名不填包名全路径时,tradefed 会自动匹配。比如跑某个类下的全部方法,直接写类名;跑某个具体方法,就写成“类名#方法名”。这种精确筛选方式在开发阶段特别有用,可以省掉大量等待时间。
2.2 从 test_result.xml 里准确提取失败项
测试跑完后,所有结果都归档在 gts 目录下的 results 文件夹里,每次 session 一个独立子目录,核心文件是 test_result.xml。我第一次看这个 XML 的时候也犯过迷糊,不知道怎么快速抓失败用例。后来找到一个非常直接的办法:用 grep 过滤 Failed 和 NotExecuted 节点。
grep -E "<(Failed|NotExecuted)" test_result.xml这样能一次性把失败的测试 name 和 failed_scenario 抓出来,再配合日志文件里的 stack trace 定位具体原因。如果失败项很多,也可以写个简单的 Python 脚本把 XML 解析成表格结构,方便后面按模块汇总。说句实在话,用脚本整理结果不是装优雅,而是 GtsPermissionTestcases 失败项经常会出现“同样一个类,多个方法连续挂掉”的情况,靠肉眼扫文本容易漏。
2.3 重试机制的正确用法
GTS 第一轮跑完,一定有那种偶发失败,比如设备响应超时、弹窗遮挡、性能波动导致的用例中断。遇到这种情况不用急着排除,先重试一次再看。tradefed 里重试命令通常是这样:
run gts --retry在 gts-tradefed 的交互 shell 里直接执行,不带参数就是重试上一次 session 里失败和未执行的用例;如果想指定重试某个历史 session,可以在命令后面跟上 session id。重试能过滤掉一部分环境类假失败,剩下的才是真需要关注的逻辑问题。我个人建议第一轮全量跑完后,无论失败多少,都先重试一次,再开始逐个分析,不然很容易把精力浪费在“跑一次都不一定复现”的偶发问题上。
3. 跳过权限检查的几种实操方式
3.1 执行阶段的以命令直接跳过
最直接、最容易出效果的跳过方式,就是在 tradefed 执行命令时加上排除参数。比如我只想验证除 GtsPermissionTestcases 之外的其他模块,又不想改任何系统配置,可以在跑 GTS 时把权限模块的某个类或者某个方法排除掉:
run gts -m GtsPermissionTestcases --exclude-filter <测试类名>这个方法级别的排除同样支持:
run gts -m GtsPermissionTestcases --exclude-filter <测试类名>#<测试方法名>从实际经验来看,排除掉单个方法是最常用的。很多时候你只是临时要跑另一个模块,不想让权限模块里某个已知的环境类用例卡住整个流程;或者你在开发阶段已经确认某个权限逻辑和 AOSP 不一致,需要后续版本修复,但现在又要验证其他功能,这时候用 exclude-filter 最省事。不过要提个醒:排除参数在 tradefed 里是按类名精确匹配的,拼错一个字母会直接导致排除不生效。正确做法是从 test_result.xml 里复制完整类名和方法名,不要手敲。
多个排除项用逗号分隔也可以,比如:
run gts -m GtsPermissionTestcases --exclude-filter <类A>#<方法1>,<类B>#<方法2>这种方式在完整成文的命令行里比较方便,不用反复进入交互模式。还有一个选项是--skip-all,它表示把所有测试模块的 precondition 都跳过,但这不等同于跳过权限用例本体,不要混为一谈。
3.2 测试计划里的 ignore 和 skip 机制
如果你拿到的是可修改源码的 GTS 测试工程,或者在自己的 AOSP 维护分支里同步了 GTS 测试代码,那还可以从测试代码本身入手,给特定用例加@Ignore注解。不过这里必须说清楚:GTS 官方包是不允许改测试代码的,因为 GTS 的最终结果需要上传给 Google 做 GMS 认证审核,改动测试代码到后期很难解释清楚。所以这个办法只适用于你本地有测试源码、且不是为了最终认证提交的探索性场景。
还有一种做法是在测试工程的 AndroidTest.xml 里通过配置项跳过某类用例,但对于 GtsPermissionTestcases 来说,实际用得比较少。因为权限测试用例大多数是直接测试系统行为的,跟测试用例配置的关联不如那些需要额外 mock 的模块强。我自己的判断标准是:如果是本地验证和调试,用 @Ignore 或者 exclude-filter 都行;如果是准备最终认证,就不要在测试层面上动手脚,老老实实去修系统行为,或者把问题上报给 Google 作为已知问题沟通。
3.3 从系统配置层面让“权限检查”直接通过
这才是真正解决权限测试失败的根治方案。很多失败不是测试套件本身的 bug,而是设备系统里的权限相关配置和 AOSP 预期不一致,导致测试检查时发现异常。最典型的几个问题点:
第一,default-permissions.xml 被厂商改过。设备上的/etc/permissions/default-permissions.xml或/system/etc/permissions/default-permissions.xml里如果加了自定义的默认授权,GTS 比对时就会发现和 AOSP 不匹配。这一类问题通常的修复方式是把文件内容还原到 AOSP 默认版本,或者把不合理的权限授予项删掉。
第二,预置应用在安装时被额外授予了权限。有些项目在第一次开机安装预置应用时会调用 PackageManager 的 grantRuntimePermission,导致测试中途检查到权限状态异常。排查时可以用 adb 查询:
adb shell cmd permission list-permissions -t看设备当前的运行时权限授予状态,和测试用例预期逐个对比。
第三,appops 状态不同步。权限授予了,但 appops 表里对应的 op 状态异常,也是常见的失败原因。可以先用:
adb shell cmd appops get <包名>确认某个 app 的各 op 模式。如果和预期不一致,可以尝试通过重置命令恢复:
adb shell cmd appops reset --package <包名>或者干脆把设备恢复出厂再重新配置。系统侧的“跳过权限检查”,本质上不是跳过,而是把系统行为修正到测试期望的状态,让检查自然通过。这也是我强烈建议优先采用的路径,因为最终 GTS 报告里如果充满大量“被排除”“被跳过”的用例,Google 在认证审核时一定会找你要解释,到时候再回头修系统,代价比一开始就修大得多。
3.4 什么时候才应该用“跳过”方案
说到底,跳过权限检查只是权宜之计。我一般把请求分成三类:第一类是临时验证,比如我只需要跑 GTS 别的大模块,不想等权限模块的 N 个用例跑完,直接用 exclude-filter 过滤掉,这个没有任何问题;第二类是已知问题,失败根因已经定位到是 AOSP 已知行为或设备硬件差异,有完整日志和复测记录,这种情况下可以暂时排除并在提交认证时做说明;第三类是尚未定位的失败,这种绝对不能靠跳过混过去,一定要先把根因弄清楚再决定怎么处理。
我见过最坑的项目是,前期开发图省事,遇到权限失败就加 exclude-filter,最后到认证前才发现有 20 多个用例根本没法编进白名单,底层权限策略完全是乱的,最后一个月都在返工。所以实操上我建议:每天跑权限模块时,如果遇到失败先看一眼根因,哪怕是相同的用例连续三天失败,也值得花十分钟分析一下 crash 日志,而不是无脑屏蔽。
4. 常见问题与排查技巧实录
4.1 高频失败场景速查表
| 现象 | 常见原因 | 推荐处理方式 |
|---|---|---|
| 整模块大量失败,失败点分散 | default-permissions.xml 或系统预置应用默认权限被改动 | 还原 AOSP 默认权限文件,测试前完成开机向导 |
| 某个权限用例反复超时 | 系统弹窗、UI 动画或无障碍服务干扰 | 关闭动画、弹窗,恢复出厂后重试 |
| exclude-filter 不生效 | 类名/方法名拼写错误,参数化用例实际名称和源码不同 | 从 test_result.xml 中复制完整名称 |
| 重试后仍失败,日志里有 NPE | 系统权限数据库状态异常 | 尝试pm clear-permission-flags或恢复出厂 |
| 跳过后再跑其他模块仍失败 | 权限服务状态被污染 | 重启 system_server 或整机 reboot |
表格里的第四项值得单独展开一下。权限数据库状态异常在 Android 16 上出现的概率不低,尤其是从旧版本 OTA 升级上来的设备,旧用户空间里的 runtime-permissions.xml 记录经常和 GTS 预期不一致。这类问题用排除命令是解决不了的,必须清理权限状态。我试过的有效做法是把相关包卸载后重装,或者直接恢复出厂。如果你不想整机重置,可以尝试:
adb shell pm clear-permission-flags <权限名> user-set --package <包名>清除掉“用户手动设置”的标志位之后,再重新授予或拒绝目标权限,有时候能临时绕过状态不一致的问题。但注意这只适合调试场景,最终认证前还是建议在干净用户空间里重跑全套。
4.2 关于跳过率和 GTS 上报的几个细节
很多工程师以为只要 GTS 跑完没有 failed,只有 skipped 就没问题。实际上 GTS 上报时会统计模块执行率和跳过率,GtsPermissionTestcases 作为认证重点模块,如果跳过率明显高于正常水平,Google 审阅报告时很可能会标记为“需提供解释”。这里分享一个我从实际认证过程中学到的经验:尽量让最终上报的那一轮 GTS 保持低跳过率,所有曾经被 exclude 过的用例,最终都要回到系统修复或明确理由的轨道上。
另外有同行问过我:能不能修改 test_result.xml 来“手动跳过”?我的建议是绝对不要。GTS 结果在提交时会经过安全和完整性校验,修改本地生成报告文件不仅无法通过校验,还可能直接影响项目认证资质,属于高风险操作。宁可失败项多一点,用重试和系统修复把它变成通过,也不要动报告的歪脑筋。
4.3 设备本身状态清理的心得
跑权限测试模块前,我建议按下面这个顺序把设备环境清理一遍:
- 恢复出厂设置,确保没有旧数据污染。
- 完整走一遍开机向导,不要直接跳过。
- 关闭“开发者选项”里的动画缩放,避免 UI 动画导致的超时。
- 检查设备上是否有多余的通知权限弹窗,必要时临时关闭预置应用的通知。
- 确认时间和时区正确,有些测试依赖系统时间戳,时间错乱会产生奇怪失败。
这套流程看起来简单,但跑 GTS 这类依赖系统状态的测试,环境清理往往决定了失败率和复现率。我接手的新项目,第一次跑权限模块前都会强制要求测试组按这个流程准备设备,之后排查问题能少走很多弯路。
5. 不要只盯着“跳过”,先把失败归因做扎实
GtsPermissionTestcases 的失败归因,说难不难,说容易也不容易。大多数失败最终都能落到四个维度:权限授予配置、appops 状态、固件版本差异、测试环境残留。我处理的时候会在每次跑完测试后,把失败用例的完整类名、错误信息、设备当时的权限状态一起导出存档,方便对比验证。日志里如果出现权限枚举不一致,优先检查设备是不是有深层定制的 permission; 如果出现空指针,优先怀疑权限状态库损坏;如果出现超时,优先排查弹窗和动画。
这里顺带说一个 Android 16 的新情况:由于系统权限策略更细,部分厂商把权限控制器裁剪或者替换成了自己的版本,导致 GtsPermissionTestcases 在调用 permission controller 相关接口时拿到异常返回值。遇到这种问题,命令行排除和系统配置都帮不上忙,只能把权限控制器还原到 AOSP 原生实现。判断方法也不复杂,在失败日志里搜“PermissionController”,如果看到版本号或者包名不是 AOSP 原生,那基本就是这条线的问题。
6. 实操总结与个人建议
跑了几轮 Android 16 GTS 之后,我最大的感受是权限模块的问题很少是孤立存在的。一个 GtsPermissionTestcases 的失败背后,往往连着默认权限配置、预置应用行为、开机向导状态、甚至权限控制器版本这一整条链。所以与其说“跳过权限检查”,我更愿意把它当成一个临时手段,而不是解决问题的终点。
最后分享两个小技巧。第一个,排查时多用cmd permission和cmd appops命令组合,能把设备当前权限状态完整拉出来,比看界面和瞎猜快得多。第二个,如果发现是权限授予状态不一致导致的失败,先试着重启 system_server 或者复跑一次该用例,不要急着改系统配置,因为很多状态类问题在服务重启后就会自愈。这样能把真正需要修改系统配置的问题压缩到最小范围。
如果你现在正被 GtsPermissionTestcases 折磨,希望这篇内容能帮你少踩几个坑。权限模块没有银弹,但把基础流程打扎实,大多数失败都是可以快速定位的。祝你跑测一把过。