================================================================
OpenDance Studio -- Android 辅助 App v0.2.0  使用说明
================================================================

手机端姿态采集器：用摄像头做端侧姿态推理，把 33 个骨骼关键点通过
WebSocket 推送给 Windows 客户端，由后者驱动 VRM 角色并实时评分。

隐私：画面不会离开手机。通过网络发送的只有 33 个关键点坐标。

--
一、安装
--

方式一（推荐）：直接安装随附的 app-release.apk
  - 在手机上允许「安装未知来源应用」
  - 点击 APK 安装

方式二：用 adb 安装
  adb install -r app-release.apk

要求：Android 10(API 29)或更高版本。

--
二、初次部署（从零跑起来）
--

本节面向第一次部署的人，给出从零到跑通的最短路径。
下文地址、路径、口令一律是占位符，请替换成你自己的值，且不要提交进仓库。

1) 前置条件

   JDK 17 或更高        AGP 8.7.3 要求，构建脚本会校验主版本 >= 17
   Gradle 8.9           仓库自带 gradlew，本机无需另装
   Android SDK          platforms;android-34 与 build-tools;34.0.0
                        compileSdk 34 / minSdk 29(Android 10+)/ targetSdk 34
   设备                 需要真机（Android 10 及以上、带摄像头）

   真机与模拟器：
     release 包只含 arm64-v8a + armeabi-v7a，装不到 x86/x86_64 模拟器；
     debug 包含 x86/x86_64，可装进模拟器，但相机是虚拟场景，姿态识别
     效果不能代表真机，不能作为验收依据。联调验收请用真机。

   权限：相机权限为运行时授予；INTERNET / ACCESS_NETWORK_STATE /
   ACCESS_WIFI_STATE 为普通权限，安装时自动授予，用于推流与局域网扫描。

2) 获取代码与本地配置

   git clone https://gitea.kasugano.cn/zijunchiang/opendance-android.git
   cd opendance-android

   local.properties 是本地文件（已在 .gitignore 中，不入库），内容：
     sdk.dir=<Android SDK 路径>

   build/build-apk.sh 在缺失时会依据 ANDROID_HOME / ANDROID_SDK_ROOT 自动生成。

   签名材料不入库：仓库只有生成脚本，没有私钥。首次发布前执行
     bash build/gen-keystore.sh
   生成 keystore/opendance-release.jks 与 keystore.properties（含 storeFile、
   keyAlias 与两个口令字段）。请自行备份，且不要提交。
   取值优先级：keystore.properties -> 环境变量（KEYSTORE_PATH /
   KEYSTORE_PASSWORD / KEY_ALIAS / KEY_PASSWORD)-> 回退 debug 签名
   （仅供 CI 产出已签名 APK，正式发布必须自备 keystore）。

3) 首次构建

   export JAVA_HOME=<JDK 17 路径>
   export ANDROID_HOME=<Android SDK 路径>

   bash build/build-apk.sh debug      # 仅 debug
   bash build/build-apk.sh release    # 仅 release
   bash build/build-apk.sh all        # 两者都构建（含产物校验）

   或直接用 Gradle:
   ./gradlew assembleDebug
   ./gradlew assembleRelease

   产物（固定路径）：
     app/build/outputs/apk/debug/app-debug.apk
     app/build/outputs/apk/release/app-release.apk

   发布整包（APK + THIRD_PARTY.md + README.txt + contracts/）：
     bash build/package-release.sh     ->  dist-release/

4) 安装到手机

   adb install -r app/build/outputs/apk/debug/app-debug.apk
   adb install -r app/build/outputs/apk/release/app-release.apk

   也可把 APK 拷到手机，允许「安装未知来源应用」后点击安装。
   首次进入运行页会申请相机权限；拒绝后可在运行页点「授予相机权限」
   重新申请，或到系统设置里手动开启。

