Files
MAG160C/docs/android_app/HANDOFF_DEVELOPMENT.md
T

21 KiB
Raw Blame History

MAG160C 统一安卓APP — 完整开发交接文档(换模型用)

生成时间:2026-09-07。给接手模型读取本文件即可恢复全部上下文。 结合 docs/android_app/session_state.md(心跳)与 docs/android_app/reverse_apk_features.md(逆向总表)。


0. 给新模型的读取提示词

你是本项目的续任开发者。请先完整阅读 C:\Project\MAG160C\docs\android_app\HANDOFF_DEVELOPMENT.mdC:\Project\MAG160C\docs\android_app\session_state.mdC:\Project\MAG160C\docs\android_app\reverse_apk_features.mdC:\Project\MAG160C\analysis\protocol_spec.md,再继续开发。项目根目录 C:\Project\MAG160C 是 git 仓库, 源码在 android\ 子目录(Gradle 工程,package com.mag160c.thermal)。当前会话状态与待办见 session_state.md。


1. 项目目标与背景

  • 把 MAG160C 热像仪(160×120, 15fps, USB VID 0x833C)的官方 4 个安卓APP (普通版 MAG-Cx / 专业版 MAG-Mx / ThermoScope 数据分析 / CoreSdkSample)逆向整合 为一款现代化安卓APP。
  • 适配 Android 16API 36;用户反馈旧专业版在 Android 16 上花屏,重写根治。
  • 逆向证据:docs/android_app/reverse_apk_features.md4 APP 功能总表)、 analysis/protocol_spec.mdUSB 协议/温度算法全逆向)。
  • 用户要求:现代 UI、构图钉死竖屏框架(竖屏锁定,见 §4.3)、不建立流氓文件夹(媒体→MediaStore DCIM/MAG160C)、 无 32 位库(纯 Kotlin,无 NDK)。

