TOTO 外部 WebSocket 接口规范
版本:v0.4 · 更新时间:2026-02-17 · 目标:后端统一网关、外部/内部解耦、权限与路由统一治理。 接口共计 35 个 op(含事件流),全部可在 联调助手 中实时验证。
文档说明: 本文档采用「左侧分支目录 + 右侧详情」布局,可直接作为本地 API 门户使用。
点击左侧导航可快速跳转。欲进行交互式测试,请打开 联调助手。
消息信封(统一格式)
请求 (Request)
{
"id": "req-1001",
"op": "device.list",
"token": "session-token",
"data": {},
"ts": 1760000000000
}
响应 (Response)
{
"id": "req-1001",
"ok": true,
"code": "OK",
"message": "success",
"data": {},
"ts": 1760000000042
}
事件 (Event)
{
"event": "notification.received",
"data": { "serial": "192.168.1.196:5555", "packageName": "com.tencent.mm" },
"ts": 1760000000060
}
权限模型
| 等级 | 说明 | 示例 |
|---|---|---|
Public | 无需登录 | system.ping |
LoginRequired | 需要登录 | device.list, ime.list |
ProRequired | 高级能力(预留) | AI 自动化策略集 |
InternalOnly | 仅内部通道 | UI 维护类命令 |
目标选择器与路由优先级
target 结构
{
"target": {
"serials": ["192.168.1.196:5555"],
"deviceIds": ["ca32f152c0f9521b"],
"displayIndexes": ["2"],
"allOnline": false
}
}
设备接口至少提供一个选择器。通知相关接口建议始终传
serial,避免多路由不确定性。
通道策略(已执行)
| 能力 | 优先 | 回退 | 说明 |
|---|---|---|---|
| 输入控制 (key/pointer) | scrcpy_control | adb_tcp | 低延迟、适合实时控制 |
| 输入法列表 (ime.list) | scrcpy_server | adb_tcp | 优先快速链路,避免 ADB 慢查 |
| 文件传输 | scrcpy_file | adb_tcp | 大文件优先快通道 |
| 应用管理 | adb_tcp | - | 以系统命令稳定性优先 |
分支 01:System / Device
| OP | 功能 | 目标要求 | 状态 |
|---|---|---|---|
system.ping | 网关探活 | 无 | 已实现 |
device.list | 列出设备及在线状态 | 无 | 已实现 |
device.identity.get | 查询 serial/deviceId | 可选 target | 已实现 |
device.update | 更新 alias/displayIndex | 需要 target | 已实现 |
分支 02:Screen
| OP | 功能 | 目标要求 | 状态 |
|---|---|---|---|
screen.capture | 单设备截图 | 需要 target | 已实现 |
screen.capture.batch | 批量截图 | 需要 target | 已实现 |
分支 03:Input / Clipboard
| OP | 功能 | 通道 | 状态 |
|---|---|---|---|
input.key | 按键 keyCode | scrcpy_control | 已实现 |
input.quickkey | home/back/recent/menu/power | scrcpy_control | 已实现 |
input.pointer | 点击/拖拽/触摸 | scrcpy_control | 已实现 |
input.scroll | 滚动 | scrcpy_control | 已实现 |
input.text | 单设备文本输入 | Toto IME | 已实现 |
input.text.batch | 批量文本输入 | Toto IME | 已实现 |
clipboard.write | 写入剪贴板 | scrcpy_control(无 ADB 回退) | 已实现 |
分支 04:IME
| OP | 功能 | 关键点 | 状态 |
|---|---|---|---|
ime.list | 获取输入法列表 | scrcpy_server 优先 | 已实现 |
ime.select | 切换输入法 | 需要 imeId |
已实现 |
ime.ensure.toto | 确保 Toto 可用 | 批量初始化 | 已实现 |
ime.install.toto | 安装 Toto IME | 需要 apkPath |
已实现 |
推荐策略:
imePolicy=toto_required,确保中英日韩等多语种稳定上屏。
分支 05:Notification
| OP | 功能 | 目标要求 | 状态 |
|---|---|---|---|
notification.monitor.set | 开启/关闭监听 | 建议 serial | 已实现 |
notification.stream.subscribe | 订阅通知事件流 | serials + pkgs | 已实现 |
notification.click | 按 key 点击通知 | serial + key | 已实现 |
注意: Android 15/16 的后台拉起限制(BAL hardening)会影响通知点击结果。返回成功不代表一定拉到前台,需结合
click.result 事件判断。
分支 06:App
| OP | 功能 | 备注 | 状态 |
|---|---|---|---|
app.list | 应用列表 | 当前固定返回三方应用 | 已实现 |
app.launch | 启动应用 | packageName | 已实现 |
app.launch.batch | 批量启动应用 | target 批量 | 已实现 |
app.stop | 停止应用 | force-stop | 已实现 |
app.install | 安装 APK | replaceExisting | 已实现 |
app.uninstall | 卸载应用 | 测试机可用 | 已实现 |
分支 07:File
| OP | 功能 | 路由 | 状态 |
|---|---|---|---|
file.upload | 上传文件 | scrcpy_file | 已实现 |
file.pull | 拉取文件 | scrcpy_file | 已实现 |
分支 08:Tag
| OP | 功能 | 目标要求 | 状态 |
|---|---|---|---|
tag.list | 列出分组 | 无 | 已实现 |
tag.create | 创建分组 | name | 已实现 |
tag.update | 修改分组 | tagId | 已实现 |
tag.delete | 删除分组 | tagId | 已实现 |
tag.device.add | 设备入组 | tagId + target | 已实现 |
tag.device.remove | 设备移出分组 | tagId + target | 已实现 |
分支 09:ADB
| OP | 功能 | 限制 | 状态 |
|---|---|---|---|
adb.exec | 执行 adb shell | 当前未强制白名单 | 已实现 |
API 实现细则(基于当前代码)
通用执行规则(先看)
- 统一信封: 请求必须是 JSON 对象,包含
id/op/token/data/ts。data不是对象会报BAD_REQUEST(多数接口)。 - 设备选择器: 网关解析顺序支持
data.serial、data.serials、data.target.serials、data.target.deviceIds、data.target.displayIndexes、data.target.allOnline。 - displayIndex 兼容:
2/02/002会归一后匹配,避免前端显示编号和接口编号格式不一致。 - 批量返回模型: 大部分设备接口外层固定
ok=true/code=OK,每台设备结果在data.results[]里看success/code/message/detail/data。 - 无目标行为: 目标为空时不报错,返回
message=no_targets,total=0。
11.1 System / Device(4)
| API | 请求字段(当前实现) | 执行逻辑(真实) | 返回与错误码 |
|---|---|---|---|
system.ping |
data 可空对象 |
网关本地直接返回,不依赖设备和 provider。 | data: status/service/op/serverTimeUnixMs/versionmessage: pong |
device.list |
无额外字段 | 调用设备快照 provider,并按 displayIndex + serial 排序。 |
data: devices[] + count,字段含 serial/androidId/model/status/displayIndex/alias/connectMode/isOnline/width/height |
device.identity.get |
data.target 可选:serials/deviceIds/displayIndexes/allOnline |
按 target 过滤设备;displayIndex 自动做 2/002 等价匹配。 |
data: identities[] + count错误: BAD_REQUEST(data JSON 非法) |
device.update |
data.items[],每项可带 serial/deviceId/androidId,更新字段是 displayIndex 或 alias |
设备定位优先 serial,其次 androidId/deviceId;displayIndex 会标准化为 3 位(如 2→002);alias 截断到 64 字符。 |
data: results[]/total/updated/failed项级错误码: DEVICE_NOT_FOUND、ANDROID_ID_UNAVAILABLE、index_conflict、invalid_index、duplicate_index_in_request、invalid_androidId、UPDATE_FAILED、BAD_REQUEST、INTERNAL_ERRORdetail: 失败时会透出 error/conflictIndex/conflictOwnerKey/requestedDisplayIndex空 items: message=no_items |
11.2 Screen / Input / IME(13)
| API | 请求字段(当前实现) | 执行逻辑(真实) | 返回与错误码 |
|---|---|---|---|
screen.capturescreen.capture.batch |
设备选择器 + 可选 saveDir、format |
逐设备截图,文件名为 {displayIndex}_{yyyyMMdd_HHmmss}.png;当前实现底层走 ADB capture。 |
项级 data: serial/filePath/sizeBytes项级错误码: CAPTURE_FAILED、INTERNAL_ERRORdetail: 含路由/原始结果 |
input.key |
keyCode 必填;action 可选(down/up,默认 down);metaState 可选 |
优先 scrcpy_control(SendKeyFromWeb),无 scrcpy 时回退 ADB input keyevent。 |
项级 data: serial/keyCode/action/metaState项级错误码: BAD_REQUEST、INPUT_FAILED、INTERNAL_ERROR |
input.quickkey |
quickKey 或 action 必填 |
支持 home/back/recentapps(recent/recents/overview)/menu/power;映射 keyCode 后走 scrcpy 或 ADB。 |
项级 data: serial/quickKey/keyCode项级错误码: BAD_REQUEST(不支持的 quickKey)、INPUT_FAILED |
input.pointer |
action/x/y,可选 pointerId/screenWidth/screenHeight |
scrcpy 时直接发送触控;ADB 回退时坐标支持 0~1 比例或绝对值,move 用短 swipe,其他用 tap。 |
项级 data: 含归一化坐标和绝对坐标 项级错误码: INPUT_FAILED、INTERNAL_ERROR |
input.scroll |
x/y/deltaX/deltaY,可选 screenWidth/screenHeight |
scrcpy 时发送滚动事件;ADB 回退按 deltaY 计算 swipe 距离(有最小/最大保护)。 |
项级 data: 含 startY/endY 等计算结果项级错误码: INPUT_FAILED |
input.textinput.text.batch |
text 必填;可选 imePolicy |
scrcpy 在线时按文本策略选路由:toto_ime_broadcast / clipboard_paste / scrcpy_text;无 scrcpy 时回退 toto_ime_broadcast_adb。 |
项级 data: serial/route/textLength项级错误码: BAD_REQUEST、TEXT_CHANNEL_UNAVAILABLE |
clipboard.write |
text 必填 |
要求活跃 scrcpy 通道(SendSetClipboard),不走 ADB 回退。 | 项级错误码: SCRCPY_NOT_READY、BAD_REQUEST项级 data: serial/textLength |
ime.list |
只需要设备选择器 | 先尝试 scrcpy_server(1.2s 超时);失败回退 ADB(ime list/settings)。 |
项级 data: currentImeId/inputMethods[]/countinputMethods 字段: imeId/packageName/serviceName/appName/enabled/isCurrentdetail: route=scrcpy_server 或 route=adb_tcp + fallback 原因 |
ime.select |
imeId 必填;enableIfNeeded 可选(默认 true) |
执行 ime enable + ime set 并校验 default_input_method;切到 Toto 后自动尝试补齐通知监听/悬浮窗链路。 |
项级 data: requestedImeId/resolvedImeId/currentImeId项级错误码: IME_NOT_FOUND、IME_SWITCH_FAILED |
ime.ensure.toto |
无额外字段 | 固定确保 com.toto.ime/.TotoImeService:enable/set + listener 权限 + overlay 权限 + monitor enable。 |
项级 data: resolvedImeId/currentImeId/monitorEnabled项级错误码: TOTO_IME_NOT_FOUND、IME_SWITCH_FAILEDdetail: 多行步骤日志 |
ime.install.toto |
可传 apkPath/localPath,不传会自动找默认路径 |
先 install -r,若命中 INSTALL_FAILED_TEST_ONLY 再走 -r -t。 |
项级错误码: APK_NOT_FOUND、INSTALL_FAILED项级 data: serial/apkPath |
11.3 Notification(3)
| API | 请求字段(当前实现) | 执行逻辑(真实) | 返回与错误码 |
|---|---|---|---|
notification.monitor.set |
enabled/autoGrant,目标可用 serial 或 target.serials/allOnline |
逐设备执行;若 enabled=true && autoGrant=true,会尝试 listener + overlay 授权,然后广播开关监听。 |
data: results[]/total/successCount/failedCountresult 字段: serial/success/enabled/error/detail无目标: message=no_targets |
notification.stream.subscribe |
serials[]、packageNames[]、dropEmptyText(默认 true) |
订阅条件按 clientId 存在网关内存;后续收到通知时按 serial/pkg/dropEmptyText 过滤推送。 | message: subscribeddata: subscribed/clientId/serialCount/packageCount/dropEmptyText错误: BAD_REQUEST(缺 client id) |
notification.click |
serial 必填,key 必填(兼容 notificationKey);可选 packageName/pkg/notificationId(id)/title/text/autoGrant |
provider 中先校验设备在线;可选先授 overlay,再广播点击通知。 | 外层: 常为 ok=true/code=OK,结果在 data.resultresult.code 常见: OK、BAD_REQUEST、DEVICE_NOT_FOUND、NOTIFICATION_CLICK_FAILED、INTERNAL_ERROR |
11.4 App / File(8)
| API | 请求字段(当前实现) | 执行逻辑(真实) | 返回与错误码 |
|---|---|---|---|
app.list |
仅设备选择器 + 可选 includeSystem |
按设备用 ADB 获取三方应用列表。 | 项级 data: apps[]/count(packageName/appName)detail: route=adb_tcp |
app.launchapp.launch.batch |
packageName 必填 |
ADB 执行 am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -p {pkg}。 |
项级错误码: APP_LAUNCH_FAILED项级 data: serial/packageName |
app.stop |
packageName 必填 |
ADB 执行 am force-stop。 |
项级错误码: APP_STOP_FAILED |
app.install |
apkPath 或 localPath 必填;replaceExisting 默认 true |
本地文件存在校验后安装,支持 test-only 失败自动重试 -t。 |
项级错误码: APK_NOT_FOUND、INSTALL_FAILED项级 data: serial/apkPath/replaceExisting |
app.uninstall |
packageName 必填 |
ADB 执行 pm uninstall。 |
项级错误码: UNINSTALL_FAILED |
file.upload |
localPath + remotePath 必填 |
先走 scrcpy 文件通道;失败回退 ADB push。remotePath 以斜杠结尾时自动补本地文件名。 |
项级 data: serial/localPath/remotePath/sizeBytes/route项级错误码: LOCAL_FILE_NOT_FOUND、UPLOAD_FAILED |
file.pull |
remotePath 必填;localDir 可选 |
先走 scrcpy 文件通道;失败回退 ADB pull。未给 localDir 时默认 Tmp/external-pulls。 |
项级 data: serial/remotePath/localPath/sizeBytes/route项级错误码: PULL_FAILED |
adb.exec |
command 必填;可选 safeMode |
按设备执行 ADB shell 命令并回传 output。safeMode 字段目前未参与后端校验。 |
项级 data: serial/command/output项级错误码: ADB_EXEC_FAILED |
11.5 Tag(6)
| API | 请求字段(当前实现) | 执行逻辑(真实) | 返回与错误码 |
|---|---|---|---|
tag.list | 无 | 读取网关内存中的标签仓库(按 name 排序)。 | data: tags[]/count注意: 当前为进程内存态,重启后需重新同步 |
tag.create |
name 必填;color/description 可选 |
生成 GUID 作为 tagId,记录创建/更新时间。 |
message: createddata: tag |
tag.update |
tagId 或 id 必填;可选更新 name/color/description |
局部字段更新,未传字段保持不变。 | message: updated错误: TAG_NOT_FOUND、BAD_REQUEST |
tag.delete |
tagId 或 id 必填 |
删除标签,若不存在不会抛错。 | message: deleted 或 not_founddata: tagId/deleted |
tag.device.add |
tagId 必填;设备来源可用 target 选择器,也可直接 deviceIds[] |
将目标设备集合合并去重后加入标签。设备集合为空时返回 no_targets。 |
data: tag/changed/total/action错误: TAG_NOT_FOUND |
tag.device.remove |
同上 | 将目标设备集合从标签中移除。 | data: tag/changed/total/action |
错误码建议
| Code | 说明 |
|---|---|
OK | 成功 |
BAD_REQUEST | 参数错误/缺失 |
UNAUTHORIZED | 未登录或 token 无效 |
FORBIDDEN | 权限不足 |
DEVICE_NOT_FOUND | 目标设备不存在或离线 |
IME_UNAVAILABLE | 输入法不可用 |
NOTIFICATION_CLICK_FAILED | 通知点击失败 |
NOTIFICATION_BAL_BLOCKED | 后台拉起受系统限制 |
TASK_FAILED | 批量任务失败 |
RATE_LIMITED | 触发限流 |
事件流(Notification 重点)
notification.received
{
"event": "notification.received",
"data": {
"serial": "192.168.1.196:5555",
"deviceId": "ca32f152c0f9521b",
"packageName": "com.tencent.mm",
"title": "微信",
"text": "2个联系人发来4条消息",
"key": "0|com.tencent.mm|-91455226|null|10323",
"id": "-91455226",
"postedAtUnixMs": 1771143920411,
"receivedAtUnixMs": 1771143920478
},
"ts": 1771143920478
}
notification.click.result
{
"event": "notification.click.result",
"data": {
"serial": "192.168.1.196:5555",
"key": "0|com.tencent.mm|-91455226|null|10323",
"success": true,
"verified": false,
"reason": "notification_still_active",
"error": ""
},
"ts": 1771143922780
}
安全与性能基线
- 网络边界: 外部 WS 默认仅监听
127.0.0.1,公网暴露需加代理鉴权。 - 鉴权审计: 强制 token 校验与会话过期,保留审计日志(账号/设备/op/耗时/结果)。
- ADB 防护: 建议上白名单与危险字符拦截;当前实现尚未强制。
- 流控: 消息体大小限制 + 连接级限流,避免拖垮 HXT 主线程。
- 性能优先: 涉及实时控制场景,优先 scrcpy 通道,避免 ADB 轮询导致延迟尖峰。
本规范为本地工程文档,最终行为以
ExternalApiGateway 和后端映射代码为准。
如需交互式验证,请打开 联调助手。
TOTO External WS API Spec · TOTO · Local HTML Doc · 2026-02-17