# UnivAdbManager **Repository Path**: Da_BingGan/UnivAdbManager ## Basic Information - **Project Name**: UnivAdbManager - **Description**: Univ ADB Manager 长安 UNI-V(2022 款 / 飞鱼 OS / Android 9)车机工具箱。 🖥️ 把中控画面原样投到仪表盘 —— 整屏镜像:中控显示什么,仪表就显示什么(同一画面,中控照常操作) 📱 把任意 App 搬到仪表盘跑 —— 真实窗口、全速、可交互 🧩 在仪表盘上摆小组件 —— 不抓屏,自己画一块桌面 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 2 - **Created**: 2026-10-16 - **Last Updated**: 2026-10-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Univ ADB Manager **长安 UNI-V(2022 款 / 飞鱼 OS / Android 9)车机工具箱。** 内置 AOSP 编译的 ADB 客户端(`libadb.so`),自己连车机 adbd 发命令 —— **不需要 root、不用连电脑、不改系统分区**。 三件核心的事: | 能力 | 一句话 | | ------ | -------------------------------------------- | | **飞屏** | 把中控画面**原样镜像**到仪表:中控显示什么,仪表就显示什么 | | **跨屏** | 把某个 App 的 **task 真正搬到**仪表上跑(真实窗口、全速、可交互) | | **仪表** | 不抓屏、不搬 App,在仪表上**自己画**一块桌面(时钟 / 日期 / 第三方小组件) | 另有应用管理、文件浏览、日志与命令行等配套页。 **许可证**:[Apache-2.0](LICENSE) | 第三方组件见 [THIRD\_PARTY\_NOTICES.md](THIRD_PARTY_NOTICES.md) | 逐按钮操作手册见 [docs/使用说明.md](docs/使用说明.md) --- ## 目录 - [先分清两种"投"](#先分清两种投) - [快速开始](#快速开始) - [顶部连接栏](#顶部连接栏) - [飞屏](#飞屏把中控画面原样搬到仪表) - [跨屏](#跨屏把-app-真正搬到仪表) - [仪表](#仪表在仪表上摆小组件) - [另外三页](#另外三页) - [注意事项](#注意事项) - [车机实测约束](#车机实测约束) - [排障](#排障) - [构建](#构建) - [许可证](#许可证) --- ## 先分清两种"投" 这是最容易混的地方,先搞清能省掉一半困惑: | 你想要的效果 | 用哪个 | 中控会怎样 | | ------------------ | ---------------------- | --------------------- | | **仪表和中控显示同一个画面** | **飞屏**(整屏镜像) | 中控照常操作,App 原地不动 | | **让某个 App 只在仪表上跑** | **跨屏 → 点 App → 在仪表打开** | 该 App 从中间消失(task 被搬走) | | **仪表上只有我摆的时钟/小组件** | **仪表 → 启动飞屏** | 中控完全不受影响 | 一句话:**飞屏投的是"屏幕",跨屏投的是"App",仪表投的是"自绘界面"**。 --- ## 快速开始 1. **车机开调试**:开发者选项 → USB 调试 / 无线调试(App 会尝试自动开); 2. **连上**:地址框默认 `127.0.0.1:5555`,点 **连接**;不知道端口就点 **自动**,或点 **▾** 从设备列表里选; 3. **投屏**:切到「飞屏」页 → 点 **激活仪表** → 点 **全屏飞屏**。 --- ## 顶部连接栏 | 控件 | 用途 | | ----------- | --------------------------------------------------- | | **IP:端口** | 连接地址,默认 `127.0.0.1:5555`,会自动记住 | | **▾** | 跑一次 `adb devices` 列出所有可用设备(含 local transport),点一下即连 | | **自动** | 扫无线调试端口 → 连接 → 切到 5555 | | **连接 / 断开** | 手动连接;连上后按钮变为「断开」 | | **退出** | 停飞屏窗口 → 断 adb → 停保活服务 → 结束进程 | - 左上角 logo **长按**=清除图标缓存并重新加载应用列表(图标显示错乱时用)。 - 连上后会自动检查 `adb_enabled`,没开就顺手打开(下次插 USB 线也能用)。 - 车机 shell 有密码门,所有命令由 App 自动经 stdin 注入密码。 > 车机 `/system/bin/verify_sh` **不支持任何 shell 语法**(重定向、分号、管道、`sh -c` 全不行)。 > > App 内部命令都以独立 argv 下发;你在「日志」页手输命令时也别写 shell 语法。 --- ## 飞屏(把中控画面**原样**搬到仪表) **整屏镜像,不是"挑内容投"**:中控显示什么,仪表就显示什么 —— 当前 App、桌面、 状态栏、弹窗全都一样,中控切界面仪表跟着切。 **流程:激活仪表 → 全屏飞屏 →(看完)关闭飞屏**。 ### 按钮职责(每个只做一件事,互不自动串联) | 按钮 | 只做什么 | | ---------- | ------------------------------------------ | | **激活仪表** | 只发投影激活序列。**不投屏、不出测试画面** | | **全屏飞屏** | 只启动飞屏。没激活过投影会等 15 秒再提示你去点「激活仪表」,**不会替你激活** | | **图片飞屏** | 只投一张静态图片(不走录屏),适合当仪表壁纸;首次选中自动算好适配参数 | | **关闭飞屏** | 只停画面(关窗口 + 停采集)。**不动投影** | | **取消仪表投影** | 只退投影,让仪表回到原车界面 | > 「关闭飞屏」≠「恢复原车仪表」。前者只是撤掉我们画的窗口,后者才是把副屏还回去。 ### 抓屏方式(5 种) | 方式 | 实测帧率 | 适合 | | --------------------- | ---------- | -------------------------------------------------- | | **截屏守护(JPEG 常驻)** | **~26fps** | **默认**。所有通道里最快,且不用推 scrcpy-server | | screencap(PNG 并发) | ~8fps | 内容最稳(截出来就是画面),排查兼容性问题时用 | | scrcpy | — | UNIV无效 功能最全、延迟低;依赖车机 `app_process` / `SurfaceControl` 兼容性 | | screenrecord | — | UNIV无效 不推 server,走系统自带 H.264 录屏 | | 本机录屏(MediaProjection) | — | UNIV无效 不走 adb,但**车机虚拟显示被禁时会黑屏** | **瓶颈始终在设备端采集**,换传输格式救不动 —— 想更快只能换通道。 其余设置:**抓屏屏幕**(不指定就不知道抓的是哪块屏,先点「列出车机屏幕」)、 **截屏画质**、**渲染方式**(副屏只有两种位图后端能显示)、**解码方式**。 ### 画面调整 - **一键适配**:按源分辨率与仪表分辨率算等比居中参数(比例不一致留黑边,不拉伸变形); - **缩放 / 水平偏移 / 垂直偏移 / 亮度**; - **裁剪(水平 / 垂直)**:50% 为铺满;向右裁边缘并放大、向左向外补边缩小,两个方向互不影响; - 所有参数存 `mirror_settings`,关掉 App 再开仍在。 ### 开机自启(三选一,互斥) | 在哪开 | 开机后的动作链 | | ----- | ---------------------------------- | | 「飞屏」页 | 连 adb → 激活仪表 → 等 8 秒 → 全屏飞屏 | | 「跨屏」页 | 连 adb → 恢复上次投到仪表的 App → 投递成功后自动关飞屏 | | 「仪表」页 | 连 adb → 启动仪表桌面(内部先上屏、再补激活) | 三个开关**互斥**,同一时刻只能开一个(都开会在开机时跑两套流程抢仪表屏)。 **触发是三层覆盖**: ``` 冷启动 / 断电重连 → BootReceiver(静态广播) 休眠唤醒 → KeepAliveService(动态注册 SCREEN_ON / USER_PRESENT) 服务被系统杀 → 车机「自启动管理 / 后台白名单」兜底 ``` > ⚠️ 车机大概率**不发 `BOOT_COMPLETED`**(熄火休眠、通电唤醒),所以日常「上车亮屏」 > > 靠 `KeepAliveService` 的亮屏监听。亮屏触发有两条规则: > > **① 8 秒防抖**(一次亮屏会连来 `SCREEN_ON` + `USER_PRESENT` 两条); > > **② 真唤醒(先息屏再亮屏)无条件执行,哪怕 App 在前台**;只有"屏幕本来就亮"的 > > 冗余事件且 App 在前台时才跳过。 --- ## 跨屏(把 App **真正**搬到仪表) 不是镜像,是把 App 的 task 搬到副屏:中控上消失、仪表上全屏跑,状态保留。 **点任意 App → 选「在车机打开 / 在仪表打开」**: | 情况 | 行为 | | ----- | --------------------------------------------------------- | | 已在目标屏 | **只提到前台,不搬**(避免"换个屏就重开") | | 在另一块屏 | 真正搬 task(`am stack move-task <目标屏stackId> true`) | | 还没运行 | 直接在目标屏启动 | > **状态是实时查的**(扫两块屏上的 task),不是只看本地记录 —— 别处投上去的也能正确标记。 > > **桌面(Launcher)不提供"在仪表打开"**:投过去只是一块空主屏。 > > **工具箱自己不能投**:投过去中控就没得操作了,各入口都会拦。 > > **投递成功后会关掉飞屏**:`Presentation` 层级高于普通 App 窗口,不关会把它整个盖住。 ### 跨屏不重建(最有价值的开关) 跨屏搬 App 时,Android 会因为"新屏幕配置不同"**重建这个 App 的界面**(导航会被打断一次)。 这个开关把**中控 dpi 对齐到仪表**,两屏配置一致后实测**重建消失,可以来回切换**。 | 状态 | 行为 | | -- | ------------------------------------------------- | | 开启 | 读仪表 dpi → `wm density <该值>` 对齐中控 → 复查并打印两屏 dpi 对比 | | 关闭 | `wm density reset` 恢复 | > **代价**:整个车机界面按新 dpi 重排,可用空间少约 25%,原生界面会明显变挤 —— > > 「要用时开、用完关」。 > > **只能改中控**:外接屏的 `wm size/density` 改不动(命令返回成功但配置不变), > > 默认屏才能改,所以方向固定是"中控向仪表对齐"。 ### 清空仪表 仪表上有 App 时三选一:**搬回中控并恢复** / **结束 App 并恢复**(状态会丢)/ **只盖住**。 - 前两项会**退出仪表投影**(仪表回原车界面),这是"恢复仪表"的本意; - **只有「只盖住」保留投影**(在仪表上盖一页提示页); - 仪表上到底有哪些 App 是**实测**出来的(`am stack list`),不是只信本地记录。 ### 重启系统UI 一键重启 SystemUI(`am force-stop` → PID 没变则 `am crash` → 复查 → 没回来就 `start-service`)。 **什么时候用**:改过 dpi 又关掉之后,车机界面(状态栏 / 导航栏 / 手势条)没跟着重排。 重启 SystemUI 比只重启桌面更彻底 —— 它一重建,整个车机界面都按当前 dpi 重排。 > **重启本身不改变外观**,只是让界面按**当前 dpi** 重排;dpi 没变的话前后一模一样是正常的。 > > 状态栏会消失 1~3 秒,所以点之前有确认框。 ### 悬浮窗(任意界面一键操作) 开启后在**任何其他 App 的界面**上挂一个操作条(回到本 App 界面时自动隐藏)。 **长按「悬浮窗」按钮切换样式**,立即生效: | 样式 | 形态 | 拖动 | | --------------- | -------- | -------- | | **底部 Dock**(默认) | 屏幕底部横向一条 | 固定 | | **经典竖条** | 右上角竖向小窗 | 可拖,位置会记住 | 内容相同:`[本应用] [投仪表] │ [仪表App1] [仪表App2] [仪表App3]` | 项 | 行为 | | -------------------- | --------------------------- | | **本应用** | 立刻切回工具箱 | | **投仪表** | 把当前前台 App 投到仪表,成功后自动关飞屏 | | **仪表上的 App(最多 3 个)** | **在仪表上 → 拉回车机;不在仪表 → 正常启动** | > 前台是桌面时不会投(会提示"当前是桌面,先打开要投的应用"); > > 首次开启需要「显示在其他应用上层」权限。 --- ## 仪表(在仪表上摆小组件) 不抓屏、不搬 App,**自己画**:用 `AppWidgetHost` 把车机上第三方 App 的小组件摆到仪表上。 切到「仪表」页直接就是画布,不需要再点按钮进入。 ### 顶部横排 | 控件 | 用途 | | -------------- | ------------------------------- | | **启动飞屏** | 把这块桌面投到仪表(先上屏、再补激活;不用先去「飞屏」页激活) | | **开机自启** | 开机自动启动仪表桌面(与另外两个自启开关互斥) | | **DPI 滑块** | 内容 DPI:让第三方组件按更大的标尺重排,比拉伸更清晰 | | **组件:拉伸 / 适配** | 拉伸=容器多大内容多大;适配=拉大格子时真的放大内容 | | **网格 开/关** | 编辑时显示对齐网格 | | **分辨率** | 手动 / 实测 / 记忆 / 估算(手动优先) | ### 右侧竖栏(可滚动) | 控件 | 用途 | | ---------------------- | -------------------------- | | **选底图 / 铺满:裁剪 / 移除底图** | 仪表背景图,铺满方式可切裁剪或拉伸 | | **主题色(强调色)** | 点圆点立即换全局配色 | | **可用小组件** | 车机已安装的组件列表,点一下即加到画布(自动找空位) | ### 画布操作 | 操作 | 效果 | | ----------- | ------------------------ | | 格子左上角 **⠿** | 拖动位置(任何组件都适用,不受组件自身交互干扰) | | 格子右上角 **✕** | 删除 | | 格子右下角 **⤡** | 缩放 | - 布局改动**实时保存**,切走再回来还在; - 坐标存 0~1 相对值,所以编辑器预览和仪表实际渲染位置一定一致; - **副屏没有触摸输入**:交互只能在配置页做,所以「设置 / 点一下」这类按钮只在本页出现。 --- ## 另外三页 | 页面 | 用途 | | ------ | ----------------------------------------------------------------------------------- | | **应用** | 系统/用户、全部/已冻结/未冻结筛选 + 搜索。点应用弹出菜单:版本 / 启动 / 结束 / 卸载 / 冻结 / 解冻 / 备份 / 权限 /(root)浏览数据目录 | | **日志** | 关键节点日志;**长按日志区**切「详细模式」(帧率/广播细节);清空 / 复制 / 日志文件;手动 adb 命令 | | **文件** | 浏览内部存储、新建文件夹、上传、刷新(依赖 adb) | **激活工具**在「应用」页过滤栏最右侧(`激活工具 ▼`),展开后占满内容区、可滚动: Shizuku / 冰箱 / Tasker / App Ops / Hamibot 等,点「激活」下发对应命令。 --- ## 注意事项 1. **App 投仪表后,中控点图标打不开它**:task 绑在副屏,要回来得用「跨屏」搬回来。 2. **停止飞屏 ≠ 恢复原车仪表**:要恢复请点「取消仪表投影」。 3. **投 App 到仪表时飞屏会被自动停掉**(现在成功后会主动关):`Presentation` 层级更高,不关会盖住它。 4. **「投仪表」是搬运不是镜像**:搬动时 App 会重建一次界面(两屏 dpi 不同),要"同一画面"用飞屏。 5. **拉回中控后,仪表会停在「仪表空闲」提示页**,不是卡死;「清空仪表」或再投一个才会关掉。 6. **「跨屏不重建」开启后整个车机 UI 变挤**(dp 空间少约 25%),不需要时记得关。 7. **桌面不能被投到仪表**;工具箱自己也不能投。 8. **小组件配完可能不立刻刷新**:个别组件不推更新,切一次页面即可。 9. **悬浮窗需要「显示在其他应用上层」权限**,首次开启会跳系统设置。 10. **别在驾驶过程中操作**。 --- ## 车机实测约束 这些是**在这台车上撞出来的**,也是本项目所有设计的依据: | # | 约束 | 影响 | | | -- | ---------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------- | | 1 | **虚拟显示拿不到画面** | `createVirtualDisplay` / `MediaProjection` 组合全部黑帧 → 「本机录屏」大概率黑屏 | | | 2 | **抓中控画面只有截图链路** | `screencap` 与 `SurfaceControl.screenshot()` 反射可用,**shell uid 就够**,不需要 root | | | 3 | **上屏只有"普通 View + Canvas"** | SurfaceView 独立 layer、TextureView 的 SurfaceTexture 在 HDMI 通道上都不被合成 | | | 4 | **车机 shell 是 verify_sh** | 不支持 `>` `;` \` | ` `*` `sh -c\`,必须逐条独立 argv | | 5 | **跨屏搬 task 会让 App 重建** | 两屏 dpi 不同→ 配置变更 → Activity relaunch。**可规避 = 开「跨屏不重建」** | | | 6 | **只有 dpi 必须一致,尺寸可以不同** | dpi 一致时 `1280×720` ↔ `1920×720` 搬运也不重建 | | | 7 | **外接屏的 `wm size/density` 改不动** | 命令 exit=0 但配置不变;只有默认屏(中控)能改 | | | 8 | **搬 task 只认 `am stack move-task true`** | 第二参是 **stackId 不是 displayId**;`am display move-stack` 搬整栈、会连带同栈 App,已彻底禁用 | | | 9 | **这台车是"一屏一栈"** | display 0 上只有一个应用栈,装该屏所有 App → 必须逐 task 搬 | | | 10 | **帧率取决于抓屏通道** | 截屏守护(JPEG) ~26fps;screencap 约 8fps,瓶颈在设备端采集 | | | 11 | **副屏没有触摸输入** | 小组件的交互只能在配置页做 | | | 12 | **`Presentation` 层级高于普通 App** | 同一块屏上,飞屏窗口和"投过去的 App"只能二选一 | | --- ## 排障 1. 先看**日志文件**(`UnivAdbManager/logs/`,车机上取不出面板文字);面板不够看就**长按日志区**开详细模式。 2. **仪表黑屏** → 看日志里 `MirrorCtrl` / `screencap` 那几行: - 有帧但全黑 → 渲染侧问题(渲染方式必须选位图那两种); - 没有帧 → 抓屏侧问题(换抓屏方式 / 换抓屏屏幕)。 3. **投仪表提示"识别不到前台应用"** → 多数是停在桌面(桌面不可投);先打开要投的 App。 4. **应用列表首次慢(3~5 秒)** → 首次要逐包读 APK 信息,之后走缓存接近瞬时;装/卸后只解析变化的那些。 5. **图标错乱** → 长按左上角 logo 清缓存重载。 6. 还是不行 → 对照上面的「车机实测约束」,看是不是踩了已知的墙。 --- ## 构建 ### 环境 - JDK 17(Gradle 用) - Android SDK build-tools(`apksigner`) - 或直接用工程自带 wrapper ### 命令 ```bash # 一键:构建 + platform testkey 签名 + 校验指纹 bash build-platform.sh # 或手动 ./gradlew :app:assembleDebug ``` 产物:`outputs/univ-adbmanager-<时间戳>.apk`(AOSP platform 签名,SHA-256 指纹每次校验)。 ### 关键构建配置 - `minSdk 26 / targetSdk 28`(车机是 Android 9) - `abiFilters`:内置 `libadb.so` 的架构(arm64-v8a / armeabi-v7a / x86_64) - `signingConfig`:platform testkey(拿 `WRITE_SECURE_SETTINGS`,用于自动开 USB 调试) --- ## 许可证 本项目自身源码采用 **[Apache License 2.0](LICENSE)**。 捆绑的第三方二进制(AOSP adb、Abseil/protobuf、scrcpy-server、aapt、libpng16/libexpat/libz) 版权归各自作者,详见 **[THIRD\_PARTY\_NOTICES.md](THIRD_PARTY_NOTICES.md)**。 `capdaemon`(截屏守护)为本项目自研,源码在 `tools/capdaemon/`。 ### 合规与免责 本项目为个人自有车辆的学习与自用工具。**不需要 root**,不修改车机系统分区, 不获取、不绕过任何厂商安全机制,与任何汽车制造商 / 车机厂商无关联。 与车机交互只用了两种系统公开能力:**标准 ADB shell** 与 **标准 Android 多显示器 API** (`Presentation` / `ActivityOptions.launchDisplayId` / `AppWidgetHost`)。 仪表投影的激活握手(`InCallClusterBridge`)是对**本机系统广播**的观察与复现。 请勿在驾驶过程中操作。车上帧率、分辨率、shell 行为等结论均来自本机实测, 不同年款 / 系统版本可能存在本质差异,**套用到其它车型前请自行评估**。