feat(harmony): add folder transfers and HDC device management
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
# Harmony Connector 手机管理能力调研
|
||||
|
||||
调研日期:2026-09-10。承接 [USB 文件传输调研](harmony-connector.md),评估 Harmony Connector 从文件工具扩展为手机助手。以调研时的最新官方文档设计能力,不以 HarmonyOS 6 为硬上限;HUAWEI Pura 70 Ultra 是待验收设备,不能凭系统名称或文档命令表宣称真机已支持。
|
||||
|
||||
本文只做资料核查和本机工具定位,没有连接手机、安装应用、读取手机数据或改变设备状态。华为商业 HarmonyOS 文档与 OpenHarmony 开源资料分别标注。后者能证明对应源码/命令契约,不能直接证明商业固件的编译选项、授权策略与运行行为。
|
||||
|
||||
## 结论与实现优先级
|
||||
|
||||
建议保留 MTP 用户文件通道,并增加可选的本机官方 HDC 通道。HDC 支持 Mac,适合应用管理、调试文件和诊断;华为要求先开启并授权 USB/无线调试。普通文件传输不应因为新增管理能力而强制进入开发者模式。[华为 HDC](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)、[华为开发者选项](https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-developer-mode)、[华为 USB 文件传输](https://consumer.huawei.com/cn/support/content/zh-cn16090699/)
|
||||
|
||||
以下优先级是本项目的工程建议,表示可实现范围,尚非真机验收结果:
|
||||
|
||||
| 优先级 | 功能 | 启用条件与产品表达 |
|
||||
| --- | --- | --- |
|
||||
| P0 | HDC 工具选择、版本和连接状态;设备基本信息 | 找到可运行工具,用户选择目标,授权成功;失败分清工具缺失、未授权、离线、命令不支持。 |
|
||||
| P0 | 应用列表、应用详情、签名 HAP 安装、单应用卸载 | 包管理命令可用;安装预览包信息和覆盖影响;卸载显示具体包和数据损失。 |
|
||||
| P0 | 日志快照、系统报告导出 | 用户触发,限时/限量,保存到用户选择的位置。 |
|
||||
| P1 | 截图、应用启动、强制停止 | 先探测设备工具和真实组件;动作只针对用户选定对象。 |
|
||||
| P1 | 调试应用数据导出;导出本机已有的安装包 | 沙箱导出逐应用检查调试签名与权限;安装包归档只处理用户拥有的原包,不等同从设备提取所有应用。 |
|
||||
| P1 | 用户文件备份任务 | 明确选择范围、清单、校验和、失败项;仅称“所选文件备份”。 |
|
||||
| P2 | 调试应用数据恢复、无线调试、实时日志/进程、UI 控制 | 探测成功后开放;恢复/控制/无线切换属于改变设备状态的动作。 |
|
||||
| 暂不承诺 | 全机镜像、所有第三方应用数据备份、联系人/短信数据库直接读写、任意已安装应用导出并可恢复 | 未找到面向普通第三方 macOS 助手的官方公共接口;不能由 MTP 或 HDC shell 推导得到。 |
|
||||
|
||||
## 工具获取、发现和授权
|
||||
|
||||
华为官方获取路径为 DevEco Studio 所带 SDK,或 Command Line Tools 的 SDK;macOS 下典型位置为 `DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc`。独立运行还需保留相邻 `libusb_shared` 依赖。这说明可以使用已安装的官方工具,不能据此推断可以重新打包分发华为整个 SDK。[华为 HDC 环境准备](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)、[官方工具下载入口](https://developer.huawei.com/consumer/cn/download/)
|
||||
|
||||
实现建议:先读用户保存的绝对路径,再查应用自身继承的 `PATH`,最后检查 `/Applications`、`~/Applications` 的 DevEco Studio 应用包及用户选定的 Command Line Tools 目录。解析软链接,验证是文件且可执行,显示最终路径。不要全盘扫描、执行下载来的未知工具或自动修改 shell 配置;GUI 的 PATH 不一定与交互终端相同。
|
||||
|
||||
本次本机只读发现:`PATH` 中无 `hdc`;在 `/Applications`、`~/Applications` 和存在的 `~/Library/OpenHarmony`、`~/Library/Huawei`、`~/Library/DevEcoStudio`、`~/ohos-sdk` 范围未找到候选工具。这不是“整台电脑绝无 HDC”的结论。没有安装 SDK,也没有执行设备命令。
|
||||
|
||||
首次开启开发者选项与随后打开调试开关是不同步骤:华为手机/平板指引要求在设备名称页连续点击软件版本七次,确认并按需输入 PIN,首次开启可能触发重启。之后 USB/无线调试需在手机确认计算机授权。插件应引导用户操作设备,不绕过锁屏、信任弹窗、企业调试管控。[华为开发者选项](https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-developer-mode)
|
||||
|
||||
OpenHarmony HDC 仓库采用 Apache-2.0。若未来需要随插件交付 HDC,可评估从确切开源提交自行构建并保留许可证、版权与依赖声明;商业 Huawei 下载包的分发权需核对其实际许可,本次未确认该授权。不能把仓库许可扩展至 SDK 的其他组件或使用华为商标暗示官方产品。[OpenHarmony HDC LICENSE](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/LICENSE)
|
||||
|
||||
## CLI 调用契约
|
||||
|
||||
以下表格中 `key` 是用户选定的 `connect-key`,`bundle`/`ability`/`module` 来自设备已返回且验证过的数据;`local` 是用户选择的绝对本地路径。数组是传给 `Command::new(hdc).args(...)` 的 argv,省略可执行文件本身。带尖括号的值只作占位,不能原样执行。
|
||||
|
||||
宿主不要使用 `sh -c` 拼接 HDC 命令。`hdc shell` 的最后一个参数仍会由设备 shell 解释,必须用固定模板与严格参数验证/单引号转义;宿主 argv 安全不等于远端 shell 安全。文件名、应用名和设备返回内容都不是可信 shell 代码。
|
||||
|
||||
### 只读探测与设备信息
|
||||
|
||||
| 功能 | argv | 证据与处理 |
|
||||
| --- | --- | --- |
|
||||
| 客户端版本 | `["-v"]` | 解析 `Ver:` 字段,保留未知版本原文。 |
|
||||
| 客户端/服务端版本 | `["checkserver"]` | 记录两端版本,不能认为刚选择的二进制已经替换后台旧服务。 |
|
||||
| 设备状态 | `["list", "targets", "-v"]` | 识别 `USB/TCP` 和 `Unknown/Ready/Connected/Offline/Unauthorized`;`[Empty]` 表示没有目标。 |
|
||||
| HDC 帮助 | `["-h", "verbose"]` | 作为本机命令存在性的证据。 |
|
||||
| 包管理能力 | `["-t", key, "shell", "bm help"]` | 成功识别帮助内容后再开放相关操作。 |
|
||||
| 组件管理能力 | `["-t", key, "shell", "aa help"]` | 不通过实际启动/停止应用来探测。 |
|
||||
| 截图工具能力 | `["-t", key, "shell", "uitest help"]` | 要求返回可识别 `screenCap` 帮助。 |
|
||||
| 日志工具能力 | `["-t", key, "shell", "hilog -h"]` | 确认 `-x` 可用。 |
|
||||
| 基本设备信息 | `["-t", key, "shell", "hidumper -c base"]` | 华为文档展示 BuildId、ReleaseType、OsVersion、DeviceType 等字段。 |
|
||||
| 参数读取 | `["-t", key, "shell", "param get <name>"]` | 单项读取,键需要固件实际返回/探测;未知键不应显示成空字符串的正常值。 |
|
||||
|
||||
HDC 命令和状态依据 [华为 HDC](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc);设备信息依据 [华为 HiDumper](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/hidumper);`param get` 语法依据 [OpenHarmony param 文档](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/tools/param-tool.md)。`hdc -t key shell hdcd -v` 是守护程序版本探测,不能当作手机完整系统版本。
|
||||
|
||||
实现应把“工具不存在”“没有授权”“权限不足”“输出无法识别”“版本不支持”与“数据为空”区分开。每个能力使用 `unknown / available / unavailable` 加原因;仅凭全局 HDC 连接成功不能一次性点亮所有管理功能。连接阶段不要执行 `kill -r`、`reconnect`、`tmode`、重启或任何包管理写操作。
|
||||
|
||||
### 应用列表、安装、卸载与运行
|
||||
|
||||
| 功能 | argv | 条件 |
|
||||
| --- | --- | --- |
|
||||
| 包名列表 | `["-t", key, "shell", "bm dump -a"]` | 华为官方 FAQ 直接采用该命令查询已安装应用。 |
|
||||
| 包详情 | `["-t", key, "shell", "bm dump -n <bundle>"]` | 华为 HiDumper 指引用详情中的 `appProvisionType` 判断 debug。 |
|
||||
| 调试包列表 | `["-t", key, "shell", "bm dump -g"]` | OpenHarmony 最新文档支持;需以本机帮助/实际输出确认。 |
|
||||
| 应用显示名 | `["-t", key, "shell", "bm dump -n <bundle> -l"]` | OpenHarmony 文档标注 API 20 起;失败仍可显示包名。 |
|
||||
| 安装签名 HAP | `["-t", key, "install", local]` | 本地绝对路径,成功后重新查询包版本。 |
|
||||
| 单应用卸载 | `["-t", key, "uninstall", bundle]` | 指定包名;拒绝自动级联卸载依赖。 |
|
||||
| 启动组件 | `["-t", key, "shell", "aa start -b <bundle> -a <ability> -m <module>"]` | 从详情找可启动、已导出的真实组件;不能硬编码所有应用都有 `EntryAbility`。 |
|
||||
| 强制停止 | `["-t", key, "shell", "aa force-stop <bundle>"]` | 与正常退出不同,可能丢失尚未保存的工作。 |
|
||||
|
||||
查询依据:[华为应用市场错误排查](https://developer.huawei.com/consumer/en/doc/harmonyos-references/errorcode-appgallery)、[华为 HiDumper](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/hidumper)。`bm` 详细参数与 user/root 限制依据 [OpenHarmony bm 文档](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/tools/bm-tool.md)。`aa` 的显式启动、exported 限制、强停和响应依据 [OpenHarmony aa 文档](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/tools/aa-tool.md);商业华为 HDC 将 aa 列为配套工具,但本次华为 aa 独立页面未成功抓取,故实际能力仍需设备探测。
|
||||
|
||||
安装不是“有 HAP 就能装”:华为要求真实设备使用签名包,自动签名会绑定调试设备信息。debug/release 签名、应用标识、版本和多模块一致性受安装器校验;不能把 OpenHarmony 的公开测试证书当作商业设备通用签名,也不能从 HDC 命令存在推导任意 release 包均可安装。[华为开发入门](https://developer.huawei.com/consumer/cn/develop-novice-guide/)、[华为自动签名](https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-signing-auto)、[华为安装与更新一致性校验](https://developer.huawei.com/consumer/cn/doc/doccenter-getting-started/install-and-update-consistency-verification)
|
||||
|
||||
华为 HDC 说明 `install` 默认就是覆盖安装,省略 `-r` 不能防止覆盖;安装路径支持 HAP/应用内 HSP,APP 包从 API 22 起。`-w` 等组合参数有特殊解析约定,初版建议先只用必要参数。用户操作之前展示即将安装的应用与已装版本;签名冲突或系统拒绝时保留错误码,不能自动先卸载再安装。卸载共享库可能因其他应用依赖而被拒绝。[华为 HDC 安装说明](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)、[华为共享包依赖错误 9568312](https://developer.huawei.com/consumer/cn/doc/doccenter-dev-faq/faqs-technology-32)
|
||||
|
||||
包名清单是行文本,详情可能有标签/前缀加 JSON 内容,不能将所有 `bm` 命令输出当作同一个稳定 JSON API。建议仅接受明确命令对应的格式:列表验证包名,详情解析完整 JSON 对象并校验预期字段与查询包名;无法解析则显示原始诊断并禁用依赖该字段的操作。显示名不能作为包名、权限或可卸载性的替代。安装和卸载需同时判断进程结果、工具成功/失败信息及操作后的目标状态,不能只看是否含 `AppMod finish`。
|
||||
|
||||
### 截图、日志和系统报告
|
||||
|
||||
1. 探测 `uitest help` 后,为每次截图生成不可预测且只含安全字符的文件名。
|
||||
2. 调用 `["-t", key, "shell", "uitest screenCap -p /data/local/tmp/curios-<nonce>.png"]`。
|
||||
3. 调用 `["-t", key, "file", "recv", "/data/local/tmp/curios-<nonce>.png", localTemp]`。
|
||||
4. 校验下载文件确实是可解码 PNG,再提交到用户目标位置。只删除本次创建的远端文件;失败时不要清理整个临时目录。
|
||||
|
||||
`uitest screenCap -p`、只能存到 `/data/local/tmp/` 的限制,以及 `-d` 多屏参数从 API 20 开始,均来自 [OpenHarmony UITest 官方文档](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/application-test/uitest-guidelines.md)。华为官方也提供 HiSmartPerf 的截图采集/文件导出流程,但本次没有将 `snapshot_display` 的商业固件可用性或任何投屏帧率作为已确认能力。[华为 HiSmartPerf](https://developer.huawei.com/consumer/cn/doc/doccenter-testing/smartperf-guidelines)
|
||||
|
||||
| 导出 | argv | 实现建议 |
|
||||
| --- | --- | --- |
|
||||
| 日志快照 | `["-t", key, "shell", "hilog -x"]` | `-x` 非阻塞读完退出,宿主将 stdout 写到独立临时文件。 |
|
||||
| 实时日志 | `["-t", key, "shell", "hilog"]` | 持续读取,提供停止按钮和大小限制;不要视为普通短请求。 |
|
||||
| 系统报告 | `["-t", key, "bugreport", localTemp]` | FILE 是本地路径;内容可能较多,预留更长但有限的超时。 |
|
||||
| 已有落盘日志 | `["-t", key, "file", "recv", "/data/log/hilog", localDir]` | 只有权限与路径确认后才能导出,不需先开启新的落盘任务。 |
|
||||
|
||||
`hilog -x` 语义依据 [OpenHarmony HiLog](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/dfx/hilog.md);`bugreport` 与现有落盘日志路径依据 [华为 HDC](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)。日志与系统报告可能包含应用活动和设备标识;默认只本地保存,不自动上传。不要为普通日志导出自动执行清日志、关闭隐私过滤或开启持续日志落盘。
|
||||
|
||||
## 备份和恢复:需要分别命名的三种能力
|
||||
|
||||
### 用户文件副本
|
||||
|
||||
MTP 面向设备公开的存储对象。选择文件夹后递归复制、生成大小/校验和清单、记录失败项,是可以实现的“所选文件备份”;没有通过 MTP 暴露的沙箱、系统设置、账号和数据库不在范围内。图库模式限制沿用原调研,不能把普通文件夹可写推导为可任意恢复图库数据库。[libmtp 对象/存储 API](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.h.in)、[华为 USB 文件传输](https://consumer.huawei.com/cn/support/content/zh-cn16090699/)
|
||||
|
||||
### 调试应用数据导出与导入
|
||||
|
||||
华为明确 `shell -b`、`file send/recv -b` 只用于“调试证书签名且已经在设备上启动”的应用;不是任意应用的沙箱读取入口。`-b` 从 API 15 引入。先查询 `appProvisionType`,再对具体目录进行只读访问探测,不应单凭包名出现在应用列表就启用备份。[华为 HDC 沙箱条件](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)、[华为 HiDumper 调试类型判定](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/hidumper)
|
||||
|
||||
```text
|
||||
查询指定调试应用目录:
|
||||
["-t", key, "shell", "-b", bundle, "ls data/storage/el2/base/"]
|
||||
|
||||
导出用户选择的沙箱文件或目录:
|
||||
["-t", key, "file", "recv", "-b", bundle, "<verified-remote-path>", "<new-local-directory>"]
|
||||
|
||||
向同一调试应用导入已选择的数据:
|
||||
["-t", key, "file", "send", "-b", bundle, "<local-file>", "<verified-remote-path>"]
|
||||
```
|
||||
|
||||
这些是官方 `-b` 文件操作语法,remote path 应在该应用实际可见的根目录内,不使用 `/data/app/...` 猜测另一个应用的路径,不接受越界 `..`。导出清单应记录设备/系统、包名、版本、签名类型、所选路径、时间、实际复制文件及校验结果。正在运行的应用可能继续写入数据库;原始文件副本不自动获得事务一致性,也不保证跨设备的密钥、登录状态或应用版本可恢复。建议界面名称为“导出调试应用数据”,完整备份成功必须有明确的范围与验证。
|
||||
|
||||
恢复是覆盖写入,不应在枚举或探测时尝试。只允许用户选择明确的备份和目标应用,核对包名/版本/范围,展示将修改的文件后执行。未经该应用可恢复性验证,不提供一键“全部应用恢复”。不要通过重新签名现有商业应用或卸载重装来制造沙箱访问权限。
|
||||
|
||||
### 官方系统备份
|
||||
|
||||
华为应用备份框架允许应用接入 `BackupExtensionAbility` 并声明可备份范围,但普通应用自身不能触发系统备份;触发功能面向系统应用。官方最新验证指引使用数据克隆,由新旧设备配对触发。应用可不接入,也可限制备份内容。因此额外装一个普通手机伴随应用,并不自然获得其他应用全部数据的备份权。[华为备份概述](https://developer.huawei.com/consumer/en/doc/harmonyos-guides-V13/app-file-backup-overview-V13)、[华为应用接入](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/app-file-backup-extension)、[华为数据克隆验证](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/app-file-backup-dataclone)
|
||||
|
||||
特别检查了 OpenHarmony `filemanagement_app_file_service` 提交 `589be5d84d3c23967be62ca7f0317690237a0041`:
|
||||
|
||||
- `backup_tool/BUILD.gn` 仅在 `build_variant == "root"` 编入真实操作,且 `install_enable = false`。
|
||||
- 未定义 `BACKUP_TOOL_ENABLE` 时,`main()` 直接返回 0,不执行备份。
|
||||
- 备份服务检查 BACKUP 权限;HAP 调用还检查系统应用身份,shell 调用亦受 UID 条件限制。
|
||||
|
||||
这是开源实现证据,不能证明商业设备使用完全相同实现;却直接说明“找到 backup_tool 且退出码为 0”不是备份成功证据。本轮不将该工具作为商业设备备份后端,也不自动尝试备份/恢复命令。[OpenHarmony 构建约束](https://github.com/openharmony/filemanagement_app_file_service/blob/589be5d84d3c23967be62ca7f0317690237a0041/tools/backup_tool/BUILD.gn)、[无操作入口](https://github.com/openharmony/filemanagement_app_file_service/blob/589be5d84d3c23967be62ca7f0317690237a0041/tools/backup_tool/src/main.cpp)、[服务权限检查](https://github.com/openharmony/filemanagement_app_file_service/blob/589be5d84d3c23967be62ca7f0317690237a0041/services/backup_sa/src/module_ipc/service.cpp)
|
||||
|
||||
## 与官方助手的能力对照
|
||||
|
||||
| 官方路线 | 已确认范围 | 对本插件的结论 |
|
||||
| --- | --- | --- |
|
||||
| HiSuite / 华为手机助手 | 当前官网针对 HarmonyOS 5+ 列出基础数据、部分第三方应用及数据、部分设置备份;受系统/应用兼容性限制。明确 5+ 暂不支持 macOS 连接。 | Windows 官方能力不等于 macOS 第三方插件可调用的协议/API。不能用旧 Android/HDB 教程保证原生鸿蒙兼容。 |
|
||||
| 手机“备份和恢复 → 外部存储” | 官方 5+ 指引支持第三方应用和部分应用数据等,并需备份密码;范围以实际设备为准。 | 可以提供官方操作指引,或归档用户已经生成的备份文件;尚无公开证据支持插件在 Mac 直接触发/解密其备份。 |
|
||||
| 数据克隆 | 最新开发者指引通过两台设备触发已接入应用的数据迁移验证。 | 是系统侧迁移流程,不是通用电脑备份 CLI。 |
|
||||
| 华为分享 + 鸿蒙星河互联 | 官方提供与 Mac 的图片、视频、文档无线互传流程。 | 可链接官方方案;没有据此获得可嵌入分享 SDK、应用安装或全机备份能力。 |
|
||||
|
||||
分别依据:[华为 HiSuite 当前支持范围](https://consumer.huawei.com/cn/support/hisuite/)、[华为外部存储备份](https://consumer.huawei.com/cn/support/content/zh-cn16024725/)、[华为数据克隆验证](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/app-file-backup-dataclone)、[华为与 Mac 互传](https://consumer.huawei.com/cn/support/content/zh-cn16093657/)。产品可以覆盖“文件、应用、设备、诊断、备份”模块;不同模块应展示实际通道与数据范围,不把官方系统级功能画成已经可执行的按钮。
|
||||
|
||||
## 无线与其他能力
|
||||
|
||||
用户先在手机开启无线调试,输入手机显示的 IP 和端口,然后调用 `["tconn", "<ip>:<port>"]`;断开用 `["tconn", "<ip>:<port>", "-remove"]`。官方仍将 TCP 调试标为测试场景。`tmode port` 会重启设备守护进程并断开会话,连接探测不应使用它。端口转发、重启、UI 输入可以作为后续显式操作,不默认启用。[华为 HDC 无线和连接管理](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)
|
||||
|
||||
最新文档版本提示用于解释能力差异,不作为 HarmonyOS 大版本硬编码:`-b` 与 API 15/HDC 3.1.0e 配套;API 20/HDC 3.2.0b 增加部分网络能力;APP 安装 API 22;安装自动授权 `-g` API 24;沙箱无命令交互式 shell、reconnect、子服务器等 API 26 新能力仍需两端支持。实现按本机帮助和设备实际结果逐项判定,不自动调用新命令来“修复”连接。[华为 HDC 版本表](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)
|
||||
|
||||
## 输出解析、操作风险与验收
|
||||
|
||||
建议为 HDC 建立独立执行适配器:固定可执行路径、每次携带目标、有限并发、逐设备串行写入、进程组取消、stdout/stderr 大小上限、操作超时。长日志和文件任务走流式输出,UI 接收结构化事件,不在前端解释 shell 字符串。不要为了隔离无条件另开 USB server,也不要在退出插件时杀掉用户 DevEco Studio 的共享 HDC 服务。
|
||||
|
||||
最小验收应覆盖可观察行为:
|
||||
|
||||
- 无工具、工具依赖缺失、未连接、拒绝授权、多设备、未知输出格式、版本不匹配,都不得显示假空列表或操作成功。
|
||||
- 应用列表中中文显示名与包名分离;详情解析失败不影响其他条目;安装拒绝/签名冲突/包格式错误保留真实结果;成功安装/卸载后刷新验证。
|
||||
- 签名 HAP 安装只能使用用户专门选取的测试包;卸载只能操作明确选择的应用。验证不得卸载用户日常应用来测试失败路径。
|
||||
- 截图下载后能解码,报告文件非空且包含实际内容,日志导出有限且可取消;中断不会把临时文件报告为完成。
|
||||
- 调试沙箱必须验证 release 应用被限制、debug 未启动的处理、权限失败、部分传输、中文/空格路径;导出实际文件校验和对应清单。恢复只在专门测试应用验证,并检查恢复后的应用行为。
|
||||
- 系统备份无法实现时明确展示边界,不执行空壳 `backup_tool`,不以退出码 0、模拟输出、无设备测试或构建通过替代真机验收。
|
||||
|
||||
本调研最终产物仅为本文。文档命令来自第一方资料,设备兼容性仍需按实际固件验证;本机未找到 HDC 的范围见上文,未对手机执行任何命令。
|
||||
|
||||
## 补充:应用输出解析与真实入口选择
|
||||
|
||||
这一节按 2026-09-10 获取的 OpenHarmony 源码追踪输出生产链:Bundle Framework `e0b9be9be36186e040385895f7f764e6e111a809` 与 bm CLI `6b9b9ad316b764f472194a962081c0fb99c32fdf`。下面是**由源码生成规则还原、省略无关字段的代表样例**,不是 HUAWEI Pura 70 Ultra 的实测输出。商业设备返回新结构时应报告格式未知,不能假装已经得到相同数据。
|
||||
|
||||
### `bm dump -a` / `bm dump -g`
|
||||
|
||||
成功输出是按用户分组的纯文本,不是 JSON;当前 CLI 直接转发服务结果,没有额外 `all bundle names:` 标题。样例中的应用名是占位数据,缩进实际为制表符。服务支持多个用户段;CLI 默认查询当前用户,指定不活跃 `-u` 可能产生前置警告并退回当前用户。[BMS 列表生成](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/bundle_mgr_host_impl.cpp#L2999)、[bm CLI 查询和转发](https://github.com/openharmony/bundlemanager_bundle_tool/blob/6b9b9ad316b764f472194a962081c0fb99c32fdf/frameworks/src/bundle_command.cpp#L1153)
|
||||
|
||||
```text
|
||||
ID: 100:
|
||||
com.example.notes
|
||||
com.example.reader
|
||||
```
|
||||
|
||||
解析建议:保留 userId 分组;只在已识别 `ID: <integer>:` 段内读取并验证包名;允许空段和空行;未知诊断行不能被当作应用。不要把任意含点字符串提取成包名,也不要把错误文本归一化成零应用。空结果或查询失败时,当前 CLI 会输出下面的错误,且 dump 分支仍可能返回成功退出码。[bm 空结果处理](https://github.com/openharmony/bundlemanager_bundle_tool/blob/6b9b9ad316b764f472194a962081c0fb99c32fdf/frameworks/src/bundle_command.cpp#L1246)、[缺包查询官方系统测试](https://github.com/openharmony/bundlemanager_bundle_tool/blob/6b9b9ad316b764f472194a962081c0fb99c32fdf/test/systemtest/bm/bm_command_dump_system_test.cpp#L106)
|
||||
|
||||
```text
|
||||
error: failed to get information and the parameters may be wrong.
|
||||
```
|
||||
|
||||
### `bm dump -n <bundle>` 的字段
|
||||
|
||||
服务先输出包名和冒号,再输出完整 JSON 对象。它显式移除顶层 `abilityInfos`,组件应从 `hapModuleInfos[].abilityInfos[]` 读取;不能把 manifest 的 `abilities` 字段直接当作 dump 格式。[BMS 详情组装](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/bundle_mgr_host_impl.cpp#L3094)、[模块序列化](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/src/hap_module_info.cpp#L927)
|
||||
|
||||
```text
|
||||
com.example.notes:
|
||||
{
|
||||
"name": "com.example.notes",
|
||||
"versionCode": 1000000,
|
||||
"versionName": "1.0.0",
|
||||
"entryModuleName": "entry",
|
||||
"applicationInfo": {
|
||||
"bundleName": "com.example.notes",
|
||||
"appProvisionType": "debug"
|
||||
},
|
||||
"hapModuleInfos": [
|
||||
{
|
||||
"moduleName": "entry",
|
||||
"mainAbility": "NotesAbility",
|
||||
"mainElementName": "NotesAbility",
|
||||
"isStageBasedModel": true,
|
||||
"abilityInfos": [
|
||||
{
|
||||
"bundleName": "com.example.notes",
|
||||
"moduleName": "entry",
|
||||
"name": "NotesAbility",
|
||||
"type": 1,
|
||||
"visible": true,
|
||||
"enabled": true,
|
||||
"isStageBasedModel": true,
|
||||
"isLauncherAbility": false,
|
||||
"skills": [
|
||||
{
|
||||
"actions": ["ohos.want.action.home"],
|
||||
"entities": ["entity.system.home"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
准确映射与限制:
|
||||
|
||||
| 需要的信息 | dump 字段与解释 |
|
||||
| --- | --- |
|
||||
| 应用身份/版本 | 顶层 `name`、`versionCode`、`versionName`;并校验 `applicationInfo.bundleName` 与查询目标一致。 |
|
||||
| 调试签名 | `applicationInfo.appProvisionType`;调试值 `debug`,缺失是未知。 |
|
||||
| 入口模块 | 顶层 `entryModuleName`;模块使用 `moduleName`,不是模块显示用的 `name`。 |
|
||||
| 模块主组件 | `hapModuleInfos[].mainElementName`、`mainAbility`;当前 Stage 解析都来源于 manifest 的 `mainElement`。 |
|
||||
| 实际组件 | `hapModuleInfos[].abilityInfos[]`,每项携带 `bundleName`、`moduleName`、`name`。 |
|
||||
| 可见/导出状态 | dump 使用布尔 `visible`。Stage manifest 的 `exported` 写入内部 `visible`,且比旧 `visible` 配置优先;dump 不使用 `exported` 字段。 |
|
||||
| UI 组件类型 | `type` 是数字枚举:`UNKNOWN=0`、`PAGE=1`、`SERVICE=2`、`DATA=3`、`FORM=4`、`EXTENSION=5`;Stage UIAbility 解析为 PAGE。 |
|
||||
| 是否可用 | `enabled` 是布尔;显式 false 时不能作为启动候选。缺字段不应凭空生成 true。 |
|
||||
| 桌面入口 skill | 同一项 `skills[]` 中,`actions` 含 `ohos.want.action.home` 或 `action.system.home`,且 `entities` 含 `entity.system.home`。 |
|
||||
| `isLauncherAbility` | 不是普通应用“可从桌面启动”的通用标志。当前实现只在特殊 launcher entity、预装且系统应用等条件下设置。不能要求普通入口它必须为 true。 |
|
||||
|
||||
这些映射依据:[BundleInfo 序列化](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/src/bundle_info.cpp#L898)、[ApplicationInfo 序列化](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/src/application_info.cpp#L1064)、[组件 JSON 字段](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/include/common_json_converter.h#L669)、[组件类型枚举](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/include/ability_info.h#L57)、[exported 解析](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/module_profile.cpp#L636)、[Stage 类型赋值](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/module_profile.cpp#L2702)、[模块入口赋值](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/inner_bundle_info.cpp#L1800)、[HOME skill 与 launcher 判定](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/module_profile.cpp#L3005)、[HOME 字符串常量](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/include/bundle_constants.h#L174)。
|
||||
|
||||
实现建议:从合法详情中选择 `type == 1 && visible == true`,排除明确禁用项,并校验组件包名/模块名;优先具有 HOME skill 的组件,再用入口模块和主组件字段消除歧义。没有唯一可确定入口时展示实际组件选择,不自动启动任意 exported 页面。`visible`、`type` 缺失时不能直接宣称可启动;旧格式适配应增加明确来源与 fixture。权限仍由系统决定,元数据筛选通过不保证启动一定成功。以上代表样例对应 argv 为 `["-t", key, "shell", "aa start -b com.example.notes -a NotesAbility -m entry"]`。
|
||||
|
||||
解析 JSON 时应先识别查询包名的前缀,再对完整对象解析;校验身份和字段类型,限制输入大小/嵌套深度。不要从错误文本中随意截取第一个到最后一个花括号,或在详情中递归搜同名 `name` 来猜应用身份。没有完整有效详情时保留应用条目和错误,禁用依赖组件/调试状态的操作。
|
||||
|
||||
## 补充:`hdc file recv -b` 目录层级
|
||||
|
||||
OpenHarmony HDC `e93f663afa159d3e601cad1e1869d70326731af6` 的收件端根据**传输开始时本地目标是否存在**决定是否保留源目录 basename;`-b` 在设备端解析沙箱路径,本地主机仍走同一文件接收逻辑。以下以已确认可读的 `data/storage/el2/base` 目录中存在 `files/a.txt` 为例。[HDC 源目录任务生成及沙箱处理](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/src/common/file.cpp#L266)、[接收端目标目录和文件名处理](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/src/common/transfer.cpp#L724)
|
||||
|
||||
| 调用前本地状态 | 接收目标 argv 最后一个参数 | 最终文件位置 |
|
||||
| --- | --- | --- |
|
||||
| `/exports/job` 已存在且为目录 | `/exports/job` | `/exports/job/base/files/a.txt` |
|
||||
| `/exports/job` 不存在 | `/exports/job` | `/exports/job/files/a.txt` |
|
||||
| `/exports/job` 已存在且为普通文件 | `/exports/job` | 目录模式报错,不能作为合法备份目标。 |
|
||||
|
||||
源路径加尾部 `/` 不构成“只复制内容”的开关;源 basename 提取前会去除尾部分隔符。目标不存在时接收器先建目标,再从相对任务路径剥掉第一层;目标已经存在则保留第一层。不要用 rsync 的尾斜杠规则推断 HDC。[basename 实现](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/src/common/base.cpp#L1823)、[CheckFilename 首层处理](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/src/common/transfer.cpp#L801)
|
||||
|
||||
建议先创建唯一、空的 staging 父目录,例如 `/exports/.curios-job-<nonce>`,再传 `["-t", key, "file", "recv", "-b", bundle, "data/storage/el2/base", "/exports/.curios-job-<nonce>"]`;验收期望位于 `staging/base/...` 的输出,记录 `remoteRoot` 与 `localRelativeRoot: "base"`。完成后按清单提交用户目标。恢复必须依清单映射,不能将整个 `base` 再送入设备已经存在的 `base`,制造 `base/base`。若想把目录内容直接放入目标,可使用尚不存在的目标路径,但实现必须控制初始状态,并在对应本机 HDC 版本做行为验收。
|
||||
|
||||
该源码递归任务以文件为单位,纯空源目录直接报 `Operation failed, because the source folder is empty.`;空子目录也不能按文件任务数量证明完整保留。不能把这类报错改写为完整备份成功。若产品承诺保留空目录,应另行枚举并记录空目录、处理权限失败,或将“不保留空目录”列入导出范围。源码不替代当前华为 SDK 二进制的行为测试。[目录遍历实现](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/src/common/transfer.cpp#L658)、[空目录官方测试](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/test/scripts/testModule/test_hdc_file.py#L502)
|
||||
@@ -0,0 +1,116 @@
|
||||
# Harmony Connector:MTP 会话恢复与能力探测
|
||||
|
||||
调研日期:2026-09-10。本文补充 [初始技术调研](harmony-connector.md),只记录源码、公开问题和建议验证路线,不声称完成了真机修复。支持范围按实际 MTP 能力判定,不以 HarmonyOS 版本、机型字符串或 USB product 字符串限制。
|
||||
|
||||
本次任务提供的现象:macOS、libmtp 1.1.23、libusb 1.0.30;Pura 70 Ultra 的 USB 标识为 `12d1:1101`,USB product 为 `HDC Device`,MTP friendly name 为 `HUAWEI Pura70Ultra`,MTP model 为 `HBP-AL00`。0.1.1 在 `LIBMTP_Release_Device` 的 CloseSession 等待约 61 秒后超时;界面已异步处理断开,但用户报告需要拔插才能重连。上述为主任务提供的真机信息,本研究代理未操作 USB。
|
||||
|
||||
## 可用于实现决策的结论
|
||||
|
||||
1. **libmtp 现有路径已经会 reset,不能把“新增 reset”当成修复。** `12d1:1101` 表项使用 `DEVICE_FLAGS_ANDROID_BUGS`,其中同时包含 `LONG_TIMEOUT` 和 `FORCE_RESET_ON_CLOSE`。前者选择 60 秒 USB 超时,后者在释放接口后、关闭 USB handle 前 reset。约 61 秒现象与该等待路径吻合,但仍须日志确认实际失败阶段。[设备表](https://github.com/libmtp/libmtp/blob/v1.1.23/src/music-players.h#L3290)、[标志定义](https://github.com/libmtp/libmtp/blob/v1.1.23/src/device-flags.h#L320)、[USB 实现](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libusb1-glue.c#L2087)
|
||||
2. **OpenHarmony 开源代码存在能产生 CloseSession 无响应的明确控制流。** 它把协议成功码 `0x2001` 当成内部函数返回值,但组包调用方要求 `0`;因此在写响应前退出。该路径也未清除会话状态。它能解释同类症状,尚不能证明这台商业固件包含相同代码。完整链条见下节。
|
||||
3. **`LIBMTP_Reset_Device` 不是 USB reset。** 它仅在设备广告 `ResetDevice(0x1010)` 时发送 MTP 操作;上述 OpenHarmony 实现又把该操作路由到 CloseSession 的同一实现。不能用它替代 USB 层恢复。[libmtp 实现](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.c#L3633)、[OpenHarmony 路由](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/mtp_operation.cpp#L249)
|
||||
4. **建议优先验证“短时限关闭 → 释放接口 → 一次 USB reset → 关闭 handle → 新枚举重连”。** 必须记录每阶段结果,不能静默保留会话并显示已断开。单纯更换 session ID、再加既有标志或一直重试,均没有充分依据。
|
||||
|
||||
## OpenHarmony CloseSession 的具体问题
|
||||
|
||||
此次 GitHub API 查询到的 `openharmony/multimedia_media_library` 当前提交为 `4a411de74cfaf694c14820a51a0e900b04241fe6`。以下结论固定到该提交,描述其开源实现,不将 OpenHarmony 与商业 HarmonyOS 固件等同。
|
||||
|
||||
### 响应没有写入的调用链
|
||||
|
||||
| 层次 | 所查行为 | 影响 |
|
||||
| --- | --- | --- |
|
||||
| 内部成功码 | `MTP_SUCCESS = 0`。 | C++ 内部处理成功应返回 0。[定义](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/frameworks/utils/include/medialibrary_errno.h#L30) |
|
||||
| 协议成功码 | `MTP_OK_CODE = 0x2001`。 | 这是 MTP 响应容器的 response code。[定义](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/include/mtp_constants.h#L413) |
|
||||
| CloseSession payload | `Parser` 在 session 已开时返回 `MTP_OK_CODE`;`Maker` 写入该数值后也返回 `MTP_OK_CODE`。 | 返回值与调用层的内部成功码约定不一致。[实现](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/payload_data/close_session_data.cpp#L34) |
|
||||
| MtpPacket | `ParserPayload` 传播返回值;`MakerPayload`、`Maker` 只有返回 `MTP_SUCCESS` 才继续。 | CloseSession 的组包结果为 `0x2001`,被当作失败返回。[实现](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/mtp_packet.cpp#L166) |
|
||||
| MtpOperation | 收包失败也尝试 `SendMakeResponsePacket`,但该方法在 `responsePacketPtr_->Maker(false) != MTP_SUCCESS` 时提前返回。 | 无论从正常还是错误响应路径进入,都会在 `Write` 之前结束。[实现](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/mtp_operation.cpp#L129) |
|
||||
|
||||
这是静态控制流分析:在所查代码和正常对象初始化前提下,可以追踪到“收到关闭请求,主机却收不到关闭响应”的路径;并非手机端日志或固件反编译结果。`CalculateSize` 也调用这个返回协议码的 `Maker`,不过无需依赖长度问题,调用方对返回值的检查已足以解释写入被跳过。
|
||||
|
||||
### 会话状态与重新打开
|
||||
|
||||
`DealRequest` 在 OpenSession 时检查 `sessionOpen`,未打开时保存参数中的 `tempSessionID`,已打开时返回 SessionAlreadyOpen;`GetOpenSession` 在成功响应时设置 `sessionOpen=true`。CloseSession 的 `Parser` 只检查已有会话,`GetCloseSession` 只创建 payload,没有将 `sessionOpen` 设为 false 或将 `sessionID` 清零;`ResetOperation` 只重置当前命令、transaction ID 和数据阶段。所查 `services/media_mtp` 源文件未找到关闭时清除这些会话字段的其他赋值。[打开处理与每次操作重置](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/mtp_operation.cpp#L213)、[会话响应处理](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/mtp_operation_utils.cpp#L668)、[初始状态](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/include/mtp_operation_context.h#L49)
|
||||
|
||||
因此,新的非零 session ID 不会自动绕过 `sessionOpen` 检查。设备端 `ResetDevice` 和 CloseSession 又使用同一个 payload 与响应处理,不能从 OperationsSupported 含 `0x1010` 推断它真的重置了会话。[payload 工厂](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/packet_payload_factory.cpp#L65)
|
||||
|
||||
## libmtp 的关闭、打开与身份语义
|
||||
|
||||
1.1.23 的 `LIBMTP_Release_Device` 返回类型是 `void`:它调用 `close_device`,然后清错误栈、释放 `PTP_USB`、`PTPParams`、存储和 device 对象。因而 Release 返回后,旧 `device`、`usbinfo` 和 USB handle 都不可继续使用;仅靠该 API 的返回值也拿不到关闭是否成功。[释放实现](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.c#L2513)
|
||||
|
||||
关闭顺序为 `ptp_closesession → close_usb`;后者默认执行 `clear_stall → libusb_release_interface → 按标志 libusb_reset_device → libusb_close`。reset 和 release 的返回值在此没有向调用者报告。连接路径先以 5 秒时限尝试 OpenSession,遇 `PTP_ERROR_IO` 已会 reset、关闭 USB、重新初始化并重试;接受 `OK` 和 `SessionAlreadyOpened`。若回到 retry,正常设备超时可能又是 60 秒。[1.1.23 USB 流程](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libusb1-glue.c#L2304)
|
||||
|
||||
截至本次调研的 libmtp 当前提交 `9de46078d15d290ac5c46039937b1164b918dbf0`,CloseSession 和 OpenSession 的上述恢复路径仍存在,未发现可通过直接升级主分支获得的对应修复。[当前代码](https://github.com/libmtp/libmtp/blob/9de46078d15d290ac5c46039937b1164b918dbf0/src/libusb1-glue.c)
|
||||
|
||||
libmtp 使用 session 参数 `1`。`ptp_opensession` 自行将 OpenSession transaction ID 归零,并在事务后将本地 session ID 设为传入值。修改随机 ID 必须连同调用约定理解;它不会修复无响应或设备端 `sessionOpen` 不清除。代码中 InvalidTransactionID 分支先加 10,再调用会归零的 `ptp_opensession`,因此也不能把该分支视为可靠的 ID 恢复保证。[PTP 实现](https://github.com/libmtp/libmtp/blob/v1.1.23/src/ptp.c#L1364)
|
||||
|
||||
`12d1:1101` 的上游记录来自 Nova14 的 ID 报告,只证明这个 USB ID 已入表;它不是对 Pura 70 Ultra 的型号鉴定。USB product `HDC Device` 也不否定同一复合设备上的 MTP 接口。应用应展示实际 MTP friendly/model 信息,使用选中 USB 实例及实际接口建立会话;不能将表中的 Nova14 名称硬编码为真实机型。[上游 issue #334](https://github.com/libmtp/libmtp/issues/334)、[设备表](https://github.com/libmtp/libmtp/blob/v1.1.23/src/music-players.h#L3290)
|
||||
|
||||
libmtp README 本身记录了设备 CloseSession 实现不佳、首次打开成功而后续失败的历史问题;这是固件兼容问题的先例,不是当前设备根因的独立证明。[libmtp README](https://github.com/libmtp/libmtp/blob/v1.1.23/README)
|
||||
|
||||
## macOS USB reset 的限制与调用位置
|
||||
|
||||
`libusb_reset_device` 的公开契约是 USB port reset,并尝试恢复先前 configuration/alternate settings;如果重枚举后的设备改变而返回 `LIBUSB_ERROR_NOT_FOUND`,调用者应关闭旧 handle 并重新发现设备。它不是“手机已经接受新的 MTP 会话”的保证。[libusb API](https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html#gafee9c4638f1713ca5faa867948878111)
|
||||
|
||||
libusb 1.0.30 Darwin 后端在普通、未 capture 的 macOS 路径调用 `USBDeviceReEnumerate`,等待重枚举,再恢复状态;源码说明 10.11 起旧 `ResetDevice` 路径不再有效。它有专门的重枚举等待和返回值,不能省略检查,也不能与另一线程的 close/open/reset 并发。[Darwin reset 实现](https://github.com/libusb/libusb/blob/v1.0.30/libusb/os/darwin_usb.c#L2139)
|
||||
|
||||
2026-08 的上游 [issue #1923](https://github.com/libusb/libusb/issues/1923) 报告 Darwin 在接口仍 claimed 时 reset、再 claim 的旧接口泄漏,以及 restore 失败后 claim 位丢失。对应 [PR #1942](https://github.com/libusb/libusb/pull/1942) 在本次查询时仍未合并。报告包含 macOS 实验,但设备是 SEGGER J-Link、部分失败来自故障注入,不能当成本项目手机已命中该问题。它足以说明:**不能因为其他库先 reset 就无条件把本项目 reset 移到 release_interface 之前。**
|
||||
|
||||
建议的调用位置:
|
||||
|
||||
| 位置 | 判定 |
|
||||
| --- | --- |
|
||||
| 传输中、另一个线程仍执行 MTP 时 | 不使用;先串行收束当前事务,避免重置仍在使用的句柄。 |
|
||||
| CloseSession 失败后、USB handle 尚有效 | 可以由拥有该 handle 的底层实现处理;优先释放接口后 reset,检查结果,再 close。 |
|
||||
| `LIBMTP_Release_Device` 返回后,拿旧 `usbinfo` reset | 错误:对象和 handle 已释放。 |
|
||||
| Release 完成后,新枚举并打开同一 USB 实例的全新 libusb handle,再 reset | 可作为受控恢复路线;这时不要先 OpenSession 再 reset。需要准确定位设备、检查返回值并重新枚举,不能仅按 VID/PID 匹配第一台。 |
|
||||
| 正常关闭也无条件反复 reset | 不作为额外策略;现有 quirk 已可能 reset,重复执行增加重枚举和占用干扰。 |
|
||||
|
||||
在本项目固定版本的 C 边界内,关闭超时和原始 reset 状态若无法经公开 API取得,可用小范围、可重现的依赖补丁返回状态;避免在 Rust/前端按内存偏移解包私有结构。若用新 handle 做恢复,不应复用旧 bus/device address 作为永久身份;重枚举可能使它失效。[libusb 枚举与 handle 说明](https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html)
|
||||
|
||||
## 其他 macOS MTP 工具能提供什么证据
|
||||
|
||||
| 实现 | 观察 | 可借鉴与边界 |
|
||||
| --- | --- | --- |
|
||||
| OpenMTP 的 go-mtpx/go-mtpfs | `go-mtpx` 固定到 `go-mtpfs` 提交 `1c3302b3c476`。`Close` 遇 CloseSession 错误会先 USB Reset,再 ReleaseInterface、Close;`Configure` 遇 SessionAlreadyOpened 先尝试 CloseSession/OpenSession,其余失败 reset、close、等约一秒、重新打开。 | 证明一种现实恢复路线存在,但不是这台设备或当前 libusb 1.0.30 的验证。不要直接继承“约一秒足够”的假设。[依赖](https://github.com/ganeshrvel/go-mtpx/blob/18f12db021cc4256396748dbd2996d15853d7033/go.mod)、[关闭和配置](https://github.com/ganeshrvel/go-mtpfs/blob/1c3302b3c476f69e787a0339122a804dd518a007/mtp/mtp.go#L94) |
|
||||
| go-mtpfs session ID | 每次选择非零、非 `0xffffffff` 的随机 session ID。 | 是它自身的实现选择;没有证明随机化能解决 OpenHarmony 的状态未清除。[实现](https://github.com/ganeshrvel/go-mtpfs/blob/1c3302b3c476f69e787a0339122a804dd518a007/mtp/ops.go#L16) |
|
||||
| Android File Transfer for Linux/macOS(whoozle) | Session Close 发 CloseSession 并等响应;通用 Device Open 有可选 reset,但其 Darwin backend 的 `Device::Reset()` 是空实现。 | 不能从工具支持 macOS 或方法名推断它进行了有效 USB reset。这是开源独立项目,非 Google 的同名 Android File Transfer。[项目](https://github.com/whoozle/android-file-transfer-linux)、[session](https://github.com/whoozle/android-file-transfer-linux/blob/88926930db41238c7b4d7237fc5849b9586cc7b8/mtp/ptp/Session.cpp#L105)、[Darwin 空实现](https://github.com/whoozle/android-file-transfer-linux/blob/88926930db41238c7b4d7237fc5849b9586cc7b8/mtp/backend/darwin/usb/Device.cpp#L38) |
|
||||
|
||||
本次未取得 Google 闭源 Android File Transfer 的第一方 host 源码,因此不声称其关闭顺序或私有恢复方式。OpenMTP 的 Huawei P30 Pro [公开问题 #452](https://github.com/ganeshrvel/openmtp/issues/452) 报告的是目录/传输故障,也不能作为 HarmonyOS 当前设备的重连成功证明。
|
||||
|
||||
## 按实际设备能力开放功能
|
||||
|
||||
建立会话后读取设备 `OperationsSupported`、`DevicePropertiesSupported`,对象功能还要结合格式、属性描述、存储写权限和操作实际返回值。广告支持不等于本次操作成功,也不等于所有对象类型都可用。OpenHarmony 所查代码声明了 delete、move、copy、thumbnail、battery 和对象属性,但实际商业设备返回值才是应用开关依据。[OpenHarmony DeviceInfo 声明](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/payload_data/get_device_info_data.cpp#L32)
|
||||
|
||||
以下为 libmtp 1.1.23 的公开 API 映射;操作码由其 PTP 定义核对。[公开头文件](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.h.in)、[PTP 操作定义](https://github.com/libmtp/libmtp/blob/v1.1.23/src/ptp.h)
|
||||
|
||||
| 功能 | 公开 API | 探测与实现边界 |
|
||||
| --- | --- | --- |
|
||||
| 改名 | `LIBMTP_Set_File_Name`、`LIBMTP_Set_Folder_Name` | 按实际格式查询 `ObjectFileName(0xdc07)`;要求 `GetObjectPropDesc(0x9802)` 的 GetSet 为可写,并有可用的 `SetObjectPropValue(0x9804)` 或 SetObjectPropList。库的改名 helper 自己检查 GetSet。不要新用已弃用的 `LIBMTP_Set_Object_Filename`。 |
|
||||
| 删除 | `LIBMTP_Delete_Object` | `DeleteObject(0x100b)`;仍处理 ObjectWriteProtected、AccessDenied 等对象级拒绝。目录递归由应用计划与执行,库文档不保证删除目录会删除全部子对象。 |
|
||||
| 移动 | `LIBMTP_Check_Capability(...MoveObject)`、`LIBMTP_Move_Object` | `MoveObject(0x1019)`;目标根 parent 参数是 `0`。目录和跨存储行为须验证。标准命令无字节进度,不显示虚假百分比。 |
|
||||
| 设备内复制 | `LIBMTP_Check_Capability(...CopyObject)`、`LIBMTP_Copy_Object` | `CopyObject(0x101a)`;无字节进度;API 仅返回成功/失败,不返回新对象 ID,完成后重新枚举目标目录。不支持时不可把移动偷偷变成复制后删除。 |
|
||||
| 缩略图 | `LIBMTP_Get_Thumbnail` | `GetThumb(0x100a)`,并按对象是否有缩略图处理。失败显示普通图标;它不是整图预览保证。RepresentativeSample 是另一组属性/API。 |
|
||||
| 电量 | `LIBMTP_Get_Batterylevel` | `BatteryLevel(0x5001)` 在设备属性列表中;API还检查 broken battery quirk。只有调用成功且最大值有效时换算,失败/不支持显示未知,不能显示 0%。 |
|
||||
| 对象属性读取 | `LIBMTP_Is_Property_Supported`、`LIBMTP_Get_String_From_Object`、`LIBMTP_Get_u64/u32/u16/u8_From_Object` | `GetObjectPropsSupported(0x9801)` 按格式,`GetObjectPropDesc(0x9802)`、`GetObjectPropValue(0x9803)` 按具体属性。`Is_Property_Supported` 返回正数/0/负数分别为支持/不支持/查询失败,不能把 -1 当成 true。 |
|
||||
| 对象属性写入 | `LIBMTP_Set_Object_String/u32/u16/u8`、`LIBMTP_Get_Allowed_Property_Values` | 存在不等于可写;遵循 GetSet、数据类型、枚举/范围。后一个 API 返回合法值,不直接暴露 GetSet;不能据它推断写权限。 |
|
||||
| 目录下载 | `LIBMTP_Get_Files_And_Folders` + `LIBMTP_Get_File_To_File_Descriptor` | 主机遍历对象树,使用各 storage ID/parent ID;不是单一“递归传输”操作。按实际项汇总大小、进度和失败。 |
|
||||
| 目录上传 | `LIBMTP_Create_Folder` + `LIBMTP_Send_File_From_File_Descriptor` | 以 association/folder 对象建立层级,再逐文件传输;基础操作为 SendObjectInfo/SendObject 或设备支持的 SendObjectPropList 路径。保留空目录、逐项处理重名和失败。 |
|
||||
|
||||
公开 `LIBMTP_Check_Capability` 仅覆盖 GetPartialObject、SendPartialObject、EditObjects、MoveObject、CopyObject,不能拿它询问任意操作码。若前端需要全部 OperationsSupported,应在与固定 libmtp 一起编译的 C 桥接层提供只读投影,或给依赖增加受控 API,不从 UI 解析 `mtp-detect` 文本。[能力实现](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.c#L4132)
|
||||
|
||||
GetObjectPropList 也要遵循 libmtp 的已知兼容标志,不能为了“一次取全树”强制启用。对于当前 ID,上游已有 BROKEN_MTPGETOBJPROPLIST/BROKEN_SET_OBJECT_PROPLIST/BROKEN_SEND_OBJECT_PROPLIST;这些是库的兼容策略,不表示设备完全不能改名或传输。[兼容标志](https://github.com/libmtp/libmtp/blob/v1.1.23/src/device-flags.h#L320)
|
||||
|
||||
递归删除宜先枚举用户选中的子树,再从叶到根执行。复制/移动目录时,不能仅凭标准操作支持就承诺任意层级、跨存储、同名覆盖或取消的原子性。libmtp 文档明确警告目录操作语义与耗时;应用级递归必须保留部分成功状态,不能自动删除原文件作为未说明的降级。[删除/移动/复制契约](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.c#L6922)
|
||||
|
||||
## 最小验证路线
|
||||
|
||||
以下是建议给主任务执行的实验,不是本文已经执行的验证。
|
||||
|
||||
1. 串行记录一次连接、只读目录、断开、重连全过程。区分 CloseSession 写请求、等响应、release_interface、USB reset、USB close 的耗时与返回码;确认旧后端进程/线程是否退出、旧 handle 是否完成释放。不要把设备名可枚举当成 MTP 会话已恢复。
|
||||
2. 将关闭独立于正常传输超时:CloseSession 超时后继续清理,避免反复调用它;不改变大文件传输的合理超时。先保留当前 release→reset 顺序,检查这次 reset 是否真的完成。若产品需要状态回传,在固定库边界内显式提供结果。
|
||||
3. 旧 handle 全部关闭后,按新枚举结果重连,实际完成 GetStorageIDs 与至少一次目录读取。只观察 UI 按钮恢复、扫描返回设备、reset 返回 0 均不足以通过。
|
||||
4. 只在基线有完整日志后比较候选方案:释放接口后 reset;必要时用全新 raw USB handle 做独立恢复。一次实验只变一处,避免短超时、随机 ID、不同 reset 顺序、额外库补丁同时变化而无法定位。
|
||||
5. 若 reset 返回 NOT_FOUND,按 API 重新发现设备;若 reset 无响应或设备不重新出现,显示恢复失败和真实状态,不无限循环。硬件重新插拔能恢复,不足以证明软件重枚举一定具备等价效果。
|
||||
6. 修复通过后,在同一 USB 插接周期连续完成多次 connect→browse→disconnect→connect,并包含插件关闭/退出再打开。写操作能力在专用测试目录验证:改名、删除、移动、复制、中文/emoji、空文件、空目录、嵌套传输,下载回传比对哈希;缩略图和电量失败应保留其余浏览能力。
|
||||
|
||||
本研究实际完成:读取项目固定依赖源码;在线查询并固定第一方仓库提交;核对 OpenHarmony 会话调用链、libmtp API、libusb Darwin reset、OpenMTP 与 whoozle 实现;查询相关公开 issue/PR 状态。未修改实现、未打开或重置 USB、未把开源静态分析标为商业设备实测根因。
|
||||
@@ -0,0 +1,134 @@
|
||||
# Harmony Connector 投屏与键鼠控制调研
|
||||
|
||||
调研日期:2026-09-10。范围是 macOS 上的 Harmony Connector,目标包括原生 HarmonyOS 和 OpenHarmony,按最新公开资料评估,不以某个系统大版本作硬上限。承接 [设备管理调研](harmony-device-management.md)。本轮仅阅读官方文档及项目源码,未安装工具、执行手机命令或改变设备状态;以下命令是实现契约/待验证方案,不是真机验收记录。
|
||||
|
||||
## 结论
|
||||
|
||||
**官方 scrcpy 不能直接作为原生鸿蒙投屏后端。** 它的设备端是 Android framework 上的 Java/Dex 程序,经 ADB 启动;把可执行文件名从 adb 换成 hdc,不能替代 Android 运行时、屏幕采集和输入 API。官方 scrcpy 当前 README 为 v4.1,设备要求 Android API 21+,macOS 是支持的电脑平台。只有设备确实提供兼容 Android 的运行时、ADB shell 及相应屏幕/输入能力时,才适合另外启用 scrcpy 通道;手机品牌、能运行某些 APK 或存在兼容容器都不是充分条件。[scrcpy 官方项目](https://github.com/Genymobile/scrcpy)、[服务端架构](https://github.com/Genymobile/scrcpy/blob/master/doc/develop.md)
|
||||
|
||||
本项目可按以下顺序推进,优先级是工程建议:
|
||||
|
||||
| 路线 | 画面与输入 | 本项目建议 |
|
||||
| --- | --- | --- |
|
||||
| HDC + UITest CLI | 单次截图;可轮询预览。点击、长按、滑动、指定文本、常用键。 | P0:复用现有 HDC 后端;显示“屏幕预览与操作”,不承诺视频级流畅度。 |
|
||||
| 官方 DevEco Studio / DevEco Testing | 官方已提供设备投屏、点击/滑动/字符输入等。 | P0:提供官方工具入口及指引;未查到可嵌入第三方插件的公开投屏协议。 |
|
||||
| 官方系统录屏 CLI | 录制后导出 MP4。 | P1:作为“录屏导出”独立功能,不把 MP4 文件导出当实时镜像流。 |
|
||||
| 自建正常签名的鸿蒙伴随应用 + AVScreenCapture + HDC 转发 | 经手机用户同意采集,编码后持续传输;输入可另走 UITest。 | P2:真实视频流的优先独立原型,必须验证商业设备授权、后台持续采集和编码器。 |
|
||||
| OpenHarmony UITest addon | 自建 native 扩展,JPEG 帧回调及原子输入 API。 | P2:开源实现可研究,商业固件可加载性/ABI 未验证。 |
|
||||
| 鸿镜 / OpenHarmony_Scrcpy | 已有连续编码视频及控制实现。 | 面向可配置 OpenHarmony 系统的参考,当前系统权限/root 要求不适合作为普通华为手机的默认方案。 |
|
||||
| 官方 scrcpy | Android 视频流及键鼠控制。 | 仅为已验证 Android/ADB 设备提供独立可选入口。 |
|
||||
|
||||
## scrcpy 依赖与直接集成边界
|
||||
|
||||
scrcpy 客户端推送 server 到设备并启动,视频、音频、控制使用分离 socket。server 使用 Android `app_process` 加载 Dex,屏幕关联 Surface 经 `MediaCodec` 编码,输入通过 Android `InputManager.injectInputEvent()` 等方法处理;其中包含通过反射调用的隐藏 API。客户端解码使用 FFmpeg、窗口输入使用 SDL。项目明确客户端和 server 必须版本一致,其内部协议不保证跨版本兼容。[官方开发说明](https://github.com/Genymobile/scrcpy/blob/master/doc/develop.md)
|
||||
|
||||
因此 `hdc fport` 只能替代传输中的一小部分,不能运行 Android server;要复用 scrcpy UI/解码需要替换连接、启动器、设备端采集/编码/输入,并协调协议,属于移植工程。不能声称下载官方 scrcpy 后便支持原生 HarmonyOS。对具有 Android 兼容能力的设备,按实际授权 ADB 目标识别,运行受控的原生 scrcpy 会话验收;不要根据 HarmonyOS 名称硬编码“可用/不可用”。[scrcpy 连接方式](https://github.com/Genymobile/scrcpy/blob/master/doc/connection.md)、[服务端约束](https://github.com/Genymobile/scrcpy/blob/master/doc/develop.md)
|
||||
|
||||
若用户已经安装官方 scrcpy 和 adb,候选 Android 通道的只读预检可使用 `scrcpy --version`、`adb devices -l`、`adb -s <serial> shell getprop ro.build.version.sdk`。实际会话由用户点击后执行以下 argv;macOS 上优先启动独立窗口,减少嵌入原生渲染的成本。此处参数组合是本项目建议,不是鸿蒙兼容证明。[官方连接参数](https://github.com/Genymobile/scrcpy/blob/master/doc/connection.md)、[视频参数](https://github.com/Genymobile/scrcpy/blob/master/doc/video.md)、[控制参数](https://github.com/Genymobile/scrcpy/blob/master/doc/control.md)
|
||||
|
||||
```text
|
||||
scrcpy argv: ["--serial", serial, "--no-audio", "--max-size=1280", "--max-fps=30"]
|
||||
仅观看时再添加: ["--no-control"]
|
||||
```
|
||||
|
||||
scrcpy 的 `--otg` 通过 Android Open Accessory 模拟 USB HID,不依赖 ADB,但它**没有投屏**。不能将 OTG 模式宣传成绕开鸿蒙设备端视频支持的办法,也不能未验证便承诺手机实现相应 USB accessory 协议。[官方 OTG 文档](https://github.com/Genymobile/scrcpy/blob/master/doc/otg.md)
|
||||
|
||||
macOS 官方文档提供预编译包和包管理方式。集成可优先使用用户已安装工具;本轮没有下载运行任何发布二进制。scrcpy 为 Apache-2.0,重新分发还需审核所选构建中的第三方依赖及其声明;这不授予 Huawei SDK 的重打包许可。[macOS 获取方式](https://github.com/Genymobile/scrcpy/blob/master/doc/macos.md)、[scrcpy LICENSE](https://github.com/Genymobile/scrcpy/blob/master/LICENSE)
|
||||
|
||||
## 可立即实现:HDC 屏幕预览和操作
|
||||
|
||||
HDC 需设备开启开发者调试并信任电脑。以下固定使用用户选择的 `key`;宿主用 argv 调用,不执行本机 `sh -c`。设备端 shell 参数仍需严格转义,坐标和速度仅接受有界数字。[华为 HDC](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)
|
||||
|
||||
| 操作 | HDC argv(省略 hdc 本身) |
|
||||
| --- | --- |
|
||||
| 版本 | `["-t", key, "shell", "uitest --version"]` |
|
||||
| 工具帮助 | `["-t", key, "shell", "uitest help"]` |
|
||||
| 输入帮助 | `["-t", key, "shell", "uitest uiInput help"]` |
|
||||
| 截图 | `["-t", key, "shell", "uitest screenCap -p /data/local/tmp/curios-screen-<nonce>.png"]` |
|
||||
| 下载截图 | `["-t", key, "file", "recv", remoteTemp, localTemp]` |
|
||||
| 点击 | `["-t", key, "shell", "uitest uiInput click 160 240"]` |
|
||||
| 长按 | `["-t", key, "shell", "uitest uiInput longClick 160 240"]` |
|
||||
| 滑动 | `["-t", key, "shell", "uitest uiInput swipe 160 720 160 320 600"]` |
|
||||
| 拖拽 | `["-t", key, "shell", "uitest uiInput drag 160 240 480 640 600"]` |
|
||||
| 当前焦点文本 | `["-t", key, "shell", "uitest uiInput text <escaped-text>"]` |
|
||||
| 指定输入框文本 | `["-t", key, "shell", "uitest uiInput inputText 160 240 <escaped-text>"]` |
|
||||
| 返回/主页 | `["-t", key, "shell", "uitest uiInput keyEvent Back"]` / `Home` |
|
||||
|
||||
精确 CLI 来源是 [OpenHarmony UITest 指引](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/application-test/uitest-guidelines.md)。截图 `-p` 只支持 `/data/local/tmp/`;`-d <displayId>` 从 API 20 起。`swipe`/`drag` 最后参数是速度 px/s,范围 200–40000,**不是毫秒时长**。`text` 从 API 18 起,只有当前焦点支持输入时才有实际效果。键码取 OpenHarmony `KeyCode`,不能照搬 Android keycode 或 macOS keycode;特殊 `Back/Home/Power` 不支持组合键。[UITest 操作定义](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/README_zh.md)、[OpenHarmony KeyCode](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/reference/apis-input-kit/js-apis-keycode.md)
|
||||
|
||||
工程实现建议:
|
||||
|
||||
- 查看预览和启用控制分开;进入页面不自动注入按键。开始预览后串行截图、下载、解码,完成一帧再请求下一帧,避免积压。可先设置约 1 秒的刷新目标,实际显示采集时间,不宣传固定 FPS。
|
||||
- 按当前 PNG 真实尺寸和画面显示区域计算坐标,排除留白;旋转/折叠后刷新尺寸,暂停旧坐标输入。鼠标拖动收集起终点后发送一次 swipe/drag,不能伪装成实时多点触控。
|
||||
- 每个目标限制一个屏幕会话,输入队列有上限;用户停止、窗口隐藏、设备断开或授权失效时立即停止排队,避免恢复连接后补发旧输入。文本使用单独输入框和发送按钮,不全局捕获电脑键盘。
|
||||
- 对中文、空格、引号、emoji、新行和输入法组合做真实验证。不要为此同步读取剪贴板或关闭密码键盘。输出可解析/退出码正常只能表示命令执行结果,截图刷新或实际界面变化才是操作效果证据。
|
||||
- 保存截图使用临时文件并校验 PNG;默认帧只在本机短暂缓存。清理只删除本会话生成的远端/本地文件,不碰用户相册。锁屏和受保护画面保持系统行为,不自动解锁或关闭隐私保护。
|
||||
|
||||
`uiRecord record` 是操作记录,不是视频录屏;其记录可写到 CSV,并不产生 H.264 视频。不要把它接成“录制投屏”。[UITest uiRecord](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/application-test/uitest-guidelines.md)
|
||||
|
||||
## 官方工具:确有投屏,但没有可确认的第三方嵌入协议
|
||||
|
||||
DevEco Studio 官网当前明确支持连接设备投屏、点击、滑动、字符输入及拖拽包/文件;26.0.0 Release 的官方说明将设备投屏列为新增能力。产品提供 macOS Intel/ARM 版本。它是真实设备投屏能力,不能与“鼠标控制模拟器”的教程混用。[DevEco Studio 官网](https://developer.huawei.com/consumer/cn/deveco-studio)、[26.0.0 官方版本概览](https://developer.huawei.com/consumer/cn/doc/doccenter-release-notes/overview-2600)、[仅针对模拟器的操控屏幕文档](https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-emulator-control-screen)
|
||||
|
||||
DevEco Testing 官方 FAQ 确认 Windows/Mac 客户端、设备投屏等测试工具;要求 HarmonyOS NEXT 设备开启开发者模式和 USB 调试,并能被 HDC 识别。官方工具介绍明确其投屏支持在 PC 上操作设备。最新 26.0.0 兼容表仍将“设备投屏”的模拟器列标为不支持;不要把云手机特定接入指引外推到所有本地模拟器。[DevEco Testing FAQ](https://devecotesting.huawei.com/userPortal/help?jobType=100000102)、[官方工具介绍](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V14/devecotesting-V14)、[最新兼容表](https://developer.huawei.com/consumer/cn/doc/doccenter-release-notes/deveco-testing-releasenotes-2600)
|
||||
|
||||
本轮没有从这些第一方资料确认可供第三方插件使用的投屏 SDK、稳定 socket 协议、可重新分发的 device agent 或指定设备投屏 deep link。因此可以引导用户打开已安装的官方应用并选择设备;不能提取其私有 agent 并当作插件依赖。`Device Manager` 只是工具内管理入口的名称,不能据二手教程编造其稳定 CLI。本轮未检视商业工具安装包,不判断其内部实现。
|
||||
|
||||
## 官方录屏 CLI:结束后导出视频
|
||||
|
||||
华为官方录屏文档要求设备解锁;录制中锁屏会退出,文档不支持模拟器录屏。其命令流程是启动系统录屏组件、停止、定位媒体、必要时复制到可下载目录、下载文件,未给出实时 stdout 视频流协议。[华为录屏文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-screen-recording)
|
||||
|
||||
```text
|
||||
开始:
|
||||
["-t", key, "shell", "aa start -b com.huawei.hmos.screenrecorder -a com.huawei.hmos.screenrecorder.ServiceExtAbility --ps CustomizedFileName curios-record-<nonce>.mp4"]
|
||||
|
||||
停止(官方写法仍为 aa start,同组件但不传文件名):
|
||||
["-t", key, "shell", "aa start -b com.huawei.hmos.screenrecorder -a com.huawei.hmos.screenrecorder.ServiceExtAbility"]
|
||||
|
||||
定位录制结果:
|
||||
["-t", key, "shell", "mediatool query curios-record-<nonce>.mp4 -u"]
|
||||
|
||||
查询含 uri 时,用查询返回且已验证的 URI 导出:
|
||||
["-t", key, "shell", "mediatool recv <escaped-returned-uri> /data/local/tmp"]
|
||||
|
||||
下载工具实际返回的路径:
|
||||
["-t", key, "file", "recv", returnedRecordFile, localTemp]
|
||||
```
|
||||
|
||||
官方说明有 URI 时不能直接下载查询结果里的原始媒体路径;需 `mediatool recv` 得到可下载路径。实现不要只盲取“第几行”,应验证 URI、返回路径和唯一文件名。开始/停止是设备写操作,不能作为能力探测;录屏组件的停止写法也不能无限重试。用会话状态管理、手机通知和最终可解码文件验收,未知状态引导在手机停止。本功能会生成设备媒体文件,默认不能顺手删除相册原件。[华为录屏文件导出流程](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-screen-recording)
|
||||
|
||||
## 真正视频流:自建伴随应用路线
|
||||
|
||||
AVScreenCapture 支持将采集结果作为码流交给其他模块,以实现桌面共享。最新 OpenHarmony 指引区分文件输出和码流输出,并说明码流录制开始会出现隐私选择弹窗;受保护内容可被屏蔽。华为最新窗口级录屏文档亦明确 Phone/Tablet 的 Picker 能力和权限条件。因此“普通应用一概不能录屏”不正确;需要区别用户授权录屏与系统免授权采集/输入注入。[OpenHarmony 录屏基础流程](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/media/media/avscreencapture-c-basic-process.md)、[华为窗口级录屏与授权](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/using-avscreencapture-for-file-with-window)
|
||||
|
||||
建议独立原型链路为:用户启动已签名 HAP并在手机允许共享 → `OH_AVScreenCapture_Create/Init` → 绑定编码器输入 Surface 后 `OH_AVScreenCapture_StartScreenCaptureWithSurface` → `OH_VideoEncoder` 输出 H.264 → 应用内 loopback TCP → HDC fport → macOS VideoToolbox/受控播放器。采集、编码和传输分别处理错误与背压;音频初版关闭。停止使用 `OH_AVScreenCapture_StopScreenCapture/Release`,并关闭编码器和本会话转发。详细 API 配置从配套 SDK 的官方示例实现,不复制系统免授权权限。[官方采集到编码器 Surface 示例](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/media/media/avscreencapture-c-custom-scenarios.md)
|
||||
|
||||
传输示例为 `["-t", key, "fport", "tcp:<hostPort>", "tcp:<companionPort>"]`,停止后仅移除本次建立的转发 `["-t", key, "fport", "rm", "tcp:<hostPort>", "tcp:<companionPort>"]`;两个端口是自建协议配置,HDC 本身不会生成视频。使用 loopback、每会话随机 token、长度上限和协议版本;需要网络直连时另行设计用户配对与认证,不默认监听 `0.0.0.0`。手机端普通 HAP 不由录屏授权获得任意输入注入权限;键鼠仍可通过已授权 HDC 的 UITest 命令完成。此路线需新建手机工程、签名与真机验证,当前插件未具备。[华为 HDC 端口转发](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)
|
||||
|
||||
## OpenHarmony UITest addon:可研究的 native 扩展
|
||||
|
||||
检查官方 `testfwk_arkxtest` 提交 `b04e30c40cf266c9abfbd52933e43a0456eb99a2`:`uitest/addon/extension_c_api.h` 定义 `UiTestExtension_OnInit`、`UiTestExtension_OnRun`,通过 `UiTestPort.initLowLevelFunctions` 获取 `startCapture/stopCapture`、`atomicTouch`、`atomicMouseAction` 等函数。`startCapture` 名称 `copyScreen` 接收 JSON 选项 `scale`(严格 0 到 1 之间)、`displayId`;回调提供 JPEG 字节,当前开源实现反复 `GetScreenshot` 并编码变化帧,不是 H.264 编码流。[扩展 C ABI](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_c_api.h)、[调用实现](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_executor.cpp)、[JPEG 采集实现](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/screen_copy.cpp)
|
||||
|
||||
源码可导出的启动契约是 `uitest start-daemon singleness --extension-name curios-screen-<nonce>.so`,加载 `/data/local/tmp/` 下相应文件,默认名 `agent.so`。扩展须自行实现有界传输、停止以及主机认证。**这是源码入口,不是本次确认的商业公共 CLI。** 官方仓库 README 将 start-daemon 标为测试端注入用途;商业固件可能改变加载策略、系统库、ABI 或采集实现。先在自己可控制的 OpenHarmony 测试镜像验证,再专门评估真实商业设备,不应默认为任何手机加载 native 代码。[加载器](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_executor.cpp#L354)、[命令分派](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/server/server_main.cpp#L471)、[官方 README](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/README_zh.md)
|
||||
|
||||
不能从论坛提到的 `xdevice_scrcpy` 或 `DevEcoScrcpy` 日志推断商业工具私有协议已公开,也不能把上面的开源 JPEG 路径描述成与商业 H.264 路径完全相同。
|
||||
|
||||
## 已核查的开源成品
|
||||
|
||||
| 项目与锁定提交 | 真实机制、macOS、授权边界 | 结论 |
|
||||
| --- | --- | --- |
|
||||
| [鸿镜 ohos-scrcpy-app](https://github.com/guoxiucai/ohos-scrcpy-app/tree/1d470ea8f571f19fcbb6883cc6118fafed6fe14f) | Flutter macOS/Windows;H.264、VideoToolbox、鼠标/触摸/按键、录制,编码失败可降级 JPEG。设备端为系统 HAP,作者明确要求 Full SDK、目标系统信任签名和权限白名单。 | 可参考本机解码与协议设计;不能普通签名安装到商业手机便承诺工作。 |
|
||||
| [OpenHarmony_Scrcpy](https://github.com/luodh0157/OpenHarmony_Scrcpy/tree/85890e693fb5b3e4b3ef50a16af688787ca85070) | 项目声明跨桌面平台,设备端明确要求 OpenHarmony 5+ root 版本;原生 AVScreenCapture + H.264/H.265。安装脚本 remount 并写 `/system/bin` 和 `/system/etc/init`,服务具媒体采集权限。 | 只适合受控开发镜像;不用于本项目普通手机自动安装。 |
|
||||
| [scrcpy-ohos](https://github.com/l376571926/scrcpy-ohos/tree/5d938ac1d125406705c764d7061192d91c33b9d1) | JavaFX/JDK11;源码 `snapshot_display` 截图下载,操作后刷新,uinput 模拟事件;README 明确非实时。未找到根 LICENSE。 | 不是官方 scrcpy 的鸿蒙兼容层,也不提供连续视频流;可看思路,不能据公开代码便假定可复制分发。 |
|
||||
|
||||
鸿镜设备权限包括 `CAPTURE_SCREEN`、`EXEMPT_CAPTURE_SCREEN_AUTHORIZE`、`INJECT_INPUT_EVENT`,其文档明确需 ACL、系统签名和指纹白名单。仅将 HAP 换成自己的普通调试证书不解决这些限制。[鸿镜签名要求](https://github.com/guoxiucai/ohos-scrcpy-app/blob/1d470ea8f571f19fcbb6883cc6118fafed6fe14f/README.md#签名与系统权限)、[设备权限声明](https://github.com/guoxiucai/ohos-scrcpy-app/blob/1d470ea8f571f19fcbb6883cc6118fafed6fe14f/scrcpy_server/entry/src/main/module.json5)
|
||||
|
||||
鸿镜可复核的传输为 `hdc fport tcp:<localPort> tcp:53535`,设备只监听 `127.0.0.1:53535`。它使用自定义包:大端 u32 类型 + 大端 u32 payload 长度;`0x02` 配置、`0x03` 视频帧、`0x10` 控制。H.264 帧 payload 是 flags u8、PTS u64、NAL 数据;控制下含触摸/键鼠子类型。它与官方 scrcpy wire protocol 不兼容,不能拿其 server 对接官方 scrcpy client。[鸿镜协议](https://github.com/guoxiucai/ohos-scrcpy-app/blob/1d470ea8f571f19fcbb6883cc6118fafed6fe14f/scrcpy_client_flutter/lib/net/protocol.dart)、[loopback 监听](https://github.com/guoxiucai/ohos-scrcpy-app/blob/1d470ea8f571f19fcbb6883cc6118fafed6fe14f/scrcpy_server/entry/src/main/cpp/TcpServer.cpp)
|
||||
|
||||
许可分别是鸿镜 MIT、OpenHarmony_Scrcpy Apache-2.0;仍需保留声明、审查依赖和自行构建的分发物。root 项目的安装脚本会遍历目标设备并更改系统分区,本项目不能直接调用它。[鸿镜 LICENSE](https://github.com/guoxiucai/ohos-scrcpy-app/blob/1d470ea8f571f19fcbb6883cc6118fafed6fe14f/LICENSE)、[OpenHarmony_Scrcpy LICENSE](https://github.com/luodh0157/OpenHarmony_Scrcpy/blob/85890e693fb5b3e4b3ef50a16af688787ca85070/LICENSE)、[root 服务要求](https://github.com/luodh0157/OpenHarmony_Scrcpy/blob/85890e693fb5b3e4b3ef50a16af688787ca85070/Server/README.md)、[系统分区安装脚本](https://github.com/luodh0157/OpenHarmony_Scrcpy/blob/85890e693fb5b3e4b3ef50a16af688787ca85070/Server/install_ohscrcpy_server.sh)
|
||||
|
||||
## 验收与后续决策
|
||||
|
||||
先交付可探测的屏幕预览和明确输入,再进行视频流原型。每条能力分别保存 available/unavailable/unknown 及原因;不把 HDC 已连接等同全部投屏/输入可用。最小真机验收需要覆盖截图解码、旋转坐标、点击/滑动效果、中文输入、断线取消、锁屏、拒绝授权、受保护画面、停止采集后不再传帧和本会话文件/转发清理。视频流另外测首帧时延、实际 FPS、CPU/温度、背压、编码器重建与应用后台状态;完成这些前不使用“低延迟 60 FPS”等宣传。
|
||||
|
||||
本轮明确未验证:Pura 70 Ultra 当前固件的 UITest 输入效果、HDC 版本、官方工具真机投屏、伴随应用采集权限、UITest addon 加载、任何社区 HAP/二进制。已确认的是公开能力与实现边界;没有执行设备动作,也没有修改插件代码。
|
||||
Reference in New Issue
Block a user