5) 配置与联通

   本 App 只连接 Windows 客户端，不访问 Web 服务端。
   地址来自 AppSettings.webSocketUrl(): ws://<host>:<port>/pose
   子协议 opendance.pose.v1。

   连接页字段与默认值（见 data/AppSettings.kt）：
     IP 地址          host           默认是 RFC 5737 文档网段占位地址
                                     （源码为 192.0.2.100，非真实主机）
     端口             port           8765
     目标帧率         targetFps      30(5~60)
     分辨率           resolution     640x480(可选 480x360 / 1280x720)
     镜像画面         mirror         开
     仅上半身模式     upperBodyOnly  关

   填写示例（占位符，请替换成你电脑的局域网 IP）：
     IP 地址：192.0.2.10
     端口：  8765

   不想手填可点「扫描局域网」：App 由本机 WiFi IPv4 推导 /24 网段，
   以 32 并发、单机 300ms 超时探测开放 8765 的主机（约 2~3 秒），
   按延迟升序展示，点候选即填入。也可先点「测试连接」确认端口可达。

6) 第一次跑通

   手机与电脑连同一局域网 -> 电脑启动 Windows 客户端(监听 8765)->
   App 填地址并测试连接 -> 点「开始推流」并授予相机权限 -> 站到画面中央。

   成功标志：
     App 运行页  「连接状态」为「已连接」，「目标」显示 ws://<IP>:<端口>/pose
     App 运行页  服务端返回 welcome 后出现「会话」ID
     App 运行页  「已发送」持续增长，「发送帧率」「识别帧率」大于 0
     App 运行页  「人体识别」为「已识别」，出现骨架叠加（黄点好、红点置信度低）
     Windows 端  显示已连接并收到关键点，VRM 模型随动作实时动起来

7) 自检清单

   ./gradlew testDebugUnitTest
     预期：BUILD SUCCESSFUL，全部用例通过
     报告：app/build/reports/tests/testDebugUnitTest/index.html

   bash build/build-apk.sh all
     预期：debug 与 release 均显示 "OK ... APK", release 显示
           "已签名(APK Signature Scheme v2)"

   $ANDROID_HOME/build-tools/34.0.0/apksigner verify app/build/outputs/apk/release/app-release.apk
     预期：退出码 0（无输出即通过）

   0a) native 库体检（T-AND-015；无设备也能查「一装上就崩」的打包问题）
      python3 build/check_native_libs.py app/build/outputs/apk/release/app-release.apk
      预期：「✓ native 库体检通过」——每个 ABI 下每个 .so 的 ELF 类型、
            依赖闭包（DT_NEEDED，非系统库必须同 ABI 自带）、页对齐（16 KB 兼容）。

   0) 发布包级验收（对**发布包**而不是本地构建；文档 §7.5）
      bash build/accept-release.sh dist-release/opendance-android-v0.2.0.zip
      预期末行「发布包级验收全部通过」。它会校验 sha256、解压、核对文件清单、
      读包内 APK 的 AndroidManifest 版本元数据、核对 ABI 与 MediaPipe native 库、
      模型资源、dex 里的 PoseLandmarker、验签，以及包内契约快照与仓库是否一致。

   bash build/check-apk-version.sh app/build/outputs/apk/release/app-release.apk
     预期：AndroidManifest versionName = <project.yaml 的 version>
           AndroidManifest versionCode = 10000*主 + 100*次 + 修订
           "APK 版本元数据一致"
     说明：安装界面 / 应用信息页读的是 APK 的 AndroidManifest，而不是
           build.gradle.kts 的配置。构建缓存或拷错文件会让两者不一致，
           所以这里直接读产物（不需要 aapt2，只用 python3 解析二进制 AXML）。

   adb install -r app/build/outputs/apk/debug/app-debug.apk
     预期：Success