2. 技术栈与工具链(已部署)

  • 纯 Kotlin + Jetpack Compose (Material 3),无 NDK、无 .so、无 native。
  • 工具链(2026-09-09 本机重装;交接文档旧机器路径 C:\Tools\gradle-8.14.3、 Zulu、C:\Users\ZXC\AppData\Local\Android\Sdk 均已失效)
    • JDK Temurin 21C:\Tools\jdk-21
    • Android SDKC:\Tools\android-sdkcmdline-tools\latest、platforms/android-36、 build-tools 36.0.0、platform-tools/adb;许可已接受;无模拟器/AVD/system-images
    • Gradle:用工程 wrapper android\gradlew.bat8.14.3 自动下载)
    • android\local.properties 已写 sdk.dir(已 gitignore
    • 构建需 JAVA_HOME=C:\Tools\jdk-21;国内下载慢可给 JVM 挂本机代理 (Clash 7890GRADLE_OPTS="-Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=7890 ..."
  • 工程版本目录 gradle\libs.versions.toml AGP 8.11.1 / Kotlin 2.2.0 / Compose BOM 2025.06.01 / lifecycle 2.9.1 / junit 4.13.2)。
  • 磁盘轻量约定:不装 NDK/CMake/zig(曾占用 2.4GB+,已卸载);全 Kotlin。
  • 模拟器实测命令(无窗口):
    Start-Process "$env:LOCALAPPDATA\Android\Sdk\emulator\emulator.exe" -ArgumentList @('-avd','Medium_Phone_API_36.1','-no-window','-no-audio','-gpu','swiftshader_indirect') -WindowStyle Hidden
    # 安装/启动/截图:
    adb -s emulator-5554 install -r build-artifacts\mag160c-app-debug.apk
    adb -s emulator-5554 shell am force-stop com.mag160c.thermal
    adb -s emulator-5554 shell am start -n com.mag160c.thermal/.MainActivity
    adb -s emulator-5554 exec-out screencap -p > analysis\preview\xxx.png   # 注意:exec-out 才二进制安全
    adb -s emulator-5554 emu rotate                                         # 旋转模拟器(每次90°)
    

3. 源码结构与核心模块

android/app/src/main/kotlin/com/mag160c/thermal/
├─ MainActivity.kt           启动,enableEdgeToEdge + 隐藏状态栏(沉浸)
├─ ui/AppRoot.kt             App壳:底部导航(实时/相册/分析/设置)恒贴竖屏底边(Activity 竖屏锁定)
├─ ui/DeviceOrientation.kt   加速度计→手机物理朝向(条栏/OSD 文字补偿旋转用)
├─ ui/UiInsets.kt            单例:底部导航高度 px(渲染器用它留白)
├─ ui/theme/Theme.kt         M3 动态取色(Android12+
├─ ui/live/                  ★实时画面(最核心)
│  ├─ LiveViewModel.kt      状态机/多点测温/追踪开关/拍照/录像/inset上报/PIP状态/
│  │                        远程服务端开关+rawHook→RemoteHost
│  ├─ LiveScreen.kt         稳定单分支相机布局:顶栏5控制项(FFC/变倍/追踪/调色板/画中画)
│  │                        +底部快门区(相册/拍照/录像)+SurfaceView+PIP浮层
│  ├─ PipCameraView.kt      可见光PIP相机引擎(Camera2最小实现,异常只记日志)
│  └─ LiveRenderer.kt       SurfaceView 软件渲染:图像恒90°旋转(3:4竖)钉在竖屏框架固定区域,
│                           文字恒屏幕水平;色标条/圆圈标记/中心温OSD
├─ ui/gallery/              相册页(MediaStore DCIM/MAG160C 扫描 + 内嵌JPEG缩略图 + 运行时媒体权限)
├─ ui/analyze/              MDT 离线分析(缩放/调色板重渲染/温度条+点击测温/备注回写/PDF报告)
├─ ui/settings/             设置页(默认调色板/发射率/报警温度/语言/云同步/远程预览两行/关于)
├─ ui/remote/               远程预览客户端(Phase F
│  ├─ RemoteClientListScreen.kt 主机列表(UDP发现/手动IP/重新扫描)
│  ├─ RemoteViewerViewModel.kt  本地管线渲染远端原始帧(调色板/变倍本地化)
│  ├─ RemoteViewerScreen.kt     查看页(与实时页同构图,快门区换成红色"断开"
│  └─ RemoteRendererHost.kt     与 LiveRenderer 同构图的渲染器(LiveRenderer 已冻结)
├─ net/                     网络互连(Phase F,纯 java.net,可 JVM 单测)
│  ├─ RemoteContract.kt     端口/魔数/JSON data/帧封包与流式重组 FramePacketReader
│  ├─ RemoteHost.kt         服务端:UDP 47510 广播 + TCP 47511 单客户端流
│  └─ RemoteClient.kt       客户端:发现(去重3s) + 连接/行模式→帧模式/10s判死
├─ cloud/CloudApi.kt        云接口占位(Retrofitopt-in 默认关闭,api() 未开启即抛异常)
├─ media/
│  ├─ Mdt.kt                MDT 容器格式(JPG + DDT段[info块+原始帧] + 152B Tail+ parse
│  ├─ PhotoSaver.kt         MediaStore 保存(DCIM/MAG160C,无权限也可写自有文件)
│  ├─ Mp4Recorder.kt        MP4录像(Surface 输入 H.264MediaCodec+MediaMuxer
│  ├─ DebugLog.kt           现场调试日志(落盘 DCIM→Download→私有目录 + logcat 镜像)
│  └─ PdfReport.kt          PDF 巡检报告(PdfDocument
├─ usb/
│  ├─ UsbTransport.kt       UsbManager 枚举(VID 0x833C)/权限/claim/EP
│  ├─ MagProtocol.kt        命令码 0x6BB6B6xx(小端)/响应 0x5BB5B5xx
│  └─ IrSession.kt          连接/启动流/读线程→FrameStream→RenderPipeline→回调;
│                           FFC回调;rawHook(远程预览)/recorderHook(录像);
│                           deviceLifetimeMs675);cali缓存MD5对照
├─ core/
│  ├─ OfficialTables.kt     自动生成:PALETTE256_ARGB(官方铁虹)/T2E(646)/T2E274/E2T
│  ├─ VendorPalettes.kt     官方运行时时序生成器的移植结果(11/12 精确表,生成物)
│  ├─ Palettes.kt           12调色板(0..10=官方精确表,11 红热=近似)
│  ├─ RenderPipeline.kt     ★官方渲染管线 Kotlin 移植(与C参考逐字节一致)
│  ├─ TempMath.kt           温度换算(counts→毫度,T2E+ tempMapFromPixelsMDT温度图)
│  └─ FrameStream.kt        流帧重组(0x1BB1B11B 搜索)
└─ task/TaskParser.kt       任务巡检 sqlite/xml 解析
assets/mag160c.ddt          官方 DDT 标定文件(1.8MB,渲染必需)
res/drawable/*.xml          自绘矢量图标(双弧圆等可靠几何图形)

4. 核心知识(必须知道)

4.1 渲染管线(已逐字节验证)

  • core/RenderPipeline.kt 移植自 csdk/src/mag160c_render.c(官方管线,像素级验证)。
  • 输入:USB 帧(0x38 头 + 38400B u16 小端 + 尾部);输出 320×240 ARGB。
  • FFC 状态机:暖机20帧内暂停;第75帧强制FFCFFC(0)→5隐藏→FFC(1)→4参考帧→4隐藏→正常。
  • 移植陷阱(已解决,改代码时务必保持)
    1. C 的 32 位无符号回绕:所有 u32() 掩码必须保留(cdfdenom、0xffc0000-iv70x40000、(v-lo)*S 等)。
    2. lutRebuild 里 u12 的基址是常量 iv7*0x10+0x10不是链式 u21v。
    3. ByteArray 存 255=-1:判 0xFF 必须 (b.toInt() and 0xFF)
    4. 暖机窗口检查在 ffc_step 之前(顺序不能改)。
  • 验证方式:android/app/src/test/.../RenderPipelineTest.ktJVM 差分测试,参照 analysis/render_offline.c 的 C 输出,60帧序列与官方DDT)。

4.2 USB 协议(权威版:analysis/magcx_official_flow.md

  • 2026-09-10 已反编译官方普通版全量源码jadxanalysis/sdk_re/android_app/jadx_magcx/ UsbCommunication.java 是 USB 层真身;Ghidralibcxsdk 全量伪代码 analysis/sdk_re/android_app/libcxsdk_decomp.txt1290 函数)。
  • 协议要点:命令 4 字节小端;超时全线 800ms;序列 GetParameter1(66b)→GetParameter2(66c)→GetCaliInfo(66f)→[缓存缺失] GetCaliFile(670)+EP 0x84 拉 16KB 块→StartTransferImg(673)FFC=672(帧驱动)。
  • 官方响应格式:0x5BB5B55B BasePara160B,含 serial/devType/宽/高/fps)。
  • 旧 analysis/protocol_spec.md 的 66f/670 描述("prepare/version query")不准, 以 magcx_official_flow.md 为准。

4.3 实时画面渲染(最终约定:构图钉死竖屏框架)

  • Activity 锁定竖屏manifest screenOrientation="portrait"):屏幕相对手机框架 永不旋转。顶栏、热像区域、底栏的绝对位置永远贴着手机竖屏的物理顶边(挖孔侧)、 中间、物理底边;手机怎么物理旋转,构图都不动(用户明确要求:屏显方向必须始终 与热像镜头实际方向对应,横屏后显示区域跟着屏幕转是错的)。不要改回 fullSensor。
  • 图标/文字按物理持机朝向补偿旋转(第六轮定稿):ui/DeviceOrientation.kt 用 加速度计得出手机相对竖屏的顺时针物理转角 φ(0/90/180/270,带滞回;竖屏锁定下 Display.rotation 恒 0 不可用)。顶栏/底部导航条目 graphicsLayer rotationZ=-φ 原位预旋转;渲染器 OSD 文字 canvas.rotate(-φ) 绕锚点旋转(标记圆点、色标条 几何仍钉死在图像上)。对话框与其他页签暂不补偿。
  • 加速度计符号约定(第七轮教训,勿改回):真机 TYPE_ACCELEROMETER 静止读数 指向世界上方(竖屏正持 y=+9.81);模拟器虚拟传感器是反的重力约定(y=-9.81)。 DeviceOrientation 映射按真机约定写,模拟器测试须用反号值驱动 (φ=0→adb emu sensor set acceleration 0:9.81:0)。
  • 图像恒 90°CW 绘制为 3:4 竖向,填满可用区域(顶栏下~底导航上)。
  • 文字/图标恒屏幕水平(可读);标记文字位置自动跟随(probeToScreen 固定 90° 映射: fx=1-sy/120, fy=sx/160)。
  • 顶栏=4 个相机控制项(FFC / 变倍×N / 追踪·开 / 调色板+名称),恒在顶部并带小字 标签,safeDrawing 顶部inset 适配挖孔屏。
  • 底部(仅实时页)导航栏上方为相机快门区:相册快捷入口 / 大快门拍照 / 录像-停止;快门区高度并入 vm.uiBottomPx= 导航高+快门区高)。
  • 顶栏高度经 onSizeChangedvm.uiTopPx必须挂在 safeDrawing inset 之前, 即包含挖孔安全区高度,否则真机热像顶端会插进顶栏背后);底导航高度经 UiInsets.navPx、快门区高度经其 onSizeChangedvm.uiBottomPx(= 导航+快门区), 渲染器据此留白。
  • 演示模式已撤下2026-09-09 用户要求,接真机实测显示,勿加回来):无设备时 connect()status="no_device"LiveScreen 显示占位文案"未检测到热像仪, 请插入MAG160C",渲染器无帧黑底;startDemo/合成帧代码已删除。

4.4 温度

  • 温度 = 毫度 int(÷1000 = ℃)。counts_to_temp_mc(NUC域→T2E逆映射)用于探针/OSD, 在真机上需按 DDT 标定核对绝对值(待办)。
  • 离线 MDT 温度解码(ConvertResponse2Temperature + 标定参数)未实现(待真机文件对照)。

5. 构建 / 测试 / 提交

# 构建 + 单元测试(无 --daemon 单次,约1分钟;首次在本机需下载依赖较久)
cd C:\Project\MAG160C\android
$env:JAVA_HOME="C:\Tools\jdk-21"
.\gradlew.bat :app:assembleDebug test --no-daemon
# 产物
Copy-Item app\build\outputs\apk\debug\app-debug.apk C:\Project\MAG160C\build-artifacts\mag160c-app-debug.apk
# 提交(每里程碑一个 commit,不 push
git -C C:\Project\MAG160C add -A
git -C C:\Project\MAG160C commit -m "android: ..."

6. 严重教训(反复踩坑,务必遵守)

  1. 编码:所有源码必须 UTF-8。本机默认编码是 GBK!
    • python 读写文件必须显式 encoding='utf-8'open(p,'w') 裸写会变 GBK→编译出的中文全乱码)。
    • 检测/修复脚本:C:\Users\ZXC\AppData\Local\Temp\opencode\fix_encoding.pycheck_mojibake.py。改完必须跑一遍确认全 OK,再构建。
    • gradle.properties 已有 -Dfile.encoding=UTF-8gradle + kotlin daemon)。
  2. 图标:手写 pathData 的弧线易坏,一律用双弧圆 M x,y a r,r 0 1,0 2r,0 a r,r 0 1,0 -2r,0z + 直线/矩形。
  3. SurfaceView 与旋转:不要在横竖屏间切换不同布局分支(SurfaceView 会被销毁且 surface 不重建)。 用"稳定单分支 + 覆盖层"布局(当前实现即如此)。
  4. 写代码要谨慎:本会话多次因急于成稿写出语法错误/残留死代码。每写一个文件后立即编译。
  5. ByteBuffer.putInt 默认大端:USB 命令必须小端(官方 intToByteArray)。 16 轮黑屏调试的根因就是它(66b 回文数掩盖了字节反转),MagProtocolTest 已锁死。
  6. 官方 App 源码已入库,先读它再写协议代码analysis/sdk_re/android_app/jadx_magcx/Java)、 analysis/sdk_re/android_app/libcxsdk_decomp.txtnative)、 analysis/magcx_official_flow.md(权威结论)。不要凭旧文档猜协议。

7. 当前状态与待办

已完成:核心管线移植(字节级验证)、USB 层、实时画面(相机式布局: 顶栏5控制项 FFC/变倍/追踪/调色板/画中画 + 底部快门区 相册/拍照/录像 + 底导航)、 图标文字按物理持机朝向补偿旋转(加速度计)、拍照 MDT 容器→MediaStore、 MP4 录像、媒体库、离线分析(含温度条+点击测温)、PDF 报告、任务解析、设置页、 Android16 沉浸(状态栏隐藏+竖屏锁定+挖孔安全区)、乱码多次修复。 (演示模式 2026-09-09 按用户要求撤下,见 §4.3。)

执行计划 2026-09-10 七阶段全部落地docs/android_app/execution_plan.md):

  • Phase AGetLifeTime(675) 查询→deviceLifetimeMs + 心跳 stats 带 lifetime cali 缓存与内置 DDT 的 MD5 一致性日志;真机自检清单 docs/android_app/real_device_checklist.md24 步,含预期日志行)。
  • Phase BMdt.parse 返回 MdtFilejpg 按 FFD9 裁尾、text 去 NUL 填充、 framePixels 齐全);TempMath.tempMapFromPixels 毫度图;分析页温度条 (中心/最低/最高)+ 点击测温探针。注意:分析页图像按原生横向绘制, 探针映射是直接映射,不要照抄实时页的 90° 逆映射。
  • Phase C:厂商调色板——结论是 libcxsdk.so 无静态表,全部由 CFunctions::SetColorPalette 运行时算术生成;移植该函数后 11/12 个表 已达官方精确(0..10,铁虹与官方表 256/256 一致),索引 11 红热保留近似。 详见 analysis/sdk_re/android_app/palette_extraction_findings.md
  • Phase D:云模块占位(Retrofit 2.11.0AppSettings.cloudEnabled 默认 false CloudClient.api() 未开启即 check 抛异常——不允许静默联网)。
  • Phase E:可见光 PIP(Camera2 最小实现,三档尺寸、可拖动、双击关闭; 相机任何异常只记 [pip] 日志并收窗,绝不影响热像主画面)。
  • Phase F:局域网远程预览(UDP 47510 发现 + TCP 47511 控制与原始帧流, 客户端本地管线渲染,调色板/变倍不过网;单测含真实 TCP loopback 端到端)。

布局约定(最终,勿回退)

  • Activity 锁定竖屏portrait),构图钉死手机竖屏框架:顶栏/热像区域/快门区/底导航 绝对位置永不随手机物理旋转变化(屏显方向永远对应镜头方向)。
  • 图标/文字持机朝向补偿:ui/DeviceOrientation(加速度计,真机约定 y=+9.81=0°; 模拟器是反的重力约定)→ rotationZ=-φCompose/canvas.rotate(-φ)Canvas)。
  • 顶栏 uiTopPx 必须挂在 safeDrawing inset 之前(包含挖孔安全区,否则真机热像 顶端被顶栏盖住);uiBottomPx = 底导航高 + 快门区高。

禁改清单(详见 execution_plan.md §0,改前先读):Manifest 的 screenOrientation="portrait"MagProtocol 命令字节序(MagProtocolTest 锁定)、 IrSession 握手序列、LiveRenderer 构图逻辑、analysis/ 目录。

待办

  1. 真机 USB 实测:温度绝对值标定、FFC/录像/MDT 保存端到端——照 docs/android_app/real_device_checklist.md 24 步走。
  2. 红热调色板(索引 11)精确表:官方 APK 的预览图是占位副本,需真机抓帧反推。
  3. 双机远程预览实测(清单第 16-24 步)。
  4. 云服务真实接口对接(当前仅占位,需账号与接口文档)。

8. 文件路径速查

  • 心跳/状态:docs/android_app/session_state.md
  • 逆向功能总表:docs/android_app/reverse_apk_features.md
  • USB 协议/温度算法:analysis/protocol_spec.md
  • C 参考实现(Kotlin 管线的蓝本):csdk/src/mag160c_render.cmag160c_temp.cmag160c_ir.c
  • PC 参考工具:analysis/render_offline.canalysis/gen_kotlin_tables.py
  • 预览截图:analysis/preview/preview_*.png
  • APK 产物:build-artifacts/mag160c-app-debug.apk

9. 下一轮提示词(交付新模型直接用)

你是本项目的续任开发者。请先完整阅读 C:\Project\MAG160C\docs\android_app\HANDOFF_DEVELOPMENT.mdC:\Project\MAG160C\docs\android_app\session_state.mddocs\android_app\reverse_apk_features.mdC:\Project\MAG160C\analysis\protocol_spec.md,再继续开发。 项目根 C:\Project\MAG160C 是 git 仓库,源码在 android\Gradle 工程,package com.mag160c.thermal)。Android 16 已适配,目标是一个 MAG160C 热像仪(160×120, 15fps, USB VID 0x833C PID 1)统一 APP。

当前已完成到第 10 轮反馈修复:核心渲染管线 Kotlin 移植(逐字节验证)、USB 层、 相机式实时布局(顶栏4控制项+底部快门区+底导航)、图标文字持机朝向补偿旋转、 拍照MDT存MediaStore、MP4录像、媒体库、离线分析、PDF报告、任务解析、设置页、 Android16沉浸(竖屏锁定+挖孔适配)。 第 10 轮(2026-09-09):按用户要求撤下演示模式合成热像图(无设备只显示 "未检测到热像仪"占位),接真机+热像仪实测显示。

硬性约定(勿回退):① Activity 竖屏锁定 portrait,构图钉死手机竖屏框架, 屏显方向必须始终对应镜头方向;图标/文字用 ui/DeviceOrientation(加速度计, 真机约定 y=+9.81=0°)补偿旋转。② 源码全 UTF-8;python 读写必须显式 encoding='utf-8'(本机默认 GBK,裸写会乱码)。③ 图标用双弧圆+直线/矩形。

下一步建议按 session_state.md「待办」推进:

  1. 真机 USB 实测——温度绝对值标定(对 DDT 核对 counts→毫度);
  2. 网络互连远程预览——UDP 广播自动发现 + 手动内网 IP,主机手机插热像仪、 局域网另一台手机远程实时预览(自定义轻量协议:控制通道 JSON + 图像流, 不走厂商 33596/33597);
  3. 离线 MDT 温度解码(ConvertResponse2Temperature + 标定参数);
  4. 厂商 12 调色板精确提取;5. 可见光 PIP 融合、云模块(Retrofit opt-in 预留)。

构建/提交:

cd C:\Project\MAG160C\android; $env:JAVA_HOME="C:\Tools\jdk-21"
.\gradlew.bat :app:assembleDebug test --no-daemon
git -C C:\Project\MAG160C add -A; git -C C:\Project\MAG160C commit -m "android: ..."

环境(2026-09-09 本机重装):JDK Temurin 21 C:\Tools\jdk-21、SDK C:\Tools\android-sdk (无模拟器/AVD)、Gradle 用工程 wrapper。注意:git 工作树里有大量与本任务无关的脏文件 (.tools/IR_Camera_SDK/csdk/analysis/ 等),不要提交它们,只提交你的改动。