配合 auto_control 平台的设备端补位 App(接口契约见 INTERFACE.md,与平台仓库
doc/DEVICE_AGENT.md 是同一份)。
四个 adb 可拉起的 Activity + 一个商店界面:
- ClipActivity 剪贴板注入(透明 Activity,**必须等窗口获得焦点再写** ——
Android 10+ 只有前台应用能写剪贴板,这正是 u2 set_clipboard
"成功但内容被丢弃"的原因);写完立即退出,不驻留
- InfoActivity 全屏大字显示设备身份(名称/IP/序列号/平台地址),
替代原来"推 HTML 让设备浏览器打开"那套
- ConfigActivity 一次性配置下发(平台用 adb 把 server/token/指纹推进来),
一批设备不用一台台手输
- PingActivity 自检(版本/指纹/配置状态),结果写 logcat + 外部私有目录的
status.json,平台据此判断"装没装、哪一版"
- MainActivity 应用商店:拉平台清单 → 选应用 → 安装;已装的显示「已安装」,
有新版显示「更新到 vX」
实机踩出来的几个坑(都已处理):
- **应用身份读不到 ro.serialno**(shell 能读、应用被 SELinux 挡)→ 指纹改由平台
通过 ConfigActivity 下发(平台本来就知道),本机 getprop 只作兜底
- **Android 9+ 默认拦明文 HTTP** → 平台是局域网 http,需 usesCleartextTraffic
- **Android 11+ 软件包可见性** → 不加 QUERY_ALL_PACKAGES 会"明明装了却显示未安装"
- **MIUI 安装确认不止一步** → 装完检测窗口给到 3 分钟(人会依次点继续→勾选→继续更新)
- 上报带重试(实测遇到过一次网络抖动导致 timeout)
构建:JDK 17 + Android SDK(minSdk 29 / targetSdk 34),只依赖 androidx.core,
HTTP 用 HttpURLConnection、JSON 用系统内置 org.json。
3.6 KiB
3.6 KiB
设备 Agent(DeviceAgent)
装在每台 Android 手机上的设备端补位 App,配合 auto_control 平台使用。 平台通过 adb 驱动设备;这个 App 负责三件 adb 干不好/干不了的事:
| 能力 | 为什么 adb 不行 | 入口 |
|---|---|---|
| 剪贴板注入 | Android 10+ 只有有焦点的应用能写剪贴板,shell/后台写会被系统静默丢弃 | ClipActivity(透明 Activity,adb 拉起) |
| 应用分发(商店) | adb install 会被 MIUI 的「USB 安装」拦成 USER_RESTRICTED;由设备上的 App 自己调系统安装器走的是普通安装流程,不触发那道拦截 |
MainActivity(商店界面,自己联网拉清单) |
| 设备身份显示 | 以前要把 HTML 大字页推给设备浏览器打开,依赖浏览器 | InfoActivity(全屏大字,adb 拉起) |
接口契约
平台的接口协议见 ../auto_control/doc/DEVICE_AGENT.md(那是两个仓库共享的契约;
改协议必须同时改两边)。本仓库里也留了一份副本:INTERFACE.md。
快速开始
# 1. 构建(需要 JDK 17 + Android SDK;也可以用 Android Studio 直接打开)
./gradlew assembleDebug
# 2. 装到设备(平台用网络 adb 管理设备,serial 形如 IP:5555)
adb -s 192.168.20.100:5555 install -r app/build/outputs/apk/debug/app-debug.apk
# 3. 一次性配置平台地址与令牌(不用在手机上手输)
adb -s 192.168.20.100:5555 shell am start -n com.example.deviceagent/.ConfigActivity \
--es server "http://192.168.20.220:18050" --es token "<平台面板里的设备令牌>"
# 4. 自检:确认装上了、看是哪一版
adb -s 192.168.20.100:5555 shell am start -n com.example.deviceagent/.PingActivity
adb -s 192.168.20.100:5555 shell cat /sdcard/Android/data/com.example.deviceagent/files/status.json
# 5. 手动试试剪贴板注入
adb -s 192.168.20.100:5555 shell am start -n com.example.deviceagent/.ClipActivity \
--es text_b64 "$(printf '测试中文' | base64)"
# 6. 让设备显示自己的身份大字页
adb -s 192.168.20.100:5555 shell am start -n com.example.deviceagent/.InfoActivity \
--es name "A08" --es ip "192.168.20.100" --es serial "192.168.20.100:5555" \
--es server "http://192.168.20.220:18050"
然后用手机打开 App 图标 → 就是应用商店(前提是平台侧已启用设备端商店并给了令牌)。
权限说明
只申请三个,都是必需的:
INTERNET—— 从平台拉应用清单、下载 APKREQUEST_INSTALL_PACKAGES—— 调系统安装器装应用(首次会引导去系统设置里授权)ACCESS_NETWORK_STATE—— 判断有没有网
不需要 root、不需要设备管理员、不常驻后台、不申请任何读取隐私的权限。
代码结构
app/src/main/java/com/example/deviceagent/
MainActivity.kt 应用商店主界面(清单/下载/调安装器/回报)
ClipActivity.kt 剪贴板注入(透明,等窗口获得焦点后再写)
InfoActivity.kt 设备身份全屏大字页
ConfigActivity.kt 一次性配置下发(平台用 adb 推地址+令牌)
PingActivity.kt 自检(版本/指纹/配置状态,写 logcat + status.json)
Api.kt 平台接口客户端(HttpURLConnection,无第三方库)
Prefs.kt 配置存储 + 设备指纹(getprop ro.serialno)
刻意只依赖 androidx.core(就为了 FileProvider):HTTP 用 HttpURLConnection、
JSON 用系统内置的 org.json,依赖越少越不容易被构建问题绊住。