8) 常见问题

   Q: 相机黑屏 / 一直提示需要相机权限
   A: 权限被拒绝或已被永久拒绝，或相机被其他应用占用。在运行页点
      「授予相机权限」；不再弹窗时到系统「设置 -> 应用 -> OpenDance -> 权限」
      手动开启相机，并关闭占用相机的其它 App。

   Q: 连不上 Windows 客户端 / 扫描不到主机
   A: 核对 IP 与端口、确认客户端已启动并监听 8765、在 Windows 防火墙放行
      OpenDance 的 TCP 8765 入站、确认手机与电脑同一 WiFi 且路由器未开 AP 隔离。

   Q: 点「开始推流」闪退（或提示 MediaPipe 初始化失败）
   A: MediaPipe 原生库加载 / 静态初始化失败抛出的是 Error 子类
      (UnsatisfiedLinkError、ExceptionInInitializerError)，不是 Exception;
      常见诱因是 ABI 不匹配（例如把只含 arm 的 release 包装到 x86 模拟器）
      或模型资源缺失。处理：改用与设备 ABI 匹配的包（真机用 release、
      模拟器调试用 debug）；确认 APK 内含 assets/pose_landmarker_lite.task。
      仓库已在 PoseDetector.initialize() 中 catch Throwable 兜底并把原因显示在
      运行页；CrashLogger 会把未捕获堆栈写入应用私有目录，并在连接页展示，
      可截图反馈，或用 adb logcat -s PoseDetector 查看。

--
三、快速开始
--

1. 在 Windows 电脑上启动 OpenDance Studio 客户端
   (Windows 客户端会自动监听 8765 端口)

2. 确保手机与电脑连在同一个 WiFi

3. 打开本应用，进入「连接设置」页

4. 填写电脑的 IP 地址，或直接点「扫描局域网」自动发现
   （扫描会探测局域网内开放 8765 端口的主机，约 2~3 秒）

5. 可点「测试连接」确认端口可达

6. 按需调整推流参数（见下）

7. 点「开始推流」，授予相机权限

8. 站到画面中央，屏幕上会出现骨架叠加：
   - 黄色圆点 = 识别良好的关节
   - 红色圆点/红色连线 = 置信度低（光线不足或被遮挡）

9. 在 Windows 客户端上确认已连接，即可开始跟练

--
四、推流参数
--

分辨率
  低(480x360)    最省电，适合老机型
  中(640x480)   推荐，识别精度与功耗平衡
  高(1280x720)  最清晰，但推理更慢、更耗电

目标帧率
  默认 30 FPS。低端机若「识别帧率」明显低于目标值，可降到 20 或 15。

镜像画面
  默认开启。前置摄像头的常见用法。
  注意：本应用发送的是未镜像的原始坐标，是否镜像由帧头 flags.bit0 声明，
  Windows 客户端据此正确还原左右。因此无论开关如何，动作方向都不会错。

仅上半身模式
  下半身关键点置信度置 0（显示为红点），Windows 端会插值。
  带宽或算力不足时开启可明显降负载。

--
五、运行页状态说明
--

连接状态     未连接 / 连接中 / 已连接 / 重连中（第 N 次）/ 已断开
目标         正在连接的 WebSocket 地址
会话         服务端返回的会话 ID
已发送       累计发送帧数
已丢弃       因推理或连接跟不上而丢弃的帧数（>0 说明设备吃力）
发送帧率     实际推流帧率
识别帧率     实际推理帧率
推理耗时     单帧 MediaPipe 推理毫秒数
人体识别     已识别 / 未识别到人体
平均置信度   < 70% 时转黄，提示改善光线或减少遮挡
最近错误     连接或推理的错误原因

网络中断时会自动重连（1s -> 2s -> 4s... 最长 15s 退避），
无需手动操作。

--
六、常见问题
--

Q: 扫描不到电脑
A: 1) 确认手机与电脑在同一 WiFi（不是手机热点）
   2) 确认 Windows 客户端已启动并正在监听 8765
   3) 在 Windows 防火墙放行 OpenDance.exe 的 TCP 8765 入站
   4) 部分路由器开启了「AP 隔离」，会阻断设备互访，需在路由器关闭

