dsh-apk-vm 0.0.0-stage → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +345 -0
- package/CODE_OF_CONDUCT.md +130 -0
- package/CONTRIBUTING.md +177 -0
- package/DEMO.md +85 -0
- package/DESIGN.md +335 -0
- package/LICENSE +204 -0
- package/NOTICE +74 -0
- package/README.md +920 -2
- package/SECURITY.md +108 -0
- package/client.js +3780 -0
- package/cordis.patch.yml +56 -0
- package/docs/PRIVACY.md +36 -0
- package/docs/README.md +16 -0
- package/docs/RELEASING.md +104 -0
- package/docs/SUBMISSION.md +75 -0
- package/docs/screenshot-panel.png +0 -0
- package/docs/screenshot-sidebar.png +0 -0
- package/icon.svg +8 -0
- package/index.js +2532 -0
- package/lib/android.js +2502 -0
- package/lib/instances.js +559 -0
- package/lib/scrcpy.js +2370 -0
- package/lib/setup.js +1624 -0
- package/lib/vendor/scrcpy-server-v5.0.1.jar +0 -0
- package/lib/video-stream.js +329 -0
- package/lib/ws.js +198 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +79 -4
- package/scripts/privacy-scan.mjs +94 -0
package/DEMO.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Demo & verification script
|
|
2
|
+
|
|
3
|
+
This file is the reproducible demo script behind `docs/demo.gif` and the screenshots referenced
|
|
4
|
+
in [README.md](README.md). No media is checked in yet; the commands below produce the real
|
|
5
|
+
artifacts on a Windows reference host (AEHD + AOSP Android 14). Do not invent numbers — copy them
|
|
6
|
+
from the outputs.
|
|
7
|
+
|
|
8
|
+
## Install (local link)
|
|
9
|
+
|
|
10
|
+
```powershell
|
|
11
|
+
# from the DSH profile that can use plugin_manager
|
|
12
|
+
plugin_manager install_bundle target=<path-to-repo>
|
|
13
|
+
# or, if the bundle is already linked:
|
|
14
|
+
plugin_manager set_plugin target=include:dsh-apk-vm enabled=true
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Refresh the DSH page after installing the client half.
|
|
18
|
+
|
|
19
|
+
## 30-second demo flow
|
|
20
|
+
|
|
21
|
+
1. Open **APK Virtual Machine** in the sidebar.
|
|
22
|
+
2. Expand **Environment** — show SDK/adb/emulator/system image/AVD/acceleration all green.
|
|
23
|
+
The "Create AVD" action is offered when the image exists but the AVD does not.
|
|
24
|
+
3. Press **Start VM** (or just open the panel with `autoStart` on). Show the measured boot label
|
|
25
|
+
(`quick N s` / `cold N s`).
|
|
26
|
+
4. **Upload APK** or drag a file onto the panel. The app installs and launches; the phone frame
|
|
27
|
+
switches to the H.264 canvas.
|
|
28
|
+
5. Expand **Rooted debug**: run `id -u; getprop ro.build.version.release`; click **UI tree**,
|
|
29
|
+
**Perf**, **Record 5s**, **Rotate**, **Screenshot**.
|
|
30
|
+
6. Interact with the phone frame, then note the live `FPS` reading in the header. A static home
|
|
31
|
+
screen has a low frame rate because scrcpy only emits frames on change; use an animated app or
|
|
32
|
+
a scroll gesture for the 60 fps path.
|
|
33
|
+
|
|
34
|
+
Useful companion commands (same origin, local only):
|
|
35
|
+
|
|
36
|
+
```powershell
|
|
37
|
+
$base = 'http://127.0.0.1:<dsh-port>'
|
|
38
|
+
Invoke-RestMethod "$base/apk-vm/setup" | ConvertTo-Json -Depth 6
|
|
39
|
+
Invoke-RestMethod "$base/apk-vm/avds"
|
|
40
|
+
Invoke-RestMethod "$base/apk-vm/debug/info"
|
|
41
|
+
Invoke-RestMethod "$base/apk-vm/debug/perf"
|
|
42
|
+
Invoke-RestMethod "$base/apk-vm/debug/ui" | Select-Object nodes,bytes,rotation
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Raw APK upload (500 MiB limit, ZIP magic checked, same-origin route):
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
curl.exe -sS -X POST -H "x-apk-name: anim60.apk" `
|
|
49
|
+
--data-binary "@apk-vm-preview\spike\android\anim60.apk" `
|
|
50
|
+
"$base/apk-vm/upload"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
60 fps stream smoke test (no browser needed; counts H.264 packets):
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
cd apk-vm-preview
|
|
57
|
+
node spike/android/ws-stream-test.mjs
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Expected on the reference host with the 60 Hz test app in the foreground: ~59 fps raw packets,
|
|
61
|
+
first `init` in about 1 s, `width`/`height` matching the current orientation.
|
|
62
|
+
|
|
63
|
+
## Recording the media
|
|
64
|
+
|
|
65
|
+
- `docs/demo.gif`: record the flow above at ~1280 px width; trim to 20–30 s; keep file < 8 MiB.
|
|
66
|
+
- `docs/screenshot-panel.png`: main panel with the Environment + APK cards and the phone frame.
|
|
67
|
+
- `docs/screenshot-setup.png`: expanded Environment card on a green host.
|
|
68
|
+
- `docs/screenshot-debug.png`: expanded Rooted debug card with `ui_dump` / `perf` / record output.
|
|
69
|
+
|
|
70
|
+
## Acceptance checklist
|
|
71
|
+
|
|
72
|
+
- [ ] Environment card reports Ready on a host with SDK + image + AVD + acceleration.
|
|
73
|
+
- [ ] Upload and drag-drop both install and launch an APK.
|
|
74
|
+
- [ ] Phone frame uses `<canvas>` (H.264) when WebCodecs is available; PNG fallback otherwise.
|
|
75
|
+
- [ ] Header FPS changes when the guest screen animates.
|
|
76
|
+
- [ ] Rooted debug shell returns `uid=0(root)`; rotate/ui_dump/perf/record/screenshot work.
|
|
77
|
+
- [ ] Cross-origin request to `/apk-vm/*` is rejected (`403`).
|
|
78
|
+
- [ ] Quick boot measured under 30 s after the snapshot exists.
|
|
79
|
+
|
|
80
|
+
## Notes
|
|
81
|
+
|
|
82
|
+
- Windows x64 is the only end-to-end verified host so far. macOS/Linux SDK discovery is covered by
|
|
83
|
+
`spike/setup-smoke.mjs` but the emulator path is not end-to-end tested upstream.
|
|
84
|
+
- Emulator integration tests require nested virtualization; keep them on a local Windows machine
|
|
85
|
+
(see `.github/workflows/ci.yml` for the headless CI checks).
|
package/DESIGN.md
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# 设计方案:DSH 侧边栏 APK 虚拟机预览插件
|
|
2
|
+
|
|
3
|
+
> 目标:在 DeepSeek Harness 的侧边栏里,以「打开一台虚拟机」的方式预览 APK ——
|
|
4
|
+
> 启动开源 Android 虚拟机、安装 APK、把 VM 画面流进侧边栏面板、把鼠标/键盘输入送回 VM。
|
|
5
|
+
|
|
6
|
+
## 1. 需求与约束
|
|
7
|
+
|
|
8
|
+
| 项 | 内容 |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| 入口 | 侧边栏(`sidebar.panellist`)新增「APK 虚拟机」图标;点击后主区(`main`,key `apk-vm`)渲染完整面板 |
|
|
11
|
+
| 体验 | 像虚拟机控制台:开机/关机、状态、屏幕、输入、安装 APK、日志 |
|
|
12
|
+
| 运行环境 | Windows 10/11 x64;无管理员权限;**不允许使用 MuMu Player**;需从 GitHub 选择开源模拟器 |
|
|
13
|
+
| 宿主能力 | 无 Hyper-V/WHPX/AEHD/KVM 驱动;只有用户态虚拟化(QEMU TCG)可用 |
|
|
14
|
+
| 插件规范 | DSH 静态插件:Host 半 `apply(ctx)` + `dsh.client` 声明的浏览器 bundle;UI 只用主题 token、locale、slot |
|
|
15
|
+
|
|
16
|
+
## 2. 开源框架选型(GitHub / AOSP)
|
|
17
|
+
|
|
18
|
+
通过 GitHub/AOSP 调研了以下候选:
|
|
19
|
+
|
|
20
|
+
| 候选 | 许可证 | 结论 |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| **AOSP Android Emulator**(`aosp-mirror/platform_external_qemu` + official `emulator` / `system-images`) | Apache-2.0 | **主选 VM**。官方开源模拟器,adb 生态完整,可无窗口、可指定 AVD |
|
|
23
|
+
| **Genymobile/scrcpy** v5.0.1(仅 scrcpy-server,自研 headless relay) | Apache-2.0 | **主选串流/控制框架**。H.264 低延迟、触摸/按键/滚轮/文本控制协议完善,可无桌面客户端 |
|
|
24
|
+
| Android-x86 / Bliss OS + QEMU | GPL/Apache-2.0 | 备选:无硬件虚拟化时的 QEMU TCG 路径;本机无法从网络取得 ISO(SourceForge/OSDN/Archive.org 均被阻断) |
|
|
25
|
+
| LineageOS-TV-x86(GitHub Releases) | Apache-2.0 | 备选:GitHub 上可下载的 x86_64 Android 21 OTA,但需要 payload2iso/recovery,不能直接 boot |
|
|
26
|
+
| ws-scrcpy | MIT | 备选:开箱即用的 web 串流,但需要独立 Node 服务 + iframe,和 DSH 主题/插件规范不符 |
|
|
27
|
+
| Waydroid / redroid / Anbox | GPL/Apache-2.0 | 仅 Linux;Windows 无内核 binder/ashmem,排除 |
|
|
28
|
+
| Genymotion / Appetize / Corellium | 商业 | 非开源,排除 |
|
|
29
|
+
| ARChon | 旧版 Apache-2.0 | Android 4.4 Chrome runtime,APK 兼容性差,排除 |
|
|
30
|
+
|
|
31
|
+
**最终栈**:AOSP Android Emulator(VM) + scrcpy-server(画面/控制协议) + adb(安装/启动/截图/输入)。
|
|
32
|
+
无 npm 运行时依赖;Host 半只使用 Node 内置模块。
|
|
33
|
+
|
|
34
|
+
## 3. 总体架构
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
┌──────────────────────────── DSH Web UI ─────────────────────────────┐
|
|
38
|
+
│ sidebar.panellist [apk-vm] ──click──▶ main[apk-vm] │
|
|
39
|
+
│ │ │
|
|
40
|
+
│ Client half (client.js) │
|
|
41
|
+
│ ┌──────────────────────────┼──────────────────┐ │
|
|
42
|
+
│ │ 工具栏 / APK 列表 / 手机框 │ 2s state, 500ms frame │
|
|
43
|
+
│ └──────────────────────────┼──────────────────┘ │
|
|
44
|
+
└────────────────────────────────────────────┼────────────────────────┘
|
|
45
|
+
│ same-origin HTTP (fetch)
|
|
46
|
+
┌────────────────────────────────────────────┼────────────────────────┐
|
|
47
|
+
│ DSH Host (desktop process) ▼ │
|
|
48
|
+
│ index.js ── /apk-vm/{state,apks,vm,install,frame,input} │
|
|
49
|
+
│ │ │
|
|
50
|
+
│ ├─ lib/android.js AndroidVm: AVD lifecycle / adb / aapt2 │
|
|
51
|
+
│ │ • spawn emulator (open-source AOSP) or attach serial │
|
|
52
|
+
│ │ • install -r, am start, wm size, screencap -p │
|
|
53
|
+
│ │ • input tap/swipe/keyevent/text │
|
|
54
|
+
│ │ │
|
|
55
|
+
│ └─ lib/scrcpy.js ScrcpySession (可选 H.264 快通道) │
|
|
56
|
+
│ • push scrcpy-server 5.0.1, adb forward, app_process │
|
|
57
|
+
│ • video demux + SPS/PPS → avcC + control messages │
|
|
58
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
59
|
+
│ adb
|
|
60
|
+
┌─────────▼─────────┐
|
|
61
|
+
│ Android VM (AVD) │
|
|
62
|
+
│ system.img + APK │
|
|
63
|
+
└───────────────────┘
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 状态机(Host 侧 AndroidVm)
|
|
67
|
+
|
|
68
|
+
`stopped → starting → booting → ready → stopped`
|
|
69
|
+
失败进入 `error`(附带 `error` 与有界 `logTail`);`start()` 幂等,先 attach 已在线 adb
|
|
70
|
+
设备,再考虑 `adb connect <serial>`,最后才按配置 spawn AVD;`stop()` 只杀自己 spawn 的
|
|
71
|
+
子进程,并清理 adb 连接。
|
|
72
|
+
|
|
73
|
+
## 4. DSH 插件落点
|
|
74
|
+
|
|
75
|
+
- **Host 半**(`index.js`):`export function apply(ctx, config)`;`ctx.inject(['webServer'], host => host.effect(() => mountRoutes(host.webServer)))`。
|
|
76
|
+
路由注册在 `webServer.register({ kind: 'exact', path })`;返回 disposer,插件卸载时清理
|
|
77
|
+
route 与 `AndroidVm`。不使用任何未公开 API。
|
|
78
|
+
- **Client 半**(`client.js`):`window.__ModuleLoader__.load({ id, factory(require) })`,
|
|
79
|
+
只 `require('react')`,`React.createElement` 手写。`apply(ctx)` 中:
|
|
80
|
+
- `ctx.locale.register('apk-vm', { en, zh })` + `bind`;
|
|
81
|
+
- `ctx.slots.inject('main', () => ctx.slots.register({ name:'main', key:'apk-vm', locale:'apk-vm' }, ApkVmPage))`;
|
|
82
|
+
- `ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({ name:'sidebar.panellist', id:'apk-vm', order:30, label:()=>t('nav.label'), locale:'apk-vm' }, PhoneIcon))`。
|
|
83
|
+
- **显示元数据**:`locale/en.json`、`locale/zh.json` 提供 `meta.title/meta.description`,
|
|
84
|
+
`icon.svg` 为原创 currentColor 手机图标;`exports` 暴露 `./locale/*.json` 与 `./icon`。
|
|
85
|
+
- **主题**:仅使用 `--dsw-alias-bg-* / border-* / label-* / brand-primary / state-*` token;
|
|
86
|
+
手机框等“内容物”允许多一点自由色,但面板容器/按钮全部走 token。
|
|
87
|
+
|
|
88
|
+
## 4.5 与 dsh-better-sidebar 的兼容层
|
|
89
|
+
|
|
90
|
+
`dsh-better-sidebar`(MIT,GitHub)是社区侧栏工作台,它在**客户端**提供 `ctx.betterSidebar`
|
|
91
|
+
服务(`registerTab` / `registerFileViewer` / `openTab` / `getSnapshot`),并把这些 tab
|
|
92
|
+
类型投影到 DSH 原生右侧栏(`sidebar.right.pane.tab`)与它自己的底部 workbench。
|
|
93
|
+
|
|
94
|
+
兼容策略(不 fork、不硬依赖、不操作其 DOM):
|
|
95
|
+
|
|
96
|
+
1. **可选注入**:在 `apply(ctx)` 末尾使用 `ctx.inject(['betterSidebar'], sub => …)`,
|
|
97
|
+
等到服务出现才注册;没有该插件的 DSH 上行为完全不变(`main` 面板 + 左侧图标照常)。
|
|
98
|
+
2. **注册 tab**:`sub.betterSidebar.registerTab({ id:'apk-vm:preview', title, description,
|
|
99
|
+
icon, order:60, single:true, component })`;component 复用同一 `ApkVmPage`,标记
|
|
100
|
+
`embedded:true` 以切换窄栏布局(列表在上、手机框更小)。
|
|
101
|
+
3. **打开入口**:主面板 header 增加「在侧栏打开」(仅服务存在时显示),调用
|
|
102
|
+
`openTab({ type:'apk-vm:preview', target:'right' })`;无活动会话时给出明确提示,
|
|
103
|
+
因为 better-sidebar 的右侧栏按会话隔离。
|
|
104
|
+
4. **生命周期**:注册放入 `sub.effect(...)`,服务卸载/HMR 时自动反注册;组件通过
|
|
105
|
+
`visible` prop 暂停不可见 tab 的帧轮询,避免后台开销。
|
|
106
|
+
5. **验证**:本机 live slot tree 中 `sidebar.right.pane.tab` 出现 occupant
|
|
107
|
+
`dsh-better-sidebar:apk-vm:preview`(active);另有 `spike/ui-better-sidebar-smoke.cjs`
|
|
108
|
+
覆盖描述符、本地化、dispose、服务已存在路径、服务缺失容错。
|
|
109
|
+
|
|
110
|
+
## 4.6 开机加速设计(目标 <30s)
|
|
111
|
+
|
|
112
|
+
冷启动 Android 的时间由 CPU 虚拟化决定,因此按“硬件加速 → GPU → 快照 → 预热”四层解决:
|
|
113
|
+
|
|
114
|
+
1. **CPU 加速探测**:`lib/android.js` 调用 SDK 的 `emulator-check accel`,AEHD/WHPX/KVM/
|
|
115
|
+
HAXM/HVF 可用时不传 `-accel off`(emulator 自动使用硬件加速);仅在确实没有 hypervisor
|
|
116
|
+
时才退回 `-accel off`(此路径 x86_64 不可用,会显式报错而不是静默卡住)。
|
|
117
|
+
2. **GPU host**:默认 `-gpu host`(RTX 5070 Ti),替代 SwiftShader 软件渲染;无 GPU 的
|
|
118
|
+
headless 主机可用 `gpuMode: swiftshader_indirect` 回退。
|
|
119
|
+
3. **Quick-boot 快照**:首次成功冷启动后执行 `adb emu avd snapshot save default_boot`;
|
|
120
|
+
之后的启动传 `-snapshot default_boot`,直接恢复内存态(通常数秒)。快照有效性以
|
|
121
|
+
`snapshot.pb` 为准,失败 TCG 运行留下的 `ram.img` 不会被误当快照;同时修复了“残留
|
|
122
|
+
offline adb 设备把启动卡在 attach 路径”的问题。
|
|
123
|
+
4. **预热/自动启动**:`prewarm: true` 在插件激活 3s 后后台启动 VM;`autoStart: true`
|
|
124
|
+
在第一次 `state/frame` 请求时启动。用户打开面板时 VM 通常已就绪,主观“打开即可见”。
|
|
125
|
+
5. **可观测性**:`/apk-vm/state.vm` 暴露 `bootKind`(cold/snapshot/attach)、`bootMs`、
|
|
126
|
+
`accel`、`gpuMode`、`snapshotAvailable`;面板状态 pill 显示 `cold Ns` / `quick Ns`,
|
|
127
|
+
后续验收可直接读取实测值。
|
|
128
|
+
6. **帧率(双路径)**:默认走 **scrcpy H.264 + WebCodecs**(见 4.8),最高 **60 fps**;
|
|
129
|
+
PNG 回退由 `/apk-vm/stream {fps:1..10}` 控制,`/apk-vm/state.stream` 同时返回
|
|
130
|
+
`fps / hostFps / h264 {active, fps, clients, maxFps, width, height}`;header 显示实测 FPS。
|
|
131
|
+
|
|
132
|
+
本机实测:重启释放 AMD-V 后 AEHD 生效;修复 AVD 的 `fastboot.forceColdBoot/forceFastBoot`
|
|
133
|
+
与 `snapshot.present` 后,快照真正参与启动。首次冷启动 **29.5s**(含重建 AVD 配置后的
|
|
134
|
+
新快照),快照重存后再启动 **8.4s**(日志 `Loading snapshot 'default_boot'`、
|
|
135
|
+
`Boot completed in 8371 ms`、`bootKind: snapshot`),已达标。
|
|
136
|
+
|
|
137
|
+
## 4.7 Root / ADB / 调试接口设计
|
|
138
|
+
|
|
139
|
+
目标:模拟器默认 root、adb 直接可用,并向 DSH 暴露底层调试能力。
|
|
140
|
+
|
|
141
|
+
1. **默认 root**:镜像本身是 `userdebug`(`ro.debuggable=1`、`ro.adb.secure=0`,无 adb 授权
|
|
142
|
+
弹窗)。每次启动 ready 后 Host 自动执行 `adb root`,等待 adbd 重启并校验
|
|
143
|
+
`uid=0(root)`;`--prop` 只接受 `qemu.*`,因此不用命令行属性伪造 root,而是启动后应用
|
|
144
|
+
并写入 quick-boot 快照,后续快照启动再幂等补一次 `adb root`。
|
|
145
|
+
2. **调试友好配置**:启用 adb/开发者设置,关闭安装校验(`verifier_verify_adb_installs`、
|
|
146
|
+
`package_verifier_enable`),`persist.sys.usb.config=adb`、`persist.sys.root_access=3`。
|
|
147
|
+
3. **机内工具包**:`/data/local/tmp/dsh/debug.sh` + README(755):
|
|
148
|
+
`info / logs / prop / pkgs / install / help`,用户可在 root shell 里直接用。
|
|
149
|
+
4. **DSH 调试接口**:
|
|
150
|
+
- HTTP:`/apk-vm/debug/{info,shell,logcat,prop,packages,root,push,pull,reboot,action,rotate,ui,record,perf,screenshot}`,
|
|
151
|
+
参数/输出有上限,push/pull 路径受限;
|
|
152
|
+
- Agent Tool:`apk_vm`(`ctx.tools.register`,全局可见),动作覆盖 info/shell/logcat/
|
|
153
|
+
getprop/setprop/packages/install/push/pull/reboot/root/force_stop/clear_data/uninstall/
|
|
154
|
+
rotate/ui_dump/record/perf/screenshot,与 HTTP/面板共用同一 Host 方法;
|
|
155
|
+
- UI:面板新增「Root 调试」卡(root/shell 徽章、Android/API/ABI、命令输入、Logcat/
|
|
156
|
+
Props/Root/UI 树/性能/录屏/旋转按钮、输出控制台),并支持截图下载。
|
|
157
|
+
5. **错误可见性**:无硬件加速时 fail-fast 报错,不再 20 分钟“启动中”;工具注册 schema
|
|
158
|
+
错误会显式抛出(本次已修正为严格 JSON Schema)。
|
|
159
|
+
|
|
160
|
+
## 4.8 60fps H.264 串流设计
|
|
161
|
+
|
|
162
|
+
目标:把 PNG 轮询(~1 fps)替换为可用的 60fps 实时画面,同时保留无 WebCodecs 环境的回退。
|
|
163
|
+
|
|
164
|
+
1. **链路**:浏览器 `<canvas>` ← `VideoDecoder` ← `/apk-vm/ws`(无依赖 RFC6455 服务端,
|
|
165
|
+
`lib/ws.js`)← `annexbToAvcc` 分片 ← `ScrcpyVideoDemuxer` ← scrcpy-server 5.0.1(
|
|
166
|
+
`lib/vendor/scrcpy-server-v5.0.1.jar`,启动时 adb push + forward + `app_process`)。
|
|
167
|
+
2. **参数**:`maxSize=960`(540×960,面板足够清晰且软件解码可达 ~60fps)、
|
|
168
|
+
`videoBitRate=6_000_000`、`maxFps=60`;scrcpy 只在屏幕变化时出帧,静态桌面 FPS 低
|
|
169
|
+
是正常语义,不等于串流故障。
|
|
170
|
+
3. **协议**:文本 `init` 一次(`codec/codecString/description(avcC base64)/width/height/
|
|
171
|
+
maxFps/bitrate`),随后二进制帧 `[0]=1, [1]=keyFlag, [2..9]=pts(LE int64), [10..]=AVCC`。
|
|
172
|
+
4. **关键帧**:客户端配置解码器后先丢 delta,等首个 keyframe;已运行的会话接到新客户端时
|
|
173
|
+
发送 `RESET_VIDEO`(scrcpy control type 17)请求新 keyframe,避免二次打开面板黑屏。
|
|
174
|
+
5. **会话管理**:一个 scrcpy 会话广播给多个 WS 客户端;客户端断开后 15s 无连接自动停止
|
|
175
|
+
会话(释放编码/端口),VM 不停止;VM stop/restart/插件 dispose 时主动停流。
|
|
176
|
+
6. **回退**:无 `WebSocket`/`VideoDecoder`/`EncodedVideoChunk`、初始化失败、连续 3 次连接
|
|
177
|
+
失败、或面板不可见时,自动退回 PNG 轮询;输入仍走 `/apk-vm/input`(不受串流影响)。
|
|
178
|
+
7. **旋转/编码器重置**:scrcpy `session` 包更新宽高时 Host 重发 `init`,客户端重建
|
|
179
|
+
`VideoDecoder`、更新画布尺寸与输入坐标映射,避免横竖屏拉伸/错位。
|
|
180
|
+
8. **实测**(本机 60Hz 动画测试 APK `test.dshanim`):
|
|
181
|
+
- `/apk-vm/ws` 原始包速率 **59.3fps**(425–474 包/8s;首 init 6–1000ms);
|
|
182
|
+
- headless Edge 软件 WebCodecs 解码 **318 帧/6.32s = 50.3fps**(扣除启动约 57fps),
|
|
183
|
+
0 协议错误;
|
|
184
|
+
- 第二个客户端在 4s 内收到 234 包(~58.5fps)+ 1 个 keyframe(RESET_VIDEO 生效);
|
|
185
|
+
- 视觉 GPU 加速的 DSH 窗口预期更接近 60fps;headless 软件解码是下限。
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
## 4.9 开源化 / 环境自检 / 上传与安全
|
|
189
|
+
|
|
190
|
+
为“别人能装得上、敢用、可维护”补齐的工程能力:
|
|
191
|
+
|
|
192
|
+
1. **环境自检(lib/setup.js)**:跨平台探测 SDK 根目录、`adb`、`emulator`、
|
|
193
|
+
`sdkmanager`/`avdmanager`(Windows 下 `.bat` 通过 argv-only `cmd.exe` trampoline)、
|
|
194
|
+
system image、AVD、`emulator-check accel`(AEHD/WHPX/KVM/HAXM/HVF)以及 JDK
|
|
195
|
+
(`JAVA_HOME` 未设置时自动寻找并仅注入子进程环境)。只读,不自动下载。
|
|
196
|
+
`GET /apk-vm/setup` 返回 `ok/missing/actions/hypervisorNotes`;`POST /apk-vm/setup`
|
|
197
|
+
支持 `refresh`、`create-avd`,以及仅在 `allowSetupDownloads: true` 时允许的
|
|
198
|
+
`install-image`(~1 GB,显式动作)。
|
|
199
|
+
2. **上传 APK**:`POST /apk-vm/upload` 接收 raw APK,`x-apk-name` 传文件名,500 MiB 上限,
|
|
200
|
+
检查 ZIP/APK magic,写入 OS 临时目录后走与路径安装相同的 `installApk`;面板提供上传
|
|
201
|
+
按钮与拖放覆盖层,插件卸载时清理临时目录。
|
|
202
|
+
3. **安全边界**:所有 `/apk-vm/*` HTTP 路由与 `/apk-vm/ws` 升级校验 `Origin`/`Host` 与
|
|
203
|
+
`Sec-Fetch-Site`,跨站请求 403/destroy;JSON body ≤64 KiB;上传 ≤500 MiB;调试输出/命令
|
|
204
|
+
长度有界;push/pull 路径限制;所有子进程 argv 数组,不拼接 shell 字符串。
|
|
205
|
+
4. **多 AVD**:`GET /apk-vm/avds` 列出 emulator 可见 AVD;`POST /apk-vm/avd` 停止当前 VM、
|
|
206
|
+
切换 `AndroidVm.avdName`、清空快照探测并自动重启。环境卡在多于一个 AVD 时显示切换器。
|
|
207
|
+
5. **调试接口扩展**:`rotate`(锁定 user_rotation)、`uiDump`(UIAutomator XML)、
|
|
208
|
+
`screenRecord`(5s MP4 拉回 Host 临时目录)、`perfSnapshot`(loadavg/meminfo/cpuinfo)、
|
|
209
|
+
`screenshot`(PNG 附件);Agent `apk_vm` 同步新增 action。
|
|
210
|
+
6. **开源工程**:README(含中文简介/Quickstart/故障排查/安全说明)、Apache-2.0 LICENSE、
|
|
211
|
+
NOTICE(scrcpy/AOSP 归属)、CONTRIBUTING/SECURITY、GitHub Actions(Node 24,headless
|
|
212
|
+
检查 + mock 测试)、Issue/PR 模板;二进制 scrcpy JAR 随包,系统镜像不随包分发。
|
|
213
|
+
7. **跨平台**:`candidateSdkRoots` 增加 Linux/macOS 常见路径;`resolveEmulatorPath` 支持
|
|
214
|
+
SDK 目录与 PATH;加速错误提示按 win32/darwin/linux 区分。Windows x64 为当前端到端验证平台。
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
## 4.10 多 Agent / 多虚拟机实例
|
|
218
|
+
|
|
219
|
+
问题:DSH 同一 profile 下多个 Agent 会共享一个插件进程,但 `apk_vm` 工具、面板、adb 端口、
|
|
220
|
+
AVD、快照、scrcpy 会话原本都是单例;两个 Agent 同时 install/input 会互相踩状态。
|
|
221
|
+
|
|
222
|
+
设计:
|
|
223
|
+
1. **实例模型**:默认实例继续由 `index.js` 持有(UI/兼容路径不变);额外实例由
|
|
224
|
+
`lib/instances.js` 的 `InstanceManager` 管理。每个 `ExtraVmInstance` 拥有独立:
|
|
225
|
+
AVD(`<base>_<id>`)、emulator console port(基准 5558 + 2*序号,自动找空闲)、
|
|
226
|
+
独立 userdata/snapshot(每个 AVD 自己的 `default_boot`)、`AndroidVm`、PNG frame 缓存、
|
|
227
|
+
lastApk/install 锁。
|
|
228
|
+
2. **AVD 创建**:首次创建实例时用 `lib/setup.js` 的 `avdmanager create avd` 建 AVD,并
|
|
229
|
+
写入 `fastboot.forceColdBoot=no / forceFastBoot=yes / snapshot.present=true`;首次冷启动
|
|
230
|
+
后保存该实例自己的 quick-boot 快照,后续 6–8s 启动。
|
|
231
|
+
3. **Agent 归属**:`apk_vm` 在 `exec.agent.session` 上取稳定 key;第一个 Agent 使用默认
|
|
232
|
+
实例,后续 Agent 自动创建隔离实例(受 `maxInstances` 限制)并在首次真实操作时启动。
|
|
233
|
+
也支持显式 `instance` 参数、`list_instances/create_instance/destroy_instance/start/stop`。
|
|
234
|
+
4. **HTTP 命名空间**:`GET/POST /apk-vm/instances` + 每实例
|
|
235
|
+
`/apk-vm/instances/<id>/{state,frame,vm,input,install,upload,debug/*}`;额外实例的
|
|
236
|
+
debug 路径与默认实例同构(shell/logcat/prop/packages/root/rotate/ui/record/perf/
|
|
237
|
+
screenshot/push/pull/reboot/action)。精确路由在实例创建时注册、销毁时释放。
|
|
238
|
+
5. **视频**:默认实例与每个额外实例各有独立 H.264/WebCodecs 流(`lib/video-stream.js` 的
|
|
239
|
+
`ScrcpyStream`):独立 scrcpy server、空闲端口、init/AVCC/关键帧、旋转重发、15s 无客户端
|
|
240
|
+
自动停流。额外 AVD 固定 720×1280 预览分辨率,避免 pixel_5 1080×2340 + 第二路编码器导致的
|
|
241
|
+
GPU 卡顿;WebCodecs 不可用时回退各自 PNG frame。
|
|
242
|
+
6. **面板**:header 增加实例选择器,环境卡/实例列表;支持 New VM / Destroy VM;切换实例时
|
|
243
|
+
重置 frame/解码器并重载该实例 state。
|
|
244
|
+
7. **实测**:默认 `emulator-5558` 与额外 `emulator-5560` 同时 ready/root;额外实例冷启动
|
|
245
|
+
约 27.5s、快照后 quick boot ~6.5s;额外实例 PNG frame(~1.29MB)和 root shell 验证成功,
|
|
246
|
+
默认实例保持 ready。
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
## 5. 关键接口契约
|
|
250
|
+
|
|
251
|
+
`GET /apk-vm/state` →
|
|
252
|
+
```json
|
|
253
|
+
{ "ok": true,
|
|
254
|
+
"vm": { "state": "ready", "serial": "emulator-5558", "avd": "dsh_apk",
|
|
255
|
+
"bootMs": 95000, "error": null, "logTail": ["..."] },
|
|
256
|
+
"screen": { "width": 720, "height": 1280 },
|
|
257
|
+
"apk": { "path": "...", "packageName": "com.example.app", "label": "Demo", "launched": true },
|
|
258
|
+
"installing": false, "roots": ["<workspace>"] }
|
|
259
|
+
```
|
|
260
|
+
`GET /apk-vm/frame?t=<ms>` → `image/png`(`Cache-Control: no-store`;VM 未 ready 时 409 + JSON);
|
|
261
|
+
`POST /apk-vm/input` 支持 `tap {x,y}` / `swipe {x1,y1,x2,y2,duration}` /
|
|
262
|
+
`key {key:'home'|'back'|'recents'|'power'|'enter'}` / `text {text}`;
|
|
263
|
+
`POST /apk-vm/install {path}` 先 `aapt2 dump badging` 解析 package/launcher,再
|
|
264
|
+
`adb install -r` + `am start`,返回安装/启动结果。
|
|
265
|
+
|
|
266
|
+
## 6. 安全与稳健性
|
|
267
|
+
|
|
268
|
+
- **无 shell 字符串拼接**:所有 adb/emulator 调用用 `child_process.spawn(exe, argv[])`;
|
|
269
|
+
文件路径仅通过参数传递,避免命令注入。
|
|
270
|
+
- **输入边界**:JSON body ≤ 64 KiB;上传 ≤ 500 MiB 且校验 ZIP/APK magic;坐标/时长做数值校验;
|
|
271
|
+
text ≤ 4096 字符;APK 路径必须存在且以 `.apk` 结尾,相对路径只在配置 roots 下解析;
|
|
272
|
+
debug push/pull 限制远程路径,pull 本地限制在 roots+临时目录。
|
|
273
|
+
- **同源边界**:所有 `/apk-vm/*` 路由和 `/apk-vm/ws` 升级校验 Origin/Host 与 Sec-Fetch-Site,
|
|
274
|
+
跨站请求返回 403(WS 直接断 socket)。
|
|
275
|
+
- **资源回收**:所有路由注册在 `ctx.effect` 中;插件卸载时 dispose `AndroidVm`,
|
|
276
|
+
杀自己 spawn 的 emulator 进程树,清理 frame 缓存、scrcpy 会话与上传临时目录。
|
|
277
|
+
- **失败可见**:VM 启动/安装/输入失败返回结构化 `{ok:false,error}`;面板展示
|
|
278
|
+
`logTail`(有界 200 行)而不是吞错。
|
|
279
|
+
- **本地路由面**:DTO 只服务同源 Web UI,绑定在本机 DSH web 端口;不监听公网。
|
|
280
|
+
|
|
281
|
+
## 7. 验证与已知限制
|
|
282
|
+
|
|
283
|
+
已验证(本机):
|
|
284
|
+
- 插件安装 `application: applied`;`sidebar.panellist` 与 `main` 两个 slot 均 active;
|
|
285
|
+
- Host 路由真实 HTTP 调通(state/apks/frame 409/stop/start 集成路径);
|
|
286
|
+
- APK 发现可用(发现工作区 sample.apk 与 Downloads 下 APK);`aapt2` 解析通过;
|
|
287
|
+
- 新功能实测:`GET /apk-vm/setup`(ok/sdk/accel)、`GET /apk-vm/avds`(dsh_apk/dsh_apk_arm/ss_test)、
|
|
288
|
+
`POST /apk-vm/upload`(anim60.apk 安装启动成功)、`debug/rotate`(rotation 1/0)、
|
|
289
|
+
`debug/ui`(节点数)、`debug/perf`(loadavg/mem)、`debug/record`(2s MP4 ~0.5–1MB)、
|
|
290
|
+
`debug/screenshot`(PNG 附件)、跨站请求 403、非法 upload 400、install-image 未授权 403;
|
|
291
|
+
- 多实例实测:默认 `emulator-5558` + 额外 `emulator-5560`(`dsh_apk_test1`)同时 ready/root;
|
|
292
|
+
额外实例独立上传安装 anim60.apk、H.264 init `540×960`(412 包/10s,60Hz 动画)、PNG frame
|
|
293
|
+
与 root shell 正常,默认实例保持 ready;额外实例 quick boot 约 10.6s,销毁后仅剩 default。
|
|
294
|
+
- 环境模块 `lib/setup.js`:`spike/setup-smoke.mjs` 30/30;真实探测 294ms,AEHD 可用;
|
|
295
|
+
- 开源工程:Apache-2.0 LICENSE/NOTICE、CONTRIBUTING/SECURITY、GitHub Actions(Node 24 +
|
|
296
|
+
headless 测试)、Issue/PR 模板;README/DEMO 为 UTF-8。
|
|
297
|
+
- Client bundle 136/136 沙箱断言通过;Host/backend 模块 `node --check` 通过;
|
|
298
|
+
- scrcpy relay 160 条协议断言(含假设备端到端)通过;并已真实接入 `/apk-vm/ws`:
|
|
299
|
+
60Hz 动画 APK 下原始包速率 **59.3fps**(425–474 包/8s),headless Edge WebCodecs
|
|
300
|
+
解码 **318 帧/6.32s = 50.3fps**(扣除启动 ~57fps、0 错误);晚加入的第二个客户端
|
|
301
|
+
4s 收到 234 包(~58.5fps)+ RESET_VIDEO keyframe。
|
|
302
|
+
- Fast-boot 启动计划单测通过:有快照时 argv 含 `-snapshot default_boot -gpu host` 且不含
|
|
303
|
+
`-accel off`;无 hypervisor 的冷启动计划才加 `-accel off`;关闭 quickBoot 时含
|
|
304
|
+
`-no-snapshot`;安装后的 profile 配置 live 生效(`bootKind/accel/gpu/snapshotAvailable`
|
|
305
|
+
可在 `/apk-vm/state` 读到,prewarm 已按新计划拉起 host 侧 emulator)。
|
|
306
|
+
- 开机实测:AEHD 重启后生效;修复 AVD `fastboot.forceColdBoot/forceFastBoot` 与
|
|
307
|
+
`snapshot.present` 后,首次冷启动 **29.5s**、快照重存后的 quick boot **8.4s**
|
|
308
|
+
(`bootKind: snapshot`、`Boot completed in 8371 ms`),30s 目标达成。
|
|
309
|
+
- Root/调试实测:`adb shell id` = `uid=0(root)`;`root:true/prepared:true`;工具包
|
|
310
|
+
`/data/local/tmp/dsh/debug.sh` 已安装;`/debug/info|shell|logcat|prop|packages` 与
|
|
311
|
+
snapshot save 全部实测 `ok`;Agent Tool `apk_vm` 已注册(DSH 推送了更新后的 Tool schema)。
|
|
312
|
+
- 一个 AVD 配置修正:快照首次加载报 “different AVD configuration” 后,重存 snapshot 即恢复
|
|
313
|
+
秒级 quick boot;快照有效性仍以 `snapshot.pb` 判断。
|
|
314
|
+
|
|
315
|
+
本机限制 / 备注:
|
|
316
|
+
- Windows 组件存储损坏(DISM 14098)导致 WHPX 备选路径不可用,当前 AEHD 为唯一加速路径;
|
|
317
|
+
- 为使 AEHD 获得 AMD-V,MuMuVMMDrv 已改为 Manual 并停止(MuMu 数据未动;恢复需改回
|
|
318
|
+
system 并重启);
|
|
319
|
+
- 模拟器 `-prop` 只接受 `qemu.*`,因此 root 属性不走命令行,而是启动后 `adb root` +
|
|
320
|
+
机内配置并写入快照;若要真正修改 build.prop,需要 `-writable-system` 冷启动后 remount,
|
|
321
|
+
当前未启用(会改变快照配置)。
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
## 8. 演进路线
|
|
325
|
+
|
|
326
|
+
1. 已落地:scrcpy H.264 → `/apk-vm/ws` → `VideoDecoder` canvas(60fps)、多客户端 keyframe、
|
|
327
|
+
旋转重配;后续可加 scrcpy 控制通道(触摸/键盘直连)进一步降低输入延迟;
|
|
328
|
+
2. 物理 adb 设备的运行时切换(当前支持配置 `serial` 附加;UI/路由切换待补);
|
|
329
|
+
3. 音频转发(scrcpy audio socket + WebAudio)与录屏回放/自动断言;
|
|
330
|
+
4. 预置 ATD/更小镜像与冷启动压测(当前以 cold boot → snapshot → quick-boot 为主,目标是
|
|
331
|
+
首次冷启动也压到 30s 内;ATD 镜像可作为可选 `avdName`);
|
|
332
|
+
5. 用 `-writable-system` + `adb remount` 真正修改 `/system/build.prop`(把 adbd 默认 root、
|
|
333
|
+
`persist.sys.usb.config` 写进镜像),并重新生成基准快照;
|
|
334
|
+
6. QEMU + Android-x86 后端适配器(无硬件虚拟化的 Windows/Linux 通用方案)与静态 APK
|
|
335
|
+
预览(不启 VM 先看图标/manifest/权限)。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright (C) 2018 Genymobile
|
|
191
|
+
Copyright (C) 2018-2026 Romain Vimont
|
|
192
|
+
|
|
193
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
194
|
+
you may not use this file except in compliance with the License.
|
|
195
|
+
You may obtain a copy of the License at
|
|
196
|
+
|
|
197
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
198
|
+
|
|
199
|
+
Unless required by applicable law or agreed to in writing, software
|
|
200
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
201
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
202
|
+
See the License for the specific language governing permissions and
|
|
203
|
+
limitations under the License.
|
|
204
|
+
|