1. 6a828 平台 Android 5.0 USB 触摸屏 SHOW_TOUCHES 轨迹异常问题复现
在 6a828(MStar pitaya 方案)的 Android 5.0 电视盒子上,我遇到过一类很典型的现象:USB 免驱触摸屏平时用得好好的,一旦打开开发者选项里的「显示触摸位置」(也就是Settings.System.SHOW_TOUCHES),系统就开始抽风。具体表现是多次点击、双点操作时系统没反应,遥控器按键打印正常但界面卡死不动,am start也拉不起 Activity,而且不会重启,属于「假死」状态。
这个问题的核心检索词就是 android usb触摸屏 SHOW_TOUCHES 轨迹异常,它牵扯到hid-multitouch上报、ENABLE_HWCURSOR编译开关、以及 Android 输入子系统里 PointerController 的绘制逻辑。适合正在做 Android 5.0 智能硬件、机顶盒、一体机触摸调试的嵌入式工程师参考。
先把现象拆清楚,方便你对照自己的板子:
当SHOW_TOUCHES = false时,多次点击、双点、遥控器操作全部正常,触摸屏走hid-multitouch标准驱动,PC 上和其他硬件平台都工作正常,说明触摸屏硬件和驱动本身没问题。
当SHOW_TOUCHES = true时,问题来了。getevent抓到的原始事件完全正常,logcat也有输出,但系统点击就是没反应。触摸按下的白色圆点(就是 SHOW_TOUCHES 画出来的那个轨迹点)再也不出现了。比如进计算器 App,白色圆点消失,但按屏幕数字键还能起作用;从计算器用遥控器返回桌面就黑屏了。即使黑屏,getevent依然正常上报。更关键的一点:只要两个手指同时按下,马上触发这个问题。
这里有个容易误判的点:很多人看到getevent正常就以为输入链路没问题,其实getevent读的是/dev/input/eventX的原始事件,而 SHOW_TOUCHES 的轨迹绘制发生在 Android framework 的 InputReader/InputDispatcher 之后,走的是 PointerController 和 SpriteController。原始事件正常不代表上层绘制正常,这两层要分开看。
getevent -p的输出能帮我们确认设备注册了哪些事件类型。以我这块板子为例:
shell@pitaya:/ # getevent -p add device 1: /dev/input/event4 name: "SZiB0ardelectronics IboardDevice" events: KEY (0001): 014a ABS (0003): 0000 : value 21987, min 0, max 32767, fuzz 0, flat 0, resolution 0 0001 : value 22047, min 0, max 32767, fuzz 0, flat 0, resolution 0 002f : value 0, min 0, max 9, fuzz 0, flat 0, resolution 0 0035 : value 0, min 0, max 32767, fuzz 0, flat 0, resolution 0 0036 : value 0, min 0, max 32767, fuzz 0, flat 0, resolution 0 0039 : value 0, min 0, max 65535, fuzz 0, flat 0, resolution 0 input props: INPUT_PROP_DIRECT could not get driver version for /dev/input/mouse2, Not a typewriter add device 2: /dev/input/event0注意INPUT_PROP_DIRECT这个属性,它表示这是直接输入设备(触摸屏),不是间接设备(鼠标)。ABS_MT_POSITION_X/Y(0035/0036)、ABS_MT_TRACKING_ID(0039)、ABS_MT_SLOT(002f)都在,说明hid-multitouch正确注册了多点触控能力。问题不在驱动注册,而在上层对轨迹的处理。
我实测下来,这个现象和ENABLE_HWCURSOR编译开关强相关。在./mstar/pitaya/BoardConfigCommon.mk第 144 行有:
ENABLE_HWCURSOR := true把它改成undef ENABLE_HWCURSOR(或者注释掉),问题就消失了。这个开关控制的是硬件光标层,涉及PointerController.cpp和SpriteController.cpp两个文件。有意思的是,只注释PointerController.cpp不行,必须同时注释SpriteController.cpp才好用。这说明 SHOW_TOUCHES 的轨迹绘制和 HWCURSOR 的光标绘制在 Sprite 层产生了冲突,两个绘制源抢同一块 sprite 资源,导致输入事件被吞掉。
下面我会从内核配置、input 事件抓取、TaoToken 调试通道整理日志三个角度,给出一套可复现的排查清单。
2. TaoToken 统一 Key/API 通道整理调试日志
排查这类输入子系统问题时,最烦的不是定位,而是日志太散:getevent一份、logcat一份、内核dmesg一份、编译配置又一份,来回切换很容易漏掉关键线索。我习惯用 TaoToken 的统一 API 通道把这些日志喂给模型做交叉分析,省得自己一行行对。
TaoToken 是一个统一的大模型 API 接入通道,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。它能做什么?简单说,就是把不同模型的调用收敛到一个 Base URL 和一把 Key 上,你不用为每个模型单独配环境。适合谁?像我这样在嵌入式调试里需要频繁让模型帮忙读日志、比对配置、生成排查脚本的人。
为什么调试输入子系统要用它?因为这类问题的线索是跨层的:getevent的原始事件、logcat里 InputReader 的 dispatch 记录、dmesg里 hid-multitouch 的 probe 信息、还有BoardConfigCommon.mk的编译开关。人眼在四个终端之间跳,很容易看花。把这几份日志拼成一段上下文丢给模型,让它找出「SHOW_TOUCHES 打开后哪一层开始丢事件」,效率高很多。
前置准备很简单,你只需要:
第一,拿到一把可用的 API Key。进控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 创建,或者在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 管理你的密钥。建议给调试用途单独建一把 Key,方便随时吊销。
第二,确认 Base URL。API 调用统一走 https://taotoken.net/api ,注意这个地址不带 UTM 参数,是纯接口地址。
第三,选一个模型 ID。调试日志分析这种任务,用长上下文、推理稳的模型比较合适,具体模型名在模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 能看到当前可用的列表。
如果你是要长期做 Android 输入子系统调试、甚至想接 Agent 自动跑排查流程,可以看 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,它更适合持续性的编码和调试任务,不用每次手动拼上下文。
这里要提醒一句:TaoToken 是调试辅助通道,不是替代你本地 adb 和串口工具的东西。getevent、logcat、dmesg还是得在板子上跑,TaoToken 负责的是把这些输出汇总分析。别指望它直接连你的设备,它连的是模型。
我踩过的坑是:一开始把整份logcat不加过滤全丢进去,几万行里大部分是无关的 GC 和 SurfaceFlinger 日志,模型反而抓不住重点。后来改成只截取问题复现前后 30 秒的窗口,再配合getevent的原始事件,定位速度快了一倍。所以日志整理这一步,筛选比堆量重要。
3. 可复制的内核配置与 settings 片段
这一节给你可以直接抄的配置片段。先说结论:问题的根因在ENABLE_HWCURSOR和 SHOW_TOUCHES 的绘制冲突,所以配置要分两块处理——一块是编译期的 BoardConfig,一块是运行期的 Settings。
先看编译期配置。打开./mstar/pitaya/BoardConfigCommon.mk,找到第 144 行附近:
# 原始配置,会导致 SHOW_TOUCHES 轨迹异常 ENABLE_HWCURSOR := true # 修改方案一:直接 undef undef ENABLE_HWCURSOR # 修改方案二:注释掉 # ENABLE_HWCURSOR := true改完之后要重新编译 system 镜像并刷机,因为这是编译期宏,运行期改不了。刷完后验证:
adb shell getprop | grep -i hwcursor # 期望输出为空,说明 HWCURSOR 未启用如果你不想动编译配置,只想在运行期控制 SHOW_TOUCHES,可以用 Settings 命令。Android 5.0 里 SHOW_TOUCHES 是Settings.System的一个整型值:
# 打开触摸轨迹显示 adb shell settings put system show_touches 1 # 关闭触摸轨迹显示 adb shell settings put system show_touches 0 # 读取当前值 adb shell settings get system show_touches代码里对应的写法就是 excerpt 里那段:
Settings.System.putInt( ShowTouchEnableActivity.this.getContentResolver(), Settings.System.SHOW_TOUCHES, Integer.parseInt(result));注意Settings.System.SHOW_TOUCHES这个常量在 Android 5.0 里是隐藏 API,普通 App 调不了,得是系统签名或者有WRITE_SETTINGS权限。这也是为什么很多人在应用层改这个值没效果。
再看hid-multitouch相关的内核配置。确认你的 defconfig 里有:
CONFIG_HID_MULTITOUCH=y CONFIG_HID_GENERIC=y CONFIG_INPUT_EVDEV=y CONFIG_INPUT_FF_MEMLESS=y如果触摸屏是 USB 免驱的,hid-multitouch会自动匹配。可以在dmesg里确认:
adb shell dmesg | grep -i multitouch # 期望看到类似: # hid-multitouch 0003:xxxx:xxxx.0001: input,hidraw0: USB HID v1.10 Device ...关于PointerController.cpp和SpriteController.cpp的修改,如果你选择走源码路径而不是编译开关,位置在:
frameworks/base/services/input/PointerController.cpp frameworks/base/libs/ui/SpriteController.cpp只注释PointerController.cpp里的 sprite 更新逻辑不够,因为SpriteController还在独立维护 sprite 状态,两个控制器对同一个 sprite 的引用计数会打架。必须同时处理SpriteController.cpp里的 sprite 创建和销毁路径,才能彻底避免冲突。这也是为什么「只注释一个不行,两个一起注释才好用」。
这里给一个对照表,方便你判断该改哪一层:
| 配置项 | 位置 | 作用 | 改动后是否需重编 |
|---|---|---|---|
| ENABLE_HWCURSOR | BoardConfigCommon.mk | 控制硬件光标层 | 是 |
| SHOW_TOUCHES | Settings.System | 控制触摸轨迹显示 | 否 |
| CONFIG_HID_MULTITOUCH | defconfig | 多点触控驱动 | 是 |
| PointerController | frameworks/base | 指针绘制 | 是 |
| SpriteController | frameworks/base | Sprite 绘制 | 是 |
注意:改
BoardConfigCommon.mk后一定要make installclean或者至少清掉 system 镜像的中间产物,否则宏可能不生效。我遇到过改了没重编、刷完还是老样子,白折腾半天。
4. 验证请求与成功结果
配置改完,怎么确认问题真的解决了?不能只看「感觉好了」,要有可复现的验证步骤。下面这套流程我实测有效。
第一步,抓原始事件,确认触摸屏上报正常。开两个终端,一个跑getevent,一个操作触摸屏:
adb shell getevent -lt /dev/input/event4正常的多点触控上报长这样:
[ 12345.678901] EV_ABS ABS_MT_SLOT 00000000 [ 12345.678902] EV_ABS ABS_MT_TRACKING_ID 00000001 [ 12345.678903] EV_ABS ABS_MT_POSITION_X 00001234 [ 12345.678904] EV_ABS ABS_MT_POSITION_Y 00005678 [ 12345.678905] EV_SYN SYN_REPORT 00000000双指按下时,应该看到两个不同的ABS_MT_SLOT和ABS_MT_TRACKING_ID。如果这里就异常,那问题在驱动层,跟 SHOW_TOUCHES 无关。
第二步,打开 SHOW_TOUCHES,复现问题:
adb shell settings put system show_touches 1然后快速多次点击、双指同时按下。如果问题复现,界面会卡住,白色圆点消失。这时候抓logcat:
adb logcat -v threadtime -b main -b system > /tmp/touch_bug.log重点看 InputReader 和 InputDispatcher 的 tag:
adb logcat -s InputReader InputDispatcher PointerController问题复现时,你可能会看到类似这样的输出,说明事件被吞:
E InputDispatcher: Dropping event because the pointer is not down W PointerController: Sprite already in use, skip update第三步,改配置后重新验证。把ENABLE_HWCURSORundef,重编刷机,再跑一遍上面的流程。成功的结果是:
adb shell settings put system show_touches 1 # 多次点击、双指按下,界面正常响应 # 白色圆点正常显示和消失 # 遥控器返回桌面正常,不黑屏第四步,用 TaoToken 做交叉验证。把getevent输出和logcat片段拼成一段上下文,通过 API 发给模型分析。请求示例:
curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "your-model-id", "messages": [ {"role": "user", "content": "以下是 Android 5.0 触摸屏调试日志,getevent 正常但 logcat 显示 InputDispatcher 丢事件,请分析 SHOW_TOUCHES 与 ENABLE_HWCURSOR 的冲突点:\n<粘贴日志>"} ] }'成功的话,模型会指出 sprite 资源竞争的具体位置,和你手动定位的结论互相印证。这一步不是必须的,但在日志量大、线索乱的时候能帮你省时间。
验证清单总结成一张表:
| 验证项 | 命令 | 期望结果 |
|---|---|---|
| 原始事件 | getevent -lt | 多点 slot/tracking_id 正常 |
| 轨迹显示 | settings put show_touches 1 | 白点正常出现消失 |
| 双指操作 | 双指同时按下 | 界面不卡死 |
| 遥控器返回 | 按返回键 | 不黑屏 |
| 日志分析 | TaoToken API | 定位 sprite 冲突 |
5. 本篇常见报错排查
这一节把排查过程中真实会撞到的报错列出来,对照着看能少走弯路。
报错一:local proxy failed或连接超时
这个通常出现在你用 curl 调 TaoToken API 的时候。先确认 Base URL 写的是https://taotoken.net/api,不要带多余路径。再确认网络能通:
curl -I https://taotoken.net/api # 期望返回 HTTP/2 200 或 401(401 说明通了但没带 Key)如果返回 401,说明地址对,是 Key 的问题,往下看。
报错二:401 Unauthorized
Key 没带、带错、或者过期。检查请求头:
-H "Authorization: Bearer $TAOTOKEN_API_KEY"注意Bearer后面有一个空格,Key 不要有多余换行。如果你在 shell 里用变量,先echo $TAOTOKEN_API_KEY确认变量真的有值。Key 在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 管理,过期了就重新生成一把。
报错三:reading choices相关解析错误
这个一般是你把模型返回的 JSON 直接当文本处理了。模型返回的是标准 chat completion 结构,内容在choices[0].message.content里。用jq提取:
curl ... | jq -r '.choices[0].message.content'如果jq报Cannot index array with string,说明返回结构和你预期的不一样,先curl ... | jq .看完整结构再取字段。
报错四:OAuth相关提示
如果你用的是 Claude Code 之类的工具接 TaoToken,可能会碰到 OAuth 配置问题。这类工具需要三件套齐全:Base URL、Key、Model ID。以 Claude Code 为例,配置里要写全:
{ "base_url": "https://taotoken.net/api", "api_key": "your-token-key", "model": "your-model-id" }三件套缺一个都会报 OAuth 或认证失败。Model ID 在模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 能查到当前可用的。
报错五:getevent有输出但系统无响应
这就是本篇的核心问题,不是报错,是现象。排查顺序:
先确认ENABLE_HWCURSOR是否为 true,是的话 undef 重编。再确认PointerController.cpp和SpriteController.cpp是否都处理了,只改一个不够。最后确认SHOW_TOUCHES的值,用settings get system show_touches读。
报错六:am start无响应
这是问题复现后的连带现象,不是独立故障。界面卡死后am start拉不起 Activity,因为 InputDispatcher 卡在丢事件的循环里。解决根因后这个现象自然消失,不用单独处理。
报错七:could not get driver version for /dev/input/mouse2, Not a typewriter
这个在getevent -p里很常见,是mouse2节点不是标准 input 设备导致的,属于无害提示,不影响触摸屏功能。不用管它。
排查时建议按这个顺序:先看getevent确认驱动层,再看logcat确认 framework 层,最后看编译配置确认 HWCURSOR。三层逐层排除,比一上来就改代码高效。
6. 接入文档与 API Keys 快速通道
调试通道配好之后,日常用起来其实就三步:抓日志、拼上下文、发请求。如果你想把这套流程固化下来,建议把常用的 curl 命令写成脚本,Key 用环境变量管理,别硬编码在脚本里。
接入相关的文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,里面有完整的接口说明和参数列表。Key 的管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,建议给调试、生产、测试分别建不同的 Key,方便追踪用量和随时吊销。
如果你只是想快速验证某个模型对这段触摸日志的分析能力,直接去模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 粘贴日志试一下,不用写代码。如果是要长期做 Android 输入子系统的调试和 Agent 自动化,Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 更合适,它按持续任务设计,不用每次手动拼请求。
最后说个实用技巧:把getevent和logcat的抓取命令写成一行,问题复现时一键执行,日志自动带时间戳存到固定目录。这样下次再遇到 SHOW_TOUCHES 轨迹异常,直接拿日志去分析,不用现场手忙脚乱。我现在的习惯是板子一上电就跑一个后台脚本持续抓 input 相关日志,出问题时回捞最近几分钟的窗口,比事后复现靠谱得多。