Q: 显示「已连接」但电脑端没有画面
A: 查看运行页的「已发送」是否在增长。若为 0，说明相机或推理没出帧；
   若「人体识别」一直是「未识别到人体」，请站到画面中央并保证光线充足。

Q: 帧率很低 / 已丢弃帧数持续增长
A: 依次尝试：降低分辨率 -> 降低目标帧率 -> 开启「仅上半身模式」-> 关闭其他后台应用。

Q: 关键点抖动明显
A: 正常现象，Windows 端有 DTW 带状对齐与平滑处理，不要求逐帧完全一致。
   改善光照可显著提高稳定性。

Q: 应用退到后台会怎样?
A: 返回键或离开运行页会立即停止推流并释放相机，不会在后台占用摄像头。

--
七、从源码构建
--

环境要求：
  JDK 17           AGP 8.7.3 要求（JDK 25 未经测试）
  Android SDK      platforms;android-34 与 build-tools;34.0.0
  Gradle 8.9       已随 gradlew 提供

构建：

  export JAVA_HOME=/path/to/jdk17
  export ANDROID_HOME=/path/to/android-sdk

  # 生成发布签名密钥库（首次；文件不会入库，请自行备份）
  bash build/gen-keystore.sh

  # 构建并校验产物
  bash build/build-apk.sh all

  # 一键验证（单元测试 -> 构建 -> 产物校验）
  bash build/verify.sh

产物：
  app/build/outputs/apk/debug/app-debug.apk
  app/build/outputs/apk/release/app-release.apk

Android Studio 路径：
  打开本目录 -> Build -> Generate Signed Bundle / APK -> APK -> 选择 keystore -> release

--
八、测试
--

  ./gradlew testDebugUnitTest

56 项单元测试（JVM，无需设备）。除金标准字节向量测试外，另有两组针对
本次缺陷的用例：
  - ReconnectPolicyTest（11 项）：退避序列 0.5s->1->2->4->8->15s 封顶 + ±20%
    抖动；契约 §7 的「只有真正断开才重连」（1000/1001/1002 与主动停止都不重连）；
    心跳间隔必须严格小于静默超时的一半；
  - PoseDiagnosticsTest（12 项）：从 ExceptionInInitializerError 的 cause 链
    翻出 UnsatisfiedLinkError 真因；ABI 比对（release 包装在 x86_64 模拟器必崩）；
    环状 cause 不死循环。
金标准字节向量测试仍是契约一致性的硬证据：用 opendance-web 已通过验收的
TypeScript 实现产出的 239 字节向量，断言本端 Kotlin 实现逐位相同。

测试报告：app/build/reports/tests/testDebugUnitTest/index.html

--
九、目录结构
--

  settings.gradle.kts / build.gradle.kts   工程与插件配置
  gradle/wrapper/                          Gradle 8.9 wrapper
  project.yaml                             任务清单与状态
  DSH.md                                   本项目 DSH 执行约束
  tasks/                                   单任务说明书(T-AND-001 ~ 012)
  contracts/                               只读契约快照(来源 opendance-web；六项各自独立版本)
  app/src/main/java/com/opendance/studio/
      MainActivity.kt        三页导航
      model/                 239 字节帧编解码 + 33 点拓扑
      camera/                CameraX 采集
      pose/                  MediaPipe 端侧推理
      net/                   WebSocket 推流 + 主机发现 + 重连策略
      data/                  设置（含稳定 deviceId）
      ui/                    连接页 / 运行页 / 关于页
      util/                  崩溃日志 + 连接日志（可导出）
  app/src/main/assets/pose_landmarker_lite.task   MediaPipe 模型
  build/                   打包与验证脚本

--
十、许可
--

本应用代码以 MIT 许可发布。第三方开源项目声明见 THIRD_PARTY.md
（应用内「关于」页同样可查看）。
未使用 SMPL 模型及其衍生拓扑，统一采用 MediaPipe 33 点拓扑。
