Archive reviewed candidate; not production acceptance (2026-09-24)
Android client acceptance / test-and-build (push) Has been cancelled

This commit is contained in:
2026-09-24 06:50:56 +00:00
parent 261dde435b
commit c0737d5665
35 changed files with 67736 additions and 18 deletions
+19
View File
@@ -0,0 +1,19 @@
# Android 1.0.3 本地候选验收
## 2026-09-17 源码追加:后台授权会话隔离
已在隔离单测复现并修复旧会话成功回包导致新登录误报授权失效,以及会话已替换时旧隧道停止不及时的问题。修复前定向4项中1失败;修复后定向10项、全量41项全部通过,0跳过。详见[会话隔离验收](AUTH-SESSION-ISOLATION-20260917.md)。**本次只编译源码并运行JVM测试,未重新打包/签名/安装/发布;下方1.0.3旧APK哈希不包含此次源码修复,不能混用验收证据。**
## 2026-09-08 至 09-09 原候选包记录
时间:2026-09-08,状态复核于 2026-09-09。未发布 1.0.3 安装包、未通知员工;对应 GPL 源码和验收记录已同步公司公共 Git 的 `codex/overnight-20260908` 维护分支,尚未合并正式分支。源码同步不代表真机 VPN 验收完成。
- 公司 Git 正式版检查:独立无凭据 HTTP 客户端,忽略草稿/试用版,只允许规范的公司 Git 下载链接;失败不停止 VPN。更新由用户确认后浏览器下载,尚不是应用内自动安装。
- 34 项 JVM 单元测试通过,包括 7 项更新解析测试。Windows 中文工程路径导致 Gradle 测试加载失败,采用独立 ASCII 构建副本后全量通过,未忽略失败的测试。
- 1.0.3 五种 APK 已按原公司证书签名,签名 v2/v3 检查通过;旧 1.0.2 与新包证书一致。
- 专用 Android 模拟器 `ucvl_acceptance` 上:覆盖安装 1.0.2 → 1.0.3,保留合成会话、登录名与设备标识;随后清除合成会话,验证登录输入控件及空提交提示。证据 `upgrade-1.0.3.json`。没有对实体手机操作,没有登录生产账号,没有修改主机网络。
- 初始测试截屏在绘制前呈白屏,不能作为视觉验收。随后实际重新启动应用并等待界面稳定,`login-rendered-1.0.3.png` 已复核:登录/注册/更新入口完整显示,无截断。手动检查更新实际访问公司 Git 并显示“暂无更新的 Android 正式版”(服务器目前只有试用版),记录 `update-ui-1.0.3.xml`。
- 增强后的截图测试包 visual4 已重新编译、同证书安装并执行通过,`login-instrumentation-visual4.png` 保存实际登录及空提交错误提示。visual2/3 的截图断言曾失败;复核图像发现 visual3 已正常绘制,是采样落在白色标题字形导致误判,已改为检查右侧无文字的深浅背景对比并保留失败证据。这不改变 APK 本体。
- 未完成:真实手机 VPN 授权、所有节点连接/切换、休眠/换网/长连接、Codex 认证业务、断线恢复和更新下载→系统安装流程。不能以模拟器安装测试替代。
Universal APK SHA256:`866d17bc303aa9cbc71b71c954ddd1f3ed50251326fc035a9e1d140ff230ee66`。
+25
View File
@@ -0,0 +1,25 @@
# Android 1.0.4 isolated candidate
Status: local signed candidate built and verified on 2026-09-24. Not published, not installed on a physical device, and not accepted as a stable release.
VersionName 1.0.4 / base versionCode 10004; the existing playstore ABI mapping produces APK versionCode 4010004 (previous candidate: 4010003). Includes the 2026-09-17 authorization-session isolation fix without changing the VPN or network policy.
The candidate was built in a fresh ASCII directory using cached JDK 21 / SDK 37 / Gradle 9.4.1 with `--offline --no-daemon --max-workers=2`. All 41 JVM tests passed with zero failures/errors/skips, including 10 authorization lifecycle cases for old success/401/403 responses, replaced or removed login, transient failure, and cancellation. All five ABI/universal release APKs were assembled. No emulator, Android service instrumentation or production API was used.
All five APK manifests independently report package `cn.toplc.zonghengjia`, versionName `1.0.4`, versionCode `4010004`. APK Signature Scheme v2/v3 verification passed. The certificate SHA256 is `0f1a09870d6ee1e73b62138f99463e7066f4228ce28b7f16ff0aeccca59ea14f`, independently compared with retained 1.0.2 and 1.0.3 universal APKs. Signing material was not exported. Old APKs remain unchanged.
| Artifact suffix | Bytes | SHA256 |
| --- | ---: | --- |
| android-universal.apk | 64228400 | `fc8ef82ba2ef28cb26991401c8ea5feca492f929a7bad4aa6b39e4787baaf416` |
| android-arm64-v8a.apk | 27503013 | `7527441177d858b48975aa882d1313e7cb693653ced5c5de6d17fd3abce83a9f` |
| android-armeabi-v7a.apk | 27855275 | `4121ea1f442cc3ec507ede50a19587bcaca983d147ec3af6ad27b7c62f229ca5` |
| android-x86_64.apk | 28387740 | `f24a1f9b753030a4bd68278d685a03d0fdb7d8e0e34e96bd16e8fa6eba164125` |
| android-x86.apk | 28842387 | `bd912c09f338f57f34806914fa45f86a8d7792a56bb24552ebf5a13888234b61` |
Every APK filename starts with `UCVL-Zonghengjia-1.0.4-`. The immutable build-input manifest SHA256 is `404c9c802321c39c2589c7d7c10e0e2d418af6e2d03d86f7de2ee8354f3bedeb`. The candidate report additionally binds signed APK ZIP-entry bytes to the unsigned Gradle outputs and the GPL snapshot to each compiled source input; seven AAR/JNI inputs are checked against the existing public restoration locks.
The first wrapper run stopped after successful Gradle completion because its PowerShell result aggregation used dictionaries with Measure-Object. The aggregation was corrected, and signing resumed against the same verified input manifest and completed outputs; tests/build were not falsely reported failed or silently rerun against different source.
Remaining device gates: real Android VPN permission and data path, service/broadcast behavior when a login replaces an in-flight authorization request, disconnect/recovery, Wi-Fi/mobile transitions, sleep/wake and authenticated sustained business traffic. JVM tests and APK signatures do not establish these results.
GPL source is exported separately using the publication tool that excludes the private Zero3 helper. No phone, board, host network, iOS or remote Git operation is part of this candidate preparation.
@@ -0,0 +1,40 @@
# Android 后台授权检查:旧会话与新登录隔离
状态:本地源码候选修复,未发布APK,未在手机安装。版本号仍为1.0.3;此前1.0.3已签名候选包不包含本次修复,不可将其旧哈希作为本次修复后的二进制证据。
## 复现与原因
原UcvlAuthorizationGuard在检查开始时发现保存的令牌不等于该VPN的令牌,会调用与401/403相同的revoke回调。该回调虽然不清除较新的令牌,却无条件发送AUTHORIZATION_REVOKED广播,使新登录界面被退回登录页。成功或暂时性失败的旧请求还要等下一轮60秒检查才停止旧隧道;旧401/403回包遇到新令牌时则直接返回,没有调用停止旧隧道回调。
隔离复现:新增lateSuccessMustNotRevokeNewLogin,在授权请求中模拟令牌由old变为new。原代码执行4项测试,3通过、该项真实失败;证据android-auth-before-20260917.json/.log。全部为合成令牌和回调,不是生产网络或真实用户账号复现。
## 修复
- 将sessionChanged(只停止旧VPN)与revoke(当前令牌确实被401/403拒绝)分离。
- 请求前、成功/临时失败后立即核对会话,发生变化不等待下一次60秒轮询。
- 旧401/403响应属于已替换会话时停止旧隧道,但不撤销新令牌。
- CoreVpnService只有确认仍属于同一令牌时才清除令牌并发出撤销广播;替换/退出会话仅停止旧服务。
- 协程取消继续向上传播,不把取消视为账号撤销;保持临时断网不撤销同一会话的策略。
这不是新增自动换节点功能,也不声称修复了Activity所有网络请求或所有换网问题。
## 验证
独立ASCII临时副本:`C:/Users/Administrator/AppData/Local/Temp/ucvl-auth-20260917-907433b0081d432aa9a6d763f8bb061b`。使用已有JDK21、Android SDK37、Gradle9.4.1缓存,全程--offline、--no-daemon;没有启动模拟器或读取签名私钥。
1. 原代码定向回归:4项,1项失败(预期暴露缺陷)。
2. 修复后定向回归:10项全部通过,0跳过。覆盖同会话撤销、临时离线、旧401/403、旧成功响应、预先替换/退出、取消、持续检查;成功和临时失败的会话变更需在1秒上限内退出,不等60秒。
3. 全量`:app:testPlaystoreReleaseUnitTest`:**41项全部通过,0失败、0错误、0跳过**。包括授权10、桥接5、版本解析7和其余19项;修复后的主应用Kotlin及测试Kotlin编译通过。
4. 没有运行assemble、签名、APK安装、VPN授权、换网或真实服务广播/UI仪器测试。不等于Android实机、Codex长连接或全平台验收。
定向复测证据android-auth-after-20260917.json/.log,全量证据android-auth-full-20260917.json/.log。复现工具`tools/Test-SessionGuard-Isolated.ps1`,首次不要删除原源码/旧安装,测试仅复制到专用临时目录;不会连接公司生产API。
本次源文件SHA256:
| 文件 | SHA256 |
| --- | --- |
| UcvlAuthorizationGuard.kt | 5C7B01D602EB0F5B45BBE88D7F12E3BC70FED4296AE5B6578C2F25A918FD87C4 |
| CoreVpnService.kt | 4F55A79F7179D26AF622998C9A70A0D2C64BC1C60B56DDC9E10BA06EE921D850 |
| UcvlAuthorizationGuardTest.kt | ACB7BD96F1D1766D59B4D42DC12295621EDEE98E9DCCF78FE566B83770ADF919 |
下一步:在专用Android设备/模拟器做真实service生命周期、重新登录后旧响应/撤销广播、断网恢复验证,再在实体手机补VPN及长连接验收,之后方可决定发布。当前旧候选APK、公司Git公开版本、Windows现用安装、生产后台/节点均未修改,没有员工通知。
@@ -0,0 +1,21 @@
{
"phase": "after",
"started_at": "2026-09-17T10:51:11.1475501+08:00",
"runtime": "C:\\Users\\Administrator\\AppData\\Local\\Temp\\ucvl-auth-20260917-907433b0081d432aa9a6d763f8bb061b",
"offline": true,
"exit_code": 0,
"suites": [
{
"name": "com.v2ray.ang.ucvl.UcvlAuthorizationGuardTest",
"tests": 10,
"failures": 0,
"errors": 0,
"skipped": 0
}
],
"source_copied": true,
"emulator_started": false,
"device_installed": false,
"apk_published": false,
"network_modified": false
}
@@ -0,0 +1,21 @@
{
"phase": "before",
"started_at": "2026-09-17T08:59:15.9076805+08:00",
"runtime": "C:\\Users\\Administrator\\AppData\\Local\\Temp\\ucvl-auth-20260917-907433b0081d432aa9a6d763f8bb061b",
"offline": true,
"exit_code": 1,
"suites": [
{
"name": "com.v2ray.ang.ucvl.UcvlAuthorizationGuardTest",
"tests": 4,
"failures": 1,
"errors": 0,
"skipped": 0
}
],
"source_copied": true,
"emulator_started": false,
"device_installed": false,
"apk_published": false,
"network_modified": false
}
@@ -0,0 +1,56 @@
{
"phase": "full",
"started_at": "2026-09-17T12:10:25.1756796+08:00",
"runtime": "C:\\Users\\Administrator\\AppData\\Local\\Temp\\ucvl-auth-20260917-907433b0081d432aa9a6d763f8bb061b",
"offline": true,
"exit_code": 0,
"suites": [
{
"name": "com.v2ray.ang.fmt.ShadowsocksFmtTest",
"tests": 15,
"failures": 0,
"errors": 0,
"skipped": 0
},
{
"name": "com.v2ray.ang.HttpUtilTest",
"tests": 1,
"failures": 0,
"errors": 0,
"skipped": 0
},
{
"name": "com.v2ray.ang.ucvl.UcvlAuthorizationGuardTest",
"tests": 10,
"failures": 0,
"errors": 0,
"skipped": 0
},
{
"name": "com.v2ray.ang.ucvl.UcvlConfigBridgeTest",
"tests": 5,
"failures": 0,
"errors": 0,
"skipped": 0
},
{
"name": "com.v2ray.ang.ucvl.UcvlReleaseCheckerTest",
"tests": 7,
"failures": 0,
"errors": 0,
"skipped": 0
},
{
"name": "com.v2ray.ang.UtilsTest",
"tests": 3,
"failures": 0,
"errors": 0,
"skipped": 0
}
],
"source_copied": true,
"emulator_started": false,
"device_installed": false,
"apk_published": false,
"network_modified": false
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

+14
View File
@@ -0,0 +1,14 @@
{
"url": "https://pro.ucvl.cn/admin/ucvl-software-downloads/releases/download/android-v1.0.1-candidate.1/UCVL-Zonghengjia-1.0.1-android-universal.apk",
"release": "https://pro.ucvl.cn/admin/ucvl-software-downloads/releases/tag/android-v1.0.1-candidate.1",
"source_zip": "https://pro.ucvl.cn/admin/ucvl-zonghengjia-android/archive/android-v1.0.1-candidate.1.zip",
"version": "1.0.1",
"size": 64220208,
"sha256": "87747619f838f432c94c90785e6523bf5a9e82bb3a138b47ba3c9e293e172d45",
"anonymous_download_verified": true,
"prerelease": true,
"physical_android_device_tested": false,
"stable_update_changed": false,
"employee_notification_sent": false,
"windows_and_macos_source_private": true
}
+15
View File
@@ -0,0 +1,15 @@
{
"repository": "https://pro.ucvl.cn/admin/ucvl-zonghengjia-android",
"source_zip": "https://pro.ucvl.cn/admin/ucvl-zonghengjia-android/archive/android-v1.0.1-candidate.1.zip",
"commit": "0a8bdc9fff9cc865d0619fc67d94bca5de5ac83a",
"tag": "android-v1.0.1-candidate.1",
"anonymous_download_verified": true,
"source_files": 971,
"bytes": 2450691,
"sha256": "eb22a45f8d2e2cf77edf7f8fa6e058ea04383260c40d8e336bffed2dc97cd16b",
"other_source_repositories": {
"ucvl-zonghengjia": "not anonymously readable",
"ucvl-zonghengjia-macos": "not anonymously readable"
},
"binary_release_created": false
}
@@ -0,0 +1,36 @@
{
"timestamp": "2026-09-03T11:00:29.868500+08:00",
"host_network_changed": false,
"separate_uid_no_explicit_proxy": true,
"physical_android_device_tested": false,
"results": [],
"pass": false,
"baseline": {
"transport_vpn": false,
"uid": 10150,
"explicit_proxy": false,
"results": [
{
"target": "www.google.com",
"pass": false,
"error_type": "ConnectException"
},
{
"target": "api.openai.com",
"pass": false,
"error_type": "ConnectException"
},
{
"target": "chatgpt.com",
"pass": false,
"error_type": "ConnectException"
},
{
"target": "vpn.toplc.cn",
"status": 200,
"pass": true
}
]
},
"error": "Connect test exited early: ngOs.open(ForwardingOs.java:563)\n\tat libcore.io.BlockGuardOs.open(BlockGuardOs.java:274)\n\tat libcore.io.ForwardingOs.open(ForwardingOs.java:563)\n\tat android.app.ActivityThread$AndroidOs.open(ActivityThread.java:8591)\n\tat libcore.io.IoBridge.open(IoBridge.java:560)\n\t... 37 more\n\nINSTRUMENTATION_STATUS: stream=\nError in connectFromDashboard(com.v2ray.ang.ucvl.VpnLifecycleTest):\njava.io.FileNotFoundException: /data/user/0/cn.toplc.zonghengjia/files/vpn-acceptance-fixture.json: open failed: EACCES (Permission denied)\n\tat libcore.io.IoBridge.open(IoBridge.java:574)\n\tat java.io.FileInputStream.<init>(FileInputStream.java:179)\n\tat kotlin.io.FilesKt__FileReadWriteKt.readText(FileReadWrite.kt:135)\n\tat kotlin.io.FilesKt__FileReadWriteKt.readText$default(FileReadWrite.kt:135)\n\tat com.v2ray.ang.ucvl.VpnLifecycleTest.connectFromDashboard(VpnLifecycleTest.kt:22)\n\t... 32 trimmed\nCaused by: android.system.ErrnoException: open failed: EACCES (Permission denied)\n\tat libcore.io.Linux.open(Native Method)\n\tat libcore.io.ForwardingOs.open(ForwardingOs.java:563)\n\tat libcore.io.BlockGuardOs.open(BlockGuardOs.java:274)\n\tat libcore.io.ForwardingOs.open(ForwardingOs.java:563)\n\tat android.app.ActivityThread$AndroidOs.open(ActivityThread.java:8591)\n\tat libcore.io.IoBridge.open(IoBridge.java:560)\n\t... 37 more\n\nINSTRUMENTATION_STATUS: test=connectFromDashboard\nINSTRUMENTATION_STATUS_CODE: -2\nINSTRUMENTATION_RESULT: stream=\n\nTime: 0.011\nThere was 1 failure:\n1) connectFromDashboard(com.v2ray.ang.ucvl.VpnLifecycleTest)\njava.io.FileNotFoundException: /data/user/0/cn.toplc.zonghengjia/files/vpn-acceptance-fixture.json: open failed: EACCES (Permission denied)\n\tat libcore.io.IoBridge.open(IoBridge.java:574)\n\tat java.io.FileInputStream.<init>(FileInputStream.java:179)\n\tat kotlin.io.FilesKt__FileReadWriteKt.readText(FileReadWrite.kt:135)\n\tat kotlin.io.FilesKt__FileReadWriteKt.readText$default(FileReadWrite.kt:135)\n\tat com.v2ray.ang.ucvl.VpnLifecycleTest.connectFromDashboard(VpnLifecycleTest.kt:22)\n\t... 32 trimmed\nCaused by: android.system.ErrnoException: open failed: EACCES (Permission denied)\n\tat libcore.io.Linux.open(Native Method)\n\tat libcore.io.ForwardingOs.open(ForwardingOs.java:563)\n\tat libcore.io.BlockGuardOs.open(BlockGuardOs.java:274)\n\tat libcore.io.ForwardingOs.open(ForwardingOs.java:563)\n\tat android.app.ActivityThread$AndroidOs.open(ActivityThread.java:8591)\n\tat libcore.io.IoBridge.open(IoBridge.java:560)\n\t... 37 more\n\nFAILURES!!!\nTests run: 1, Failures: 1\n\n\nINSTRUMENTATION_CODE: -1\n"
}
File diff suppressed because one or more lines are too long
+16
View File
@@ -0,0 +1,16 @@
{
"timestamp": "2026-09-08T22:13:37.462276+08:00",
"serial": "emulator-5554",
"old_version": "1.0.2",
"new_version": "1.0.3",
"passed": true,
"physical_device_tested": false,
"host_network_modified": false,
"authenticated_vpn_connection_tested": false,
"steps": [
"seed_synthetic_session_on_1.0.2",
"cover_upgrade_preserves_encrypted_session_login_and_device_id",
"login_visible_empty_submission_safe_fixture_cleared"
],
"apk_sha256": "866d17bc303aa9cbc71b71c954ddd1f3ed50251326fc035a9e1d140ff230ee66"
}