Compare commits

..

711 commits

Author SHA1 Message Date
Silent1566
c26ed77a1c build(runtime): 修复插件仓库 CI 缺口与 Python 模块构建不可复现(PLG1 S29)
Verification: 干净检出 vs 本地构建摘要一致:arm64 824947fb82c89a52…、v7a 0e7286e87989e06c…;两 ABI 各 8 库全 Stored、28 文件、loadOrder 8 项拓扑序;catalog python 摘要已更新(ijk/mpv/dv5 不变)

Task-Guard: PLG1-S29
2026-10-20 14:20:26 +08:00
Silent1566
a20bffdc72 fix(runtime): 真正启动 Python 并修复 onStart 的 context 错误(PLG1 S28)
补上此前没有做的验证:S27b 我称之为「端到端验证」,但核查后发现
没有任何测试真正启动过 Python。既有测试只验证了各部件(清单校验、库齐全、
载荷可读),若 Chaquopy 根本无法从模块启动,它们全都不会失败。

新测试首轮 3 项全失败——证明缺口真实存在。

真实 bug:
  FileNotFoundError: .../files/chaquopy/AssetFinder/app
    at Platform.onStart(Platform.java:141)
    at Python.start
根因:Platform.onStart 传给 Python 的是宿主 context,而 Chaquopy 的 Python 侧
用这个 context 的 getAssets() 打开 chaquopy/*.imy。宿主已不再带 chaquopy/
(上一步排除了),所以每次查找都 miss。
根因的根因:S25 只改了 this.am(Java 侧提取代码用的 AssetManager),
没改传给 Python 的 context——两处是不同的消费路径,只覆盖了一处。

修复:Platform 新增 assetContext 字段(模块 wrapper 或宿主),onStart 改传它。
wrapper 语义正好合适:initialize 用同一个 context 做两件事——
getAssets() 读模块资产、getFilesDir() 作解压根;wrapper 只覆盖 getAssets()、
其余委托宿主。而宿主 context 仍用于解压根/preferences/旧文件清理(必须留宿主,
因模块数据目录不可写)。

决定性证据(真机 OK 3 项):
  started version=3.10.19 [Clang 18.0.4]
  libpython3.10.so origins=[/data/app/com.silent.runtime.python-.../lib/arm64]
  spider built=com.fongmi.chaquo.Spider
三条分别证明:真实 CPython 启动、解释器库从模块映射、bridge 可用。

双向:装模块全包 91 项通过;卸载后 91 项通过,降级不崩。

教训:「端到端」不能只看部件是否齐全。S27b 覆盖了清单校验、文件齐全、
载荷可读、可用性探测——看起来全面,却没有一项真正启动解释器,
对「能否从模块启动」完全无知。真正启动一次,立刻暴露了 context 错误。

Verification: device 192.168.50.3:5555. This stage exists because the previous one overclaimed: S27b called its 6 tests 'end-to-end', but no test had ever started the interpreter, so none would have failed even if Chaquopy could not boot from a module at all. The new RuntimePythonStartDeviceTest failed all three of its tests on the first run, which is the evidence that the gap was real. Root cause of the first failure: com.chaquo.python.PyException FileNotFoundError .../files/chaquopy/AssetFinder/app raised from Platform.onStart at Python.start. Platform.onStart passed the HOST context to java.android.initialize, and Chaquopy's Python side opens chaquopy/*.imy through that context's AssetManager; the host no longer ships chaquopy/ after the previous stage's exclusion, so every lookup missed. The deeper cause is that S25 changed only this.am, the AssetManager the Java-side extraction code uses, and left the completely different context handed to Python untouched - two consumption paths, one covered. Fix: Platform gains an assetContext field (the module wrapper when a module supplies one, otherwise the host) and onStart passes it. The wrapper's semantics fit exactly, because initialize uses one context for two jobs - getAssets() to read the module's payload and getFilesDir() as the extraction root - and the wrapper overrides only getAssets() while inheriting the host's writable files directory. The bare host context stays in use for the extraction root, preferences and obsolete-file cleanup, all of which must remain on the host since the module's data directory is not writable. Decisive device evidence, RuntimePythonStartDeviceTest OK (3 tests): 'S28PY started version=3.10.19 (main, Nov 20 2025, 17:54:06) [Clang 18.0.4 ...]', 'S28PY libpython3.10.so origins=[/data/app/com.silent.runtime.python-.../lib/arm64]', 'S28PY spider built=com.fongmi.chaquo.Spider'. Those three prove, respectively, that real CPython started rather than a stub or a cached value, that the interpreter library was mapped from the module directory so the absolute-path load path really ran, and that the bridge works from source to object. Two-way result: with the module installed the whole runtime package passes 91 tests; after uninstalling it, 91 again with no failures, so the degradation path still does not crash. Lesson recorded: 'end-to-end' cannot mean 'all the parts are present' - S27b covered manifest verification, file presence, payload readability and the availability probe, which looks thorough yet was entirely ignorant of whether the interpreter could boot; starting it once exposed the context error immediately

Task-Guard: PLG1-S28
2026-10-20 10:03:31 +08:00
Silent1566
58db33ff9d feat(runtime): Python 构建期排除 + 端到端验证(PLG1 S27b)
S13 门禁六步计划的第 4 步与第 6 步:构建期排除,以及「装模块可用、
卸载不崩」的双向验证。

实施:app/build.gradle 加排除,两半一起走——8 个原生库与 assets/chaquopy 载荷树。
它们来源不同,排除机制也不同:
- 8 个库(chaquo 模块的 jniLibs)→ packaging.jniLibs.excludes
- assets/chaquopy/**(assets)→ androidResources.ignoreAssetsPatterns
回滚开关 -PpythonBundle=true。

体积实测(全量重打):
- arm64: 100.6 -> 80.6 MiB(-20.05 MiB)
- v7a:   91.4 -> 75.3 MiB(-16.08 MiB)
两个 ABI 的 chaquopy/python 条目均归零。
累计:arm64 从 191.1 MiB 降到 80.6 MiB,累计减少 110.5 MiB。

发现并修复一个真实 bug(本阶段最重要的产出):
端到端测试首轮即失败,20 个载荷文件全部报「declared payload is unreadable」。
根因:AssetManager.open() 的路径是相对 assets/ 目录,不是相对 APK 根。
清单里写的是 assets/chaquopy/build.json(相对 APK 根,与其它模块一致),
直接传入就是错路径 → FileNotFoundException → 报 unreadable。
对照:兄弟方法 manifestSource() 读清单用的正是 "runtime.json"(无前缀)。
修复:RuntimeAndroid 加 assetPath() 剥掉 assets/ 前缀;非 assets/ 下的路径返回 null。

为什么单测没发现:S27a 的单测用自己的内存 reader,它拿到的是清单原始路径,
与真实 AssetManager 的语义不同。只有真机端到端能发现这类「接口语义不匹配」。

双向真机验证:新增 RuntimePythonModuleDeviceTest(6 项)
- 装模块:OK (6 tests),证据 verify ok=true problem=
- 装模块全包:OK (88 tests)
- 卸载模块(零模块态):OK (88 tests),降级不崩

排除与修复属同一单元:若只排除而不修 reader,用户装模块会得到被拒的模块,
而宿主又不带内嵌副本 → Python 完全不可用,即排除本身会造成回归。
故两项一起验证、一起提交。

Verification: full rebuilds of both ABIs plus two-way device verification on 192.168.50.3:5555. Exclusion: app/build.gradle drops the Python runtime, which needs two mechanisms because the halves come from different sources - packaging.jniLibs.excludes for the eight libraries out of the chaquo module's jniLibs, and androidResources.ignoreAssetsPatterns for the assets/chaquopy payload tree. Measured after full rebuilds: arm64 100.6 MiB -> 80.6 MiB (-20.05 MiB) and v7a 91.4 MiB -> 75.3 MiB (-16.08 MiB), with chaquopy/python entries at zero in both; cumulative for arm64 is 191.1 MiB -> 80.6 MiB, 110.5 MiB removed. The most important outcome is a real bug found by the end-to-end test on its first run: all twenty payload files reported 'declared payload is unreadable'. Root cause: AssetManager.open takes a path relative to the assets DIRECTORY, not to the APK root, so 'assets/chaquopy/build.json' as written in the manifest (relative to the APK root, consistent with every other module) produced FileNotFoundException. The sibling manifestSource() reads 'runtime.json' with no prefix for exactly this reason. Fixed by adding assetPath() in RuntimeAndroid to strip the 'assets/' prefix, returning null for paths outside assets/ because a package's AssetManager exposes nothing else and pretending otherwise would misreport the file as missing rather than unsupported. Unit tests could not have caught this: S27a's tests use their own in-memory reader, which receives the raw manifest path and therefore has different semantics from the real AssetManager - only a device end-to-end run reaches the real API. Device results: with the module installed, RuntimePythonModuleDeviceTest passes 6 tests and logs 'verify ok=true problem=', i.e. the module verifies through the production RuntimeLoader path; the whole runtime package passes 88 tests with the module installed and again 88 in the zero-module state after uninstalling, so the degradation path does not crash. hostNoLongerShipsTheRuntime is the guard that keeps this honest, since without it the other tests could pass against a bundled copy and prove nothing. Note also that the exclusion and the reader fix belong to one unit rather than two commits: excluding the bundled runtime while the reader cannot read means a user who installs the module gets a rejected module with no bundled fallback, i.e. the exclusion alone would be a regression - so the fix is a precondition for the exclusion being safe, and both were verified together

Task-Guard: PLG1-S27b
2026-10-20 00:22:59 +08:00
Silent1566
fd50960a0f fix(runtime): 修复 PayloadReader 接线缺口(PLG1 S27a)
修复 S24 引入的真实接线缺口:契约层加了载荷校验缝,
但没有任何调用方提供实现。

缺口:
- RuntimeVerifier 有 PayloadReader 参数
- RuntimeLoader 从未传入(两处调用都走 5 参重载,payloads = null)
- RuntimeAndroid 未提供实现

后果:声明了载荷的模块(即 python)会被失败关闭拒掉,报
「declared payload cannot be verified without a reader」——行为符合设计
(宁可拒绝也不放行未校验载荷),但功能不可用。

为什么没被发现:既有模块全部只声明 lib/**,这条缝从未被真实路径走到;
S24 的单测直接调 RuntimeVerifier 并自己传 reader,绕过了 RuntimeLoader。

修复过程中发现的第二个设计缺陷:
PayloadReader.open(path) 的签名无法知道该读哪个包——载荷在某个已装模块的
APK 内,而 reader 只拿到一个路径。第一版实现里写了个 currentPackage()
返回 null 的占位,等于让 reader 永远不可用。
已改为 open(packageName, path),与既有 ManifestSource.read(packageName) 对齐。

实施:
- RuntimeVerifier:PayloadReader 签名加 packageName;verifyFiles/verifyPayload 透传
- RuntimeLoader:新增 6 参构造器(带 payloads);两处校验调用传入;
  5 参构造器保留并委托
- RuntimeAndroid:新增 payloadReader(),用
  createPackageContext(pkg).getAssets().open(path),与 manifestSource() 同一模式;
  loader() 传入
- 两个测试类适配新签名

验证:新增 2 项接线测试(此前正是缺这类测试):
- passesThePayloadReaderThroughToVerification 断言两个方向(提供则通过、
  不提供则被拒),所以若 reader 再次被漏传它会失败
- rejectsAPayloadWhoseDigestDoesNotMatchThroughTheLoader 确认经
  RuntimeLoader 驱动时摘要校验确实生效

runtime 包 196 项单测全绿。

发现方式:在实施第 4 步(构建期排除)时检查「装模块后能否校验通过」才暴露——
即接线缺口由端到端视角发现,而非单测。与 S21 教训同类:
验证范围必须匹配改动范围。

Verification: unit tests only, no device change. Fixes a real wiring gap introduced in S24: the verifier grew a PayloadReader seam, but no caller supplied one - RuntimeLoader passed null at both verification sites and RuntimeAndroid provided no implementation - so a module declaring a payload (the new python module) was rejected fail-closed with 'declared payload cannot be verified without a reader'. That behaviour is correct by design (never accept an unverified payload) but leaves the feature unusable. It went unnoticed because every existing module declares only lib/**, so the seam was never reached through a real path, and S24's own tests call RuntimeVerifier directly with a reader they construct themselves, bypassing RuntimeLoader entirely. A second design flaw surfaced while fixing it: PayloadReader.open(path) had no way to know which package to read from, since a payload lives inside a specific installed module's APK; my first attempt stubbed a currentPackage() returning null, which would have made the reader permanently useless. The signature is now open(packageName, path), aligning with the existing ManifestSource.read(packageName) - the verifier knows which package it is checking and the reader should not have to guess. Implementation: RuntimeVerifier's PayloadReader takes the package name and verifyFiles/verifyPayload pass it through; RuntimeLoader gains a six-argument constructor taking the reader while the five-argument one is kept and delegates; RuntimeAndroid gains payloadReader() using createPackageContext(packageName).getAssets().open(path), the same pattern manifestSource() already uses, and loader() now supplies it; the two test classes were adapted. Verification: two new wiring tests, which is exactly the kind that was missing - passesThePayloadReaderThroughToVerification asserts BOTH directions (supplying the reader lets the module verify, withholding it does not), so it fails if the reader is dropped again, and rejectsAPayloadWhoseDigestDoesNotMatchThroughTheLoader confirms the digest rule is actually reached when RuntimeLoader drives verification. Runtime package: 196 tests, 0 failures. The gap was found by taking an end-to-end view during the build-exclusion step, not by unit tests - the same lesson as S21, that verification scope must match the scope of the change

Task-Guard: PLG1-S27a
2026-10-19 20:12:31 +08:00
Silent1566
f222b983ec feat(runtime): Python 第 3 步——惰性化 + 可用性门禁(PLG1 S26)
S13 门禁六步计划的第 3 步:消除 Python 的急切初始化,
使模块缺失时应用能正常启动而非崩溃。S12 已证明不改会崩。

要修的问题(S12 已定位):
  BaseLoader.Loader.INSTANCE(静态初始化)
    -> new BaseLoader() -> new PyLoader() -> new Loader()   ← 触发 Python.start
Loader 构造器调用 Python.start(Platform.create()),而 Platform 构造器会读
chaquopy/build.json 并加载 8 个库。模块缺失时这条链在类初始化期间抛异常,
后果不是「Python 功能不可用」而是配置加载整体失败——所有源类型都受影响。
PythonSubtitleScriptRuntime 有同一形状。

实施:
- RuntimePython 新增 isRuntimeAvailable(Context) 与 isBundled(Context)
- PyLoader 的 loader 改 volatile + 双检锁惰性创建;新增 isAvailable()
- PythonSubtitleScriptRuntime 同上;保留包内构造器供测试注入替身

可用性探测的设计:isBundled 探测的正是 Chaquopy 自己要打开的
chaquopy/build.json——答案不可能与运行时的实际判断不一致。
失败被限制在真正需要 Python 的那一次调用内,而非扩散到类初始化。

验证:新增 PythonRuntimeLazyInitTest 8 项全绿,分行为与结构两半:
- 行为:纯 JVM 下构造两个类不抛异常(改动前会失败);探测报不可用而非抛异常
- 结构:构造器内不得有 new Loader();创建点必须在可用性检查之后

为什么还要结构断言:行为测试对任何在测试时恰好避开该调用的实现都会通过,
而不变量在于调用所在的位置。

验证断言有牙齿(决定性检查):用 git show HEAD:<file> 取改动前源码跑同一套
结构断言逻辑,两个文件均为「构造器含 new Loader() = True」,改动后为 False——
即这两条断言在改动前确实会失败,不是恒真检查。

单测:runtime + loader + subtitle 共 287 项全绿;
全仓库 5271 项中 7 项失败,与改动前完全相同的既有失败集合(总数 +8 即本阶段新增),
无回归。

Verification: unit tests only, no device change. The problem this fixes was located in S12: BaseLoader's static initialiser creates a PyLoader, whose constructor called new Loader(), which calls Python.start - and Platform's constructor reads chaquopy/build.json and loads eight libraries. With the runtime delivered as an optional module that chain throws during class initialisation, so the failure is not 'the Python feature is unavailable' but 'configuration loading fails', affecting every source type. PythonSubtitleScriptRuntime had the same shape via this(new Loader()). Implementation: RuntimePython gains isRuntimeAvailable(Context) and isBundled(Context); the bundled probe opens the very file Chaquopy's own Platform opens, so the answer cannot disagree with what the runtime will find, and isRuntimeAvailable asks the platform for the module first rather than the host's registry. PyLoader and PythonSubtitleScriptRuntime now create the bridge lazily behind a double-checked lock and an availability check, so a failure is confined to the one call that actually needs Python instead of spreading to class initialisation; the package-private constructor that takes a Loader is kept for tests. Verification: new PythonRuntimeLazyInitTest, 8 tests, 0 failures, 0 errors, split deliberately into behavioural and structural halves. The behavioural half constructs both classes on a plain JVM where no runtime exists - which the old eager constructor could not survive - and requires the availability probes to answer false rather than throw. The structural half reads the sources to require that no new Loader() remains in a no-argument constructor and that the single creation site sits behind an availability check, because a behavioural test passes for any implementation that happens to avoid the call at test time while the invariant is about where the call sits. I checked the structural assertions actually have teeth rather than being vacuous: running the same assertion logic against git show HEAD:<file> gives 'constructor contains new Loader() = True' for both files before the change and False after, so they would have failed beforehand. Package results: runtime + api.loader + subtitle = 287 tests, 0 failures. Whole repository: 5271 tests, 7 failed - the identical pre-existing failure set as before this change (the total rose by exactly the 8 new tests), so no regression. The seven are MpvFontConfigTest, MpvHlsCacheCoordinatorTest, ReaderPlaybackRoutingSourceTest x2, TmdbUIAdapterTest, TmdbSourceOnlyInteractionTest and WebThemeTokenSourceTest, unrelated to PLG1 and reported rather than fixed per AGENTS.md section 2

Task-Guard: PLG1-S26
2026-10-19 19:11:10 +08:00
Silent1566
0c4d9a746e feat(runtime): Python 第 2 步——Platform 从模块读资产、按拓扑序加载(PLG1 S25)
S13 门禁六步计划的第 2 步。

三个约束决定了设计,均已实测而非假设:

约束 1:chaquo 模块不能引用 app 的 runtime 包(:app 依赖 :chaquo,反向即循环)。
故在 chaquo 侧定义注入缝 Platform.RuntimeModule,宿主启动时安装实现,
与既有 HostLibraries / ManifestSource 同一模式。

约束 2:宿主无法写模块的数据目录。Chaquopy 的 initialize(context, ...) 用同一个
context 决定四件事(importer.py 实测):getAssets() 读 chaquopy/**、
getFilesDir() 作解压根(需可写)、getSharedPreferences() 记账、
getApplicationInfo().nativeLibraryDir(赋值后从未被读取)。
故用 ContextWrapper 只覆盖 getAssets();整体换模块 context 会让解压因权限失败。

约束 3:库的加载次序是拓扑序,不是 Chaquopy 的原序。S12 实测
crypto_chaquopy→crypto_python、ssl_chaquopy→ssl_python、
sqlite3_chaquopy→sqlite3_python、ssl_python→crypto_python,
而裸名序列把其中几个依赖方排在前面——裸名靠搜索路径兜住,
绝对路径加载则必须依赖已映射,故 manifest 必须声明拓扑序。

实施:
- chaquo/Platform.java 新增 RuntimeModule 缝;am 改从缝取;
  loadNativeLibs() 优先按声明路径 System.load,无模块时回退原裸名序列
- RuntimePython.java(新增):校验模块 → 解析库路径 → createPackageContext 取
  模块 assets → 安装 ContextWrapper provider
- App.java:onCreate 接入(须在触碰 com.chaquo.python 之前)

用 createPackageContext 而非隐藏 API addAssetPath——后者不可逆地改动进程级
AssetManager,模块卸载后难以恢复(S9/S11 已论证)。

纯函数 resolveLibraryPaths 使次序规则可在纯 JVM 单测,与 RuntimeSherpa.prepare 同做法。

验证:runtime 包 186 项单测全绿(含新增 RuntimePythonTest 10 项)。
全仓库 5263 项中 7 项失败,均为既有失败,与 PLG1 无关。

发现的 CI 缺口(比 DV5 更重):Python 的 8 个库与 assets/chaquopy/** 全部是构建产物
(git ls-files 确认未跟踪),由 Chaquopy Gradle 插件产出,依赖本机 Python 3.10
与 pip 网络安装 8 个依赖。插件仓库 CI 需相应改动,三方案已记录,推荐方案 A。

Verification: unit tests only, no device change: runtime package 186 tests, 0 failures, 0 errors, including RuntimePythonTest 10 tests (confirmed in the XML, not just BUILD SUCCESSFUL). Whole-repository run: 5263 tests, 7 failed, all seven pre-existing and unrelated to PLG1 (MpvFontConfigTest, MpvHlsCacheCoordinatorTest, ReaderPlaybackRoutingSourceTest x2, TmdbUIAdapterTest, TmdbSourceOnlyInteractionTest, WebThemeTokenSourceTest) - verified by comparing the failing class set against the known list, which matched exactly. Three constraints shaped the design and each was measured rather than assumed. First, the chaquo module cannot reference the host's runtime package because :app depends on :chaquo, so a reference back would be a cycle; the seam is therefore declared on the chaquo side as Platform.RuntimeModule and installed by the host at startup, matching the existing HostLibraries and ManifestSource pattern. Second, the host cannot write the module's data directory, and Chaquopy's initialize(context, ...) uses one context for four things: getAssets() to read chaquopy/**, getFilesDir() as the extraction root (must be writable), getSharedPreferences() for extraction bookkeeping, and getApplicationInfo().nativeLibraryDir (assigned but never read, per importer.py). So a ContextWrapper overrides only getAssets(); swapping the whole context would make extraction fail on permissions. Third, the library order is a topological sort rather than Chaquopy's own sequence: S12 measured that libcrypto_chaquopy depends on libcrypto_python, libssl_chaquopy on libssl_python, libsqlite3_chaquopy on libsqlite3_python and libssl_python on libcrypto_python, while the bare-name sequence loads several dependents first - bare names survive that via the namespace search path, but a module directory is not on it. The pure resolveLibraryPaths function keeps the ordering rule testable on a plain JVM, following RuntimeSherpa.prepare. createPackageContext was used rather than the hidden AssetManager.addAssetPath, which mutates the process-wide asset manager irreversibly and is hard to undo on uninstall. Also found and recorded a CI gap heavier than the DV5 one: the eight Python libraries and the whole assets/chaquopy/** tree are build products (git ls-files confirms none are tracked), produced by the Chaquopy Gradle plugin, which needs a local Python 3.10 and a networked pip install of eight requirements - so the plugins repository cannot package this module without CI changes, and three options are recorded with adding Python 3.10 plus pip to CI recommended

Task-Guard: PLG1-S25
2026-10-19 18:29:06 +08:00
Silent1566
6b6b595e1c feat(runtime): Python 第 1 步——契约层支持 assets 载荷校验(PLG1 S24)
S13 门禁六步计划的第 1 步:让契约层能校验「非原生库」载荷。
这是 Python 模块化的前置条件——Chaquopy 的主体是 assets/chaquopy/** 树,
而现有 verifyFiles 以 nativeLibraryDir 为根,覆盖不到 assets/**。

实施:
- RuntimeIntegrity 新增 sha256(InputStream)(载荷在 APK 内,不能整份读入内存)
- RuntimeManifest 新增 isNativeLibraryPath(path),按载荷根分类
- RuntimeVerifier 新增 PayloadReader 缝;verifyFiles 按前缀分派到
  库校验(File 基)/载荷校验(流基)
- RuntimeCompatibility 白名单加 chaquopy-python310-v1;包名表加 python

PayloadReader 做成注入接口,使校验逻辑保持无 Android API 依赖,可在纯 JVM 单测,
与既有 HostLibraries / ManifestSource 同一模式。

关键安全决定(失败关闭):清单声明了载荷但没有可用 reader 时拒绝而非接受。
接受等于让未校验的载荷进入加载器,与「失败关闭」的目的相反;
这也意味着「不能读模块资产的宿主构建」无法被诱导加载载荷。

向后兼容:新增带 payloads 参数的重载,原有签名保留并委托,
既有 lib/** 型模块无需 reader。

验证:runtime 包 176 项单测全绿(含新增 RuntimePayloadVerificationTest 10 项,
覆盖摘要不符、载荷缺失、无 reader、reader 抛异常、纯库模块兼容、
路径穿越、绝对路径、流式摘要一致性)。

过程中的顺序依赖:首轮 5 项失败,原因是 chaquopy-python310-v1 尚未加入
契约白名单——契约检查在载荷检查之前执行,所以白名单必须先加。
这正是 S13 门禁把「契约层扩展」列为第 1 步的原因。

Verification: contract-layer change verified by unit tests: runtime package 176 tests, 0 failures, 0 errors (RuntimePayloadVerificationTest 10 tests including the new cases, RuntimeVerifierTest 15 tests unchanged). What this enables: the Python runtime's payload is a tree of assets/chaquopy/** files inside the module APK, and the existing verifyFiles rooted at nativeLibraryDir could not cover them, so a manifest declaring such a payload would have been either rejected or silently unchecked. Implementation: RuntimeIntegrity gains sha256(InputStream) because an APK-internal payload cannot be read into memory as a whole; RuntimeManifest gains isNativeLibraryPath to classify declared paths by payload root, stated once rather than inferred per call site; RuntimeVerifier gains a PayloadReader seam and splits verifyFiles into verifyNativeLibrary (File-based, unchanged behaviour) and verifyPayload (stream-based, digest-checked); RuntimeCompatibility gains the chaquopy-python310-v1 contract and the python package name. The seam keeps the verification logic free of Android APIs so it stays unit-testable on a plain JVM, matching the existing HostLibraries and ManifestSource pattern. The safety-relevant decision is fail-closed: a manifest that declares a payload while no reader is available is REJECTED, because accepting it would let an unverified payload reach the loader, which is the opposite of what this class is for; it also means a host build that cannot read module assets cannot be talked into loading one. Backward compatibility is preserved by adding overloads that take the reader while the existing signatures delegate to them, so every existing lib/** module verifies with no reader. A sequencing dependency surfaced and was fixed: the first run failed five tests because chaquopy-python310-v1 was not yet in the accepted-contract whitelist, since the contract check runs before the payload check - which is exactly why the S13 gate lists the contract-layer extension as step 1

Task-Guard: PLG1-S24
2026-10-19 18:04:27 +08:00
Silent1566
ac6d57c167 feat(runtime): DV5 构建期排除 + 双向验证(PLG1 S23)
产物已就绪(插件仓库 972d606)后,实施宿主侧构建期排除,
并完成正向路径(装模块)与降级路径(未装模块)的双向验证。

实施:app/build.gradle 加排除,带回滚开关 -Pdv5Bundle=true。
S16 已实测 packaging.jniLibs.excludes 对 CMake 产物同样生效。

体积实测(全量重打 A/B 对比):
- arm64: 含 DV5 115,206,636 B -> 排除后 105,497,770 B,差值 9,708,866 B
- 差值归因:库 9,708,848 B + classes17.dex 1 B + classes20.dex 17 B
- 那 18 字节不是排除引起的:同一 flag 重复构建 dex 也会变
  (115,206,645 vs 115,206,636),属既有构建非确定性
- 除该库外,两个 APK 的条目集合与其余条目大小完全一致
- 对照项全部保留,含 DV7 用的 libdovi_android.so

双向真机验证:
- 未装模块(库已排除):OK (82 tests),hostShipsLibrary=false probeAvailable=false
- 装模块:OK (4 tests),reason=missing-capability(而非 load-*,证明库成功加载)
- 零模块态(全新安装):OK (82 tests),RuntimeNoModuleDeviceTest 10 项全部执行

新增 RuntimeDv5ModuleLoadDeviceTest(4 项),断言映射来源而非加载成败——
宿主已不带副本,成功只可能来自模块,但显式检查来源能捕获
「未来重新引入内嵌副本」的回归;并断言宿主不自带副本。

修正的测试覆盖缺口(本阶段更有价值的产出):
RuntimeNoModuleDeviceTest 的模块列表硬编码且缺 dv5,而其「全新安装态」检查
遍历该列表,导致装着 dv5 模块时也会通过——一个静默失效的测试。
已补 dv5 并加注释说明该列表须与 RuntimeCompatibility.PACKAGES 同步。

Verification: build-time exclusion plus two-way device verification on 192.168.50.3:5555. A/B full rebuilds: arm64 with DV5 115,206,636 B, excluded 105,497,770 B, difference 9,708,866 B, which is the 9,708,848 B library plus 18 B of dex drift. That 18 B is not caused by the exclusion: rebuilding with the same flag twice gives 115,206,645 vs 115,206,636, so the dex output is non-deterministic on its own; apart from the library the two APKs have identical entry sets and identical sizes for every other entry. Controls confirm nothing else was caught: libavcodec.so, libc++_shared.so, libexo_ass.so, assets/lab/7zz, assets/lab/libc++_shared.so, and the DV7 library libdovi_android.so (446,240 arm64 / 336,208 v7a) all remain. Both ABIs exclude the library. Device results: with no module installed and the library excluded, the whole runtime package passes 82 tests and the probe logs hostShipsLibrary=false probeAvailable=false; with the module installed, RuntimeDv5ModuleLoadDeviceTest passes 4 tests and logs moduleInstalled=true probeAvailable=false reason=missing-capability, where missing-capability rather than load-* is the evidence that the library actually loaded (the emulator lacks Vulkan 1.2 and AImageReader, so the capability probe legitimately fails) - exactly the degradation semantics S14 established. In the zero-module fresh-install state the whole package again passes 82 tests with RuntimeNoModuleDeviceTest executing all ten assertions. The new test class asserts the mapping origin rather than mere load success, because the host no longer ships a copy so a successful load could only come from the module, but checking the origin catches a regression that reintroduced a bundled copy; it also asserts the host does not ship its own copy for the same reason. A silent test-coverage gap was found and fixed: RuntimeNoModuleDeviceTest hardcoded its module list without dv5, and its fresh-install checks iterate that list, so the class would have passed even with the dv5 module installed - a test that fails silently is worse than no test. dv5 was added to both the list and the known-id check, with a comment that the list must stay in step with RuntimeCompatibility.PACKAGES

Task-Guard: PLG1-S23
2026-10-19 17:44:32 +08:00
Silent1566
659e1f9f86 feat(runtime): DV5 宿主侧接入 + 发现 CI 构建缺口(PLG1 S22)
实施 DV5 渲染器模块化的宿主侧部分(契约、resolver、接入、单测),
并核实插件仓库能否产出该模块。本阶段不加构建期排除——理由见下。

已实施:
- RuntimeCompatibility:白名单加 exo-dv5-renderer-v1;包名表加 dv5
- RuntimeDv5(新增):复用 RuntimeIjk 的 resolver 模式
  (install/uninstall/resolve/isAvailable)
- ExoDv5Native.loadLibrary():先问模块(System.load 绝对路径),再回退裸名
- App.onCreate:接入 RuntimeDv5.install(...)
- RuntimeDv5Test(新增):10 项单测

关键设计点(与 IJK 的差异):DV5 渲染器是可选增强而非播放引擎。
ExoDv5GpuMappingPolicy 已有多条降级路线,ExoUtil 用 catch (Throwable ignored) 包裹。
因此 resolver 在模块缺失时返回 null 而非抛异常;模块存在但加载失败时也回退——
「装了模块反而更差」不可接受。

验证:
- RuntimeDv5Test 10 项全绿(tests=10, failures=0, errors=0, skipped=0)
- 全包真机回归 OK (78 tests)
- 未装模块时 hostShipsLibrary=true probeAvailable=false——正确回退到内嵌库

本阶段不加构建期排除:dv5 模块产物尚不存在(webhtv-plugins/dist 只有 ijk/mpv/sherpa)。
若此时排除,用户会失去 DV5 能力却无处可装——正是 S16 踩过的坑。
排除必须与产物同步落地。

发现的 CI 构建缺口(重要):
- sherpa/mpv/ijk 的库都在 git 跟踪路径下(jniLibs / assets/mpv-libs),
  CI 克隆 WebHTV 即可打包
- dv5 的库是 CMake 从 exo_dovi_renderer.cpp + third_party 静态库构建的产物,
  仓库里没有跟踪的 .so 副本 → 现有 CI 路径不适用

已实测可行路径:CMake 可脱离 Gradle 独立构建
(配置 + 构建 + llvm-strip,21.27 → 9.14 MiB),
产物 SONAME 与 7 个 DT_NEEDED 与 Gradle 产物完全一致,
虽不逐字节相同(9.14 vs 9.26 MiB,NDK 与 strip 参数差异)但不影响正确性——
模块产物只需自身摘要与 catalog 一致。

待定方案(插件仓库侧,需单独授权):推荐在 CI 中增加 CMake 构建步骤
(静态库已跟踪,104 MB),无需入库 .so。

Verification: host-side DV5 modularisation, verified by unit tests and a full-package device run. Unit tests: RuntimeDv5Test 10 tests, 0 failures, 0 errors, 0 skipped (confirmed in the XML result, not just a BUILD SUCCESSFUL). Device: am instrument -e package com.fongmi.android.tv.runtime -> OK (78 tests) on 192.168.50.3:5555 after reinstalling the host APK, which is required because instrumentation runs in the host process. The degradation evidence from the DV5 probe reads 'S14DV5 hostShipsLibrary=true probeAvailable=false', i.e. with no module installed the resolver returns null and ExoDv5Native falls back to the bundled library exactly as before, so the change is behaviour-preserving until a module exists. Implementation: RuntimeCompatibility gains the exo-dv5-renderer-v1 contract and the dv5 -> com.silent.runtime.dv5 package mapping; RuntimeDv5 reuses the RuntimeIjk resolver pattern (install/uninstall/resolve/isAvailable) and returns null rather than throwing when no module is present; ExoDv5Native.loadLibrary tries the module by absolute path first and then falls back to System.loadLibrary, with the module path wrapped so that a present-but-unloadable module cannot make the renderer worse off than having no module; App.onCreate installs the resolver alongside the existing IJK one. The deliberate difference from IJK is that this renderer is an optional enhancement rather than a playback engine - ExoDv5GpuMappingPolicy already has fallback routes and ExoUtil wraps renderer construction in catch (Throwable ignored) - so absence must be a value the caller branches on. No build-time exclusion was added in this stage, because the dv5 module artifact does not exist yet (webhtv-plugins/dist holds only ijk, mpv and sherpa); excluding now would remove DV5 capability with nowhere for users to get it back, which is the trap S16 already hit. The exclusion must land together with the artifact. Also found and recorded a CI gap: the three existing modules take their libraries from git-tracked paths (app/src/main/jniLibs and app/src/arm64_v8a/assets/mpv-libs) so a WebHTV clone is enough to package them, but libexo_dovi_renderer.so is a CMake build product and no tracked .so copy exists in the repository. A standalone CMake build was measured as viable - configure with the NDK toolchain, build the exo_dovi_renderer target, strip with llvm-strip, taking 21.27 MiB down to 9.14 MiB - and the resulting library has the same soname and the same seven system-library DT_NEEDED entries as the Gradle product, though it is not byte-identical (9.14 vs 9.26 MiB, different NDK and strip settings), which does not matter because a module artifact only needs to match its own catalog digest. Three options are recorded for the plugins repository, with adding a CMake step to CI recommended since the 104 MB of static libraries are already tracked; that change is outside this repository and needs separate authorisation

Task-Guard: PLG1-S22
2026-10-19 16:40:17 +08:00
Silent1566
26a7a0f98b test(runtime): 全包回归 78 项全绿 + 修正测试隔离缺陷(PLG1 S21)
S20 改的是打包内容(移除一个资产),但当时只跑了 forcetech 专项测试——
这不构成「APK 仍正常工作」的证据。本阶段补上这个决定性检查。

全包回归(真机 arm64,18 个测试类):
  OK (78 tests)

这确认 S20 的资产移除没有破坏任何东西。

过程中发现的测试隔离缺陷(我 S12 引入):
首轮全包运行报 1 个失败:
  preloadingTheDependencyMakesTheDependentLoadable
    expected: .../files/s1[2-python-order]
    but was:  .../files/s1[0-soname-lib]

不是产品回归,是测试缺陷:一个进程只能映射一个库一次,而
RuntimeNoSonameDependencyDeviceTest(S10)与 RuntimePythonLoadOrderDeviceTest(S12)
都用 libcrypto_python.so。谁先跑谁拥有该映射,后跑的那个断言必然失败。
S12 当时只单独跑方法(-e class ...#method),所以从未暴露。

修正:给三个受影响的测试补上前提守卫(assumeTrue),使「前提不成立」表现为跳过
而非假失败。用 assumeTrue 而非删除或改断言,是因为这些测试的价值在于
「单独运行时可观察到具体机制」(S10/S12 的结论正是这样得出的),
守卫保留了该能力,同时让全包运行时诚实跳过。

教训(与 S20 同类):验证的范围必须匹配改动的范围。
S20 改打包却只窄验,S12 改测试却只窄跑,两次都靠扩大验证范围才发现。

同时修正一处我自己的口径错误:此前各阶段累计写「89 项」,那是逐次运行的累加,
不是独立测试集合。runtime 包实际共 78 个测试方法,全包 OK (78 tests) 即为全部。
文档已改为该口径,不再逐阶段累加。

Verification: device 192.168.50.3:5555, whole runtime package: am instrument -e package com.fongmi.android.tv.runtime -> OK (78 tests) across all 18 test classes. This is the check S20 lacked: S20 changed what gets packaged (removed an asset) but only ran the forcetech-specific tests, which is not evidence that the APK still works. The full-package run now supplies that evidence. The first full-package run failed one test, preloadingTheDependencyMakesTheDependentLoadable in RuntimePythonLoadOrderDeviceTest, with 'expected .../files/s1[2-python-order] but was .../files/s1[0-soname-lib]'. That is a test-isolation defect I introduced in S12, not a product regression: a process maps a given library once, and RuntimeNoSonameDependencyDeviceTest (S10) and RuntimePythonLoadOrderDeviceTest (S12) both use libcrypto_python.so, so whichever runs first owns the mapping and the later one's assertion cannot hold. S12 only ever ran its methods individually via -e class ...#method, which is why this never surfaced. Fixed by adding assumeTrue preconditions to the three affected methods (preloadingTheDependencyMakesTheDependentLoadable, and the two in RuntimeNoSonameDependencyDeviceTest), so an unmet precondition shows up as a skip rather than a false failure. assumeTrue was chosen over deleting or weakening the assertions because those methods' value is that they demonstrate a specific mechanism when run alone - which is exactly how the S10 and S12 conclusions were reached - and the guard preserves that while letting a whole-package run skip honestly. The lesson, recorded in the document, is the same shape as S20's: verification must match the scope of the change. S20 changed packaging but verified narrowly; S12 changed tests but ran them narrowly. Both were caught only by widening the verification. Also corrected a reporting error of my own: previous stages accumulated '89 device verifications', but that was a sum of repeated runs, not a set of distinct tests - the runtime package actually contains 78 test methods, so the whole-package OK (78 tests) is the complete set. The document now states that instead of accumulating per stage

Task-Guard: PLG1-S21
2026-10-19 16:22:28 +08:00
Silent1566
90741ac7ed build(runtime): arm64 排除 forcetech 引擎资产,并修正 S17/S19 错误结论(PLG1 S20)
实施 forcetech 资产移除(仅 arm64),并在实施过程中发现并修正 S17/S19 的实质性推理错误。

修正的错误:
S17 测得的事实成立(arm64 上该资产从不被裸名加载),但推理不完整——它只追到
System.loadLibrary("mitv") 加载 jni 版本这一步就停了,据此推断「assets 版本只是白拷一次,
v7a 也不需要它」,没有继续追查桩库之后的行为。

用符号表与反汇编补上这一步后,两个同名文件其实是不同用途的库:
- jni/armeabi-v7a/libmitv.so(54,548 B)= 加载器桩,导出
  Java_com_gsoft_mitv_MainActivity_loadLibrary/decrypt/encrypt,含字符串
  "%s/libmitv.so"、"res/raw/libmitv.so"、"com.gsoft.mitv.zip"、
  getCacheDir/getPackageCodePath/System.load/unzip/zdecompress/aes_decrypt
- assets/libmitv.so(3,745,900 B)= 真正的引擎,SONAME libforcetv.so,导出
  Java_com_forcetech_android_ForceTV_start/stop——正是 com.forcetech.android.ForceTV
  声明的 native 方法

即桩库读 <cacheDir>/libmitv.so(checkLibrary() 刚从 assets 拷来的那份)并 System.load 它。
所以 v7a 需要该资产,arm64 因 abiFilters 过滤掉桩库而无法触达。
S17 的「v7a 也无用」是错的;所幸 S17 自己的建议就是「仅 arm64 排除」,故未导致错误实施。

过程中踩到并修复的第二个陷阱:
把 androidResources { } 写在 productFlavors.arm64_v8a 块里会全局生效——
AGP 9.2.1 中 ignoreAssetsPatterns 只声明在 com.android.build.api.dsl.AndroidResources
与 com.android.build.api.variant.AndroidResources 上,不在 ProductFlavor 上,
Groovy 闭包回退到 android 块级。首次尝试确实把 v7a 的资产也删了,靠实测两个 APK 发现。
已改用 Variant API(androidComponents.onVariants + selector().all() + 按 abi 判定)。

验证(全量重打两个 ABI):
- arm64: 113.4 -> 109.9 MiB(-3.57 MiB),assets/libmitv.so 已排除
- v7a:   97.9 MiB 不变,assets 与 jni 版均保留

真机验证(arm64)9 项全绿:
RuntimeForcetechAbiDeviceTest / RuntimeForcetechCacheCopyDeviceTest /
RuntimeForcetechExtractorAvailabilityDeviceTest 各 OK (3 tests)

三个测试类中前提被本次变更推翻的断言已按新事实修正(改为 assumeTrue 跳过而非删除),
其中 theAssetIsStillPackaged 反转为 theAssetIsExcludedFromTheArm64Build。

教训(已记入文档):追查「某文件是否被使用」时,只测到第一层加载就下结论是危险的。
判据应是「该文件的内容是否被消费」,而不是「该文件名是否出现在某次加载调用里」。

Verification: full rebuilds of both ABIs plus device runs on 192.168.50.3:5555. Implementation: app/build.gradle excludes assets/libmitv.so for the arm64 flavor only, via the variant API (androidComponents.onVariants with selector().all(), checking variant.productFlavors for abi == 'arm64_v8a'). Measured: arm64 118,951,446 B (113.4 MiB) -> 115,205,478 B (109.9 MiB), a 3,745,978 B reduction matching the 3,745,900 B asset plus its zip entry, with no assets/libmitv.so present; v7a stays at 102,603,685 B (97.9 MiB) and keeps both assets/libmitv.so and lib/armeabi-v7a/libmitv.so. Device runs, all green: RuntimeForcetechAbiDeviceTest OK (3), RuntimeForcetechCacheCopyDeviceTest OK (3), RuntimeForcetechExtractorAvailabilityDeviceTest OK (3). The important outcome is a correction, not the 3.57 MiB. S17 measured a real fact (the asset is never reached by a bare-name lookup in arm64) but reasoned incompletely: it stopped at System.loadLibrary('mitv') loading the jni copy and concluded the asset was dead weight in both builds. It never followed what the jni copy does next. Reading the dynamic symbol tables and disassembly shows the two same-named files are different libraries: jni/armeabi-v7a/libmitv.so (54,548 B) is a loader exporting Java_com_gsoft_mitv_MainActivity_loadLibrary/decrypt/encrypt, with strings '%s/libmitv.so', 'res/raw/libmitv.so', 'com.gsoft.mitv.zip', getCacheDir, getPackageCodePath, System.load, unzip, zdecompress and aes_decrypt; assets/libmitv.so (3,745,900 B) is the engine, soname libforcetv.so, exporting Java_com_forcetech_android_ForceTV_start/stop - exactly the natives com.forcetech.android.ForceTV declares. So the loader reads <cacheDir>/libmitv.so, which checkLibrary() just copied from assets, and System.loads it: v7a needs the asset, arm64 cannot reach it because abiFilters removes the loader. S17's conclusion that v7a did not need it was wrong; fortunately S17's own recommendation was already arm64-only, so no incorrect change shipped from it. A second trap was hit and fixed during this stage: writing androidResources { } inside the productFlavors.arm64_v8a block applies globally, because on AGP 9.2.1 ignoreAssetsPatterns is declared only on com.android.build.api.dsl.AndroidResources and com.android.build.api.variant.AndroidResources, not on ProductFlavor, so the Groovy closure falls through to the android-level block - the first attempt did remove the asset from v7a and was caught by measuring both APKs. Three assertions whose premise the change invalidated were updated rather than deleted: they now assumeTrue-skip when the asset is absent, since they remain the evidence for the arm64 removal and can be re-run against v7a or a build with the exclusion disabled, and theAssetIsStillPackaged was inverted to theAssetIsExcludedFromTheArm64Build so that 'absent because we removed it' stays distinguishable from 'absent for another reason'. Lesson recorded in the document: when asking whether a file is used, stopping at the first layer of loading is unsafe - loadLibrary('mitv') loads a loader, and what the loader loads is what is actually consumed, so the criterion must be whether the file's contents are consumed, not whether its name appears in a load call

Task-Guard: PLG1-S20
2026-10-19 16:13:40 +08:00
Silent1566
7e70e58dd9 test(runtime): forcetech 在 arm64 整体失效取证(PLG1 S19)
S17 证明 assets/libmitv.so 从不被加载后,追问一个更大的问题——
依赖它的提取器本身是否可用。

结论:arm64 构建上整个 forcetech 提取器不可用,且这暴露了一个既有缺陷。

结构性原因:forcetech AAR 的原生库只有 armeabi-v7a
(libmitv.so 54,548 / libp2p.so 787,324 / libp3p.so 824,872),
arm64 flavor 的 abiFilters arm64-v8a(app/build.gradle:74)把 jni/armeabi-v7a/
全部过滤掉,而 assets/ 不受 abiFilters 约束,所以只有那个 32 位的
assets/libmitv.so(3,745,900)进了 arm64 APK。

RuntimeForcetechExtractorAvailabilityDeviceTest(真机 OK 3 项)决定性证据:
  nativeLibraryDir=/data/app/com.silent.android.webhtv-.../lib/arm64 present=[]
  bareNameLookups p2p=UnsatisfiedLinkError p3p=UnsatisfiedLinkError mitv=UnsatisfiedLinkError

三个库全部缺失,三个裸名查找全部失败——而这三条查找正是 Force 的加载路径
(P2PService -> p2p、P3PService -> p3p、MainActivity -> mitv)。

两个不同性质的结论:
- 3.7 MB 资产可无损失移除(本阶段要支持的结论)
- arm64 上 forcetech 提取器已不可用(既有缺陷,独立于本阶段)

既有缺陷(只报告,不修,按 AGENTS.md §2):Source.java:42 无条件注册 new Force(),
arm64 上必然失败,用户遇到 p2p:///mitv:// 源会走失败路径而非得到「能力不可用」的
明确提示。与 S6 修好的「内核选择器无条件列出全部内核」同类,但不在本任务范围内。

对移除方案的影响:arm64 上零能力损失。v7a 的 assets 版本虽同样未被加载
(S17 已证),但未逐一实测 v7a 其他路径,故实施时仅对 arm64 排除,v7a 保持现状。

Verification: device 192.168.50.3:5555 instrumented run of RuntimeForcetechExtractorAvailabilityDeviceTest -> OK (3 tests), establishing that the whole forcetech protocol extractor is non-functional in an arm64 build, not merely that one asset is unused. Structural cause: the forcetech AAR ships its native code only under jni/armeabi-v7a/ (libmitv.so 54,548, libp2p.so 787,324, libp3p.so 824,872), and the arm64 flavor's abiFilters 'arm64-v8a' at app/build.gradle:74 removes all of it, while assets/ is not subject to abiFilters so the 32-bit assets/libmitv.so (3,745,900) is the only forcetech artifact that reaches the arm64 APK. Device evidence: 'nativeLibraryDir=/data/app/com.silent.android.webhtv-.../lib/arm64 present=[]' and 'bareNameLookups p2p=UnsatisfiedLinkError p3p=UnsatisfiedLinkError mitv=UnsatisfiedLinkError' - all three libraries are absent and all three bare-name lookups fail, and those three lookups are exactly the load path Force uses (P2PService to p2p, P3PService to p3p, MainActivity to mitv). Two consequences of different kinds are recorded separately: the 3.7 MB asset can be removed with zero capability loss in arm64, which is what the removal depends on; and the extractor is already broken there, which is a pre-existing defect independent of this work. That defect is reported rather than fixed, per AGENTS.md section 2: Source.java:42 unconditionally registers new Force(), so a p2p:// or mitv:// source takes a failure path instead of being told the capability is unavailable - the same class of registration-versus-availability mismatch that S6 fixed for the kernel selector, but outside this task's scope. For the removal itself, only the arm64 build should exclude the asset: the v7a assets copy is likewise never loaded (S17 showed loadLibrary consults only nativeLibraryDir, and the v7a jni copy is the one a lookup reaches), but v7a's other paths were not measured individually, so v7a keeps its current contents

Task-Guard: PLG1-S19
2026-10-19 14:57:39 +08:00
Silent1566
977b5c8f3a docs(runtime): 剩余候选量化与优先级排序(PLG1 S18)
纯评估,不改任何代码。对剩余可拆项做完整量化并按「收益 ÷ 风险」排序。

精确测量(arm64 debug,全量重打 113.4 MiB):
- Python/Chaquopy 21,026,527 B(17.7%,S13 门禁已就绪,中风险)
- DV5 渲染器 9,708,848 B(8.2%,S15 门禁已就绪 + S16 已解决唯一未决点,低风险)
- thunder 6,445,320 B / jianpian 5,288,528 B(S7 已判定不可模块化)
- lut_presets 4,283,610 B
- forcetech 死重量 3,745,900 B(S17 已取证,最低风险:无需模块体系)
- tflite 3,549,376 B / quickjs 1,327,232 B / avif 899,752 B / pdf 454,695 B

三项新发现(不在原候选清单):
- libexo_dovi_renderer.so 9.26 MiB(S14 发现)
- assets/libmitv.so 3.57 MiB(S17 取证为死重量)
- assets/lab/ 2.67 MiB(是用户可见功能,4 个 Activity 已注册、LabEnv 1,419 行,
  非死重量,但属播放核心之外的独立模块候选)

系统性死重量扫描(把 S17 的方法应用到整个 APK):
- assets/ 下全部 ELF 做 ABI 匹配检查,仅 assets/libmitv.so 不匹配
- assets/lab/proot/loader32 也是 ELF32/ARM,但是有意设计——
  LabEnv:131 明确「loader32 只存在于 arm64 包里」,供 32 位兼容层用
- 结论:forcetech 资产是唯一的 ABI 型死重量

优先级建议(按收益 ÷ 风险):
1. forcetech 死重量移除(3.57 MiB,零架构成本——不新增契约/降级/UI)
2. DV5 渲染器模块化(9.26 MiB,门禁已就绪,无 hostLibraries、有 SONAME、
   降级链已真机验证)
3. Python 模块化(20.05 MiB,收益最大但需改 Platform 资产定位、
   加载次序改拓扑序、PyLoader 改惰性并加可用性门禁——S12 已证不改会崩)

明确不建议项含理由,其中 libc++_shared.so 必须留宿主(事故 commit 2a56751af0)。

两处自我纠正:累计数字原写 77.0 MB,实测差值为 81,435,675 B = 77.7 MiB;
且文档混用 MiB 与十进制 MB,已加口径说明并警示不可混用。

Verification: assessment-only stage, no code changed. Produces a quantified ranking of the remaining removable items measured from a full-rebuild arm64 debug APK (118,951,446 B = 113.4 MiB): Python/Chaquopy 21,026,527 B (17.7%, S13 gate ready, medium risk), DV5 renderer 9,708,848 B (8.2%, S15 gate ready and S16 resolved its only open risk, low risk), thunder 6,445,320 B and jianpian 5,288,528 B (both closed by S7 because vendor binaries load by bare name), lut_presets 4,283,610 B, forcetech dead weight 3,745,900 B (S17 already proven, lowest risk because it needs no module machinery at all), tflite 3,549,376 B, quickjs 1,327,232 B, avif 899,752 B, pdf 454,695 B. Three items are newly identified and were absent from the original candidate list: libexo_dovi_renderer.so (9.26 MiB, found in S14), assets/libmitv.so (3.57 MiB, proven dead in S17), and assets/lab/ (2.67 MiB, which is a live user-visible feature with four registered Activities and a 1,419-line LabEnv, so it is not dead weight but a separate module candidate outside the playback-core scope). S17's method was then applied across the whole APK: every ELF under assets/ was checked for ABI match, and only assets/libmitv.so mismatches (ELF32/ARM against a 64-bit process); assets/lab/proot/loader32 is also ELF32/ARM but is deliberate, with LabEnv:131 stating loader32 only exists in the arm64 package for the 32-bit compatibility layer. So the forcetech asset is the only ABI-type dead weight. The recommendation orders work by gain divided by risk: first the forcetech removal (3.57 MiB, zero architectural cost, no new contract, degradation path or UI), then DV5 (9.26 MiB, gate ready, no hostLibraries, has a soname, degradation chain already device-verified), then Python (20.05 MiB, largest but requires changing Platform asset location, converting the load order to a topological sort, and making PyLoader lazy with an availability gate because S12 proved an eager init would throw). Items explicitly not recommended are recorded with reasons, including libc++_shared.so which must stay in the host per the incident at commit 2a56751af0. Two self-corrections are recorded: I first wrote the cumulative figure as 77.0 MB, but the measured difference is 81,435,675 B = 77.7 MiB, and the document mixed MiB and decimal MB, so a units note now states both (200.4 to 119.0 MB, 81.4 MB decimal) and warns against mixing them

Task-Guard: PLG1-S18
2026-10-19 14:54:51 +08:00
Silent1566
2de4bd0405 test(runtime): forcetech 资产死重量取证,3.7 MB 可移除(PLG1 S17)
重新审查 S7 划定的「协议提取器不可模块化」边界时,发现其中一项与模块化无关的
独立问题——assets/libmitv.so(3.7 MB)在 arm64 构建里根本无法加载。

S7 的结论仍然成立(厂商代码按裸名加载 → 不能模块化)。本阶段发现的是另一个问题:
该资产不是「能不能模块化」,而是「它有没有用」。

加载链(反汇编实证,非推测):
  Force.java(仓库内源码)
    -> Util.intent(context, scheme)      把 scheme 作为 scheme extra
    -> PxPService.onBind()               取出 extra 传给 ForceTV.start(String, int)
    -> ForceTV.start(String lib, int)    System.loadLibrary(lib)  ← 裸名,库名运行时传入
    -> MainActivity.<init>()
         checkLibrary()                  Asset.open(libmitv.so) -> Path.copy -> Path.cache()
         System.loadLibrary(mitv)      ← 从 nativeLibraryDir 找,不是 cacheDir

RuntimeForcetechCacheCopyDeviceTest(真机 OK 3 项)决定性证据:
  bareName=mitv outcome=failed: UnsatisfiedLinkError cacheCopyMapped=false cacheCopySize=3745900
  asset=libmitv.so size=3745900 elfClass=1 machine=0x28 process64=true

两条独立原因(任一条都足以使该资产无用):
- 架构不匹配:资产是 32 位 ARM,arm64 构建的进程是 64 位
- 位置不在搜索路径:loadLibrary 只查 nativeLibraryDir 与系统路径,cacheDir 不在其中
  (与 S7 测得的同一机制)

ABI 对比(实测两个构建):
- arm64:lib/ 中无 mitv;assets 版本 3.7 MB(32 位)→ 无法加载
- v7a:lib/armeabi-v7a/libmitv.so(54,548 B,32 位)→ loadLibrary 命中 jni 版本,
  assets 版本仍无用
两个 libmitv.so 哈希不同(e0fed3b5... vs db429e31...),assets 版本只是每次
Service 启动都白拷一次。

收益 3.7 MB——大于候选清单里的 quickjs(1.3 MB)与 tflite(3.5 MB),
且不需要模块化。与 S7 不冲突:S7 回答「能否移出由模块提供」(否),
本阶段回答「是否被使用」(否)。

因涉及裁剪第三方 AAR 内容,属兼容性变更,需 §7 设计门禁与用户批准后实施;
实施时应按 ABI 条件化(仅 arm64 排除,v7a 保持现状)。

Verification: device 192.168.50.3:5555 instrumented run of RuntimeForcetechCacheCopyDeviceTest -> OK (3 tests), establishing that assets/libmitv.so (3,745,900 B) is dead weight. The load chain was established by disassembly rather than inference: Force.java (repository source) -> Util.intent(context, scheme) puts the scheme into a 'scheme' extra -> PxPService.onBind takes that extra and calls com.forcetech.android.ForceTV.start(String lib, int port) -> System.loadLibrary(lib) with a runtime-supplied bare name -> and separately com.gsoft.mitv.MainActivity's constructor calls checkLibrary(), which does Asset.open('libmitv.so') then Path.copy into Path.cache() (= getCacheDir()), followed by System.loadLibrary('mitv'). Reproducing that exact vendor sequence on the device gives: 'bareName=mitv outcome=failed: UnsatisfiedLinkError cacheCopyMapped=false cacheCopySize=3745900' and 'asset=libmitv.so size=3745900 elfClass=1 machine=0x28 process64=true'. So the bare-name load throws UnsatisfiedLinkError and the copied file is never mapped. Two independent reasons make the asset useless: its architecture is 32-bit ARM (ELF class 1, machine 0x28) while the arm64 build runs as a 64-bit process, and a file placed in the cache directory is not on the linker's search path at all - the same mechanism measured in S7 for a module directory. An ABI comparison of both builds shows the arm64 build ships no lib/ copy of mitv while the v7a build ships lib/armeabi-v7a/libmitv.so (54,548 B); the two libmitv.so files have different digests (e0fed3b5... vs db429e31...), so the jni copy is the one a bare-name lookup can reach and the 3.7 MB asset is only ever copied and discarded. This is a different question from S7, which asked whether these libraries can be moved into a module (answer: no, vendor code loads them by bare name); this stage asks whether the asset is used at all (answer: no). The gain is 3.7 MB, larger than the quickjs (1.3 MB) and tflite (3.5 MB) candidates, and it needs no module machinery, no hostLibraries contract and no degradation design. Because trimming a third-party AAR's contents is a compatibility change, the recommendation is recorded for a section 7 design gate and user approval rather than implemented, and any implementation should be conditional on ABI so that v7a keeps its current contents

Task-Guard: PLG1-S17
2026-10-19 14:51:41 +08:00
Silent1566
d7aaccbd25 docs(runtime): CMake 产物排除实测,S15 唯一未决点已解决(PLG1 S16)
实测 packaging.jniLibs.excludes 对 CMake 产出的 .so 是否生效。
这是 S15 设计门禁里唯一的未决风险——若不生效,方案需改为 CMake 目标条件化。

结论:生效。无需 CMake 条件化,S15 方案可直接实施。

实测方法(避免增量打包干扰,两次均全量重打):
- 含 DV5(-Pdv5Bundle=true):118,951,446 B = 113.4 MiB
- 排除 DV5(默认):109,242,598 B = 104.2 MiB

差值 9,708,848 B 恰好等于 libexo_dovi_renderer.so 本身,说明排除精确生效。

对照项(排除后仍在,证明未误伤):libavcodec.so、libc++_shared.so、
libdovi_android.so(第三方 AAR,DV7 用)、libexo_ass.so、assets/lab/libc++_shared.so。

过程中的自我纠正(重要):我最初把这个排除写进了默认构建,随后发现 dv5 模块
产物尚不存在(webhtv-plugins/dist 只有 ijk/mpv/sherpa)。那会让用户失去 DV5
能力却无处可装模块——是能力倒退。已完全回退 app/build.gradle(git diff 为空),
本阶段只保留实测结论,不改默认构建行为。实施时须与模块产物同步落地。

同时纠正一个体积口径:115.3 MiB 的旧基线含增量打包残留(S8 已记录此坑),
全量重打后含 DV5 的真实体积是 113.4 MiB。后续体积对比一律以全量重打为准。

Verification: build-time measurement, not a device test. Question: does packaging.jniLibs.excludes reach a .so produced by CMake rather than by the jniLibs source set? This was the single open risk in the S15 design gate, and if it did not hold the plan would have needed a conditionalised CMake target. Answer: it does hold. Two full rebuilds were used, because incremental packaging leaves stale data holes (a trap already recorded in S8): with -Pdv5Bundle=true the APK is 118,951,446 B (113.4 MiB) and contains libexo_dovi_renderer.so; with the exclude applied it is 109,242,598 B (104.2 MiB) and does not. The difference is 9,708,848 B, exactly the size of libexo_dovi_renderer.so, so the exclusion is precise rather than incidental. Controls confirm nothing else was caught: libavcodec.so (6,055,480), libc++_shared.so (1,374,336), libdovi_android.so (446,240, from the third-party exoplayerhdrutils AAR and used for DV7), libexo_ass.so (2,775,600) and assets/lab/libc++_shared.so (1,374,336) all remain. Self-correction: I first added the exclusion to the DEFAULT build, then found that no dv5 module artifact exists yet (webhtv-plugins/dist holds only ijk, mpv and sherpa), so shipping that change would have removed DV5 capability while giving users nowhere to get it back - a capability regression. app/build.gradle was therefore fully reverted (git diff is empty) and this stage records only the measurement, leaving default build behaviour untouched; implementation must land the exclusion together with the module artifact. A second correction: the old 115.3 MiB baseline contained incremental-packaging residue, so the true full-rebuild size with DV5 present is 113.4 MiB, and future size comparisons must use full rebuilds

Task-Guard: PLG1-S16
2026-10-19 14:13:29 +08:00
Silent1566
b1cd9cc22b docs(runtime): DV5 模块化设计门禁(PLG1 S15)
AGENTS.md 第 7 节要求的实施前门禁产物,记录为文档 16.31 节。
在获得明确批准前不实施。

内容:
- 要实现的能力:未装模块时 DV Profile 5 片源仍走既有降级路径
  (CODEC_TONE_MAP / LEGACY_HDR10_FALLBACK)可播,只失去 GPU 映射路径;
  装模块后行为不变。属可选增强而非核心能力
- 现有实现(具体位置):exo_dovi_renderer.cpp(1251 行,仓库内源码);
  CMakeLists 静态链接 libplacebo/shaderc/libdovi;
  ExoDv5Native.loadLibrary 用裸名 + catch Throwable 返回 LoadResult;
  probe() 返回值而非抛异常;shouldCreate 门禁;ExoUtil:1144-1157 无条件添加标准渲染器。
  引用面 40 处 / 5 文件,无跨模块泄漏
- 涉及仓库完整 commit ID:本仓库 d7e0dde8ee...、插件仓库 a00fb34
- 最佳实践证据表:本仓库 readelf 实测(只依赖系统库、有 SONAME);
  S14 真机证明降级链返回值而非抛异常;S7 裸名查找看不到模块目录;
  S2-d 可用性查平台的先例。并如实说明上游无对应实现(该库是 WebHTV 自研)
- 四方案对比:不改 / 拆成模块(推荐)/ 运行期下载到 filesDir(否决:绕过签名与摘要
  校验,等于新增 RCE 路径,与否决复用 lab 同因)/ 改用预编译 AAR(否决:无收益且失去可重建性)
- 设计复用 RuntimeIjk 的 resolver 模式,并明确点出唯一未决风险:
  该库由 CMake 产出而非 jniLibs 源集,packaging.jniLibs.excludes 是否生效
  必须在实施第一步实测,备选是 CMake 目标条件化
- 区分 libdovi_android.so(446 KB,来自第三方 AAR exoplayerhdrutils-0.4.0,DV7 用)
  与本模块目标,避免混淆
- 风险、与现有功能的关系、六步最小实施、所需验证、回滚路径

结尾给出「建议实施」并明确仍需用户批准。未改动任何生产代码。

Verification: documentation-only design gate for modularising libexo_dovi_renderer.so, added as section 16.31 of docs/PLG1-playback-core-plugin-architecture.md as required by AGENTS.md section 7 before implementation. It records the user-visible capability (with no module installed, Dolby Vision Profile 5 sources still play through the existing CODEC_TONE_MAP or LEGACY_HDR10_FALLBACK routes and only lose the GPU-mapping path; with the module installed behaviour is unchanged) and establishes that this is an optional enhancement rather than a core capability, since ExoDv5GpuMappingPolicy.decide already has multiple fallback routes and ExoUtil wraps the renderer creation in catch (Throwable ignored). Existing implementation is cited at concrete locations: app/src/main/cpp/exo_dovi_renderer.cpp (1251 lines, repository source), CMakeLists.txt statically linking libplacebo/shaderc/libdovi, ExoDv5Native.loadLibrary using the bare name with catch Throwable returning a LoadResult, probe() returning a value, ExoDv5GpuRendererFactory.shouldCreate gating on probe.available(), and ExoUtil.java:1144-1157 adding the standard renderers unconditionally afterwards; the reference surface is 40 uses across 5 files with no cross-module leakage. Full 40-character commit IDs are given for this repository (d7e0dde8ee) and the plugins repository (a00fb34). The evidence table grades this repository's own readelf measurement (system libraries only, soname present), the S14 device proof that the degradation chain returns a value rather than throwing, the S7 finding that bare-name lookup cannot see a module directory, and the S2-d precedent of asking the platform for availability - while stating plainly that no upstream equivalent exists because this renderer is WebHTV's own. A four-way comparison covers no change, a module (recommended), runtime download to filesDir (rejected as it bypasses signature and digest verification and would create an RCE path, the same reasoning that rejected reusing lab), and switching to a prebuilt AAR (rejected as it loses rebuildability for no gain). The design reuses the RuntimeIjk resolver pattern, and names the single open risk explicitly: the library is produced by CMake rather than the jniLibs source set, so whether packaging.jniLibs.excludes applies must be measured as the first implementation step, with conditionalising the CMake target as the fallback. It also distinguishes libdovi_android.so (446 KB, from the third-party exoplayerhdrutils-0.4.0 AAR, used for DV7) from this module's target. Risks, effects on existing features, six implementation steps, verification, and the rollback path are recorded, ending with a recommendation to implement and an explicit note that user approval is still required, so no production code was touched

Task-Guard: PLG1-S15
2026-10-19 14:03:35 +08:00
Silent1566
d7e0dde8ee test(runtime): DV5 渲染器降级验证,发现更大可拆项(PLG1 S14)
重新实测 APK 构成(原候选清单有遗漏),并验证新发现的最大单项能否模块化。

新发现:libexo_dovi_renderer.so = 9.26 MiB,是剩余最大单项。它不在原候选清单
§5.2 中,因为该清单按「上游对应」筛选,而此库是 WebHTV 自研。

为什么它是优质候选(三项条件均优于已完成的模块):
- 依赖:只依赖系统库(libandroid/liblog/libmediandk/libvulkan/libdl/libm/libc,
  readelf 实测)→ 无需 hostLibraries 契约
- SONAME:有 → 无 S10/S12 那类无 SONAME 依赖风险
- 源码:仓库内(app/src/main/cpp/exo_dovi_renderer.cpp,1251 行)→ 可注入 resolver
- 降级:已存在且是设计意图
体积达 9 MB 是因为 CMakeLists 把 libplacebo/shaderc/libdovi 作为 IMPORTED STATIC
静态链接进来,故自包含。

RuntimeDv5DegradationDeviceTest(真机 OK 3 项):
- probe 返回结果而非抛异常
- 不可用或 null 的 probe 被 shouldCreate 拒绝
- 标准渲染器不受 DV5 渲染器影响

决定性证据:hostShipsLibrary=true probeAvailable=false
即库存在且加载成功,但 probe 报告不可用(模拟器缺 Vulkan 1.2/AImageReader),
且是返回结果而非抛异常——恰好演示了模块化后未装模块时的路径。

过程中的自我纠错:首轮 2 项失败均为测试假设错误而非产品缺陷——
断言「宿主带库则 probe 应可用」但模拟器缺 Vulkan 返回 missing-capability;
Probe 是包私有 record,反射构造需 setAccessible。已改为断言契约而非设备能力。

建议:该库应作为下一个模块化目标,优先级高于 Python——收益 9.26 MiB 且风险显著更低。
实施前仍需 §7 设计门禁与用户批准。

Verification: device 192.168.50.3:5555 instrumented run of RuntimeDv5DegradationDeviceTest -> OK (3 tests), establishing the precondition for modularising libexo_dovi_renderer.so, which a fresh APK composition measurement shows is 9.26 MiB and the largest remaining single item - it was absent from the candidate list in section 5.2 because that list was filtered by upstream correspondence and this library is WebHTV's own. Three conditions make it a stronger candidate than the completed modules: readelf shows it depends only on system libraries (libandroid, liblog, libmediandk, libvulkan, libdl, libm, libc) so no hostLibraries contract is needed; it carries a soname so the S10/S12 dependency-resolution risks do not apply; and its source is in this repository (app/src/main/cpp/exo_dovi_renderer.cpp, 1251 lines) so a resolver can be injected as with IJK. Its size comes from CMakeLists.txt linking libplacebo, shaderc and libdovi as IMPORTED STATIC. The tests verify the degradation chain rather than trusting a reading of it: probeReportsOutcomeInsteadOfThrowing (probe returns a recognised state instead of throwing), factoryReturnsNullWhenRendererIsUnavailable (shouldCreate rejects unavailable and null probes), and standardRenderersAreAddedRegardlessOfTheDv5Renderer. Decisive device evidence: 'S14DV5 hostShipsLibrary=true probeAvailable=false' - the library exists and loaded, yet the probe reports unavailable because the emulator lacks Vulkan 1.2 and AImageReader, and it returns that as a value rather than throwing, which is exactly the path an uninstalled module will take. Two first-round failures were my own test assumptions, not product defects: I asserted that a shipped library implies an available probe, but the emulator returns missing-capability; and Probe is a package-private record whose constructor needs setAccessible. Both were corrected to assert the contract rather than the device's capabilities

Task-Guard: PLG1-S14
2026-10-19 13:58:10 +08:00
Silent1566
ad72e08f12 docs(runtime): Python 模块化设计门禁(PLG1 S13)
AGENTS.md 第 7 节要求的实施前门禁产物,记录为文档 16.29 节。
在获得明确批准前不实施。

内容:
- 要实现的能力:未装模块时应用仍能启动、加载配置、播放;.py 源给明确引导
  而非静默失败;装模块后行为与现在一致
- 现有实现(具体文件与符号):chaquo/Platform.java 是仓库内源码;
  PyLoader:24 与 PythonSubtitleScriptRuntime:14 急切构造 Chaquopy Loader;
  BaseLoader:35,165 静态急切初始化;LoginStateSync:655 已把 files/chaquopy/ 视为同步路径
- 涉及仓库完整 commit ID:本仓库 b8a3a027da...、插件仓库 a00fb34
- 最佳实践证据表:Python.Platform 官方 Javadoc;Chaquopy 上游 7b690f7e 的
  importer.py initialize() 显示单个注入 context 同时决定 nativeLibraryDir、
  getFilesDir()(ASSET_PREFIX 解压根)、getSharedPreferences(记账)与 AssetZipFile(读资产);
  维护者 mhsmith 在 issue 637 警告不要修改 AssetFinder 路径;本仓库 S9/S10/S12 真机证据。
  并如实说明:上游无「从模块 APK 加载 Chaquopy 资产」的先例,对应关系是架构对应而非照搬
- 四方案对比:不改 / 照搬上游 / 最小适配(推荐)/ 重打包 Chaquopy 二进制(否决)
- 推荐设计:契约的 loadOrder 用 S12 实测拓扑序而非原序;hostLibraries 为空;
  RuntimeVerifier 需扩展(verifyFiles 现以 nativeLibraryDir 为根,覆盖不到 assets/**);
  降级改动(S12 已证必须做)
- 风险、与现有功能的关系、六步最小实施、所需验证、回滚路径

结尾给出「建议实施」并明确仍需用户批准。未改动任何生产代码。

Verification: documentation-only design gate for the Python (Chaquopy) modularisation stage, added as section 16.29 of docs/PLG1-playback-core-plugin-architecture.md and required by AGENTS.md section 7 before any implementation. It records: the user-visible capability (app must still start, load config, and play with no module installed; .py sources get an explicit module prompt instead of failing silently; installing the module restores current behaviour); the existing implementation at concrete file and symbol locations (chaquo/Platform.java is repository source extending Python.Platform, PyLoader.java:24 and PythonSubtitleScriptRuntime.java:14 eagerly construct the Chaquopy Loader, BaseLoader.java:35,165 statically and eagerly initialises PyLoader, LoginStateSync.java:655 already treats files/chaquopy/ as a synced path); full 40-character commit IDs for this repository (b8a3a027da) and the plugins repository (a00fb34); a best-practice evidence table grading the official Python.Platform Javadoc, Chaquopy upstream source at 7b690f7e (importer.py initialize() shows the single injected context decides nativeLibraryDir for ctypes, getFilesDir() for the ASSET_PREFIX extraction root, getSharedPreferences for extraction bookkeeping, and AssetZipFile for reading assets), maintainer mhsmith's warning in issue 637 not to modify AssetFinder paths, and this repository's own device evidence from S9/S10/S12 - while stating plainly that no upstream precedent for loading Chaquopy assets from a module APK exists, so the correspondence is architectural rather than copied; a four-way comparison of no change, copying upstream, minimal adaptation (recommended), and repacking the Chaquopy binary (rejected); the recommended design including a contract whose loadOrder is the S12-measured topological order rather than Platform.loadNativeLibs()'s original sequence, an empty hostLibraries map, a RuntimeVerifier extension because verifyFiles currently roots at nativeLibraryDir and cannot cover assets/**, and the degradation changes that S12 proved necessary; risks, effects on existing features, six minimal implementation steps, the verification needed, and the rollback path. It ends with the recommendation to implement and an explicit statement that user approval is still required, so no production code was touched

Task-Guard: PLG1-S13
2026-10-19 13:50:09 +08:00
Silent1566
b8a3a027da test(runtime): Python 加载次序实验,契约必须拓扑排序(PLG1 S12)
确定 Python 模块的 loadOrder 契约。chaquo/Platform.java 用裸名按固定次序加载:
crypto_chaquopy, ssl_chaquopy, sqlite3_chaquopy, python3.10, chaquopy_java

但实测依赖图与该次序不一致:crypto_chaquopy 依赖 crypto_python、
ssl_chaquopy 依赖 ssl_python、sqlite3_chaquopy 依赖 sqlite3_python、
ssl_python 依赖 crypto_python——依赖方都在被依赖方之前加载。

结论:现有裸名次序在绝对路径加载下不成立,模块契约必须声明拓扑排序的 loadOrder。

RuntimePythonLoadOrderDeviceTest(真机 OK 2 项,各自单独运行):
- 依赖未从模块目录满足
- 预加载依赖后正常

决定性设备日志:
  late-load libcrypto_chaquopy.so = loaded;
    libcrypto_python.so origins = [/data/app/com.silent.android.webhtv-.../lib/arm64];
    hostDir                     = /data/app/com.silent.android.webhtv-.../lib/arm64

即加载成功了,但依赖是从宿主自己的目录解析的,不是暂存目录。现有次序之所以能
工作,纯粹因为宿主 nativeLibraryDir 在 linker 搜索路径上——而真实模块里宿主根本
不提供这些库,故该次序会失败。

断言映射来源而非加载成败:宿主自带这 8 个库的全部副本,且宿主目录在搜索路径上,
「不预加载依赖也能成功」是假成功,不能迁移到模块场景。

另一项已核实约束(影响降级设计):BaseLoader 的 Loader.INSTANCE 是静态初始化,
急切执行 new BaseLoader() -> new PyLoader() -> new Loader() -> Python.start(...),
无 try/catch,故模块缺失时配置加载会直接抛异常而非优雅降级。

Verification: device 192.168.50.3:5555 instrumented runs of RuntimePythonLoadOrderDeviceTest (each method run alone, because the first loads a library the second preloads): dependencyIsNotSatisfiedFromTheModuleDirectoryWhenLoadedLate -> OK (1 test) and preloadingTheDependencyMakesTheDependentLoadable -> OK (1 test). The decisive evidence is the device log from the first: 'late-load libcrypto_chaquopy.so = loaded; libcrypto_python.so origins = [/data/app/com.silent.android.webhtv-.../lib/arm64]; hostDir = /data/app/com.silent.android.webhtv-.../lib/arm64' - the load SUCCEEDED but the dependency resolved from the HOST's own directory, not the staged one. Chaquopy's current bare-name order (crypto_chaquopy, ssl_chaquopy, sqlite3_chaquopy, python3.10, chaquopy_java) loads crypto_chaquopy before crypto_python, ssl_chaquopy before ssl_python, sqlite3_chaquopy before sqlite3_python, and ssl_python before crypto_python, so it only works because the host nativeLibraryDir is on the linker search path - and a real module does not ship these libraries at all. Hence the module manifest must declare a topologically sorted loadOrder, matching how the mpv module already orders its nine libraries. The assertion is deliberately about the mapping ORIGIN rather than about load success, because the host ships its own copy of all eight libraries and its directory IS on the search path, so success alone would be a false positive that does not transfer to a module. Also verified and recorded a second constraint affecting the degradation design: BaseLoader's Loader.INSTANCE is a static initializer that eagerly runs new BaseLoader() -> new PyLoader() -> new Loader() -> Python.start(...) with no try/catch (BaseLoader.java:35,165), so a missing Python module would throw during config loading instead of degrading gracefully

Task-Guard: PLG1-S12
2026-10-19 13:45:52 +08:00
Silent1566
cd8f597803 test(runtime): 模块资产读取机制对比,实施可避开隐藏 API(PLG1 S11)
确定宿主读取模块资产的实施用机制。S9 证明了 AssetManager.addAssetPath 可行,
但它是隐藏 API,且会不可逆地改动进程级 AssetManager。本阶段比较三种机制,
为 Python 模块化实施选定设计。

结论:实施走公开 API,不需要隐藏 API。

RuntimeModuleAssetAccessDeviceTest(真机 OK 5 项):
- createPackageContext(pkg).getAssets() —— 公开 API,可读模块资产
- 直接按 zip 读模块 APK —— 纯 Java,不涉 Android API,同样可读
- 两条路径字节一致,排除「读到别的东西」
- 模块 context 不暴露宿主资产,证明读的确实是模块的
- 模块缺失时抛 NameNotFoundException,不静默回退到宿主资产

设计取舍:
- 首选 createPackageContext:公开 API、无反射、无进程级副作用,
  且天然带包存在性检查
- 备选直接读 APK zip:完全不依赖 Android API,可用于契约层纯 JVM 单测
- 不再使用 addAssetPath:隐藏 API 可能被收紧,
  且其进程级不可逆副作用已在 S9 造成测试顺序陷阱

至此 Python 模块化的两个技术前提全部落实且都走公开路径——资产读取(本阶段)
与无 SONAME 依赖解析(S10)。预计收益约 21 MB。

Verification: device 192.168.50.3:5555 instrumented run of RuntimeModuleAssetAccessDeviceTest -> OK (5 tests) comparing the three mechanisms for reading an installed module's assets, with the IJK module as subject because it ships assets/runtime.json which the host does not have: (1) createPackageContext(MODULE_PACKAGE).getAssets().open('runtime.json') returns non-empty content, so the PUBLIC API works and the implementation does not need the hidden AssetManager.addAssetPath that S9 used for feasibility; (2) reading the module APK as a zip via ZipFile.getEntry('assets/runtime.json') also returns non-empty content, giving a plain-Java fallback with no Android API at all; (3) bothMechanismsAgreeOnTheContent asserts the two paths yield identical bytes, ruling out either one silently reading something else; (4) moduleContextDoesNotExposeHostAssets confirms the module context cannot open the host's own lab_template.json, so the reads above really are the module's assets; (5) missingModulePackageIsReported confirms createPackageContext throws NameNotFoundException for an absent package rather than silently falling back to host assets. Design consequence recorded: prefer createPackageContext (public API, no reflection, no process-wide side effect, and a built-in existence check), keep direct zip reading as the contract-layer/JVM-testable fallback, and stop using addAssetPath because it is hidden API whose irreversible process-wide mutation already caused a test-ordering trap in S9

Task-Guard: PLG1-S11
2026-10-19 13:30:22 +08:00
Silent1566
cb36506840 test(runtime): 无 SONAME 依赖实验,Python 模块化可行(PLG1 S10)
验证「预加载无 DT_SONAME 的库能否满足后续按名依赖」——这决定 Python 运行时
(Chaquopy,约 21 MB)能否模块化。

为什么必须先做这个实验:libpython3.10.so 没有 DT_SONAME(llvm-readelf -d 确认),
而 13 个库按裸名依赖它(libchaquopy_java.so + 12 个 bootstrap-native 扩展模块)。
bionic 源码明确警告:basename 回退以 targetSdk < 23 为条件,之后「无 DT_SONAME 的库
不再能被另一个库通过 DT_NEEDED 找到」。本设备 API 28、targetSdk >= 23,
故按源码推断应当失败。

结论:实测通过——预加载确实满足后续按名依赖,Python 模块化可行。

RuntimeNoSonameDependencyDeviceTest(真机 OK 2 项):
- 对照:libcrypto_python.so(有 SONAME),预加载后按绝对路径加载依赖方,复用预加载副本
- 结论:libpython3.10.so(无 SONAME)同样复用预加载副本,且未出现来自宿主目录的第二份映射

设计使结论可归因且贴合真实布局:
1. 两库分别放在不同子目录,都不在 linker 搜索路径上——真实场景正是如此
   (扩展模块解压到 filesDir/chaquopy/bootstrap-native/<abi>/,解释器在模块目录)
2. 断言被依赖库只有一个映射来源;宿主自己也带 libpython3.10.so,
   若出现第二个来自宿主目录的映射,说明名字是经搜索路径解析的,结论就不成立
3. 有 SONAME 的一对作为对照,使失败可归因于 SONAME 缺失而非绝对路径加载本身

两次自我纠错(记录以免重犯):
- 首版判别测试报 origins=[],看似「库未映射」,实际是 origins=[""]——
  对 /proc/self/maps 的字段切分有误,空字符串列表被打印成 []
- 修正后出现 /data/user/0 与 /data/data 比较失败:同一目录的符号链接别名,
  改用 getCanonicalPath 比较

本次只验证可行性,未实施。预计收益约 21 MB。

Verification: device 192.168.50.3:5555 instrumented run of RuntimeNoSonameDependencyDeviceTest -> OK (2 tests): control preloadedLibraryWithSonameSatisfiesDependency (libcrypto_python.so has DT_SONAME; after preloading it by absolute path and then loading libssl_python.so from a DIFFERENT files-dir subdirectory, the dependency resolves to the preloaded copy and is mapped from exactly one directory) and finding preloadedLibraryWithoutSonameSatisfiesDependency (libpython3.10.so has NO DT_SONAME per llvm-readelf -d, and 13 libraries depend on it by bare name; the same experiment passes, so the preloaded copy is reused and no second mapping from the host nativeLibraryDir appears). Design makes the result attributable and faithful to the real layout: the two libraries are staged in separate directories, neither on the linker search path, mirroring a module where the interpreter lives in the module nativeLibraryDir while extension modules are extracted to filesDir/chaquopy/bootstrap-native/<abi>/; the assertion requires exactly one mapping origin so a search-path resolution via the host's own copy would be caught; and the soname-carrying pair is the control. This contradicts the inference from bionic's own comment that libraries without DT_SONAME can no longer be found by DT_NEEDED from another library once the basename fallback is gated on targetSdk < 23 - measurement wins over that inference. Two self-corrections recorded: an earlier probe reported origins=[] which was actually [""] from wrong /proc/self/maps field splitting, and /data/user/0 vs /data/data symlink aliasing required getCanonicalPath comparison

Task-Guard: PLG1-S10
2026-10-19 13:24:23 +08:00
Silent1566
e6f52b88e7 test(runtime): 资产重定向实验,Python 模块化可行性(PLG1 S9)
验证 AssetManager.addAssetPath 能否让模块资产对宿主可见——这决定 Python
运行时(Chaquopy,20.9 MB,剩余最大可拆项)能否模块化。

结论:资产可以重定向,且 Python 具备模块化的结构条件。

RuntimeAssetPathDeviceTest(真机 OK 3 项):
- 探针有效(宿主自己的资产可读)
- 模块资产默认不可见(单独运行时)
- 决定性:addAssetPath 后模块的 assets/runtime.json 可被宿主读取;
  宿主没有该资产,故成功不可能来自宿主副本

为什么 Python 与 thunder 结论不同(关键区别):
thunder/jianpian 的资产定位与原生库加载都在厂商 AAR 二进制里,不可改;
而 chaquo/Platform.java 是仓库内源码,同时控制 getPath()/extractAssets()
(资产)与 loadNativeLibs()(原生库)。因此 Python 模块化不需要 S7 那条走不通的
「预加载满足裸名查找」路径,可直接改成从模块目录加载。

测试顺序陷阱:addAssetPath 是进程级且不可撤销,同进程内先跑的决定性测试会把
模块 APK 加进去,使「默认不可见」基线失效。首轮因此报 1 个失败,单独运行则通过。
已改为 assumeTrue 跳过而非假失败,并在类注释写明本类需单独运行。

本次只验证可行性,未实施。预计收益约 20.9 MB。

Verification: device 192.168.50.3:5555 instrumented run of RuntimeAssetPathDeviceTest -> OK (3 tests): hostAssetsAreReadable (probe works), moduleAssetsAreNotVisibleByDefault (passes when run alone: addAssetPath is process-wide and irreversible, so a sibling test that adds the module APK first invalidates this baseline - now skipped via assumeTrue instead of reported as a false failure), and addAssetPathMakesModuleAssetsReadable (decisive: after AssetManager.addAssetPath(moduleApk) the module's assets/runtime.json becomes readable through the host AssetManager, and the host does not ship that asset so the success cannot come from a host copy); structural review of chaquo/src/main/java/com/fongmi/chaquo/Platform.java shows it is repository source controlling BOTH getPath()/extractAssets() (assets) and loadNativeLibs() (native libraries), which is what makes Python modularisable where thunder/jianpian are not

Task-Guard: PLG1-S9
2026-10-19 11:59:26 +08:00
Silent1566
1a68bc7285 docs(runtime): 修正体积口径说明(PLG1 S8)
核实并记录此前所有体积数字的口径——它们都基于 debug 构建。

发现 app/src/debug/assets/exo-ass/ 含 9.3 MB 测试样本
(sample-fonts.mkv 7.3 MB + sample-embedded.mkv 2.5 MB),只在 debug 源集,
不进正式包。main/leanback/mobile 三个源集都没有 exo-ass 目录。

口径说明:
- 我报告的所有体积都是 debug 构建(本仓库约定不打正式包)
- 相对收益依然有效——同一构建类型的前后对比
- 绝对数字是 debug 数字,比正式包大约 9.3 MB
- 当前 debug APK 115.3 MB;推算正式包约 106.0 MB

为什么值得单独记录:若不说明,读者会把 115.3 MB 当成正式包体积并据此
判断收益。相对值不受影响,但绝对值必须以 debug 为前提来读。

Verification: source-set inspection: app/src/debug/assets/exo-ass contains 9,746,246 B of test fixtures (sample-fonts.mkv 7,277,511 + sample-embedded.mkv 2,464,983) while app/src/main/assets, app/src/leanback/assets and app/src/mobile/assets contain no exo-ass directory at all, so those fixtures never ship; zip entry accounting confirms all assets/exo-ass entries total 9,746,246 B in the debug APK (120,872,956 B total), so the shipping size is roughly 111,126,710 B (106.0 MB); relative savings reported earlier (-44.6 / -9.9 / -22.5 MB) remain valid because each compares the same build type before and after

Task-Guard: PLG1-S8
2026-10-19 11:52:46 +08:00
Silent1566
d56de2fb58 test(runtime): 预加载去重实验,划定模块化边界(PLG1 S7)
验证「按绝对路径预加载能否满足后续裸名查找」——这决定协议提取器
(thunder/jianpian/forcetech,共 15.5 MB)能否模块化。

这些库由第三方 AAR 的二进制代码加载(XLLoader 调
System.loadLibrary("xl_thunder_sdk")),不像 IJK 那样是仓库内源码、可注入
resolver。宿主唯一手段是预加载模块副本,指望后续裸名请求复用它。

结论:不行。System.loadLibrary 抛 UnsatisfiedLinkError,即使该库已通过绝对
路径加载且已在 /proc/self/maps 中可见。原因是 Android 在进入 dlopen 之前先由
class loader 的 nativeLibraryDirectories 把裸名解析成路径;模块目录不在其中,
失败发生在名字解析阶段,与库是否已映射无关。

三项测试构成可归因的证据链:
- 对照:宿主目录内的 libc++_shared.so 裸名加载正常(机制没坏)
- 发现:模块库预加载后裸名查找仍失败(归因于位置)
- 反差:宿主自己控制调用时能加载模块库(RuntimeIjk 的做法)

没有第一个对照,第二个结论可能被误读成「裸名机制坏了」。

架构影响:可模块化的是宿主自己控制加载的库(mpv/sherpa/ijk,IJK 可行是因为
其 loader 是仓库内源码);thunder/jianpian/forcetech 是二进制 AAR,不改厂商
代码就无法模块化。故这 15.5 MB 必须留在 APK——这是架构边界,不是待办事项。

真机 OK (3 tests)。

Verification: device 192.168.50.3:5555 instrumented run of RuntimePreloadDedupDeviceTest -> OK (3 tests): (1) control - a library inside the host's own nativeLibraryDir loads by bare name, so the mechanism itself works; (2) finding - a module library preloaded by absolute path is STILL invisible to a later System.loadLibrary(bare) call, which throws UnsatisfiedLinkError while the preloaded copy remains mapped in /proc/self/maps, proving the failure is at name resolution (class-loader nativeLibraryDirectories) rather than at loading; (3) contrast - the host can load a module library when it controls the call, which is what RuntimeIjk does

Task-Guard: PLG1-S7
2026-10-19 11:38:10 +08:00
Silent1566
f9845ec4c5 test(runtime): 内核过滤的真机验证(PLG1 S6-b)
S6 只做了单测与编译验证,这里补设备级证据。

RuntimeNoModuleDeviceTest 新增 2 项(零模块状态):
- 菜单只列可用内核,数量与 availableKernels 一致;不含 MPV/IJK;每行标签非空;
  可用内核恰为 {EXO, SYSTEM}(两个内置引擎仍被提供,应用不会不可用)
- availableKernelAt(1) 为系统内核而非 IJK——过滤后菜单下标与 KERNEL_ORDER
  位次不再对应,映射写错会让零模块用户选到启动不了的引擎

真机 OK (10 tests)。

又一次踩到同一个坑:S6 改了宿主代码,必须重建并重装宿主(仪器化测试的类
跑在宿主进程内)。这次在跑之前就重建并确认 APK 内含 availableKernels,未再浪费一轮。

Verification: device 192.168.50.3:5555 in the fresh-install state (all three module packages uninstalled): RuntimeNoModuleDeviceTest -> OK (10 tests), including the 2 new kernel-picker assertions - the menu lists exactly the usable kernels (2: EXO and SYSTEM), contains no MPV/IJK row, every row has a non-blank label, and availableKernelAt(1) resolves to the system engine rather than IJK (proving menu indices go through the available list, not the KERNEL_ORDER rank table); host APK was rebuilt and reinstalled before the run (confirmed it contains availableKernels) since instrumented tests execute inside the host process

Task-Guard: PLG1-S6b
2026-10-19 11:13:47 +08:00
Silent1566
478e3adc43 fix(player): 内核选择器按可用性过滤(PLG1 S6)
PlayerKernelDialog.kernels() 无条件返回 KERNEL_ORDER 全量标签,不看可用性。
模块化之前四个内核都在所以无碍,但移除 bundled 库后,零模块时它仍提供 MPV
与 IJK——用户选中只会得到 resolveManualPlayer 的失败提示。

- PlayerSetting:新增 availableKernels / availableKernelIndex / availableKernelAt
- PlayerKernelDialog:kernels() 过滤;notifySelected 改用 availableKernelAt
- TmdbDetailActivity 与 mobile/VideoActivity:菜单下标回推同步改用 availableKernelAt

关键陷阱:过滤后菜单下标与 KERNEL_ORDER 位次不再对应。任何从菜单下标反推
内核的地方都必须改用 availableKernelAt,否则零模块时选第 2 项会拿到 IJK
(启动不了的引擎)而不是系统内核。三处调用点全部修正并补测试钉住。

顺带修复 S0-f 引入的真实缺陷:LeanbackForegroundContrastTest 点名
dialog_runtime_modules.xml——它用 shape_config_history_dialog(白天浅色卡片)
却配 ?attr/webhtvColorOnWallpaper(浅色前景),白天模式白字白底。已对齐
同背景的 dialog_history.xml 所用 @color/config_history_text。

验证:两 flavor 编译通过;PlayerSettingTest 新增 4 项;对比度测试修复后通过。
7 个预先存在的失败已逐一取证(5 个 stash 后仍失败,2 个断言目标在 HEAD 即不存在),
不属本任务范围,按 AGENTS.md 只报告不修。

Verification: bash ./gradlew :app:compileMobileArm64_v8aDebugJavaWithJavac :app:compileLeanbackArm64_v8aDebugJavaWithJavac -> BUILD SUCCESSFUL (both flavors); bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.setting.PlayerSettingTest -> BUILD SUCCESSFUL with 4 new tests (availableKernels lists only usable kernels, availableKernelAt maps menu index through the available list, availableKernelIndex falls back when the selected kernel is gone, availableKernels is a subset of KERNEL_ORDER); --tests com.fongmi.android.tv.ui.activity.PlayerControlFocusIntegrationTest -> BUILD SUCCESSFUL after updating 2 source assertions to availableKernelAt; --tests com.fongmi.android.tv.theme.LeanbackForegroundContrastTest -> BUILD SUCCESSFUL (fixed the S0-f white-on-white defect); the 7 remaining suite failures were each proven pre-existing: 5 still failed with my changes stashed, and 2 assert targets that are absent at HEAD (leanback setTmdbRematchVisible 0 hits; eclipse.html is CRLF while the test expects LF)

Task-Guard: PLG1-S6
2026-10-19 10:40:38 +08:00
Silent1566
ed8a7921d6 test(runtime): 零模块状态验证(PLG1 S5)
验证每个新用户都会遇到的初始状态——一个模块都没装。

这是现场最常见的配置,也最容易漏测:开发模块的人手上总是装着模块。它尤其
关键,因为 bundled 库已全部移除——若可用性判定或加载路径假设了模块存在,
全新安装会崩溃或提供一个无法启动的内核。

RuntimeNoModuleDeviceTest(8 项)断言负空间:
- MPV 与 IJK 均报告不可用,isPlayerAvailable 为 false(内核选择器不会提供
  无法启动的引擎,回退链也不会选中)
- EXO 与系统内核仍可用(应用整体不会因缺模块而不可用)
- 模块探测不抛异常;MPVLib.isBundled 为 false(确认 bundled 库确已移除)
- IJK resolver 拒绝解析,回退 System.loadLibrary
- MPV 准备干净失败、不半加载
- 宿主不谎称缺模块已安装;不存在的模块目录不被当作已安装
- 每个已知模块 id 都有可信包名(侧载回退路径有据可依)

测试用 assumeTrue 守卫:模块已装时跳过而非失败,跳过会明确显示,不伪装成通过。

真机 OK (8 tests)。累计真机验证 49 项。

Verification: device 192.168.50.3:5555 in the FRESH-INSTALL state (all three module packages uninstalled, registry cleared): RuntimeNoModuleDeviceTest -> OK (8 tests) - MPV and IJK both report unavailable, isPlayerAvailable(MPV/IJK) false, EXO and SYSTEM remain available, module probes report absence without throwing, MPVLib.isBundled false (confirming the bundled libraries are gone), the IJK resolver declines so IjkLibLoader falls back to System.loadLibrary, MPV preparation fails cleanly without half-loading, the host does not claim absent modules are installed, every known module id has a trusted package name, and a non-existent module directory is not mistaken for an install

Task-Guard: PLG1-S5
2026-10-19 09:47:39 +08:00
Silent1566
e6106bec88 feat(build): 移除宿主 sherpa 库并修复侧载模块不可加载(PLG1 S4)
移除宿主打包的 sherpa 库(-22.5 MB),并修复一个移除后才会暴露的真实缺陷。

缺陷:可用性查平台,但加载查注册表。
S2-d 把可用性改为查平台(侧载模块显示可用),但 RuntimeLoader.verify 仍只认
宿主安装注册表。后果是最糟的组合:侧载模块显示可用,实际加载却判「未安装」
而失败。移除 bundled 库前这条路径被 assets 回退掩盖,移除后成为唯一回退。
真机立刻复现:设备上 sherpa 已侧载,注册表却只有 mpv。

修复:注册表无记录时回退到「已安装模块」校验。
- RuntimeCompatibility:新增宿主侧 模块id→包名 可信表(与契约白名单同类)
- RuntimeVerifier.verifyInstalled:不比对 catalog,但仍强制清单结构、ABI、
  无路径穿越、契约白名单、逐文件摘要、宿主库摘要
- RuntimeLoader:无记录时用可信表解析包名并走 verifyInstalled

安全边界:来源可信性由平台保证(APK 签名在安装时已由系统校验);
注册表管元数据,平台管存在性。

- app/build.gradle:默认排除 libsherpa-onnx-jni.so;-PsherpaBundle=true 回滚
- 实测:默认 119,700,946 B (114.2 MB) vs 含 sherpa 143,310,371 B (136.7 MB)

修正 6 个测试预期(语义变更所致,非实现缺陷):原先用「空注册表 + 真实
libDir」建模未安装,新语义下这正确表示侧载已安装;准确的未安装模型是
libDir == null。并为每个模块补了「侧载模块可用」的正向用例。

一处测试自身错误:RuntimeMpvDeviceTest 首轮仍失败,因为桩 PackageInfo 传给了
prepare 而非 loader——验证用的是 loader 里的探测。桩必须注入到真正做判定的那一层。

验证:单测 226 项全绿;真机 41 项全绿(含 sherpa 侧载加载)。

Verification: bash ./gradlew :app:assembleMobileArm64_v8aDebug (default, full rebuild after deleting app/build/outputs/apk/mobileArm64_v8a) -> BUILD SUCCESSFUL, APK = 119,700,946 B (114.2 MB) with sherpa entries = 0 and zip testzip() OK; bash ./gradlew :app:assembleMobileArm64_v8aDebug -PsherpaBundle=true -> BUILD SUCCESSFUL, APK = 143,310,371 B (136.7 MB) with sherpa entries = 1 and testzip() OK; measured reduction = 23,609,425 B (22.5 MB); bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests runtime.* --tests subtitle.* -> BUILD SUCCESSFUL, 226 tests / 0 failures (runtime 156 / subtitle 70); DEVICE 192.168.50.3:5555 with the sherpa-free APK installed and a SIDELOADED sherpa module (no registry record): 41 instrumented tests all green - RuntimeModuleDeviceTest OK(10), RuntimeMpvDeviceTest OK(9), RuntimeMpvLoadDeviceTest OK(3), RuntimeMpvFunctionalDeviceTest OK(3), RuntimeMpvPlaybackDeviceTest OK(3), RuntimeIjkDeviceTest OK(6), RuntimeRepositoryDeviceTest OK(4), RuntimeSherpaLoadDeviceTest OK(3)

Task-Guard: PLG1-S4
2026-10-19 01:28:47 +08:00
Silent1566
96191272de feat(build): 默认不再打包 ijk 库,APK 减少 9.9 MB(PLG1 S3-b)
IJK 的库在共享 jniLibs 源集里(与 MPV 的 flavor assets 不同),所以从打包
阶段排除而非资源阶段。

- app/build.gradle:默认排除 libijkffmpeg/libijksdl/libijkplayer;
  -PijkBundle=true 恢复原行为作为回滚路径
- 实测:默认 143,310,371 B (136.7 MB) vs 含 ijk 153,648,897 B (146.5 MB),
  减少 10,338,526 B (9.9 MB)

同样用 add() 而非 +=:AGP 9.x 上是只读 ListProperty,get-then-set 会报
Cannot set the value of read-only property(S2-b 已踩过)。

决定性验证:设备装上不含 ijk 库的 APK(条目数 0)后,RuntimeIjkDeviceTest
仍 OK (6 tests)——三库真实加载且映射自模块目录(/proc/self/maps 取证)。
证明移除宿主副本安全:模块路径不依赖它。

验证:单测 222 项全绿;两种开关 APK 均构建成功且完整性 OK。

Verification: bash ./gradlew :app:assembleMobileArm64_v8aDebug (default, full rebuild after deleting app/build/outputs/apk/mobileArm64_v8a) -> BUILD SUCCESSFUL, APK = 143,310,371 B (136.7 MB) with libijk* entries = 0 and zip testzip() OK; bash ./gradlew :app:assembleMobileArm64_v8aDebug -PijkBundle=true -> BUILD SUCCESSFUL, APK = 153,648,897 B (146.5 MB) with libijk* entries = 3 and testzip() OK; measured reduction = 10,338,526 B (9.9 MB); DEVICE 192.168.50.3:5555 with the ijk-free APK installed (unzip -l confirms 0 libijk entries): RuntimeIjkDeviceTest -> OK (6 tests), proving the three libraries still load and map from com.silent.runtime.ijk via /proc/self/maps without any host copy; bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests runtime.* --tests subtitle.* -> BUILD SUCCESSFUL, 222 tests / 0 failures

Task-Guard: PLG1-S3b
2026-10-19 00:42:12 +08:00
Silent1566
4f152b2f0b feat(runtime): IJK 内核模块化(PLG1 S3)
IJK 与 MPV 的加载方式根本不同:IjkMediaPlayer 用
System.loadLibrary("ijkffmpeg") 按裸名搜索,只查宿主自己的
nativeLibraryDir,模块里的库对它完全不可见;且 LOADER 是
private static final,无法从外部替换。

方案:给 IjkLibLoader 加宿主可注入的 Resolver。loadLibrary 先问 resolver,
返回文件就用绝对路径加载,返回 null 才回退 System.loadLibrary——既让模块库
能被找到,又保留「仍打包库」时的原行为。

- IjkLibLoader:新增 Resolver 注入点与 loadLibraryOrResolved
- IjkMediaPlayer.AnonymousClass1.loadLibrary:改走解析路径
- RuntimeIjk:安装 resolver、可用性判定、裸名匹配
- RuntimeCompatibility:登记 ijk-jni-v1
- PlayerSetting.isIjkAvailable:接模块状态(原为硬编码 true)
- App.onCreate:启动时安装 resolver

readelf 实测三库只依赖系统库与彼此(libijkffmpeg <- libijksdl <-
libijkplayer),不依赖 libc++_shared.so,故 hostLibraries 为空。

resolver 每次查找都重新检查模块而非捕获快照,使「先装模块」与「先启动播放」
的顺序无关紧要;代价可接受,因为 IjkLibLoader 自身缓存加载尝试。

验证:单测 152 项全绿;真机 OK (6 tests),含 /proc/self/maps 取证库映射自模块。

Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests "com.fongmi.android.tv.runtime.*" -> BUILD SUCCESSFUL, 152 tests / 0 failures (RuntimeIjkTest 10 new); device 192.168.50.3:5555 instrumented run of RuntimeIjkDeviceTest -> OK (6 tests): module exposes all 3 libraries, bare names resolve to the module's files, the three libraries REALLY load and map from com.silent.runtime.ijk via /proc/self/maps, availability true with the module installed, resolver independent of install order, absent module falls back without throwing; plugin repo pushed at a00fb34 with ijk artifacts uploaded to release tag modules (6 artifacts total)

Task-Guard: PLG1-S3
2026-10-19 00:36:17 +08:00
Silent1566
f079372cad fix(runtime): 修复模块管理页的 3 个崩溃/阻塞缺陷并跑通端到端安装(PLG1 S0-h)
真机联调暴露 4 个缺陷,全部修复:

1. 点击安装即崩溃:?attr/webhtvColorPrimary 只在 webhtv_attrs.xml 声明,
   全仓没有任何主题给它赋值,inflate 抛 UnsupportedOperationException。
   改用 ?attr/colorPrimary(mobile 布局既有先例)。两个 flavor 都会崩。

2. 点击安装即崩溃:StringBuilder.isEmpty() 是 Java 15 API,设备上不存在,
   抛 NoSuchMethodError。这是我自己引入的——早前把 length()==0 顺手清理成
   isEmpty(),把可用代码改成了崩溃。注意静态检查器 prefer-string-is-empty
   正是这样建议的,属会致崩的误报,不能采纳。

3. 安装阻塞 UI 线程:installer.install 同步下载 43 MB 且在点击回调线程执行,
   界面冻结、无进度、无日志。改为 Task.execute 派发到后台。

4. 关键路径无可观测性:原本完全没有日志;且 SpiderDebug.log 会经
   DebugLogStore.acceptsTag 过滤,默认配置下静默丢弃,不能用作诊断通道。
   改用 android.util.Log。

排查教训:长时间用 input tap 模拟点击始终无响应,实际这是 leanback(TV) 应用,
交互靠遥控器焦点+确定键。改用 KEYCODE_DPAD_CENTER 后流程立即走通。
TV 端 UI 验证必须用 DPAD 事件。

端到端已跑通:下载 -> 校验完整性 -> 安装模块 -> 系统确认框 -> 已安装。
验证:单测 212 项全绿。

Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests "com.fongmi.android.tv.runtime.*" --tests "com.fongmi.android.tv.subtitle.*" -> BUILD SUCCESSFUL, 212 tests / 0 failures; device 192.168.50.3:5555 END-TO-END install via the module manager UI using DPAD events: log shows 'install requested id=mpv catalog=true module=true' -> 'progress msg=下载 1.0.0 ...' -> 'progress msg=校验完整性 ...' -> 'progress msg=安装模块 ...' -> system installer confirmation for com.silent.runtime.mpv (43 MB) -> 'installed id=mpv version=1.0.0' + Toast; list refreshed to 已安装/卸载 and 'pm list packages' shows com.silent.runtime.mpv

Task-Guard: PLG1-S0H
2026-10-19 00:04:34 +08:00
Silent1566
11585e8b58 fix(runtime): 可用性改查平台,修复侧载模块不可用(PLG1 S2-d)
S2-b 移除 assets 后暴露的真实缺陷:isMpvAvailable 原先查宿主自己的安装注册表,
而注册表只由应用内安装流程写入。通过其他途径安装的模块(侧载、文件管理器、
adb install)在设备上真实可用,宿主却认为不可用,MPV 内核直接不显示。

移除 assets 之前该缺陷被掩盖——assets 回退总让判定为真。

修复:可用性改问平台(nativeLibraryDir != null)。注册表仍负责版本与摘要
元数据,但不再是「什么存在」的事实来源。

- RuntimeMpv:新增 MODULE_PACKAGE 与 isModuleInstalled(PackageInfo)
- PlayerSetting.isMpvAvailable:改用平台探测

同时完成 S2-b 设备回归:设备已装不含 assets/mpv-libs 的新 APK(条目数 0),
模块路径的真机测试全部通过——9 库 dlopen、映射来源正确、H.264 解码出首帧。
证明移除 assets 安全。

验证:单测 212 项全绿;真机 9 项全绿。

Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests "com.fongmi.android.tv.runtime.*" --tests "com.fongmi.android.tv.subtitle.*" -> BUILD SUCCESSFUL, 212 tests / 0 failures (RuntimeMpvTest 20 incl. 3 new platform-probe regressions, subtitle 70 no regression); device 192.168.50.3:5555 with the assets-removed APK installed (unzip -l confirms mpv-libs entries = 0): RuntimeMpvFunctionalDeviceTest OK (3), RuntimeMpvLoadDeviceTest OK (3), RuntimeMpvPlaybackDeviceTest OK (3) - proving the S2-b asset removal is safe and that a sideloaded module now makes MPV available

Task-Guard: PLG1-S2d
2026-10-18 17:53:49 +08:00
Silent1566
2fa2696f46 feat(build): 默认不再打包 mpv assets,APK 减少 44.6 MB(PLG1 S2-b)
模块化前置已全部满足,现在兑现体积收益。

- app/build.gradle:默认排除 assets/mpv-libs;-PmpvBundle=true 恢复原行为
  作为回滚路径
- 实测:默认 153,648,893 B (146.5 MB) vs 含 mpv 200,387,121 B (191.1 MB),
  减少 46,738,228 B (44.6 MB)

踩到的两个坑:

1. ignoreAssetsPatterns 是只读 ListProperty,Groovy 的 += 会走 get-then-set
   并报 Cannot set the value of read-only property。必须用 .add() 原地修改。
   先试的 sourceSets.assets.filter.exclude 完全无效(APK 条目数不变)。

2. 体积一度看起来没变,是 Gradle 增量打包的假象:移除后 APK 仍报 194.5 MB,
   且 mpv-libs 条目已为 0。逐字节分析发现文件里有 45.5 MB 空洞
   (sum(compress_size) 仅占 74.9%,含 mpv 版占 97.8%),空洞起始处是
   PK\x03\x04 本地文件头签名,即被删除条目的残留旧数据。
   必须删除 app/build/outputs/apk/<variant> 后全量重打才能看到真实体积。
   只看增量结果会得出「移除 assets 没有收益」的错误结论。

验证:两种开关均构建成功且完整性 OK;默认 mpv-libs=0、回滚=10;
宿主单测 209 项全绿。

Verification: bash ./gradlew :app:assembleMobileArm64_v8aDebug (default) -> BUILD SUCCESSFUL; APK = 153,648,893 B (146.5 MB) with mpv-libs entries = 0 and zip testzip() OK; bash ./gradlew :app:assembleMobileArm64_v8aDebug -PmpvBundle=true -> BUILD SUCCESSFUL; APK = 200,387,121 B (191.1 MB) with mpv-libs entries = 10 and testzip() OK; measured reduction = 46,738,228 B (44.6 MB); both builds were full rebuilds after deleting app/build/outputs/apk/mobileArm64_v8a (incremental packaging left a 45.5MB hole of stale data and made the size look unchanged); bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests runtime.* --tests subtitle.* -> BUILD SUCCESSFUL, 209 tests / 0 failures

Task-Guard: PLG1-S2b
2026-10-18 16:55:47 +08:00
Silent1566
448b1aeb32 test(runtime): 真实播放回归(PLG1 S2-c)
S2-b-前置证明核心能初始化并读属性,但不经过解复用与解码。一个 bundle 可以
核心正常、属性可读,却在真正解媒体时失败。移除 assets 前必须覆盖这条路径。

RuntimeMpvPlaybackDeviceTest 加载真实媒体并等到 FILE_LOADED,再用 mpv 自身
日志确认整条链走通:解复用 -> H.264 解码 -> 滤镜链 -> 首帧输出。
H.264 解码器位于模块内的 libmvcodec.so,因此该证据直接覆盖模块最核心能力。

真机 OK (3 tests)。

测试方法上的关键修正:首轮 FILE_LOADED 超时,但不是模块缺陷——mpv 日志显示
'[vo/gpu/android:error] Missing surface pointer',所有真实 vo 后端都需要
Android Surface,而附加 Surface 是播放器的职责。改用 vo=null/ao=null 隔离
'解复用+解码' 与 '输出面' 两件事后通过。不做日志诊断很容易误判为模块缺陷。

Verification: device 192.168.50.3:5555 instrumented run of RuntimeMpvPlaybackDeviceTest -> OK (3 tests): loads a real media file and reaches FILE_LOADED, decodes positive width/height and a readable duration, accepts the engine's core options (hwdec/vo/video-sync/framedrop), and accepts playback commands; mpv's own log confirms the full chain ran from the module - '[demux:v] Trying demuxers', '[vd:v] Decoder format: 1080x1920 yuv420p ... CL=mpeg2/4/h264', '[vf:v] [in] 1080x1920 yuv420p bt.709', '[cplayer] first video frame after restart shown', 'playback restart complete ... video=playing'; host unit suite unchanged at 209 tests / 0 failures

Task-Guard: PLG1-S2c
2026-10-18 15:10:59 +08:00
Silent1566
420d15f7e1 test(runtime): 验证 mpv 模块功能可用(PLG1 S2-b 前置)
dlopen 成功不等于功能可用:库能加载仍可能在首次调用时失败。移除 assets
之前必须证明模块能真正跑起来,否则移除会直接破坏所有人的 MPV。

RuntimeMpvFunctionalDeviceTest:
- 用模块初始化真实 mpv 核心(tryCreate + init)
- 读取 mpv-version / ffmpeg-version —— 这两个属性只在核心运行后才可读,
  因此非空即为整条链功能可用的证据;ffmpeg 在模块内是独立库,一并覆盖编解码栈
- 可用性判定在模块已装时报告 MPV 可用
- isBundled 与实际打包资产一致

真机 OK (3 tests)。

过程中遇到的真实陷阱:首轮报 NoClassDefFoundError: RuntimeStore —— 测试 APK 是新的
但设备上宿主 APK 是旧的。仪器化测试的类跑在宿主进程内,因此改了宿主代码后必须
重装宿主,只装测试 APK 不够。

Verification: device 192.168.50.3:5555 instrumented run of RuntimeMpvFunctionalDeviceTest -> OK (3 tests), 1.78s: MPVLib.tryCreate()+init() initializes a working mpv core from the installed module, getPropertyString(mpv-version) and (ffmpeg-version) both return non-empty values (proving the JNI surface and codec stack resolve), PlayerSetting.isMpvAvailable()/isPlayerAvailable(MPV) report true with the module installed, and MPVLib.isBundled agrees with the APK's actual bundled assets; host unit suite unchanged at 209 tests / 0 failures

Task-Guard: PLG1-S2b-pre
2026-10-18 14:07:45 +08:00
Silent1566
6100e3746e feat(runtime): 内核可用性接入模块状态(PLG1 S2-a)
移除 assets/mpv-libs 前必须先接好门禁,否则内核选择器仍会列出 MPV,
用户选中后在加载时失败。

- RuntimeMpv.isAvailable(moduleInstalled, bundled):纯函数,过渡期语义为
  「模块已装 || APK 仍内置」;移除 assets 后自动收敛为「只有装了模块才有 MPV」
- MPVLib.isBundled(Context):探测 APK 是否仍带 mpv assets
- PlayerSetting.isPlayerAvailable 接 MPV;isMpvAvailable 带缓存;
  新增 resetMpvAvailability()
- RuntimeModulesDialog:安装/卸载成功后重置缓存

关键性质:19 个既有调用点零改动。isPlayerAvailable 早已被回退链、内核选择器、
手动切换共同使用(PlayerManager x6、PlaybackService x13),接进去即全部生效。

缓存失效是真实路径而非理论问题:用户在模块管理页装完模块后立刻查看内核选择器,
若缓存不重置就看不到 MPV。

验证:209 tests 全绿;两 flavor 编译通过。
真机 UI 端到端(装后出现/卸载后消失)与播放回归留待 S0-h/S2-b。

Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests "com.fongmi.android.tv.runtime.*" --tests "com.fongmi.android.tv.subtitle.*" -> BUILD SUCCESSFUL, 209 tests / 0 failures (runtime 139 incl. 4 new availability-rule tests, subtitle 70 no regression); bash ./gradlew :app:compileMobileArm64_v8aDebugJavaWithJavac :app:compileLeanbackArm64_v8aDebugJavaWithJavac -> BUILD SUCCESSFUL (both flavors)

Task-Guard: PLG1-S2a
2026-10-18 13:44:08 +08:00
Silent1566
5a69b0cc16 test(runtime): 真实 dlopen 验证 mpv 模块并取证映射来源(PLG1 S1-b)
S1 用注入的加载器验证了次序与摘要门禁,但从未真正加载。次序对、摘要对仍可能
因 soname 冲突或符号缺失而失败——这正是 commit 2a56751af0 的失败模式:
所有校验通过、加载仍失败,表现为几乎所有片源报 MPV 加载媒体失败。

RuntimeMpvLoadDeviceTest 用 System::load 真实 dlopen 整条链,再用
/proc/self/maps 取证:
- 9 个模块库全部加载成功且映射自 com.silent.runtime.mpv,无一来自宿主
- libc++_shared.so 只有一份,且来自宿主;模块未贡献第二份
- 入口库 DT_NEEDED 图完整解析

真机 OK (3 tests)。这为 S2 移除 assets/mpv-libs 提供依据。

测试自身两个缺陷(已修):
1. String.lines() 是 Java 11 API,设备不存在(NoSuchMethodError);改为 split。
   已核实生产代码无此问题:HlsManifestCleaner/DebugLogs 的 .lines() 是自定义类型的成员方法。
2. 不能假设进程干净:原生库加载后无法卸载,同类中先跑的测试已映射过它们。
   改为只断言映射来源,该结论与谁先触发加载无关。

Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests "com.fongmi.android.tv.runtime.*" --tests "com.fongmi.android.tv.subtitle.*" -> BUILD SUCCESSFUL, 205 tests / 0 failures (runtime 135, subtitle 70); device 192.168.50.3:5555 instrumented run of RuntimeMpvLoadDeviceTest -> OK (3 tests), 2.28s, using REAL System.load and /proc/self/maps evidence: all 9 module libraries dlopen successfully and map from com.silent.runtime.mpv (none from the host), exactly one libc++_shared.so is mapped and it is the host's copy with the module contributing no second copy, and the entry library's DT_NEEDED graph fully resolves

Task-Guard: PLG1-S1B
2026-10-18 11:47:48 +08:00
Silent1566
09d4022321 feat(runtime): mpv 播放核心模块化(PLG1 S1)
把 mpv 的 9 个原生库移入模块,libc++_shared.so 留给宿主——这是本方案最关键
约束(hostLibraries)首次在真机验证。

依赖图(readelf -d 实测):libc++_shared.so 被 libmvcodec/libmpv/libplayer
共同 DT_NEEDED,故模块不得自带、宿主提供唯一一份、模块声明期望摘要。

- RuntimeMpv:先按 soname 加载宿主库,再按 manifest 顺序加载模块库
- RuntimeManifest:新增 loadOrder;isValid 要求每个待加载库都在 files 中,
  以便全部经过摘要校验
- RuntimeLoader:按 loadOrder 依次加载,而非只加载单入口
- MPVLib.ensureLoaded:优先模块 → 回退 assets;抽出 ensureLoadedFromAssets

加载次序改为 manifest 显式声明,不再依赖 DT_NEEDED 隐式解析——
显式次序可被校验,隐式不行。

修复的真实缺陷:hostLibraries 摘要必须按 ABI 区分。宿主 arm64 与 v7a 的
libc++_shared.so 是不同文件(c4c2fe5c… / af383654…),脚本最初硬编码 arm64
的值,会让 v7a 模块静默失败——能装、能过安装校验,只在加载时才被门禁拒绝。
改为 @derive 从 WebHTV 检出按 ABI 推导。

过渡期保留 assets 回退,本阶段不会让现有用户失去 mpv。

验证:205 tests 全绿;真机 OK (9 tests)。
体积尚未下降,移除 assets 是 S2 的独立步骤。

Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests "com.fongmi.android.tv.runtime.*" --tests "com.fongmi.android.tv.subtitle.*" -> BUILD SUCCESSFUL, 205 tests / 0 failures (runtime 135 incl. new RuntimeMpvTest 13, subtitle 70 no regression); device 192.168.50.3:5555 instrumented run of RuntimeMpvDeviceTest -> OK (9 tests): module exposes 9 libs and does NOT bundle libc++_shared.so, host-provided digest matches the manifest declaration (device host copy = c4c2fe5c...), verification passes against the real flat nativeLibraryDir, host libc++_shared.so loads FIRST then the 9 module libs in declared order (10 loads), entry library is a real file with readable ELF header, unknown contract rejected, absent module reported not thrown; plugin repo pushed at 1ca0ec8 with mpv artifacts (arm64 45.4MB / v7a 39.5MB) uploaded to release tag modules

Task-Guard: PLG1-S1
2026-10-18 10:59:00 +08:00
Silent1566
92980af92f fix(runtime): 移除设备上不存在的 Java 9 API 并补已发布产物端到端验证(PLG1 S0-g)
真实生产缺陷:RuntimeLoader.fileTreeSource() 用了 InputStream.readAllBytes(),
这是 Java 9 API,Android 设备运行时不存在,且 coreLibraryDesugaring 不覆盖 java.io。
单测跑在 JDK 21 上一路绿灯,生产路径恰好用了手写循环才侥幸没崩。
改为显式循环读取,并在注释写明这不是风格偏好。

注意区分:java.nio.file.Files.readAllBytes()(Java 7,脱糖覆盖)是安全的,
只有 java.io.InputStream.readAllBytes() 有问题。

新增 RuntimeRepositoryDeviceTest,对已发布产物做端到端验证(4 项):
- 设备能拉取已发布 catalog 且契约被认可
- catalog 提供本机 ABI 的产物(含 minSdk 过滤与 HTTPS 校验)
- 下载产物摘要与 catalog 一致(走真实 downloader;镜像篡改/上传截断会在此暴露)
- 产物声明与 catalog 一致(包名/契约/版本/ABI),且入口库为 STORED 并摘要匹配

用仪器化测试而非 UI 点击:模块管理页的坐标导航在模拟器上不可靠
(实测多次误入 TMDB 详情页),且端到端测试覆盖更广、可重复。

验证:真机 OK (4 tests);宿主单测 192 项全绿。

Verification: Plugin repo pushed to https://github.com/Silent1566/webhtv-plugins (branch main @ 9c41fb7, Release tag 'modules' with both ABI APKs, catalog URL returns HTTP 200); device 192.168.50.3:5555 instrumented run of RuntimeRepositoryDeviceTest -> OK (4 tests): catalog reachable+valid, artifact offered for this ABI, downloaded artifact digest matches catalog, and downloaded APK declares matching package/contract/version/abi with the entry library STORED and digest-matching; local unit suite unchanged at 192 tests / 0 failures (runtime 122, subtitle 70)

Task-Guard: PLG1-S0G
2026-10-18 10:05:46 +08:00
Silent1566
88ad9101dd feat(runtime): 新增播放模块管理页(PLG1 S0-f)
在 设置 → 增强功能 增加「播放模块」入口,与「实验室」并列,用于安装/卸载
运行时模块。安装走 RuntimeInstaller,产物摘要校验通过后才会交给系统安装器。

- RuntimeModulePolicy:列表展示规则(可装/已装/可升级/ABI 不支持/契约不支持)
  与体积格式化,纯逻辑可单测
- RuntimeModuleAdapter / RuntimeModulesDialog:模块列表与操作
- leanback + mobile 两套布局;AndroidManifest 增 REQUEST_DELETE_PACKAGES

设计决定:
- 模块展示名用本地字符串映射,不渲染远程文本:catalog 不含面向用户的文案,
  且宿主只认可白名单契约,渲染远程文本只会扩大不可信输入面
- 卸载先忘记记录再调系统卸载界面:用户若在系统页取消,宿主已停止提供该能力
  (保守方向,避免注册表指向可能被卸载的包)
- 已装但清单无匹配产物时仍显示该行,否则用户无法卸载

本轮修复的 4 个真实缺陷(编译/单测/真机暴露):
1. displaySize 单位偏移一位:1024 字节显示成 1.0 MB(循环先除后进位,起点写错)
2. 拉取失败提示被 render() 覆盖回通用文案,用户看不到失败原因
3. 适配器点击回调失效:构造期绑监听并读未设置的 getTag(),且 onRemove 传 null;
   改为 onBindViewHolder 内绑定并取 getBindingAdapterPosition()
4. App.execute 不存在(应为 Task.execute)、缺字符串资源、缺 Color import

验证:192 tests 全绿;两 flavor 编译通过;真机确认入口与对话框可用。

Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests "com.fongmi.android.tv.runtime.*" --tests "com.fongmi.android.tv.subtitle.*" -> BUILD SUCCESSFUL, 192 tests / 0 failures (runtime 122 incl. new RuntimeModulePolicyTest 11, subtitle 70 no regression); bash ./gradlew :app:compileMobileArm64_v8aDebugJavaWithJavac :app:compileLeanbackArm64_v8aDebugJavaWithJavac -> BUILD SUCCESSFUL (both flavors); device 192.168.50.3:5555 overwrite-install: app starts with no crash, Settings > Enhancements shows the new 'playback modules' entry next to Lab, tapping it opens the dialog (title/hint/empty state/refresh), refresh issues a real network request and surfaces the failure reason 'HTTP 404' (expected: webhtv-plugins not yet pushed)

Task-Guard: PLG1-S0F
2026-10-18 01:08:41 +08:00
Silent1566
1075099325 test(runtime): 端到端验证 SDK 从模块目录加载原生库(PLG1 S0-e)
这是本方案最后一个高风险未验证项:sherpa_onnx.native.path 能否真正重定向
SDK 的加载路径,纯路径断言无法证明。

RuntimeSherpaLoadDeviceTest 用 /proc/self/maps 取证 libsherpa-onnx-jni.so 的
映射来源,并断言映射路径属于模块包目录、且不属于宿主目录。

关键前提:测试 APK 不自带任何 .so(unzip -l 已确认),所以加载成功即可归因,
不可能是宿主内置副本。

真机 192.168.50.3:5555 -> OK (2 tests):
- 门禁判定 Source.MODULE 且配置路径 = 模块目录
- 设置属性后 SDK 未提前加载
- LibraryUtils.load() 后库已映射,路径含 com.silent.runtime.sherpa
- 路径不含 com.silent.android.webhtv
- 空注册表时回退 BUNDLED 且不设置属性

该测试必须单独运行:SDK 每进程只加载一次并缓存结果,同进程其他测试先触碰
sherpa 类会让结论依赖执行顺序。

首轮失败源于测试自身缺陷(configurer 只记录路径未设置系统属性),
已改为与生产接线完全一致。

Verification: bash ./gradlew :app:assembleMobileArm64_v8aDebugAndroidTest -> BUILD SUCCESSFUL; device run on 192.168.50.3:5555 via 'adb shell am instrument -w -e class com.fongmi.android.tv.runtime.RuntimeSherpaLoadDeviceTest' -> OK (2 tests): gate resolves Source.MODULE with path=<module nativeLibraryDir>, /proc/self/maps proves libsherpa-onnx-jni.so is mapped from com.silent.runtime.sherpa/.../lib/arm64 and NOT from com.silent.android.webhtv/, and with an empty registry the gate falls back to BUNDLED leaving sherpa_onnx.native.path unset; test APK ships no .so so the load is attributable to the module

Task-Guard: PLG1-S0E
2026-10-18 00:03:31 +08:00
Silent1566
bfab3d3a80 fix(runtime): 修正模块清单路径与 nativeLibraryDir 的拼接(PLG1 S0-d)
真机验证暴露的缺陷:manifest 声明 lib/arm64-v8a/x.so(相对 APK 根),
而平台给出的 nativeLibraryDir 已是扁平目录 .../lib/arm64/(目录名甚至不是
ABI 的拼写)。原有代码直接拼接,会去找
.../lib/arm64/lib/arm64-v8a/x.so 而必然失败,模块永远无法加载。

- RuntimeManifest.nativeLibraryEntry:剥离 lib/<abi>/ 前缀,兼容裸文件名
- RuntimeVerifier.verifyFiles 与 RuntimeLoader 共用该解析
- RuntimeManifest.parse:抽出共享解析,消除 RuntimeLoader 的重复实现
- 补 4 项回归测试锁定该行为

同时新增 RuntimeModuleDeviceTest(仪器化),覆盖纯 JVM 测不到的平台事实:
nativeLibraryDir 可解析、runtime.json 可从已装 APK 读取、对扁平目录校验成功、
完整 loader 路径、sherpa 门禁解析为 Source.MODULE、版本/ABI 不符被拒。

配套独立仓库 webhtv-plugins(打包脚本 + catalog + CI)已初始化于
F:/Workspace/webhtv-plugins,提交 9c41fb7,尚未推送。

验证:182 tests 全绿(runtime 112 / subtitle 70);真机 am instrument OK (10 tests)。

Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests "com.fongmi.android.tv.runtime.*" --tests "com.fongmi.android.tv.subtitle.*" -> BUILD SUCCESSFUL, 182 tests / 0 failures (runtime 112 incl. 4 new path-prefix regressions, subtitle 70 no regression); device instrumented run on 192.168.50.3:5555 via 'am instrument -w -e class com.fongmi.android.tv.runtime.RuntimeModuleDeviceTest' -> OK (10 tests), covering nativeLibraryDir resolution, flat-directory verification, full loader path, sherpa gate resolving to Source.MODULE, version/ABI rejection; catalog chain independently re-verified (size+sha256 match artifacts, manifest file sha matches real .so, compress_type=Stored); plugin repo F:/Workspace/webhtv-plugins initialized as separate repo at 9c41fb7 (not pushed)

Task-Guard: PLG1-S0D
2026-10-17 23:42:12 +08:00
Silent1566
6fc33535c4 feat(runtime): 接入 sherpa 实时字幕模块门禁(PLG1 S0-c)
在模型下载之前决定原生库来源,避免引擎不可用却先下载数十 MB 模型。

- RuntimeSherpa:决定来源(模块/内置/不可用),命中模块时把
  sherpa_onnx.native.path 指向已验证的模块目录
- RealtimeSubtitleController:prepareModel 中前移到 ensureModel 之前,
  并输出 realtime-subtitle 诊断日志

SDK 机制经字节码实测确认:LibraryUtils.load() 先走
loadFromSherpaOnnxNativePath(),读 sherpa_onnx.native.path 并 System.load
绝对路径,命中失败才回退 System.loadLibrary。因此无需自行加载、无需改 SDK。

两条硬约束(均来自实测,非推测):
1. 门禁必须先于任何 com.k2fsa.sherpa.onnx 类被触碰——LibraryLoader 的静态
   初始化块即设 autoLoadEnabled=true 并缓存结果,晚设置属性无效。故
   RuntimeSherpa 刻意不引用 SDK。
2. libsherpa-onnx-jni.so 只依赖系统库(readelf -d 实测),不依赖
   libc++_shared.so,故 hostLibraries 为空、无 soname 冲突——这也是选它
   作为首个试点的依据。

降级:模块缺失或校验失败回退内置库,只有两者皆无才阻止启动。

验证:178 tests 全绿(runtime 108 / subtitle 70)。
真机已确认构建、覆盖安装、无崩溃、库就位;模块路径需 S0-d 产出模块后验证。

Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests "com.fongmi.android.tv.runtime.*" --tests "com.fongmi.android.tv.subtitle.*" -> BUILD SUCCESSFUL, 178 tests / 0 failures / 0 skipped (runtime 108 incl. new RuntimeSherpaTest 14, subtitle 70 untouched/no regression); device: debug APK built, overwrite-installed on 192.168.50.3:5555 with matching signature, app starts with no crash and libsherpa-onnx-jni.so present in nativeLibraryDir; module path (PackageInstaller + native.path redirect) NOT device-verified because no module APK exists until S0-d

Task-Guard: PLG1-S0C
2026-10-17 19:54:48 +08:00
Silent1566
747a8539f6 feat(runtime): 新增模块仓库、安装编排与加载门禁(PLG1 S0-b)
在 S0-a 契约层之上补齐模块生命周期,全部安全关键路径保持纯 JVM 可测。

- RuntimePlatform:把下载/安装/包信息/库加载收敛为四个窄接口
- RuntimeStore:已装模块注册表 + 安装/更新/重装/降级决策(拒绝降级)
- RuntimeInstaller:下载 → 校验摘要 → 安装 → 记录;校验前绝不把 APK 交给安装器
- RuntimeLoader:System.load 前的最后一道门,校验失败即拒绝加载
- RuntimeRepository:catalog.json 拉取与解析(强制 HTTPS、大小上限、schema 门禁)
- RuntimeAndroid:平台实现,用 PackageInstaller 会话而非 ACTION_VIEW

用安装会话而非 ACTION_VIEW 的原因:ACTION_VIEW 不回传结果,宿主无法得知
用户是否确认,可能把从未安装的模块记入注册表。会话配合动态注册的接收器
(无需 manifest 条目或导出组件)才能在首个终态触发一次回调。

验证:94 tests 全绿;变异测试证明安装前摘要校验非恒真。
平台路径(安装、hostLibraries 解析、sherpa_onnx.native.path)待 S0-c 真机验证。

Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests "com.fongmi.android.tv.runtime.*" -> BUILD SUCCESSFUL, 94 tests / 0 failures / 0 skipped (RuntimeCatalogTest 13, RuntimeInstallerTest 13, RuntimeLoaderTest 15, RuntimeManifestTest 10, RuntimeRepositoryTest 15, RuntimeStoreTest 13, RuntimeVerifierTest 15); mutation check: disabling the installer artifact digest check made verifiesDigestBeforeHandingTheApkToTheInstaller FAIL, restored file then full suite green

Task-Guard: PLG1-S0B
2026-10-17 18:16:50 +08:00
Silent1566
c62708aaaf feat(runtime): 新增播放模块契约层(catalog/runtime.json 校验、hostLibraries 摘要门禁)
PLG1 S0-a:为播放核心模块化建立失败关闭的契约层。

- RuntimeIntegrity/RuntimeText/RuntimeAbi:SHA-256、字符串与 ABI 归一化
- RuntimeCatalog:catalog.json 模型、schema 门禁、按 ABI+minSdk 选产物
- RuntimeManifest:模块内 runtime.json 模型(含 hostLibraries)
- RuntimeCompatibility:宿主认可的 compatibilityId 白名单
- RuntimeVerifier:编排校验,任一不符即拒绝加载

契约层不依赖任何 Android API:本包单测在纯 JVM 运行,
启用 returnDefaultValues 会让 TextUtils.isEmpty 恒为 false 而静默出错。

hostLibraries 是 soname 冲突防线:模块不得自带 libc++_shared.so,
宿主那份摘要必须匹配,对应既有事故 2a56751af0。

验证:38 tests 全绿;变异测试证明摘要校验非恒真。
契约样本取自实测的上游 FongMi/Release catalog.json 与 runtime-mpv-1.0.2 runtime.json。

Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests "com.fongmi.android.tv.runtime.*" -> BUILD SUCCESSFUL, 38 tests / 0 failures / 0 skipped (RuntimeCatalogTest 13, RuntimeManifestTest 10, RuntimeVerifierTest 15); mutation check: disabling hostLibraries digest verification made rejectsIncompatibleHostLibraryDigest FAIL, restored file then full suite green

Task-Guard: PLG1
2026-10-17 18:00:17 +08:00
Silent1566
2375d5a291 docs(c74): 记录交付坐标(双父合并提交 1e0d3ae0a4 / recovery tag / 推送结果 / PR #452)并补记交付后复核
在唯一任务文档 docs/C74-beta-merge-review-dev1-20261016.md 补记:
- 双父合并提交 1e0d3ae0a4(第一父 2d4259ef7a / 第二父 9f4ea10a29)与
  recovery tag recovery/C74-beta-merge-review-dev1-20261016/20261016100405-1e0d3ae0a4ad;
- 推送结果与 net diff 的 6 个路径(4 个 dev1 自身路径 + 本任务文档 + 评估索引);
- 创建 PR 之后的复核:再次 fetch 确认 origin/beta 未前进(仍等于第二父)、
  PR #452 base=beta/head=dev1/state=OPEN/mergedAt=null/mergeable=MERGEABLE、
  PR 文件集与本地 diff 逐项一致、未合并。

仅文档改动,不含任何代码或构建产物变更。

Verification: PR #452 state=OPEN mergedAt=null mergeable=MERGEABLE base=beta head=dev1; 再次 fetch 后 origin/beta 仍等于合并提交第二父 9f4ea10a29(未前进); PR 文件集 6 项与 git diff --name-only origin/beta HEAD 6 项用 diff 逐项一致; 远端 refs/heads/dev1=1e0d3ae0a4 与 annotated tag 解引用后同一提交; MERGE_HEAD 已清除; 工作树干净

Task-Guard: C74-beta-merge-review-dev1-20261016-coords
2026-10-16 18:08:13 +08:00
Silent1566
1e0d3ae0a4 merge: 合并 origin/beta 最新代码(PR#450/#451)并复评修正简介区推翻沉浸音频收起修复
把 origin/beta tip 9f4ea10a29(PR #451)合入 dev1,带入 dev3 的控制栏进度条
UP 返焦与 dev2 的 py 桥接/ViewModel 生命周期共 11 个提交,0 冲突。合并结果相对
beta 的净差异恰为 dev1 自身 4 个路径,且 4 个文件的 blob 与本地未推送提交
2d4259ef7a 逐一相同,未带回任何远端已移除/回退的内容(零复活六层证据)。

复评 2d4259ef7a 发现 1 项必修:该提交自己新增了「沉浸音频/纯音频播放时简介区
必须跟着一起收」的修复,但同一提交的写入点 updateOverviewPreview() 末尾无条件
setVisibility(View.VISIBLE)。而 setDetail(Vod) 里 checkFlag()(2704 行)排在
setText()(2706 行)之前,沉浸音频续播会经
checkFlag()->onItemClick()->onRefresh()->getPlayer()->setAudioStageVisible(true)
->setVideoDetailsVisible(false) 先把整列收起,随后又被无条件点亮——正好推翻同
提交自己的修复。改为按 mAudioStageVisible 门控(该门控与「列已收起」严格等价:
全仓两个 setVideoDetailsVisible(false) 入口都以它为真),详情列的显隐仍由
setVideoDetailsVisible() 独占,写入点只负责是否「有内容」。

同时补 1 条回归锁(含「不得再有无条件 View.VISIBLE」的反向断言),并收紧第 1 轮
自己放宽的一处过宽断言(contains("View.VISIBLE") -> contains("…setVisibility("))。
4 组变异探针(setActivated(false) / setText("") / 锚点改回 director / 门控改回
无条件)全部如期转红,还原后 git hash-object 与修复版逐字节相同。

全量 leanback 回归 8 项失败经「把 4 个改动文件换成 beta 自己的版本后重跑」一一
原样复现,据此判定为既有缺陷(含 2 项工作区 CRLF 导致的跨行字面量恒假),与本次
合并无关,按范围记录不修。

Verification: 定向单测 6/6 + 13/13 + 4/4 + 3/3;全量 leanback 4271 项、失败集与
开工前基线完全一致;双 flavor Java 编译 BUILD SUCCESSFUL;Python 契约 36/36
(CPython 3.10.11 与 3.14.7);git diff --check 与冲突标记扫描通过;
gradlew.bat --no-daemon clean BUILD SUCCESSFUL。

Verification: 合并 origin/beta 9f4ea10a29(11 提交,0 冲突),净差异恰为 dev1 自身 4 路径且 blob 与 2d4259ef7a 逐一相同;零复活六层证据(beta 5074=合并 5075、缺失 0、revert 路径交集 0、行级 9 条命中经分类全为通用样板、33 条 revert 真删除路径存在 0 条、评估索引行 beta 17 零丢失+1)、检测器注入 ISSUE_TEMPLATE 自检如期报出;8 项全量回归失败经换成 beta 版本重跑后一一复现,判定为既有(含 2 项 CRLF 跨行字面量恒假);复评 3 轮修正 1 项必修——updateOverviewPreview() 无条件 View.VISIBLE 会推翻同提交的沉浸音频收起修复,改为按 mAudioStageVisible 门控并加回归锁,4 组变异探针全部如期转红且还原后 hash 一致;定向 6/6+13/13+4/4+3/3、全量 leanback 4271 项失败集与基线一致、双 flavor Java 编译、Python 契约 36/36(3.10/3.14)、git diff --check、gradlew clean 全部通过

Task-Guard: C74-beta-merge-review-dev1-20261016
2026-10-16 18:04:04 +08:00
Silent1566
2d4259ef7a fix(tv): 修复影视原生播放页简介不显示与当前集无高亮
影视原生(DIRECT)模式下两处缺陷:
1. 简介区不显示内容,必须点「简介」按钮弹窗才能看到全文。该模式下
   mTmdbUIAdapter 为 null、applyTmdbDetailFields() 永不运行,而简介节点
   @id/tmdbOverview 只在 TMDB 路径被写入,站源详情文本仅存于
   @id/content 的 tag(onContent() 弹窗读它),页面因此没有任何内联简介。
2. 当前播放的那一集没有高亮边框。选集芯片走 selector_video_item,其
   「当前播放」环是 state_activated 分支,而 EpisodeAdapter.bindTextView()
   只调 setSelected()(该 selector 的 selected 仅作跑马灯开关),从不调
   setActivated(),与手机版 EpisodeGridHolder 的行为不一致。

改动:
- 抽出 updateOverviewPreview() 作为简介区唯一写入点,setText(Vod) 在
  非 TMDB 模式调用它,点「简介」按钮改走 updateTmdbOverviewButton 的
  行数夹取,超出的部分省略号截断。
- 简介节点锚点由 @id/director 改为 @id/actor。RelativeLayout 的
  getRelatedView() 会把 GONE 锚点沿链上溯,因此 TMDB 模式(actor 被
  suppressTmdbNativeTextFields() 压成 GONE)仍落在导演行下方,DIRECT
  模式下则正确落在演员行下方,不再与演员行重叠。
- setVideoDetailsVisible() 一并收起简介区:沉浸音频/纯音频播放时它叠在
  播放器右侧,此前无内容所以未暴露,现在有文字了必须跟着收。
- EpisodeAdapter 抽出 applyTextActive(),同时设置 selected(跑马灯)与
  activated(当前集环),绑定与焦点回调共用。

Verification: 设备 192.168.50.3:5555 实测(detail_open_mode=2):简介区
内联显示「简介:十六年前,天下五分…」并省略号截断,简介按钮可见可点开;
E01 显示绿色当前环,焦点移到 E01 变白色焦点环、移到 E02 时 E01 绿环保持。
定向单测 VideoActivityDetailShellSourceTest 全通过;
assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL。

Verification: 设备 192.168.50.3:5555 实测(影视原生 detail_open_mode=2,debug 覆盖安装+数据恢复):简介区 tmdbOverview 内联显示并省略号截断、简介按钮可见且可点开弹窗;E01 当前集显示绿色当前环,焦点移到 E01 时为白色焦点环、焦点移到 E02 时 E01 绿环保持;定向单测 VideoActivityDetailShellSourceTest 全通过;assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL。全量回归 4250 项中 8 项失败经 git stash 复现确认为仓库既有 CRLF 源码扫描失败,与本次改动无关。

Task-Guard: FIX-DIRECT-EPISODE-OVERVIEW
2026-10-16 16:05:20 +08:00
Silent1566
9f4ea10a29
Merge pull request #451 from Silent1566/dev2
merge: 合并 origin/beta 最新代码(PR#448/#449/#450)并复评修正 py 初始化失败静默与缓存归因文档缺陷
2026-10-16 13:38:13 +08:00
Silent1566
e6791fe1b9 docs(c73): 记录交付坐标(合并提交 0b7ebfd087 / recovery tag / 推送 / PR #451)并在评估索引补 C73 行
回填 docs/C73-beta-merge-review-dev2-20261016.md 的交付坐标:合并提交
0b7ebfd087(HEAD^2 实测等于 origin/beta 12d7d559e5)、恢复标签
recovery/C73-beta-merge-review-dev2-20261016/20261016052352-0b7ebfd0872a、
推送 origin/dev2 40c0ce860e..0b7ebfd087、PR #451(OPEN / MERGEABLE /
mergedAt=null,只创建不合并),以及 PR 文件集与
git diff --name-only origin/beta HEAD 逐项一致(各 8 条)。

并在 docs/upstream-player-dependency-merge-assessment-2026-08-20.md 的稳定
任务索引中 C72 行之后补 C73 行,记录本轮合并对象、五层零复活证据与 2 项必修
的修复口径。

Verification: 纯文档提交,无生产代码/构建/测试改动;评估索引任务行 ID 集合
由 beta 的 86 变为 87,comm 比对确认 beta 行零丢失、C73 无重复。

Task-Guard: C73-coords

Verification: documentation-only closure commit recording already-verified results: backfilled delivery coordinates in docs/C73-beta-merge-review-dev2-20261016.md (merge commit 0b7ebfd087 with HEAD^2 == origin/beta verified, recovery tag, push 40c0ce860e..0b7ebfd087, PR #451 OPEN/MERGEABLE/mergedAt=null, PR file set identical to git diff --name-only origin/beta HEAD at 8 paths each) and added the C73 row to the assessment ledger index after C72; re-verified the ledger task-row ID set is beta 86 -> now 87 with zero beta rows lost (comm check) and no duplicate C73; no build, test, or production code change in this commit

Task-Guard: C73-coords
2026-10-16 13:30:22 +08:00
Silent1566
0b7ebfd087 merge: 合并 origin/beta 最新代码(PR#448/#449/#450)并复评修正 py 初始化失败静默与缓存归因文档缺陷
把 origin/beta tip 12d7d559e5 合入 dev2,0 冲突,合并内容相对 beta 中性
(净差异恰为 dev2 自身 6 个路径)。回退类提交零复活:文件级(66 个 revert
删除路径与合并树减 beta 树交集 = 0)与行级(7329 条 revert 删除实质行与 407 条
净新增实质行交集 = 0)双口径均为 0,且检测器注入探针自检 PASS;评估索引行 ID
集合 beta 86 = 合并后 86,零丢失零多余。

复评本轮 4 个已提交未推送的提交与合并交互,3 轮评审发现 2 项必修:

- PyLoader 初始化失败在默认设置下变成完全静默(P1,可观测性回归):
  前置提交把 catch 里的 e.printStackTrace() 换成 SpiderDebug.log,而后者每条
  重载首行都是 DebugLogStore.acceptsTag(tag),其 enabled 默认 false(只有用户
  手动打开调试日志才为真)。默认设置下 py 站源初始化失败既不进诊断日志也
  不再进 Logcat,而这里正是站点全空却没有任何线索的唯一解释点。恢复
  printStackTrace 与两条 SpiderDebug.log 并存,与兄弟 JsLoader 口径一致。
- docs/integration/spider.md 第 4 层缓存归因错误(P2):原文称 load_module()
  命中已导入模块时返回 sys.modules 旧模块对象;实测(CPython 3.10 与 3.14 双
  版本复现)load_module() 每次都会重新执行源码,真正复用旧字节码的是
  __pycache__ 校验,判据只有源文件的 mtime 与 size。按该双条件精确改写,并
  补一行同大小加同 mtime 秒的实测结论。

新增 app/src/test/java/com/fongmi/android/tv/api/loader/PyLoaderDiagnosticsWiringTest.java
作为回归锁,并在断言前剥离注释行,避免把 e.printStackTrace() 注释掉也能通过
而使守卫退化成恒真检查。

Verification: 7 组变异探针全部按预期转红并干净还原(git hash-object 与修复后
版本逐字节相同:PyLoader 11f1404045、SiteViewModel 976a508d5c、JsLoader
97d5761b09);定向单测 SiteViewModelSubmitAfterClearTest 4/4、
PyLoaderDiagnosticsWiringTest 3/3、JarLoaderTest 7/7、LoaderClearQueueTest 1/1,
leanback PlayerControlFocusHelperSeekReturnTest 13/13;双 flavor 与 chaquo Java
编译 BUILD SUCCESSFUL;chaquo/src/test/python 契约测试 36/36 在 CPython 3.10
与 3.14 均 OK;git diff --check 通过;UI token 对比度 38 对 0 失败;gradlew
clean BUILD SUCCESSFUL。全量单测剩余 3 项失败为既有 Windows CRLF 跨行字面量
缺陷(ReaderPlaybackRoutingSourceTest 两项、TmdbSourceOnlyInteractionTest 一项),
相关测试类与被断言的生产文件在 beta、开工前 HEAD、最终树三处 blob 逐一相同,
既不在本任务净差异内也不在 beta 增量内,按范围记录不修。

Task-Guard: C73-beta-merge-review-dev2-20261016

Verification: merge origin/beta 12d7d559e5 into dev2 with 0 conflicts, zero-content-neutral merge (tree diff vs beta = exactly dev2's own 6 paths); 5-layer zero-resurrection evidence (beta 5070 paths missing 0, 3 additions all task-owned test files, 66 revert-deleted files vs merge-minus-beta = 0, 7329 revert-deleted substantive lines vs 407 net-added = 0, assessment ledger row-ID set beta 86 = merged 86) with falsifiable detector self-check PASS; 3 review rounds found 2 must-fix (P1 PyLoader lost its only always-on failure channel because SpiderDebug.log is gated on DebugLogStore.acceptsTag which defaults false, restoring e.printStackTrace alongside the diagnostic-store call; P2 docs/integration/spider.md mis-attributed py staleness to sys.modules when load_module re-executes every time and the real reuse is the __pycache__ mtime+size check), added PyLoaderDiagnosticsWiringTest regression lock; 7 mutation probes all turned red as designed and every probe restored byte-identical via git hash-object (PyLoader 11f1404045, SiteViewModel 976a508d5c, JsLoader 97d5761b09); targeted tests 4/4 + 3/3 + 7/7 + 1/1 + leanback 13/13 pass, dual-flavor and chaquo Java compile BUILD SUCCESSFUL, python contract tests 36/36 on CPython 3.10 and 3.14, git diff --check clean, UI token contrast 38 pairs 0 failures, gradlew clean BUILD SUCCESSFUL; the 3 remaining full-suite failures are pre-existing Windows-CRLF assertion bugs in files byte-identical across beta/pre-task-HEAD/final-tree and are recorded as out of scope

Task-Guard: C73-beta-merge-review-dev2-20261016
2026-10-16 13:23:51 +08:00
Silent1566
933ab6d73e fix(py): 补齐 app.py 三个桥接助手缺失的失败防护
writeFile / redirect / str2json 长期没有守卫,分别对应三类可复现风险:

- writeFile: open(path,'wb') 立即截断,写到一半失败(如磁盘满)会留下截断的
  py;而它写的正是 Loader.source() 在断网时回退读取的同一份缓存,等于连最后
  一份可用副本一起毁掉。改为写入同目录唯一临时文件 + fsync + os.replace 原子
  替换,失败时清理临时文件。临时名用 mkstemp 生成而不是固定的 <path>.tmp,
  否则两个线程写同一脚本会共享临时文件、字节交错、两边都损坏(已用并发用例
  覆盖该点)。
- redirect: 手写跳转却没有 timeout 也没有跳数上限 —— 服务端不响应会永久挂住
  调用线程,重定向成环会一直递归到 RecursionError。补每跳 timeout 与
  maxHops,超限抛 ValueError。verify 默认改为 True:每跳目标来自远端
  Location 头,关校验等于让中间人挑选下一个地址;确需自签名的主机可显式传
  verify=False。
- str2json: 保持严格(调用方传入的都是 Java 桥产出的 JSON,解码失败就该暴露
  契约问题而不是被吞成静默空结果),只规整错误形态:统一为带上文的 ValueError,
  避免原始 JSONDecodeError/TypeError 把整份载荷抄进 Result.error() 与诊断日志。

Verification: chaquo/src/test/python 36 tests 全通过(Python 3.10 = chaquo 目标版本,也跑了宿主 3.14;新增 writeFile/redirect/str2json 共 19 项);红证两轮:(1) 对 96771c8a98 的修复前 app.py,新增用例 12 项 FAIL/ERROR(getDependence 组仍 ok),(2) 对我自己第一版共享 .tmp 名的 writeFile,并发用例 FAIL 且 5 项 ERROR —— 证明该用例真能抓到我自查发现的并发覆盖缺陷;:chaquo:generateArm64_v8aDebugPythonSourceAssets BUILD SUCCESSFUL in 23s,打包出的 app.imy/app.pyc 中 mkstemp/tempfile/contextlib/suppress/fsync/REDIRECT_MAX_HOPS/too many redirects/str2json: invalid JSON payload 各命中 1 次;app.py 行尾保持 CRLF(187/0 bare LF),测试文件 LF

Task-Guard: PY-APP-HELPERS-HARDEN-20261016
2026-10-16 11:22:12 +08:00
Silent1566
12d7d559e5
Merge pull request #450 from Silent1566/dev3
merge: 合并 origin/beta 最新代码(零内容合并)并复评修正进度条 UP 返焦的容器判定与文档缺陷
2026-10-16 11:18:23 +08:00
Silent1566
247629aaea merge: 合并 origin/beta 最新代码(零内容合并)并复评修正进度条 UP 返焦的容器判定与文档缺陷
Verification: C72 复评完成:origin/beta tip 1125b6c9c0 是「把 dev3(51bad1e0e4) 合进 beta」的双父合并提交,git diff --stat 51bad1e0e4 origin/beta 为空且树对象同为 8064bc564a(零内容合并),0 冲突,合并后索引树与 HEAD 树逐字节相同 373809946a;零复活/零丢失四层证据(beta 5067 文件缺失 0、文件级新增 2 条均为本任务自身新增、111 个 revert 提交的 731 条触及路径文件级交集 0、行级 31 条命中经分类后实质行仅 public void setUp() { 一条且溯源为通用 JUnit 样板、检测器注入 ISSUE_TEMPLATE 探针自检 PASS);索引行 ID 集合比对 beta 12 个 C* 行零丢失、新增恰为 C72;复评修正 1 项实现(seekReturnTarget 补 isDescendant(focused, seek) 祖先方向拒绝进度条容器;探针实测该状态当前不可达,属契约加固,Javadoc 按实测口径改写并加前提锁)+ 4 项文档(TmdbDetailActivity 入口行号 10905->10887、detail_open_mode 控制器归属表、手机端 no-op 结论、测试条数/证据);修正测试夹具容器未 setFocusable(true) 导致「容器不得覆盖记忆」断言恒真(改后可聚焦,2 条断言由绿转红证明原为零保护);2 组变异测试如期转红并干净还原(CustomSeekView hash a538cbdc14 与 HEAD 一致);验证:leanback 定向 13/13(mtime 11:11:41 本轮新生成)、mobile 集成 10/10、双 flavor Java 编译 BUILD SUCCESSFUL、git diff --check 干净、无冲突标记;提交前 fetch 确认 origin/beta 仍等于 MERGE_HEAD

Task-Guard: C72-beta-merge-review-dev3-20261016
2026-10-16 11:13:04 +08:00
Silent1566
96771c8a98 docs(spider): 补充 Python 站源加载与缓存排查说明
排查「改了 py 却不生效」缺少可查的文档依据,补上加载链路与实际生效条件。

- 说明 getDependence 缺失按无依赖处理,不继承 base.spider.Spider 的
  单文件 py 源可以不实现
- 新增「Python 加载与缓存」小节:file:// 改写、api md5 缓存名、
  Spider 实例缓存、load_module 的 sys.modules 旧模块,共四层
- 记录实测结论:只替换磁盘 py 不生效,需触发配置重载或重启 App
- Source 依据补充 Loader.java / app.py / UrlUtil.java / Site.java

Verification: 文档核对:新增 Python 加载与缓存四层链路说明,并逐项对照源码(Site.objectFrom→UrlUtil.convert 的 file:// 改写、Loader.scriptName 的 api md5 缓存名、PyLoader.computeIfAbsent 实例缓存、app.spider 的 load_module/sys.modules)与真机实测(5557,同版本包同站点:只替换磁盘 py 不生效;触发真实配置重载 config/use→VodConfig.clear→BaseLoader.clear→PyLoader.clear 后生效 MK_B→MK_C;重启 App 生效);新增的 python-spider init failed 可诊断性说明与 317fa9021d 的实现一致;文档末尾 Source 依据新增的 4 个路径均已逐一确认存在于仓库;行尾保持 CRLF(195 CRLF / 0 bare LF);markdownlint 通过

Task-Guard: PY-SPIDER-CACHE-DOC-20261016
2026-10-16 10:50:51 +08:00
Silent1566
317fa9021d fix(py): 缺失 getDependence 的 py 站源不再被静默降级为空
getDependence 是 WebHTV 桥的私有扩展,不是 TVBox/猫源 py 契约的一部分。
Spider.init() 无条件通过 app.getDependence 调用它,而默认实现只存在于
内置 base/spider.py,因此不继承该基类的单文件 py 源(如用户导入的
badnews_spider.py)会抛 AttributeError;PyLoader 捕获后返回全空的
SpiderNull 并永久缓存,首页/分类/搜索全部空白且不留任何可诊断线索。

- app.py: getDependence 用 getattr 兜底,缺失即视为无依赖;用 getattr
  而非 try/except AttributeError,保证真实 getDependence 的内部异常仍会
  正常抛出而不被吞掉
- PyLoader: 初始化失败改走 SpiderDebug(python-spider tag),带上 key/api
  与异常类型,替代原先不进诊断日志的 printStackTrace
- 新增 chaquo/src/test/python/test_app_bridge_contract.py 固化上述契约

Verification: chaquo/src/test/python 契约测试 14 tests 全通过(新增 test_app_bridge_contract.py 7 项 + 既有 test_webhtv_logging.py 7 项,OK,0.005s);红证:把 app.py 的 getDependence 还原为修复前 ru.getDependence() 后同一测试集 1 failure + 2 errors,复现 AttributeError: 'SpiderWithoutGetDependence' object has no attribute 'getDependence';:chaquo:compileArm64_v8aDebugJavaWithJavac 与 :app:compileMobileArm64_v8aDebugJavaWithJavac 通过(BUILD SUCCESSFUL in 31s);app.py py_compile 通过且行尾保持 CRLF(133 CRLF / 0 bare LF)

Task-Guard: PY-GETDEPENDENCE-COMPAT-20261016
2026-10-16 09:47:41 +08:00
Silent1566
9116639e3e docs(tv): 补记控制栏进度条 UP 返焦在内嵌控制栏的设备复核证据
Verification: 详情内嵌控制栏(playerControls) 设备实测(192.168.50.3:5559,临时 detail_open_mode=0 后按原字节还原):焦点到第4颗按钮 playerFullscreenAction → DOWN 落到 timeBar → UP 回到 playerFullscreenAction(修复前该栏被硬钉 playerNext 会跳下集)

Task-Guard: TV-PLAYER-SEEK-UP-FOCUS-20261016-VERIFY
2026-10-16 09:47:20 +08:00
Silent1566
b1fe3d5686 fix(player): SiteViewModel 在 onCleared 后把提交降级为已取消请求
onCleared() 用 shutdownNow() 终止 playerExecutor/isolatedPlayerExecutor 后,
宿主的迟到提交(播放页 onRefresh/自动选集、短剧下一条预取、阅读器章节切换)
会命中已终止线程池并抛 RejectedExecutionException,顺着宿主调用栈冒出去打崩 App。

- 新增 volatile cleared,onCleared() 先置位再关池
- playerContent/playerContentIsolated/分站搜索在清理后直接作废
- 两处 submit 用 try/catch 兜住「检查通过后池才关闭」的竞态窗口
- 新增 SiteViewModelSubmitAfterClearTest 覆盖上述行为

Verification: 定向单测 :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.model.SiteViewModelSubmitAfterClearTest 通过(4 tests / 0 skipped / 0 failures / 0 errors),含 onCleared 后 7 个前台提交入口不抛异常、隔离取流返回已取消 future、并发提交与 onCleared 竞态不外泄异常;:app:compileMobileArm64_v8aDebugJavaWithJavac 通过;BUILD SUCCESSFUL in 15m 36s

Task-Guard: SITEVM-POST-CLEAR-SUBMIT-20261016
2026-10-16 09:14:28 +08:00
Silent1566
80816a15f0 fix(tv): 控制栏进度条按 UP 回到刚才停留的按钮
Verification: leanback 单测 9/9 通过(TEST-...PlayerControlFocusHelperSeekReturnTest.xml:tests=9 failures=0 errors=0,本轮 00:56 新生成);设备 192.168.50.3:5559 覆盖安装 A/B:修复前 下集→DOWN(进度条)→UP 落到 codecCapability,修复后回到 next,且第 5 颗按钮 decode 下到进度条再 UP 也回到 decode

Task-Guard: TV-PLAYER-SEEK-UP-FOCUS-20261016
2026-10-16 01:22:16 +08:00
Silent1566
1125b6c9c0
Merge pull request #449 from Silent1566/dev3
fix(exo): 修复标准 offload 输出接受数据却不消费导致部分音乐无法播放
2026-10-16 00:18:45 +08:00
Silent1566
51bad1e0e4 docs(E11): 记录交付坐标(合并提交 0d92573101 / recovery tag / 推送 / PR #449)
Verification: 文档-only 闭合提交:记录合并提交 0d92573101 / 恢复标签 recovery/E11/20261015153914-0d9257310155 / 推送 / PR #449(base=beta, OPEN, MERGEABLE);PR 文件集与 net diff 逐项一致;创建 PR 后再次 fetch 确认 origin/beta 未前进

Task-Guard: E11-coords
2026-10-15 23:42:08 +08:00
Silent1566
0d92573101 merge: 合并 origin/beta 最新代码(PR#448)并复评修正 offload 停滞诊断被静默丢弃与判据缺陷
Verification: 70 项定向单测 0 failure/0 error/1 项既有 @Ignore,Leanback+Mobile ARM64 Java 编译通过;三次独立变异各自恰好转红一个对应用例且 blob+sha256 双重确认还原;合并结果树与 origin/beta 树逐字节相同、零复活(0/601)

Task-Guard: E11
2026-10-15 23:39:13 +08:00
Silent1566
fc3d3a2b4a
Merge pull request #448 from Silent1566/dev3
合并 beta 最新代码并复评:chip 弹层文字对比度修正 + 手机版站点标签满宽
2026-10-15 14:06:03 +08:00
Silent1566
10e62fe391 docs(c69): 记录重新合并(beta 前进至 c18ebd90d9)的处置与最终交付坐标
Verification: 第 2 轮最终交付坐标已核实(针对重新合并后的 f39b3e2bd1):重新合并提交 f39b3e2bd1 双父 3a4c0fcaeb + c18ebd90d9;recovery tag recovery/C69-beta-merge-review-dev3-20261015-remerge/20261015054223-f39b3e2bd199 已推送;git push origin dev3 -> 3a4c0fcaeb..f39b3e2bd1;git ls-remote 确认 refs/heads/dev3=f39b3e2bd1 且 origin/beta 仍为 c18ebd90d9 等于提交第二父;PR #448 mergeable=MERGEABLE state=OPEN mergedAt=null changedFiles=6 base=beta head=dev3,gh api pulls/448/files 与 git diff --name-only origin/beta HEAD 逐项一致(6 项 diff 无输出);临时资源 .git/c69-pr-body.md/.git/pr448-body.md/.git/c69-remerge.md 均已清理;git diff --cached --check exit 0

Task-Guard: C69-beta-merge-review-dev3-20261015-remerge-coords
2026-10-15 13:43:57 +08:00
Silent1566
f39b3e2bd1 merge: 重新合并 origin/beta c18ebd90d9(dev2 全屏手动旋转修复)并解决评估索引 union 冲突
Verification: 重新合并 origin/beta c18ebd90d9(原 870b2f8220 在验证期间被 dev2 PR#447 推进,导致 PR#448 CONFLICTING);仅 docs/upstream-player-dependency-merge-assessment-2026-08-20.md 一处 union 冲突(双方各自追加行),按'保留本轮 C69 行 + 保留 beta 新增 C71 行'解决,beta 的 112 行全部保留、零丢失;零复活/零丢失五层证据对 NEW beta 全部通过(beta 删除面空、复活形态空、revert 111 提交/731 路径 ∩ 新增 0 = 0、合并树 5067 == beta 5067 缺失 0);冲突标记 0;git diff --cached --check exit 0;:app:testMobileArm64_v8aDebugUnitTest --tests *VodAdapterLayoutTest --tests *DialogGlassChipStyleTest --tests *PlayerPlaybackRegressionSourceTest -> BUILD SUCCESSFUL,逐套件 XML 5/5 + 6/6 + 15/15 = 26 例 0 失败(含 beta 新带入的 PlayerPlaybackRegressionSourceTest)

Task-Guard: C69-beta-merge-review-dev3-20261015-remerge
2026-10-15 13:42:23 +08:00
Silent1566
3a4c0fcaeb docs(c69): 记录第 2 轮交付坐标(合并提交 4fb486309a / recovery tag / 推送 / PR #448)
Verification: 第 2 轮交付坐标已核实:合并提交 4fb486309aada52fbc159e031c6f6b93a4ac2ef5(双父 1cbdf2d817 + 870b2f8220);recovery tag recovery/C69-beta-merge-review-dev3-20261015/20261015053508-4fb486309aad;git push origin dev3 -> 05f37bee21..4fb486309a,git ls-remote 确认 refs/heads/dev3=4fb486309a 且 tag 解引用到同一提交;PR #448 state=OPEN mergedAt=null mergeable=MERGEABLE changedFiles=6 base=beta head=dev3,gh api pulls/448/files 与 git diff --name-only origin/beta HEAD 逐项一致(6 项 diff 无输出);git diff --check exit 0

Task-Guard: C69-beta-merge-review-dev3-20261015-coords
2026-10-15 13:38:34 +08:00
Silent1566
c18ebd90d9
Merge pull request #447 from Silent1566/dev2
merge: 合并 origin/beta 最新代码并复评修正手机全屏手动旋转方向在切集后保持
2026-10-15 13:38:21 +08:00
Silent1566
4fb486309a merge: 合并 origin/beta 最新代码(dev2 弹层 chip 文字对比度修复)并复评修正手机版站点标签与 chip 对比度守卫
Verification: 合并 origin/beta 870b2f8220(0 冲突);零复活五层证据+可证伪自检(补丁摘要相等 9b43b82b81、beta 删除面空、复活形态空、缺失 0、revert 文件级交集 0);复评发现并修正 2 项(chip 对比度注释实测区间错误 7.98-9.57 -> 真实 6.84-10.60;守卫把 chip 原始色误当最坏情况 8.64 偏乐观 -> 改按三层 alpha 合成核算)+ 反空转断言(3 档渐变/核算 12 次);4 组变异测试全部如期失败并干净还原(站点标签 wrap_content、chip state_checked 改白、玻璃改亮 2.32:1 vs 旧 8.64:1 覆盖盲区、渐变塌缩为 solid);P1 纯注释(剥注释后 item 序列逐字节相同 1747a4b5);:app:testMobileArm64_v8aDebugUnitTest --tests *VodAdapterLayoutTest --tests *DialogGlassChipStyleTest -> BUILD SUCCESSFUL,逐套件 XML 5/5 + 6/6 = 11 例 0 失败;leanback ui.*+theme.* 534 例 5 失败经还原到 HEAD 后同样失败证明为既有;git diff --check exit 0;check_ui_tokens.sh --strict 相对基线零新增违规

Task-Guard: C69-beta-merge-review-dev3-20261015
2026-10-15 13:35:08 +08:00
Silent1566
40c0ce860e docs(c71): 记录交付坐标(合并提交 3c9cffa4c9 / recovery tag / 任务编号统一为 C71)
C70 已被 android6 分支的 5a5d07822f 占用,本任务改用 C71;提交信息 trailer、
任务文档名与恢复标签已统一。合并提交与标签均未推送,重命名安全。

Verification: 文档-only 收尾提交,无代码变更;合并提交 3c9cffa4c9 的双父与树在 amend 前后一致(git diff --stat HEAD <old> 为空)。
2026-10-15 13:33:07 +08:00
Silent1566
3c9cffa4c9 merge: 合并 origin/beta 最新代码(PR#445/#446)并复评修正 dev2 已提交未推送的手机全屏手动旋转改动
合并 origin/beta tip 870b2f8220(dev2 已被合入 beta 的双父合并提交):
0 冲突,合并树与 beta 树逐字节中性,零新增/零丢失,零复活(含可证伪自检)。

复评 dev2 已提交未推送的 4f75fbe451(手机全屏手动旋转方向在切集后保持):
- 实现正确:manualRotate 在 onRotate() 置位、checkOrientation() 让位、
  进出全屏按当前视频形态重建自动基线并清除标记,三条不变量均有断言锁定。
- 发现 1 项必修:PlayerPlaybackRegressionSourceTest 的跨行字面量断言在
  CRLF 工作区恒假——既让 livePlaybackAlwaysAutoplaysWhileVodUsesTheConfiguredPolicy
  直接失败,又使 assertAlwaysAutoplay 对 mobile/leanback LiveActivity 的
  回归锁实际零保护。read() 归一化行尾后修复,与同包既有写法一致。
- 三项变异验证证明修复后断言非空转,探针均已还原。

Verification: PlayerPlaybackRegressionSourceTest 15/15 + SiteDialogLayoutTest 4/4;
双 flavor Java 编译通过;git diff --check 干净;gradlew clean 成功。
Task-Guard: C71-beta-merge-review-dev2-20261015

Verification: merge origin/beta (870b2f8220) into dev2: 0 conflicts, merge tree f01ec5c437fdb8f05f64dd2c69d1b4d3956b24b0 byte-identical to beta tree except dev2 own 2 paths; zero-loss/zero-add 5066=5066; zero resurrection (184 revert commits, 10844 meaningful lines, 0 hits) with falsifiable self-check PASS; review found 1 must-fix: PlayerPlaybackRegressionSourceTest multiline literals were always-false under CRLF (failed livePlaybackAlwaysAutoplaysWhileVodUsesTheConfiguredPolicy AND silently voided assertAlwaysAutoplay for mobile/leanback LiveActivity), fixed by normalizing line endings in read(); 3 mutation probes (PlaybackActivity L353, LiveActivity L389, checkOrientation L255) all turned red as expected then reverted; PlayerPlaybackRegressionSourceTest 15/15 + SiteDialogLayoutTest 4/4 failures=0 errors=0 skipped=0; compileMobileArm64_v8aDebugJavaWithJavac + compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; git diff --check clean; gradlew clean BUILD SUCCESSFUL; check_ui_tokens.sh --strict contrast pairs=38 failures=0 min=4.28 (2 hex-scan violations pre-existing on beta, not in CI)

Task-Guard: C71-beta-merge-review-dev2-20261015
2026-10-15 13:32:16 +08:00
Silent1566
4f75fbe451 fix(mobile): 全屏手动旋转的方向在切集后保持
checkOrientation() 由每个视频尺寸回调触发,会按新视频宽高改写 rotate,
把用户「横屏视频手动转竖屏」的选择当成过时状态改回横屏,表现为切下一集后
方向复位、需要重新点旋转。

新增 manualRotate 标记:onRotate() 置位后 checkOrientation() 让位,
进出全屏时按当前视频形态重建自动基线并清除标记。
补源码级回归测试锁定该行为。

Verification: gradlew :app:testMobileArm64_v8aDebugUnitTest --tests PlayerPlaybackRegressionSourceTest: 14/15 pass incl. new mobileManualRotateSurvivesEpisodeSwitchInsteadOfBeingAutoCorrected + existing mobileRotateButtonRequestsOrientationFromTheToggledState and mobileFullscreenExitRebindsEpisodesAfterOrientationSettles; only failure livePlaybackAlwaysAutoplaysWhileVodUsesTheConfiguredPolicy is pre-existing CRLF-vs-\n mismatch on untouched PlaybackActivity.java (worktree hash == HEAD d29bd54fd3)

Task-Guard: MOBILE-MANUAL-ROTATE-PERSIST-20261015
2026-10-15 10:47:04 +08:00
Silent1566
1cbdf2d817 fix(mobile): 站点标签宽度占满剧集卡片,右缘不再短一截
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests *VodAdapterLayoutTest -> BUILD SUCCESSFUL in 3m37s; test-results XML: tests=5 failures=0 errors=0 (含新增 mobileRectSiteTagSpansThePosterWidth); :app:processMobileArm64_v8aDebugResources 执行通过(mobile 布局资源编译通过)

Task-Guard: mobile-vod-site-tag-width-20261014
2026-10-15 00:03:18 +08:00
Silent1566
870b2f8220
Merge pull request #446 from Silent1566/dev2
fix(tv): 弹层 chip 文字改用调色板无关常量,修复深色主题选中项白底白字
2026-10-14 23:47:06 +08:00
Silent1566
d158f93f94 docs(c69): 记录 dev2 交付坐标(合并提交 b515bf11bb / recovery tag / 推送 / PR #446)
合并提交 b515bf11bbe4d71664dbe6d462246b29fa27dc19(双父 2137fffc5a + a8c0d6a5fe),
恢复标签 recovery/C69-beta-merge-review-dev2-20261014/20261014153930-b515bf11bbe4,
origin/dev2 快进 4913d6ff23..b515bf11bb,PR #446(dev2 → beta,OPEN / MERGEABLE / CLEAN,只创建不合并)。
PR 净增量恰为 3 路径:selector_dialog_glass_chip_text.xml、DialogGlassChipStyleTest.java、本文件。

Verification: 交付坐标已逐条核对:gh pr view 446 => state=OPEN, isDraft=false, baseRefName=beta, headRefName=dev2, mergeable=MERGEABLE, mergeStateStatus=CLEAN,PR 文件清单恰为 3 路径;git rev-list --left-right --count origin/dev2...dev2 => 0 0(已推送且无分叉);git tag --list 'recovery/C69*' 命中 recovery/C69-beta-merge-review-dev2-20261014/20261014153930-b515bf11bbe4;本次为纯文档改动,无代码/资源/构建行为变化,故不重跑测试

Task-Guard: C69-delivery-coords-dev2
2026-10-14 23:45:09 +08:00
Silent1566
b515bf11bb merge: 合并 origin/beta 最新代码(PR#441-#444)并复评修正 dev2 已提交未推送的 TV 弹层 chip 文字对比度改动
合并:git merge --no-commit --no-ff origin/beta(a8c0d6a5fe23418027c17ce7ee0e4e1ceb5b205e,
Merge pull request #444 from Silent1566/dev3),0 冲突;MERGE_HEAD 即 beta tip。
合并基点为 4913d6ff23d7d4fee56a35f1e0b2025ee7920061(C65 的文档提交)。

本轮 beta 侧有真实增量(22 提交 / 33 路径 / +2324 -77),来自 PR#441(dev2 上一轮)、
PR#442 与 PR#444(dev3 实验室遥控焦点)、PR#443(dev1 WorkManager 直启)以及硬解自动软解回落;
dev2 自身相对基点只有 2 路径。

零复活 / 零丢失(四层证据;用户明确要求远端已移除/回退的提交不得顺带提交上去):
1. 净差异:git diff --cached --name-status origin/beta 恰为 dev2 自身 2 路径。
2. beta 删除面:git diff --diff-filter=D --name-status 4913d6ff23 origin/beta 为空 => beta 未删任何路径。
3. 复活形态:git diff --cached --diff-filter=A --name-status origin/beta 为空 => 不存在把已删文件加回来。
4. 逐行交集:beta 侧 revert 提交删除行 ∩ dev2 新增行 = 5 行,全为样板(空行、/**、*、*/、})。
更强判据——补丁摘要相等:git diff --cached --binary origin/beta 与 git diff --binary 4913d6ff23
2137fffc5a 经 git hash-object --stdin 同为 d6802118fac46812e23a881ef07cd57342a24d6b,即合并结果
逐字节等于「beta tip + dev2 自身提交」,除自身 2 路径外没有引入、删除或改写任何一个字节;
文件数 5063 = 5063。本轮 beta 确有 1 个 revert(b2bb4a249b 撤回实验室弹窗按钮钉死到根目录的连线),
但它撤的是 beta 自己刚引入的内容,dev2 从未持有(自基点只改过 chip 两个文件),
故不存在「被回退内容又被顺带提交」的形态。

复评(2 轮):对象为 dev2 已提交未推送的 2137fffc5a「fix(tv): 弹层 chip 文字改为调色板无关常量,
修复深色主题下选中项白底白字」,改动集 2 路径:app/src/main/res/color/selector_dialog_glass_chip_text.xml
与 app/src/testMobile/java/com/fongmi/android/tv/ui/DialogGlassChipStyleTest.java。

第 1 轮(Standards + Spec 双轴)共 5 项发现,修复 4 项、记录不修 3 项:
- 修 A2:选择器注释声明覆盖「音轨/视轨/字幕/标题/弹幕」,但测试只断言 track/title,未断言确实使用
  共享选择器的 adapter_danmaku.xml。已补该断言;三个布局已逐一核实同时引用 chip 背景与文字选择器。
- 修 B+S1:旧断言 occurrences(text,"?attr/colorOnSurface")==6 被删除后,选择器的状态覆盖不再被任何
  断言钉住,且 text 沦为死变量。改为断言 withoutComments(text) 上恰有 6 个 <item,并逐一断言
  state_enabled=false / checked / activated / selected / focused 五个状态条目存在。state_activated
  正是 TV 持久选中态(TrackAdapter 用 setActivated 表达当前播放轨),删掉它会让选中 chip 回落到
  跟随调色板的默认白字、重现用户报告的缺陷,而原对比度守卫只查 checked 与默认态、抓不到。
- 修 C1:注释与测试 Javadoc 称浅色 onSurface 压常态 chip ≈1.79:1,按守卫自身 WCAG 公式复算为
  1.68:1,两处均订正;并注明三个比值口径是「不透明原始色对按 WCAG 2.x 相对亮度直接计算」,
  与常态态「半透明合成后实测 7.98-9.57:1」区分开。
- 记录不修 A1:守卫在 src/testMobile,testLeanbackArm64_v8aDebugUnitTest 不执行它。守卫是纯静态
  资源检查、按路径直读 leanback 布局,与 flavor 无关;本仓库惯例对任何改动同时跑两个 lane;
  src/testMobile 内本就有大量 TV 面守卫,移动文件会放大交付面且收益不抵成本。
- 记录不修 D1(范围外):selector_dialog_glass_button_tint.xml 用调色板跟随的 colorOutline(Variant)
  作 MaterialButton 的 backgroundTint,配两表恒为 #FFFFFF 的 webhtvColorOnWallpaper 白字,宿主是
  固定深靛玻璃,浅色默认态仅 1.70:1;该文件两侧都未改动,不在评审对象内,建议另开任务。
- 记录不修 S2:WCAG 助手在测试源集已重复 8+ 处(LeanbackForegroundContrastTest 等),属仓库既有
  惯例,抽公共工具需改动 9 个无关文件,违反范围规则。

第 2 轮通过:修复只改注释文字与测试断言,选择器 <selector> 主体的 6 个状态条目逐字节未变,无回归面。

评审记录见 docs/C69-beta-merge-review-dev2-20261014.md。

Verification: 合并 0 冲突且内容中性(补丁摘要相等 d6802118fac46812e23a881ef07cd57342a24d6b;文件数 5063=5063;零复活四层证据通过);定向单测 4 类 26 例 0 失败(修复后 chip 类 6/6);变异验证删除 state_activated 使 trackAndTitleSelectionsShareTheLightGlassFocusStyle 转红(6 tests completed, 1 failed),字节备份还原后 sha256 一致(2ae28d3c…)且重跑 6/6 全绿;check_ui_tokens.sh --strict 对比度 pairs=38 failures=0 min=4.28(2 处十六进制扫描违规均非本次引入:selector_lab_link.xml 为 beta 本轮新增、item_following.xml 为既有);git diff --cached --check EXIT=0

Task-Guard: C69-beta-merge-review-dev2-20261014
2026-10-14 23:39:29 +08:00
Silent1566
952ad90226
Merge pull request #445 from Silent1566/dev3
feat(tv): 站点选择弹窗改为居中浮层面板(含 C69 复评修正)
2026-10-14 23:27:01 +08:00
Silent1566
05f37bee21 docs(c69): 终验修正零复活口径(文件级粗判据的 1 项交集经核实为误报)
- 交付后终验时 diff 已从 13 路径增至 15(新增 C69 文档 + 评估索引 1 行),
  文件级「beta 历史删除路径 ∩ 本 diff」交集因此从 0 变为 1。
- 经逐项核实为误报:docs/upstream-player-dependency-merge-assessment-2026-08-20.md
  存在于 beta tip,本轮对它只是 M(修改)而非 A(新增),且 beta 自己已用 5a5ce47394
  等提交把它放回过。
- 补做按操作类型的严格判据:A 2 项(均为本轮全新文档,非 beta 删除项)、
  M 13 项(全部存在于 beta tip)、D 0 项 -> 真实复活 0。
- 同步更正评估索引里 C69 行的同一处口径。

Verification: 纯文档准确性更正,无代码改动;零复活结论经更严格判据复核后不变(按操作类型:A 2 项均非 beta 删除过的路径、M 13 项全部存在于 beta tip、D 0 项;且 git diff 2ea9700e5d..origin/beta 为空、两树同为 f223f0aeba);交付后终验确认 dev3==origin/dev3==b8d4cc7f54、工作区干净、PR #445 OPEN 未合并

Task-Guard: C69-final-verify
2026-10-14 23:22:46 +08:00
Silent1566
b8d4cc7f54 docs(c69): 记录交付坐标(双父合并提交 aa7a0b56f1 / recovery tag / 推送 / PR #445)
- 双父合并提交 aa7a0b56f1(父 e84eb4f3e7 + a8c0d6a5fe)。
- recovery tag recovery/C69-beta-merge-review-dev3-20261014/20261014122252-aa7a0b56f19f。
- 推送 2ea9700e5d..aa7a0b56f1;PR #445(dev3 -> beta,只创建未合并,15 个文件)。
- 补记第 2 轮复审修正的两处文档结构问题,并记录 gh 需显式 --repo 的环境注记。

Verification: 纯文档坐标回写,无代码改动;本轮交付已由 aa7a0b56f1 的验证记录覆盖(mobile 定向 10/10、leanback 定向 69/69、变异测试证明断言有效)

Task-Guard: C69-coords
2026-10-14 20:33:34 +08:00
Silent1566
aa7a0b56f1 merge: 合并 origin/beta 最新代码(零内容合并)并复评修正站点面板的恒真断言与文档记录
- origin/beta tip a8c0d6a5fe 是「把 dev3 合进 beta」的双父合并提交,反向合并后净差异为空、
  合并结果树与 dev3 逐字节相同(57194a00e9),无冲突、无新内容。
- 零复活/零丢失四层证据:净差异 13 路径、beta tip 5063 个文件缺失 0、
  beta 相对其 dev3 父提交零内容差异、beta 历史删除的 622 个路径与本 diff 交集 0。
- 复评修正 1 项重要问题:SiteDialogLayoutTest 的跨行 assertFalse 因工作区 CRLF(core.autocrlf=true)
  而永远匹配不上,成为恒真断言、零回归保护;新增 read(Path) 助手归一化行尾修复,
  并用变异测试证明修复后断言确实可失败。
- 更正 docs/TV-SITE-DIALOG-PANEL-20261014.md 的 8 处自相矛盾/过期记录(验收项与 §9 冲突、
  0.82↔0.9、PANEL_MARGIN_DP、density 口径、§8 算术、断言归属、文字色描述、§7 状态),
  并补记 C69 复评轮与交付坐标。

Verification: mobile SiteDialog 定向 10/10 通过(SiteDialogLayoutTest 4/4、SiteDialogThemeSourceTest 5/5、SiteDialogSelectionScrollSourceTest 1/1,逐套件读 XML 核对);leanback 定向 15 套件 69/69 通过(含 SiteDialogActionFocusTest 3/3、SiteDialogThemeSourceTest 2/2、HomeMenuDialogSourceTest 5/5),compileLeanbackArm64_v8aDebugJavaWithJavac 执行;变异测试证明修复后的跨行断言可失败(改回 MATCH_PARENT 时 siteGridHeightSnapsToWholeRowsSoTheLastRowIsNotClipped FAILED),随后还原;零复活/零丢失四层证据:净差异 13 路径、beta 侧文件缺失 0、beta 相对 dev3 父零内容差异(merge 结果树 57194a00e9 与 dev3 相同)、beta 历史删除路径交集 0

Task-Guard: C69-beta-merge-review-dev3-20261014
2026-10-14 20:22:52 +08:00
Silent1566
2137fffc5a fix(tv): 弹层 chip 文字改为调色板无关常量,修复深色主题下选中项白底白字
Verification: DialogGlassChipStyleTest 6/6 通过(含新增对比度守卫与其反空转探针);LeanbackForegroundContrastTest + TvFixedDarkSurfaceContrastTest + ThemeBaseWiringTest 通过;leanback/mobile arm64 debug 资源与编译通过;check_ui_tokens --strict 对比度 38 组 failures=0(唯一 violation item_following.xml 为 HEAD 既有、未修改、不在本次范围)

Task-Guard: TV-GLASS-CHIP-CONTRAST-20261014
2026-10-14 20:03:05 +08:00
Silent1566
e84eb4f3e7 docs(tv): 更正站点面板实测数字
上一节把「12 个站点」误写为「15 个」——15 是分组标签数量,不是一屏站点数。
按 dumpsys/uiautomator 实测更正:面板窗口 1728x842(屏宽 90% x 屏高 78%),
网格 512px = 4 行 x 3 列 = 12 个站点(此前 3 行 9 个)。

Verification: 仅文档更正,无代码改动;实测依据:dumpsys window mFrame=[96,119][1824,961] 即 1728x842;uiautomator recycler 高度 512px = 4 行 x 3 列

Task-Guard: tv-site-doc-fix-20261014
2026-10-14 18:50:42 +08:00
Silent1566
7d7c17ef15 fix(tv): 站点面板比例改为 90%,分组标签行不再被裁
面板占比偏小的根因是 PANEL_MAX_WIDTH_DP=1180 这个 dp 宽度上限:真机电视
常见 density 1.0,1920px 屏即 1920dp,0.82x1920=1574dp 被硬压到 1180dp,
只剩 61% 屏宽,density 越低压缩越明显。

- 宽高比例统一改 0.9f,对齐仓库既有大弹窗惯例
  CacheManagementDialog.SCREEN_FRACTION = 0.9f,删除 PANEL_MAX_WIDTH_DP
- 分组标签间距 12dp->10dp、内边距 16dp->14dp,15 个标签在 90% 宽度下
  完整放下,不再被面板右缘裁掉半个
- 修正 dialog_site.xml 里过时的「屏宽 86%、上限 1180dp」注释

1080p 实机实测:内容区 1648px=屏宽 86%(含面板 padding 后整体 90%),
一屏 15 个站点(此前 9 个),15 个标签宽度均匀且末尾完整可见。

Verification: SiteDialogLayoutTest 4/4 通过(断言更新为必须用 0.9 比例且不得再有 dp 宽度上限);assembleLeanbackArm64_v8aDebug 编译通过;1080p 实机安装验证(设备 APK md5 与本地构建一致):面板内容区 1648px=屏宽 86%(含 padding 整体 90%),一屏 15 个站点(此前 9 个),15 个分组标签宽度均匀 84-112px、末尾 APP 右缘 1784 与内容区右边界一致不再被裁;logcat 无 FATAL

Task-Guard: tv-site-panel-ratio-20261014
2026-10-14 18:47:04 +08:00
Silent1566
b66a3397e2 fix(tv): 站点卡片内边距显式声明,健康点不再压住边框
SiteDialogTheme.applySiteItem() 会用程序化 GradientDrawable 整体替换卡片背景,
而该 drawable 没有 padding,selector_site_item 三个 shape 里声明的 14dp/10dp
内边距会一起丢失,导致健康点直接贴在卡片边框上。

改为在卡片 View 上显式声明 paddingStart/paddingEnd=14dp,不再依赖会被替换掉的
drawable padding;垂直留白仍由 gravity=center_vertical 提供。
新增契约测试锁住该行为。

1080p 实机实测:健康点左边缘 x=245、卡片左边界 x=213,间距 32px≈16dp。

Verification: SiteDialogLayoutTest 4/4 通过(含新增 padding 契约测试);assembleLeanbackArm64_v8aDebug 编译通过;1080p 实机安装验证:健康点与卡片左边框间距 32px≈16dp(此前为 0),logcat 无 FATAL

Task-Guard: tv-site-card-padding-20261014
2026-10-14 17:35:28 +08:00
Silent1566
3ca1da02c3 feat(tv): 站点选择弹窗改为居中浮层面板
- 窗口不再铺满:宽 = 屏宽 82%(上限 1180dp),高度 wrap_content,居中 + 暗化背景
- 网格高度收敛到整数行,并按可用高度 74% 设上限,末行不再被裁半截
- 新增标题行(标题 + 站点计数),面板高度贴合内容,筛选后自动收缩
- 卡片常态填充改用 colorSurfaceContainerHigh + 描边,卡片边界可见;
  内容左对齐、健康点紧贴站名(移除 Java 里覆盖 XML 的 setGravity(CENTER))
- 面板外壳补 1dp 轮廓,浮层边界可见
- 修复 updateCountText 走 Fragment.getString 在裸 Dialog 路径抛
  IllegalStateException: not attached to a context

Verification: leanback+mobile 定向测试 32/32 通过;assembleLeanbackArm64_v8aDebug 编译通过;1080p(density 2.0) 实机安装验证:面板 81%宽 x 65%高居中、网格为整数 3 行无裁切、D-pad 焦点与分组筛选正常、logcat 无 FATAL

Task-Guard: tv-site-dialog-panel-20261014
2026-10-14 16:00:01 +08:00
Silent1566
a8c0d6a5fe
Merge pull request #444 from Silent1566/dev3
dev3:合并 beta 最新代码(无新提交)并复评修正实验室遥控焦点改动的焦点环对比度与文档/测试缺陷
2026-10-14 11:38:04 +08:00
Silent1566
2ea9700e5d docs(c68): 记录交付坐标(修正提交 5a5ce47394 / recovery tag / 推送 / PR #444)
纯文档提交,只写回交付坐标与闭环记录:修正提交、recovery tag、推送范围、
PR #444 状态(OPEN / mergedAt=null / changedFiles=18)与 PR 文件集校验结果。

Verification: docs-only coordinate record; PR #444 verified OPEN, not merged, changedFiles=18 identical to git diff --name-only origin/beta dev3 (Compare-Object empty); origin/dev3 == dev3 == 5a5ce47394

Task-Guard: C68-beta-merge-review-dev3-20261014-coords
2026-10-14 11:29:56 +08:00
Silent1566
5a5ce47394 merge: 合并 origin/beta 最新代码(无新提交)并复评修正实验室遥控焦点改动
origin/beta tip f398ced4dd 已是 dev3 祖先,合并为空操作;本轮无远端新提交、无冲突。
零复活/零丢失四层证据:净差异 16 路径、beta 5057 文件缺失 0、26 个 revert 提交的
1336 条删除行与 782 条新增行交集 0、622 个历史删除路径与本次改动路径交集 0。

复评 7 个已提交未推送的「电视版实验室遥控焦点」提交(Standards + Spec 双轴):

重要
- selector_lab_link.xml 聚焦描边用固定 @color/accent(#2196F3),而该链接长在
  Theme.App.Lab.DayNight.Dialog 里,浅色系统面板是 lab_surface(#E7E8EF),实测只有
  2.56:1,低于仓库既有的「焦点环 >=3:1」对比度门(colors.xml / ThemeController
  已记录过同类回归)。改为语义 token @color/webhtv_color_primary 后为 5.23:1(浅)/
  7.86:1(深),并与链接文字色、弹窗主题 colorPrimary 同源。
- LAB 任务文档 Recovery anchor 的「允许路径」是会话 1 写的,漏了会话 2-5 追加的
  LabCommandSheet.java / LabActivity.java / selector_lab_link.xml /
  dialog_lab_settings.xml,已补齐为 16 个路径。

次要
- LabActivity 的 escapeVerticalKeys 上两段连续 Javadoc,第一段与实现自相矛盾,删除。
- LabTvFocusSourceTest 中 Javadoc 与 @Test 挤在同一行,拆行。
- 轮询断言锚点由 "public void run()"(第一处命中的是 mStateRunnable 匿名类)改为
  "private final Runnable mStateRunnable",不再依赖偶然顺序。
- 新增 2 条断言锁死链接环色不得回到固定 accent。

验证:lab 包 31 例 / 0 失败(修复前 5m50s、修复后 1m55s,含资源编译);
git diff --check 干净;交付面 18 路径。

Verification: lab 31/31 unit tests pass after fixes (gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.lab.*, BUILD SUCCESSFUL, resources compiled); git diff --check origin/beta clean; zero-revival line-level and file-level intersections 0; zero-loss 0 missing blobs

Task-Guard: C68-beta-merge-review-dev3-20261014
2026-10-14 11:28:23 +08:00
Silent1566
b2bb4a249b revert(lab): 弹窗按钮保留几何就近上行焦点(撤回钉死到根目录的连线)
Verification: leanback arm64 debug on 127.0.0.1:5559 (1920x1080): reverted the button-focus pin from 4fab3b0360 - LabActivity.java is now byte-identical to 2812ae02e1; with the content scrolled to the bottom, UP from 取消 lands on 不走代理的域名 [453,693][1467,797] (the nearest visible control, matching the screenshot) and UP from 确定 lands on 代理端口; RIGHT still moves between the buttons; gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.lab.* + assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL, lab 31 tests 0 failures

Task-Guard: LAB-TV-REMOTE-FOCUS-20261013
2026-10-14 10:49:19 +08:00
Silent1566
4fab3b0360 fix(lab): 配置源弹窗取消/确定可按 UP 回根目录(按钮在 ScrollView 外需显式连线)
Verification: leanback arm64 debug on 127.0.0.1:5559 (1920x1080): with content scrolled to the bottom, UP from 取消 lands on 根目录 and UP from 确定 (reached by RIGHT) also lands on 根目录, instead of the previous proxyNoProxy; RIGHT still moves 取消 <-> 确定, DOWN from 确定 still reaches the switches, and the dropdown/folder/rootInput chain is unchanged; gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.lab.* + assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL, lab 31 tests 0 failures

Task-Guard: LAB-TV-REMOTE-FOCUS-20261013
2026-10-14 10:07:12 +08:00
Silent1566
2812ae02e1 fix(lab): 配置源可按 DOWN 到文件选择按钮(焦点连线覆盖两种配置源模式)
Verification: leanback arm64 debug on 127.0.0.1:5559 (1920x1080): local mode focus order is now 配置源 -> 文件夹 -> 根目录 -> 取消 -> 开关 (and UP reverses: 开关 -> 文件夹 -> 配置源), so the file-picker button is reachable from the source dropdown (was: DOWN skipped it and went straight to 根目录); URL mode is 配置源 -> URL 输入框 -> 根目录 -> 取消 (UP reverses); the nextFocus chain skips GONE targets so both modes stay correct; gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.lab.* + assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL, lab 30 tests 0 failures

Task-Guard: LAB-TV-REMOTE-FOCUS-20261013
2026-10-14 00:10:25 +08:00
Silent1566
c3c3504d5f fix(lab): 实验室配置源弹窗可用遥控操作(上下键放行 + 确认键切换配置源)
Verification: leanback arm64 debug on 127.0.0.1:5559 (1920x1080): lab config-source dialog is now remote-operable - dropdown DOWN->rootInput->取消->switches and UP->folder->dropdown (was: focus locked on the dropdown, DOWN/UP dead); URL mode dropdown DOWN->input->rootInput->取消; OK on the focused dropdown cycles 本地配置 <-> 网络 URL with the URL field appearing/disappearing (visible=[folder] <-> visible=[url]); gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.lab.* + assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL, lab 29 tests 0 failures

Task-Guard: LAB-TV-REMOTE-FOCUS-20261013
2026-10-13 23:55:28 +08:00
Silent1566
64228db4d0 fix(lab): 导入包弹窗可用遥控操作(粘贴框放行方向键 + 链接聚焦态与初始焦点锚点)
Verification: leanback arm64 debug on 127.0.0.1:5559 (1920x1080): import dialog initial focus is now the 选择离线包文件 link (2dp accent stroke) instead of the multi-line paste box; link->DOWN 取消/导入, link->UP paste box, paste box->DOWN link (was: DOWN/UP/RIGHT all stuck in the paste box, only TAB escaped); command-row run button focus held at t0/t+3s/t+6.2s on 5559 too; gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.lab.* + assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL, lab 28 tests 0 failures

Task-Guard: LAB-TV-REMOTE-FOCUS-20261013
2026-10-13 23:33:00 +08:00
Silent1566
9fe00309e9 fix(lab): 命令弹窗初始焦点锚到按钮,遥控不再卡在命令预览输入框
Verification: leanback arm64 debug on 127.0.0.1:5561: command-sheet initial focus is now sheetBtnEdit (run disabled for the uninstalled package) instead of sheetCommandPreview, edit button shows the 2dp focus stroke, and UP returns to the preview field; gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.lab.* + assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL, lab 27 tests 0 failures

Task-Guard: LAB-TV-REMOTE-FOCUS-20261013
2026-10-13 23:16:35 +08:00
Silent1566
978bfa1e26 fix(lab): 电视版实验室遥控焦点可用(列表差异刷新 + 聚焦高亮 + 图标焦点连线)
Verification: leanback arm64 debug on 127.0.0.1:5561 (mInTouchMode=false): command-row focus held at t0/t+2.5s/t+5.5s/t+6.2s and run-button focus held at t0/t+3s/t+6.2s (pre-fix: stolen within 1.2s); focus strokes visible on row/tab/package-row/icon/run button (screenshot A/B); btnDownload->btnTerminal->btnAddCommand->btnAction reachable by D-pad; gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.lab.* + assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL, lab 26 tests 0 failures

Task-Guard: LAB-TV-REMOTE-FOCUS-20261013
2026-10-13 23:12:52 +08:00
Silent1566
f398ced4dd
Merge pull request #443 from Silent1566/dev1
fix(tv): 修复电视固件 DE 存储下 WorkManager 直启崩溃(dev1 合并 beta PR#439–#442 并复评修正)
2026-10-13 11:48:40 +08:00
Silent1566
0c2eb46d59 docs(c67): 记录交付坐标(双父合并提交 9d58d40588 / recovery tag / 推送 / PR #443)
Verification: PR 443 state=OPEN mergedAt=null mergeable=MERGEABLE changedFiles=7; PR file set matches git diff --name-only origin/beta HEAD (7 items, Compare-Object empty); merge commit 9d58d40588 has 2 parents, second = a51e4085f5; MERGE_HEAD cleared; git diff --diff-filter=D vs beta empty; working tree clean

Task-Guard: C67-beta-merge-review-dev1-20261013-coords
2026-10-13 11:42:28 +08:00
Silent1566
9d58d40588 merge: 合并 origin/beta 最新代码(PR#439-#442)并复评修正 dev1 已提交未推送的 WorkManager 直启修复
Verification: compile leanback+mobile Arm64_v8aDebug(UnitTest)JavaWithJavac passed; both process UnitTestManifest passed; leanback 4227 tests 8 failures, mobile 5039 tests 7 failures, all 15 classified as Windows environment (CRLF checkout + File.renameTo) on beta-identical files; following suite 11 classes 0 failures on both flavors incl FollowingWorkStartupSourceTest 3/3; merged manifest drops WorkManagerInitializer and keeps FollowingWorkInitializer; 2 mutations turn the new locks red; baseline sha256 restored

Task-Guard: C67-beta-merge-review-dev1-20261013
2026-10-13 11:38:49 +08:00
Silent1566
a51e4085f5
Merge pull request #442 from Silent1566/dev3
dev3:合并 beta 最新代码(无新提交)并复评修正硬解自动软解回落的开关门控与硬解判据
2026-10-13 08:39:23 +08:00
Silent1566
b3393c6092
Merge pull request #441 from Silent1566/dev2
merge: dev2 合并 beta 最新代码(PR#440)并复评 TV 横幅按 320x180dp 出图
2026-10-13 08:39:07 +08:00
Silent1566
bc756d1d7a fix(tv): guard WorkManager against direct boot storage
Verification: compileLeanbackArm64_v8aDebugJavaWithJavac passed; processLeanbackArm64_v8aDebugUnitTestManifest passed and final manifest removed androidx.work.WorkManagerInitializer; FollowingWorkStartupSourceTest 3/3 passed; diff-check clean; no device install

Task-Guard: WORKMANAGER-DIRECT-BOOT-20261012
2026-10-13 00:43:12 +08:00
Silent1566
fc790db709 docs(c66): 记录 dev3 合并评审交付坐标(修正提交 434fb60fe8 / recovery tag / 推送 / PR #442)
Verification: 文档-only 闭包提交:记录 C66 交付坐标(提交 434fb60fe8 / recovery tag / 推送 origin/dev3 / PR #442)

Task-Guard: C66-dev3-doc-coordinates
2026-10-13 00:39:44 +08:00
Silent1566
434fb60fe8 merge: 合并 origin/beta 最新代码(无新提交)并复评修正硬解自动软解回落的开关门控与硬解判据
Verification: Leanback ARM64 编译 + 6 个测试类 28/28 通过(BUILD SUCCESSFUL in 47s, exit 0);零丢失缺失 0 / 逐字节一致 5042;零复活交集 6 条经逐条核查全为样板行

Task-Guard: C66-beta-merge-review-dev3-20261013
2026-10-13 00:38:03 +08:00
Silent1566
4913d6ff23 docs(c65): 记录 dev2 交付坐标(合并提交 b6c36bdf1a / recovery tag / 推送 / PR #441)
纯文档收尾:把 C65 任务文档的 Recovery anchor 由「下一步:创建 PR」更新为本任务的
实际交付坐标,使这份持久化执行状态与实际结果一致。

- 合并提交:b6c36bdf1a37fa7d4334efe20c1cd20672f32450(双父 c0ba017885 + 37bca76737)
- 恢复标签(本地):recovery/C65-beta-merge-review-dev2-20261012/20261012160732-b6c36bdf1a37
- 推送:origin/dev2 快进 8b5f8e9026..b6c36bdf1a
- PR:#441(dev2 -> beta,OPEN / MERGEABLE,只创建不合并)

无代码、资源、构建或行为改动,因此不重跑测试。

Verification: 交付坐标已逐条核对:gh pr view 441 => state=OPEN, isDraft=false, baseRefName=beta, headRefName=dev2, mergeable=MERGEABLE;git rev-list --left-right --count origin/dev2...HEAD => 0 0(已推送且无分叉);git tag --list 'recovery/C65*' 命中 recovery/C65-beta-merge-review-dev2-20261012/20261012160732-b6c36bdf1a37;本次为纯文档改动,无代码/资源/构建行为变化,故不重跑测试

Task-Guard: C65-delivery-coords-dev2
2026-10-13 00:20:07 +08:00
Silent1566
b6c36bdf1a merge: 合并 origin/beta 最新代码(PR#440 已把 dev2 并入 beta)并复评 dev2 已提交未推送的 TV 横幅高清化改动
合并:git merge --no-commit --no-ff origin/beta(37bca767373804c381ffbe3e8f565ebf33b4b281,
Merge pull request #440 from Silent1566/dev2),0 冲突;MERGE_HEAD 即 beta tip。
合并基点为上一轮的合并提交 8b5f8e9026。

本轮结构性特点:beta 相对合并基点未新增任何内容,合并是内容中性的。
- git diff --name-status 8b5f8e9026 origin/beta => 空
- 合并后索引树 git write-tree = a186f1c817
- dev2 tip 树 c0ba017885^{tree}  = a186f1c817
两者哈希相同,即本次合并只把 beta tip 记入 dev2 历史(消除 1 behind),不改变任何一个文件。
原因是上一轮 PR #440 已把 dev2(8b5f8e9026)并入 beta,beta 侧唯一增量就是那个合并提交本身。

零复活 / 零丢失(三层证据;用户明确要求远端已移除/回退的提交不得顺带提交上去):
1. 净差异:git diff --name-status origin/beta dev2 仅 c0ba017885 自身的 7 个路径。
2. beta 侧改动:git diff --name-status 8b5f8e9026 origin/beta 为空 => beta 未删除、未修改任何
   内容,结构上不可能复活。
3. 路径状态:上述 7 个路径在 beta 树中全部为 M(既有文件)、无 A => 不存在「把已删文件加回来」
   的形态。另回溯 beta 历史上触及这 7 个路径的 23 个单亲提交,逐条 numstat 均为前向新增/改写,
   无删除交集。

复评(1 轮通过,未发现缺陷):对象为 dev2 已提交未推送的 c0ba017885
「fix(tv): TV 横幅按 320x180dp 出图,消除启动器放大导致的字标锯齿」,改动集 7 路径
(5 档 mipmap-*/ic_banner.png + AppBrandingContractTest.java + scripts/gen_app_icon.py),
实质是把 BANNER_SIZES 由 160x90/240x135/320x180/480x270/640x360 px(=160x90 dp)
上调为 320x180/480x270/640x360/960x540/1280x720 px(=320x180 dp)。核查结论:
1. dp 尺寸与同族入口自洽:AndroidManifest 的 history 别名用矢量 @drawable/ic_banner_history,
   声明为 320dp x 180dp;改动前同一 App 两个启动器入口的横幅 dp 尺寸并不一致(矢量 320x180 dp、
   位图 160x90 dp),位图侧才是异类。仓库自身即为佐证,不必只依赖外部文档。
2. 机理与用户症状吻合:位图按 160x90 dp 出图时,每一档密度交给启动器的位图都只有磁贴 dp 尺寸的
   一半,启动器只能统一放大 2 倍,字标必然出锯齿——正是用户报的「分辨率不够、锯齿很严重」。
   改为 320x180 dp 后每档 1:1。
3. 生成器确在目标分辨率绘制:render_banner(w,h) 的布局求解全部发生在最终像素尺寸上
   (_render_mark 按目标 O 直径直接绘制、_render_text 先探针量墨迹再定字号),无图层被放大。
4. 补测关闭一处验证缺口:提交记录的「5 档实测」只列了像素尺寸,放大后的档位从未实测过
   (C35 原始评审只测过 160x90 dp 那一版)。本次用生成器自身的
   make_gradient(max(w,h)).resize((w,h), LANCZOS) 复现背景并与已提交 PNG 逐像素求差,
   掩膜即真实墨迹:5 档全部完全不透明、含真实墨迹、落在四边各 6.1% 安全区内、宽高比精确 16:9。
   xhdpi 墨迹宽/宽 0.8781 相对安全区 0.8780 仅溢出 0.04 px,在求解器自身 total<=safe_w+0.5
   余量内,属亚像素舍入而非越界。
5. 放大档位是独立渲染而非放大:墨迹像素数随档位按面积增长但始终略低于纯面积倍率
   (0.9950 / 0.9770 / 0.9821 / 0.9893);若为插值放大应≈1.0 甚至更高。
6. 字节级确定性:新 mdpi/hdpi/xhdpi 与旧 xhdpi/xxhdpi/xxxhdpi 逐字节一致
   (9639 / 16295 / 24216 B),净新增 xxhdpi 960x540 与 xxxhdpi 1280x720 两档。
7. 无残留引用:本次真正淘汰的 160x90/240x135 在 app/src 与 scripts 零命中。
8. 代价:5 档横幅合计 60930 -> 155319 B(+92 KiB);运行期只解码匹配密度的那一档,
   xhdpi 单张 ARGB_8888 约 0.23 -> 0.92 MB,对启动器磁贴可接受。

非阻塞观察(记录不修):do_preview() 仍以 320x180 出预览,改动后它已是最小档而非代表档;
脚本注释把 xhdpi 320x180 px 描述为「API<=25 旧式兜底最小尺寸」属规范转述,本环境无法复核
(文档页为 JS 渲染壳、Web 检索不可用),记为未复核表述,改动正确性不依赖它。

Verification: 合并 0 冲突且内容中性(合并后索引树 = dev2 tip 树 = a186f1c817b656c213c17b87da2111b3e3c86efa;git diff --name-status 8b5f8e9026 origin/beta 为空,零复活/零丢失三层证据通过);./gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.setting.AppBrandingContractTest => BUILD SUCCESSFUL (7m41s),报告 tests=13 failures=0 errors=0 skipped=0;横幅补测(复现背景逐像素求差)5 档全部不透明/含真实墨迹/在 6.1% 安全区内/精确 16:9,墨迹面积倍率 0.9950/0.9770/0.9821/0.9893 均 <1.0 证独立渲染;新 mdpi/hdpi/xhdpi 与旧 xhdpi/xxhdpi/xxxhdpi 逐字节一致

Task-Guard: C65-beta-merge-review-dev2-20261012
2026-10-13 00:07:29 +08:00
Silent1566
3c230bac1e merge: 合并 webhtv/webhtv 分支 Silent1566 的 3 个硬解自动软解回落提交并修订 Exo 解码合同
Verification: Leanback ARM64 Java 编译 :app:compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL exit 0 (5m57s);解码模式定向测试 :app:testLeanbackArm64_v8aDebugUnitTest 覆盖 CompatFfmpegVideoRendererSourceTest + MpvHardwareDecodePolicyTest + ExoAudioCodecSelectorTest,9 tests / 0 failures / 0 errors,BUILD SUCCESSFUL exit 0 (1m21s);合并后 3 个文件相对源提交 590942503a 的新逻辑逐字节一致(无 +/- 命中 needsSoftDecode/hasHardwareVideoDecoder/softDecodeFallbackAttempted 等新增符号);设备复测未执行(无现场源 URL),边界见 docs/C65-player-hwdec-auto-soft-fallback-merge-20261012.md 第5节

Task-Guard: C65-player-hwdec-auto-soft-fallback-merge-20261012
2026-10-12 23:54:41 +08:00
Silent1566
c0ba017885 fix(tv): TV 横幅按 320x180dp 出图,消除启动器放大导致的字标锯齿
用户反馈横屏图标(Android TV banner)分辨率不够、锯齿严重。横幅此前按官方
xhdpi 最小尺寸 320x180 px(即 160x90 dp)出图,而启动器磁贴按 dp 绘制:
1080p/xhdpi 磁贴要 640x360 px、4K 要 1280x720 px,启动器只能把位图放大,
字标因此出现锯齿。

改为每档按 320x180 dp 出图(mdpi 320x180 起,xxxhdpi 1280x720),即官方
最小尺寸的 2 倍:启动器要么 1:1 取用、要么缩小,两条路径都不再放大。
图像内容未改动——新的 mdpi/hdpi/xhdpi 与旧的 xhdpi/xxhdpi/xxxhdpi 逐字节
一致,仅新增 xxhdpi 960x540 与 xxxhdpi 1280x720 两档更大的图,APK 约 +94 KB。

Verification: bash ./gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.setting.AppBrandingContractTest => 13 tests, 0 failures, 0 errors(含 tvBannerIsProvidedPerDensityAtFullResolution 按新尺寸逐档断言 + 16:9);5 档横幅实测 320x180/480x270/640x360/960x540/1280x720;新 mdpi/hdpi/xhdpi 与旧 xhdpi/xxhdpi/xxxhdpi 逐字节一致

Task-Guard: tv-banner-hidpi
2026-10-12 23:09:12 +08:00
Silent1566
590942503a fix(player): 修正硬解回软解对 Dolby Vision 等改写 mime 渲染器的误判
hasHardwareVideoDecoder 原先只要平台未列出该 mime 的硬件解码器就返回 false。
但 MediaCodecSelector.DEFAULT 即 MediaCodecUtil::getDecoderInfos,其内部
getDecoderInfosInternal -> getCodecMimeType 只匹配真正声明该 mime 的编解码器,
因此非原生 Dolby Vision 设备上 video/dolby-vision 返回空列表;而 ExoUtil 的
DV5 GPU 渲染器与 Dolby Vision HDR10 回退渲染器仍会把该轨判定为 FORMAT_HANDLED
(内部改写为 video/hevc 交给硬件 HEVC 解码)。needsSoftDecode 于是把这条本已
硬解的 Dolby Vision 轨误判为"硬解无硬件通路",强制切到软解并重建播放器。

改为:只有平台确实列出了该 mime 的解码器且全部为软件解码器时才返回 false;
空列表表示平台不声明该 mime,可能由应用自有渲染器接管,按"未证实"处理,不再
改变用户可见的解码模式。AVS3 等"完全无解码器"场景仍由 needsSoftDecode 的
decodable == null 分支触发软解,行为不变。

同时修正 ExoUtil 中"硬解绝不注册软件回退"的注释:该约束只适用于扩展渲染器
(FFmpeg);getVideoCodecSelector 保留平台软件解码器作为最后兜底是有意为之,
且正是 PlayerManager 判定需要切软解的信号,两者必须保持一致。

Verification: 编译 :app:compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL exit 0
Task-Guard: PLAYER-HWDEC-REVIEW-FIX-01

Verification: 编译 :app:compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL exit 0;证据链:MediaCodecSelector.DEFAULT = MediaCodecUtil::getDecoderInfos(严格 mime 匹配),getDecoderInfosInternal 经 getCodecMimeType 只匹配声明该 mime 的编解码器,故非原生 DV 设备上 video/dolby-vision 返回空列表;ExoUtil DV HDR10 回退渲染器(1102-1108)与 ExoDv5GpuRenderer 仍以 video/hevc 判定该轨 FORMAT_HANDLED。未做设备实测(本次无可用设备)。

Task-Guard: PLAYER-HWDEC-REVIEW-FIX-01
2026-10-12 22:52:10 +08:00
Silent1566
bd24f9d53d fix(player): 硬解无硬件解码器时自动切换软解模式
Verification: 编译 :app:compileLeanbackArm64_v8aDebugJavaWithJavac exit 0;设备 NX627J(127.0.0.1:5563) 覆盖安装后新进程以硬解起播 4K HEVC,自动切换为软解:日志 decode=software、decoder=OMX.google.hevc.decoder、firstFrameMs=2894;OSD 按钮由硬解变为软解,诊断面板 配置 EXO/软解/软解降负载 EXO跳帧滤波低分辨,画面正常渲染(36:10/44:08),重缓冲由 6 次降至 2 次

Task-Guard: PLAYER-HWDEC-MODE-ALIGN-01
2026-10-12 21:56:43 +08:00
Silent1566
37bca76737
Merge pull request #440 from Silent1566/dev2
merge: 合并 beta 最新代码(PR#439)并复评详情页季集交接改动
2026-10-12 21:44:11 +08:00
Silent1566
edb1cf8eaa fix(player): 硬解无硬件解码器时不再丢弃视频轨并自动回落软解
Verification: 编译 :app:compileLeanbackArm64_v8aDebugJavaWithJavac exit 0;设备 NX627J(127.0.0.1:5563) 覆盖安装 leanbackArm64 debug 后,硬解模式诊断面板显示 decoder OMX.google.hevc.decoder、配置 EXO/硬解、3840x1920 HEVC 画面正常渲染(30:03/44:08),未触发自动软解回落

Task-Guard: PLAYER-HWDEC-FALLBACK-01
2026-10-12 21:24:42 +08:00
Silent1566
8b5f8e9026 merge: 合并 origin/beta 最新代码(PR#439 dev4 对话框取色修复)并复评 dev2 已提交的季集交接改动
合并:git merge --no-commit --no-ff origin/beta(b0b7f1d78b5e394c0c9635a8b9c71b4417b06e25,
Merge pull request #439 from Silent1566/dev4),0 冲突;MERGE_HEAD 即 beta tip。
纳入 beta 侧 4 个提交(含 dev4 的 fix(theme): TV 对话框面板与前景取自同一张色表 及两份文档)。

零复活 / 零丢失(三层证据):
1. 合并树 ≡ beta tip + dev2 自身 4 路径:git diff --name-only origin/beta 仅 4 个 dev2 文件。
2. 零丢失(blob 级):beta tip 5047 个文件逐个比对合并结果树,缺失 0、逐字节一致 5043,
   差异仅 dev2 自己的 4 个路径;且 beta 相对 merge-base 从未改过这 4 个路径。
3. 零复活(严格判据,按「文件+行」):beta 历史上 27 个单亲回退提交共删除 4472 条
   (文件,行) 记录,与 dev2 自身新增的 282 条记录求交集 = 0。

复评(2 轮)发现并修复 3 项真实问题,全部落在 dev2 已提交的季集交接改动上:
1. F1(阻塞)单季兜底会覆盖用户显式「保持原始剧集列表」(MANUAL_FLAT):解析器只在
   MANUAL_FLAT 时返回 Status.FLAT,旧实现对非 RESOLVED 一律不绑定;兜底只要 TMDB 恰有一个
   正片季就绑定,播放页会挂 TMDB 集标题并经 TmdbEpisodeSorter.sort 原地重排选集,
   还与详情页(该模式 availableSeasonNumbers 返回空、不画卡片)表现相反。
2. F2(重要)单季兜底会覆盖解析器「已看到相反证据」的 AMBIGUOUS:TMDB 只有第 1 季而源站剧名/
   集名明确指向第 2 季时,解析器故意返回 title_season_missing_from_tmdb /
   explicit_season_missing_from_tmdb / title_and_source_season_conflict 拒绝绑定,
   兜底会无视证据把第 1 季标题静默挂到第二季源列表。
   F1+F2 同一处修复:兜底前加来源白名单,只对 Source.REQUEST(请求季不在 TMDB)与
   Source.NONE(源站路径显式关闭启发式)生效,其余来源一律不兜底。
3. F3(重要)单季兜底会把 MULTI_SLICE 的分段结论降级成单季路径:切片路径以 null 作为季号
   一致性校验的期望值,兜底返回非 null 会让调度侧改走单季绑定、执行侧的切片校验又必然否决,
   整个分段(含特别篇)一集都绑不上。修复:兜底前先判 MULTI_SLICE。
并同步修复 seedSeasonEpisodes 的注释与实现不符(「不会发起网络请求」在内存未命中时不成立)。

复评第 2 轮(修复后复审):MULTI_SLICE 守卫与来源白名单按 TmdbSeasonResolver.resolve 的
全部 source/reason 逐条推演,确认既不过宽(不再覆盖 FLAT 与带相反证据的 AMBIGUOUS)也不过窄
(REQUEST + requested_season_missing_from_tmdb 与 NONE + heuristic_guessing_disabled 两条
本次要救的路径仍能兜底);代次校验自洽(MULTI_SLICE 两侧都期望 null,RESOLVED 两侧都取
selectedSeason);无新增阻塞问题。

验证:双 flavor 编译 + 双 flavor AndroidTest 编译 + 全量 JVM 套件。

Verification: 双 flavor Java 编译 + 双 flavor AndroidTest 编译 BUILD SUCCESSFUL;双 flavor 全量 JVM 套件:leanback 4214 tests / 8 failures / 3 skipped、mobile 5026 / 7 / 3;相对合并前(leanback 4211/8/3、mobile 5026/7/3)增量仅 leanback +3 例 = beta 带入的 TvDialogPaletteModeTest 3 例且全绿,失败集合与既有环境基线(docs/C58-beta-merge-review-dev2-20261011.md:leanback 7 类 8 例、mobile 6 类 7 例;根因 core.autocrlf=true 的 CRLF 检出使含裸 \n 的守卫断言失配,以及 Windows renameTo 语义)同类同数、零新增;TmdbDetailCacheTest 14 例在两 flavor 全绿;合并零复活严格判据(文件+行)交集 0、零丢失 blob 级比对缺失 0 且逐字节一致 5043/5047

Task-Guard: C64-beta-merge-review-dev2-20261012
2026-10-12 20:40:16 +08:00
Silent1566
d5ceea9682 fix(tv): 详情页已解析季集随详情快照交接给播放页,并补单季剧季号兜底
播放页的选集卡片依赖 Episode.tmdbEpisode,而该字段只有播放页自己再取一次分季才会写入。
详情页跳转前已经取过同一季(否则它自己也画不出卡片),数据却被丢弃,于是详情页有卡片、
播放页退化成纯文本按钮;那次重复请求失败时表现更明显。

改动:
1. TmdbDetailCache 新增 5 参 put 与 Entry.getSeasonEpisodes():季集数据随详情快照交接,
   构造与读取都做防御性拷贝,过滤负季号与空季。
2. TmdbDetailActivity 跳转播放时把 tmdbSeasonEpisodes 一并交出。
3. TmdbUIAdapter 新增 seedSeasonEpisodes():把快照预填进 seasonEpisodeCache,键与
   seasonEpisodes() 的读取键同源;load(item, vod) 与 load(item, vod, cached) 两条入口都调用。
4. loadSource() 在 applySourceBundle 之后补一次 scheduleStartupEpisodeLoad():内嵌分季数据
   只写缓存不会给 Episode 挂 tmdbEpisode,不补绑定则内嵌 payload 的站点在播放页永远是纯文本选集。
5. currentEpisodeMetadataSeason() 增加 soleOrdinarySeason() 兜底:恰好一个正片季时不再因
   解析器返回 AMBIGUOUS 就放弃逐集绑定。

复评 2 轮发现并修复 3 项真实问题(已落地并加回归锁):
1. F1(阻塞)兜底会覆盖用户显式「保持原始剧集列表」(MANUAL_FLAT):解析器只在 MANUAL_FLAT
   时返回 Status.FLAT,旧实现对非 RESOLVED 一律不绑定;新兜底只要 TMDB 恰有一个正片季就绑定,
   播放页会挂 TMDB 集标题并经 TmdbEpisodeSorter.sort 原地重排选集,还与详情页(该模式
   availableSeasonNumbers 返回空、不画卡片)相反。
2. F2(重要)兜底会覆盖解析器「已看到相反证据」的 AMBIGUOUS:TMDB 只有第 1 季而源站剧名/集名
   明确指向第 2 季时,解析器故意返回 title_season_missing_from_tmdb /
   explicit_season_missing_from_tmdb / title_and_source_season_conflict 拒绝绑定,兜底会无视
   证据把第 1 季标题挂到第二季源列表。
   F1+F2 同一处修复:兜底加来源白名单,只对 Source.REQUEST(请求季不在 TMDB)与 Source.NONE
   (源站路径显式关闭启发式)生效,其余来源一律不兜底。
3. F3(重要)兜底会把 MULTI_SLICE 的分段结论降级成单季路径:切片路径以 null 作为季号一致性
   校验的期望值,兜底返回非 null 会让调度侧改走单季绑定、执行侧的切片校验又必然否决,整个
   分段(含特别篇)一集都绑不上。修复:兜底前先判 MULTI_SLICE。

测试:TmdbDetailCacheTest 由 6 例扩到 14 例(交接快照携带、防御性拷贝、空季与负季号过滤、
内层不可变、无季集返回 null,以及 3 条消费端源码守卫)。mobile 源集已存在同名
TmdbUIAdapterTest,共享源集不能再建同名类,故消费端守卫并入本文件。

验证:双 flavor Java 编译 BUILD SUCCESSFUL;双 flavor 全量 JVM 套件 leanback 4211 tests /
8 failures / 3 skipped、mobile 5026 / 7 / 3,失败集合与本机既有环境基线
(docs/C58-beta-merge-review-dev2-20261011.md:leanback 7 类 8 例、mobile 6 类 7 例)同类同数、
零新增;根因是 core.autocrlf=true 检出为 CRLF 而守卫断言使用裸 \n,以及 Windows renameTo 语义,
与本次改动无关。新增 8 例在两 flavor 全绿。

已知限制(不在本轮范围,留待合并步骤处理):源站 payload 分支(VideoActivity 的 RENDERABLE
分支走 loadSource)不消费交接快照,对「payload 只带季集数、不带 episodes」的站点,详情页已取过的
季会被丢弃,播放页补绑定会再发一次同季请求;修复需要改 scope 外的 VideoActivity(leanback + mobile)。

Verification: 双 flavor Java 编译 BUILD SUCCESSFUL;双 flavor 全量 JVM 套件:leanback 4211 tests/8 failures/3 skipped、mobile 5026/7/3,失败集合与既有环境基线(C58 文档 leanback 7 类 8 例、mobile 6 类 7 例)同类同数、零新增(根因:core.autocrlf=true 的 CRLF 检出使裸 \n 守卫断言失配、Windows renameTo 语义);新增 TmdbDetailCacheTest 14/14 双 flavor 全绿(含 3 条消费端源码守卫);复评 2 轮修复 F1(MANUAL_FLAT 被兜底覆盖,阻塞)/F2(带相反证据的 AMBIGUOUS 被覆盖)/F3(MULTI_SLICE 被降级),并补 8 条回归锁用例

Task-Guard: EPISODE-CARD-HANDOFF-20261012
2026-10-12 19:44:54 +08:00
Silent1566
b0b7f1d78b
Merge pull request #439 from Silent1566/dev4
dev4:合并 beta 最新代码(PR#438 Android 7 兼容修复)并复评 TV 对话框取色同源修复
2026-10-12 13:05:49 +08:00
Silent1566
139f2ab5d1 docs(c63): 记录 dev4 合并评审交付坐标(合并提交 a25ac9dd72 / recovery tag / 推送 / PR #439) 2026-10-12 13:03:15 +08:00
Silent1566
a25ac9dd72 merge: 合并 origin/beta 最新代码(PR#438 dev1 Android 7 兼容修复)并复评 dev4 已提交未推送的 TV 对话框取色修复
把远端 beta 最新 tip 92a0ca0bbe5(PR #438)合入 dev4,自动合并无冲突,0 未解决冲突。
beta 本轮增量 11 个提交、10 个路径:Android 7(API 24/25)真机排查确认的三个缺陷修复
——ViewCompat 守卫 setImportantForAutofill、LabRunner 用 exitValue() 替代
Process.isAlive()、MpdSanitizer 改用 API 1 的 StringBuffer matcher 重载——外加 3 个
契约测试与 2 篇文档。不执行 cherry-pick;beta 本轮未删除任何路径,两条分支的删除面
均为空,不存在被顺带重新提交的回退内容。

复评 dev4 已提交未推送的 4c5f4dc8421(TV 对话框面板与前景取自同一张色表):
applyFromPreferences(activity) 现在把同一个 Activity context 交给 frozenPalette 与
resolveWith,baseline/current 不可能再分叉;全仓生产调用方只有 leanback/mobile 的
BaseActivity 与 FollowingActivity,均传 this;resolveFromPreferences()/refresh() 无
生产调用方,null 分支保持原语义;App.java 在进程启动期即应用强制夜间模式,时序正确。

三轮评审未发现必修缺陷。验证:leanback 定向 192 例、mobile 定向 180 例,
0 failure / 0 error;双 flavor androidTest 源集编译 BUILD SUCCESSFUL。

评审记录见 docs/C63-beta-merge-review-dev4-20261012.md。
2026-10-12 13:01:18 +08:00
Silent1566
4c5f4dc842 fix(theme): TV 对话框面板与前景取自同一张色表
resolvedDark()/compiledDarkPalette() 之前用进程级应用上下文加原始系统夜间位判
深浅,而 AppCompatDelegate 的强制夜间模式只改写它代理的 Activity 资源(API < 31
不改写应用上下文)。TV 侧 applyNightModeToApp() 又按产品默认强制 MODE_NIGHT_YES,
于是系统日间的电视上「新增点播」面板取到日间表、同屏前景取到夜间表:实测 info
1.06:1、输入框提示 1.39:1、描边按钮文字 1.41:1。

改为按真正 inflate 的 Activity 上下文取表,baseline 与 current 同步对齐(无
Activity 的入口保持原行为)。同一台模拟器前后对照:info 10.48:1、提示 7.93:1、
描边按钮 7.86:1、主按钮 7.5:1;修复后日间模式渲染与夜间模式渲染逐字节相同。
2026-10-12 12:26:10 +08:00
Silent1566
92a0ca0bbe
Merge pull request #438 from Silent1566/dev1
merge(dev1): 合并 beta(PR#437)并复评 Android 7 支持改动(补齐回归锁 + 订正文档)
2026-10-12 11:14:44 +08:00
Silent1566
04b32d4e14 docs(c62): 记录交付坐标(双父合并提交 11578a6ee / recovery tag / 推送 / PR #438)
收尾补记:合并提交两个父(8daf00907 + af9a18fa4 = 当时 beta tip)、origin/dev1 已同步、PR #438 状态 OPEN/未合并/文件集 10 项与本地净差异逐项一致、收尾复核 beta tip 未推进。

Verification: 收尾补记:双父合并提交 11578a6ee(父1 8daf00907 / 父2 af9a18fa4 = beta tip)已推送 origin/dev1;PR #438 已创建(state=OPEN、mergedAt=null、mergeable=MERGEABLE、changedFiles=10),PR 文件集与 git diff --name-only origin/beta HEAD 逐项一致;收尾 fetch 后 beta tip 仍为 af9a18fa4;工作区仅 .scratch-goal/;本轮无生产代码行为改动

Task-Guard: C62-beta-merge-review-dev1-20261012-coords
2026-10-12 11:11:51 +08:00
Silent1566
11578a6eee merge: 合并 origin/beta 最新代码(PR#437 dev4 屏显对比度修复)并复评 dev1 已提交未推送的 Android 7 支持改动
复评(2 轮)发现并修复 3 项真实问题:
1. 三处 Android 7 修复缺少已提交的回归锁:新增 3 个用例类共 6 例(源码守卫 + 语义用例),并用 4 处变异验证可转红;
2. 订正 android7 文档中 MpdSanitizer 的影响机制:NoSuchMethodError 属 Error,MpdSanitizer.sanitize() 与 Source.fetch() 的 catch(Exception) 都捕不到(后者只包住 extractor 调用),原文「被捕获后静默返回未处理 URL」不成立;
3. 订正该文档的 NewApi 计数(51 处/25 处 API 26,非 52/23)与用例数口径(9197 系两 flavor 合计,leanback 单任务实测 4191)。

验证:双 flavor 编译 + AndroidTest 编译 + 全量 JVM 套件 leanback 4203/0、mobile 5018/0(各 +12,可逐项解释);UI token 门禁 PASS(violations 零新增);零复活三层证据通过(合并树 ≡ beta tip + dev1 自身 6 路径,严格复活行 0,beta 侧 0 文件丢失)。

Verification: 双 flavor Java 编译 + 双 flavor AndroidTest 编译 BUILD SUCCESSFUL;全量 JVM 套件 leanback tests=4203 failures=0 errors=0、mobile tests=5018 failures=0 errors=0(相对 C61 基线 4191/5006 各 +12,= 本轮 6 个回归锁用例 + beta PR#437 带入 6 例);变异验证:4 处缺陷形态变异使 6 例中 4 例转红,恢复后 sha256 一致且全绿;javap 产物核对 Process#isAlive=0、Matcher(StringBuffer) 重载=10、ViewCompat.setImportantForAutofill=2;scripts/check_ui_tokens.sh PASS violations=1(零新增);零复活三层证据:合并树 ≡ beta tip + 6 个 dev1 路径、严格复活行 0、beta 5039 文件中 5034 逐字节一致且 0 丢失;git diff --check EXIT=0

Task-Guard: C62-beta-merge-review-dev1-20261012
2026-10-12 11:10:13 +08:00
Silent1566
8daf00907e docs(android7): add real Android 7.1.2 device verification
Add section 9 recording the end-to-end validation on the real Android 7.1.2
(API 25) device at 192.168.50.3:5587, which confirms the audit's core findings
against real hardware rather than symbol analysis alone:

- the shipped libmpv.so cannot be dlopened on API 25 (cannot locate symbol
  vkGetPhysicalDeviceQueueFamilyProperties2) while the identical command
  succeeds on API 28
- the device's own libvulkan.so exports none of the six required Vulkan 1.1
  entry points (193 ARM / 191 x86 exports inspected)
- control experiments separate the Vulkan dependency from environmental noise
- on API 25 the pre-fix View#setImportantForAutofill is absent while
  ViewCompat.setImportantForAutofill returns early, confirming the fix

Also note the houdini translation-layer artifact (St3__1 namespace) so future
native-library triage on translated devices uses the target-architecture
linker instead of an x86 app_process probe.

Verification: real Android 7.1.2 (API 25) device verification, documented in the audit's new section 9: (1) decisive A/B - the shipped v7a libmpv.so dlopened via the device's own ARM linker fails with 'cannot locate symbol vkGetPhysicalDeviceQueueFamilyProperties2' on API 25 but loads OK on API 28 with the identical command; (2) the device's own /system/lib/arm/libvulkan.so (193 exports) and /system/lib/libvulkan.so (191 exports) export none of the 6 required Vulkan 1.1 symbols; (3) control experiments confirm the probe is valid (OK on API 28) and the failure is Vulkan-specific (a probe without the Vulkan dependency loads OK on API 25); (4) A/B on the fixed defect: on API 25 'View#setImportantForAutofill' is absent (NoSuchMethodException) while ViewCompat.setImportantForAutofill exists and returns early without touching the API-26 method; (5) leanback/armeabi-v7a debug APK built and installed via scripts/build_arm64_debug_install.sh and cold-started with 0 FATAL EXCEPTION / NoSuchMethodError. Documentation-only commit; no production code changed.

Task-Guard: ANDROID7-REALDEVICE-EVIDENCE
2026-10-12 10:24:29 +08:00
Silent1566
af9a18fa47
Merge pull request #437 from Silent1566/dev4
dev4:合并 beta 最新代码(PR#434 焦点环主题 / PR#435 停播 / PR#436 更新判定与数据库完整性)并复评屏显对比度与 Custom CSP 主题修复
2026-10-11 17:29:28 +08:00
Silent1566
29c2810638 docs(android7): align summary wording with the three fixed defects
The header and section 5 conclusions still described a single unfixed
Java-layer defect. Update them to state that three real defects were found and
fixed, leaving the libmpv.so Vulkan 1.1 / BIND_NOW blocker as the only
remaining Android 7 gap.

Verification: documentation-only wording correction: the header conclusion and the section 5 summary now state that three real defects were found and fixed (two of which crashed on Android 7) instead of implying a single remaining Java-layer defect. The only remaining Android 7 blocker is the bundled libmpv.so Vulkan 1.1/BIND_NOW issue. No production code changed.

Task-Guard: ANDROID7-AUDIT-SUMMARY-FIX
2026-10-11 17:28:04 +08:00
Silent1566
0b036f9d01 docs(android7): close out the three fixed defects and restate the MPV blocker
Record the per-fix verification evidence for the ViewCompat API guard, the
LabRunner.isAlive API-1 helper, and the StringBuffer Matcher overloads, plus
the aggregate lint error reduction from 254 to 236. The remaining Android 7
blocker is unchanged: the bundled libmpv.so references Vulkan 1.1 core entry
points that the platform libvulkan.so only exports from API 28, combined with
BIND_NOW eager binding.

Verification: documentation-only closure update: the audit document now records all three fixed defects with their individual verification evidence (ViewCompat API guard for the Git cloud/Remote trust dialog crash, LabRunner.isAlive API-1 helper replacing Process#isAlive, and StringBuffer Matcher overloads replacing the API-34 StringBuilder overloads), the aggregate lint error reduction 254->236, and restates the single remaining Android 7 blocker (bundled libmpv.so requiring API-28 Vulkan 1.1 entry points with BIND_NOW eager binding). No production code changed in this commit.

Task-Guard: ANDROID7-AUDIT-DOC-CLOSURE
2026-10-11 17:27:15 +08:00
Silent1566
8bb62a7906 fix(mpd): use API-1 StringBuffer Matcher overloads instead of API 34
MpdSanitizer passed StringBuilder buffers to Matcher#appendReplacement and
Matcher#appendTail. Those overloads only exist since API 34 and no desugaring
backports java.util.regex, so on every device below API 34 the call threw
NoSuchMethodError. Source.fetch() catches Exception and silently returned the
unsanitized URL, so MPD frame-rate/AdaptationSet-id repair quietly did nothing
(instead of crashing playback).

Switch all five buffers to StringBuffer, whose overloads have existed since
API 1 and behave identically here (single-threaded local use).

Verification: MpdSanitizer: all 5 Matcher#appendReplacement and 5 appendTail call sites switched from the StringBuilder overload (API 34) to the StringBuffer overload (API 1), which is semantically identical for these single-threaded local buffers and matches the existing LabRunner.expandNested pattern; javap on the compiled class shows 0 appendReplacement(StringBuilder)/appendTail(StringBuilder) and 5+5 StringBuffer variants; :app:compileLeanbackArm64_v8aDebugJavaWithJavac + :app:compileMobileArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; lint NewApi errors for those symbols gone and total lint errors dropped 246->236; scripts/build_arm64_debug_install.sh --flavor leanback --abi arm64-v8a built and installed over the existing package on 192.168.50.3:5555; decisive device runtime test: invoking MpdSanitizer.sanitize() on an invalid-frameRate MPD and an invalid-AdaptationSet-id MPD now returns OK with changed=true, whereas the same probe against the pre-fix APK threw java.lang.NoSuchMethodError: appendReplacement(Ljava/lang/StringBuilder;Ljava/lang/String;)

Task-Guard: ANDROID7-MPDSANITIZER-MATCHER-API34
2026-10-11 17:26:29 +08:00
Silent1566
656d3dcbc8 fix(android7): replace Process#isAlive (API 26) with an API-1 compatible check
LabRunner.isRunning/runningCount/runningKeys and three LabTerminalActivity
call sites used Process#isAlive(), which only exists since API 26. Those paths
run from LabAutoStart on app startup (and from the lab list adapters), so an
Android 7 user with an installed lab entry crashed with NoSuchMethodError.
That error is an Error rather than an Exception, so LabAutoStart's
catch (Exception ignored) could not absorb it.

Add LabRunner.isAlive(Process) built on exitValue(), which throws
IllegalThreadStateException exactly while the process is still running, and
route every call site through it. Semantics match the API-26 method and the
helper is available from API 1.

Verification: LabRunner/LabTerminalActivity: replaced all 6 Process#isAlive() (API 26) call sites with a new LabRunner.isAlive(Process) helper that uses exitValue()/IllegalThreadStateException (API 1) with identical semantics, verified by a standalone JVM probe (running process -> IllegalThreadStateException -> alive; finished process -> exitValue returns -> not alive); javap on compiled classes shows 0 remaining java/lang/Process;.isAlive invocations; :app:compileLeanbackArm64_v8aDebugJavaWithJavac and :app:compileMobileArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; full :app:testLeanbackArm64_v8aDebugUnitTest suite passes (9197 tests, 0 failures) including the existing lab tests; lint NewApi errors for java.lang.Process#isAlive gone and total lint errors dropped 252->246; scripts/build_arm64_debug_install.sh --flavor leanback --abi arm64-v8a built and installed over the existing package on 192.168.50.3:5555, and a cold start (which runs LabAutoStart at +3s) left the process alive with zero FATAL EXCEPTION/NoSuchMethodError in logcat

Task-Guard: ANDROID7-LAB-PROCESS-ISALIVE
2026-10-11 17:16:42 +08:00
Silent1566
22fe625814 docs(android7): correct libmitv.so row (32-bit ARM lib inside arm64 APK)
The flagged symbols in assets/libmitv.so are ARM compiler-runtime helpers
(__aeabi_*, _Unwind_*) resolved by the platform at runtime, and the file is
a 32-bit ARM object shipped from the third-party forcetech AAR rather than a
minimum-API problem. Record that distinction accurately.

Verification: documentation-only precision fix: corrected the assets/libmitv.so row after verifying with 'file' that the library is 32-bit ARM (EABI5) shipped inside the arm64-v8a APK from the third-party forcetech-release.aar; its undefined symbols are ARM compiler-runtime helpers provided by the platform at runtime, not an API-level issue. No production code changed.

Task-Guard: ANDROID7-API24-SUPPORT-DOC2
2026-10-11 17:03:01 +08:00
Silent1566
9564c3e58d docs(android7): record device proof and full release-APK native re-check
Append the decisive end-to-end evidence for the ViewCompat fix (Git cloud
dialog opens and renders on the dev1 device with no fatal exception) and the
complete per-symbol audit of all 54 native libraries in the shipped release
APK, which confirms libmpv.so remains the only true Android 7 blocker.

Verification: documentation-only update to docs/android7-support-audit-2026-10-11.md recording (a) the end-to-end device proof for the ViewCompat fix (Git cloud dialog opened and rendered on 192.168.50.3:5555, zero FATAL EXCEPTION for the session) and (b) the full 54-native-library re-check of the shipped Release/apk/mobile-arm64_v8a.apk, where only libmpv.so (API>=28 Vulkan 1.1 + BIND_NOW), libexo_dovi_renderer.so (API>=26, Java-guarded) and assets/libmitv.so (runtime-provided ARM EABI symbols) are flagged; no production code changed in this commit

Task-Guard: ANDROID7-API24-SUPPORT-DOC
2026-10-11 17:01:12 +08:00
Silent1566
4e4bfffcb6 fix(android7): guard View#setImportantForAutofill (API 26) behind ViewCompat
GitCloudDialog.buildView() and RemoteTrustDialog.input() called
View#setImportantForAutofill(int) directly. TextInputEditText,
AppCompatEditText, EditText and TextView do not declare that method, so it
resolved to android.view.View#setImportantForAutofill, which only exists
since API 26. Both call sites run unconditionally while the dialog is
created, so opening the Git cloud or Remote trust settings dialog on
Android 7.0/7.1 threw NoSuchMethodError and crashed.

Route both call sites through androidx.core.view.ViewCompat, which returns
early when SDK_INT < 26 and delegates to the very same framework method on
API >= 26, leaving behavior on supported API levels unchanged.

Also add docs/android7-support-audit-2026-10-11.md with the full minimum-API
audit: declared minSdk 24, dependency floors (max 24), per-symbol native
library check, runtime-downloaded node/python components, and the remaining
Android 7 blocker (bundled libmpv.so references Vulkan 1.1 core entry
points that the platform libvulkan.so only exports from API 28, combined
with BIND_NOW eager binding).

Verification: compileLeanbackArm64_v8aDebugJavaWithJavac + compileMobileArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; javap on compiled classes shows invokestatic androidx/core/view/ViewCompat.setImportantForAutofill (SDK_INT<26 early-return, API>=26 delegates to the identical framework call) replacing android/view/View#setImportantForAutofill (API 26); lint NewApi errors for that symbol gone (254->252 total errors); scripts/build_arm64_debug_install.sh --flavor leanback --abi arm64-v8a built and installed over the existing package on 192.168.50.3:5555; cold start via monkey LEANBACK_LAUNCHER left the process alive with no FATAL/NoSuchMethodError in logcat; audit document docs/android7-support-audit-2026-10-11.md records the full API-24 investigation incl. the libmpv.so Vulkan-1.1/BIND_NOW blocker

Task-Guard: ANDROID7-API24-SUPPORT
2026-10-11 16:58:03 +08:00
Silent1566
4b35e0720c docs(c58): 记录 dev4 合并评审交付坐标(合并提交 3e657bef0d / recovery tag / 推送 / PR #437)
Verification: docs-only: record PR #437 OPEN (mergedAt null, changedFiles 29, MERGEABLE), merge commit 3e657bef0d with parents e0c067cba7 + 8ed903ac2a, recovery tag recovery/beta-sync-review-dev4-20261011-r2/20261011082700-3e657bef0d97, push e0c067cba71..3e657bef0d9, PR files match git diff origin/beta HEAD 29/29

Task-Guard: c58-dev4-coords
2026-10-11 16:39:42 +08:00
Silent1566
3e657bef0d merge: 合并 beta 最新代码(PR#435/#436 停播/更新判定/数据库完整性)并复评 dev4 屏显对比度改动
Verification: origin/beta=8ed903ac2a75bf76b46ab04e17059a30070d4b63; merge commit parents e0c067cba7 + beta tip; zero conflicts after resolving 5 theme resources to beta latest; index tree f66c980de84b53b4b50c8ec9f59813c67d99cdeb; zero beta deletions and zero beta files missing from index (5030/5030); net diff app/docs only; CustomCsp/theme contrast fixes; C59 update ordering, C60 Room integrity recovery, playback stop regressions; leanback+mobile targeted tests and dual flavor Java/AndroidTest compilation BUILD SUCCESSFUL; diff check clean; Gradle stop+clean done

Task-Guard: beta-sync-review-dev4-20261011-r2
2026-10-11 16:26:57 +08:00
Silent1566
8ed903ac2a
Merge pull request #436 from Silent1566/dev1
merge: 合并 origin/beta 最新代码(PR#435 dev2 退出播放页必定停播)并复评修复 C59/C60 的更新判定与注释缺陷
2026-10-11 14:11:01 +08:00
Silent1566
565fac1fd2 docs(c61): 补记收尾复核(beta tip 未推进、临时资源清理、无 active 守卫会话)
- git fetch origin --prune 后 origin/beta 仍为 00ad80114fa971c32de3a74f072e33714b9f488e,git merge-base --is-ancestor origin/beta HEAD 为真
- /tmp/c61/** 已删除;.codex/task-state/current 无 active 会话
- PR #436 复核:state=OPEN、mergedAt=null、mergeable=MERGEABLE、changedFiles=9、文件集与本地净差异逐项一致

Verification: 收尾复核记录:git fetch 后 origin/beta 仍为 00ad80114f 且仍是 HEAD 祖先(合并基点未推进);MERGE_HEAD 已清除;git status --porcelain 仅受保护的 .scratch-goal/;/tmp/c61 已清理;无 active 守卫会话;PR #436 state=OPEN / mergedAt=null / mergeable=MERGEABLE / changedFiles=9 且文件集与 git diff --name-only origin/beta HEAD 逐项一致

Task-Guard: C61-beta-merge-review-dev1-20261011-final
2026-10-11 14:05:00 +08:00
Silent1566
a49b83b261 docs(c61): 订正验证增量表(beta 带入 +2、本轮 F1 锁定用例 +1,合计仍 +19)
实测 dev2 的 97c7ae68a 只在 PlaybackOwnershipSourceTest 新增 2 个 @Test,本轮 F1 锁定用例新增 1 个:
4172 + 12 (C59) + 4 (C60) + 2 (beta) + 1 (C61) = 4191,mobile 同式 = 5006,与 JUnit XML 实测一致。

Verification: 订正 C61 文档的验证增量表(实测 beta 侧 dev2 提交只新增 2 个用例、本轮 F1 锁定用例 +1,合计仍 +19;leanback 4191/0/0、mobile 5006/0/0 与 JUnit XML 实测一致);PR #436 文件集仍与 git diff --name-only origin/beta HEAD 逐项一致

Task-Guard: C61-beta-merge-review-dev1-20261011-coords2
2026-10-11 14:03:16 +08:00
Silent1566
adcb47375f docs(c61): 记录 dev1 合并评审交付坐标(双父合并提交 8b54e0e97 / recovery tag / 推送 / PR #436)
- 合并提交 8b54e0e97c 双父:56372b7dba(合并前 dev1)+ 00ad80114fa(beta tip)
- recovery tag recovery/C61-beta-merge-review-dev1-20261011/20261011140009-8b54e0e97c0a
- 推送 5ad9025f9..8b54e0e97 dev1 -> dev1,origin/dev1 == dev1
- PR #436 state=OPEN / mergedAt=null / mergeable=MERGEABLE / changedFiles=9,文件集与 git diff --name-only origin/beta HEAD 逐项一致(只创建,未合并)

Verification: 坐标闭环:PR #436(dev1 -> beta)state=OPEN、mergedAt=null、mergeable=MERGEABLE、changedFiles=9;PR 文件集与 git diff --name-only origin/beta HEAD 逐项一致(diff 无输出);合并提交 8b54e0e97 为双父(第二父 = 00ad80114 = 当时 beta tip);git merge-base --is-ancestor origin/beta HEAD 为真;MERGE_HEAD 已清除;git rev-parse origin/dev1 == dev1

Task-Guard: C61-beta-merge-review-dev1-20261011-coords
2026-10-11 14:01:24 +08:00
Silent1566
8b54e0e97c merge: 合并 origin/beta 最新代码(PR#435 dev2「退出播放页必定停播」)并复评 dev1 已提交未推送的 C59/C60
合并:git merge --no-commit --no-ff origin/beta(00ad80114fa971c32de3a74f072e33714b9f488e,Merge pull request #435 from Silent1566/dev2),0 冲突;纳入 beta 侧 5 个提交(含 dev2 的 fix(playback): 退出播放页必定停播与两份 C58 文档)。零复活/零丢失:beta tip 5025 个文件逐个 blob 比对,缺失 0、逐字节一致 5021,差异仅 dev1 自己的 C59/C60 路径;行级严格判据(407 个单亲回退提交的 53889 条删除行 ∩ dev1 新增 432 行)复活行数 0。

复评(2 轮)发现并修复 2 项真实缺陷:

- F1(判定方向错误,可导致自动降级):AppVersion.isNewer 原文把「长度不同的两个可用构建时间戳」当成不可比较并直接判定目标为更新,负对照实测 isNewer("v5.6.0-beta-20261009175522", "v5.6.0-202609221328") = true,即 14 位秒级时间戳的装机版本会把更早的 12 位渠道包当作更新推荐,与 BUILD_STAMP 的 \d{12,14} 及 buildStamp() javadoc 声明的 yyyyMMddHHmm[ss] 相矛盾。修复:新增 STAMP_LENGTH/padStamp(),把分钟级时间戳补齐为该分钟第 0 秒后比较,只有真正缺失时间戳才回退历史语义;新增用例 AppVersionBuildStampTest#stampsOfDifferentLengthsAreStillOrdered 锁定,修复后同一探针返回 false 且 10 条探针断言全部通过。
- F2(注释与实现矛盾):AppDatabase 的 IntegrityRecoveringOpenHelperFactory 与 quarantine() 注释称「不可读的数据库文件被保全而非删除、原文件留在磁盘上」,但实现会清理 -wal/-shm/-journal,且 renameTo 失败时删除主库文件。注释改为如实描述(主库改名保全 + 边车文件清理 + 无法改名时删除),行为零改动。

验证:bash ./gradlew :app:compile{Leanback,Mobile}Arm64_v8aDebugJavaWithJavac :app:compile{Leanback,Mobile}Arm64_v8aDebugAndroidTestJavaWithJavac :app:test{Leanback,Mobile}Arm64_v8aDebugUnitTest => BUILD SUCCESSFUL in 7m 8s;leanback 4191 tests / 0 failures / 0 errors / 2 skipped,mobile 5006 / 0 / 0 / 2(相对 C58 基线 4172/4987 各 +19 = C59 +12、C60 +4、合并带入的 PlaybackOwnershipSourceTest +3);bash scripts/check_ui_tokens.sh => UI_TOKEN_STATUS PASS,violations=1 与基线一致(零新增)。

未使用真机:本轮对生产代码的改动为 1 处判定分支 + 2 处注释,不改变正常升级方向与数据库打开/迁移路径,被复评的 C59/C60 已由用户在同一机位(192.168.50.3:5555,Android 9)按四种场景实机验收。

Verification: 合并 origin/beta 00ad80114(0 冲突,MERGE_HEAD=beta tip,双父合并提交);零复活行级严格判据=0(407 个单亲回退提交 / 53889 删除行 ∩ dev1 新增行 432 行 = 56 行交集,全部存在于 beta tip);零丢失=beta tip 5025 文件逐个 blob 比对,缺失 0、新增仅 dev1 自己的 5 个新文件;复评 2 轮,第 1 轮发现 F1(AppVersion.isNewer 跨长度时间戳误判为更新,负对照实测 isNewer(14位,更早的12位)=true)与 F2(AppDatabase 注释与实现矛盾)并修复,第 2 轮复审通过;双 flavor Java 编译 + 双 flavor AndroidTest 编译 + 双 flavor 全量 JVM 套件 BUILD SUCCESSFUL(leanback 4191/0/0、mobile 5006/0/0,相对 C58 基线 4172/4987 各 +19,可逐项解释:C59 +12、C60 +4、合并带入 beta 的 PlaybackOwnershipSourceTest +3);UI token 门禁 PASS(violations=1 与基线一致,零新增)

Task-Guard: C61-beta-merge-review-dev1-20261011
2026-10-11 14:00:09 +08:00
Silent1566
56372b7dba fix(db): Room 数据完整性校验失败不再导致启动崩溃循环,改为保全坏库后重建
同一用户反馈(Android 9 盒子装完即闪退)背后还有第二个独立缺陷:两个 APK
声明相同 versionCode 但数据库 schema 不同时,Room 会在**打开数据库时**抛
IllegalStateException: Room cannot verify the data integrity,崩溃点为
RoomConnectionManager.checkIdentity -> supportOpenHelperCallback.onOpen。
因为发生在打开期,Application/Activity 调用点无法捕获,实测每次启动必然崩溃,
与盒子日志反复出现的 E/AndroidRuntime + Force finishing HomeActivity + has died 一致。
实测本次 stable(edf04f9) 数据库 VERSION=45、beta(841a570) VERSION=48,
而两者 versionCode 同为 560,正是该失败的触发条件。

修复:经 Room.databaseBuilder().openHelperFactory() 接入
IntegrityRecoveringOpenHelperFactory,在打开期只针对 Room 的 identity 失败介入——
把不可读的库文件改名保全为 tv.incompatible-<时间戳>(wal/shm/journal 一并清理,
隔离副本最多 2 份),再用全新 helper 重建库;重试只允许一次。
非 identity 的打开失败原样抛出,不改变既有行为(已用随机字节损坏库做负对照)。

Verification: JVM focused test: ./gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.db.AppDatabaseIntegrityRecoverySourceTest => BUILD SUCCESSFUL, 4 tests / 0 failures / 0 errors / 0 skipped. Device verification on Android 9 (192.168.50.3:5555, sdk=28) after 'bash scripts/build_arm64_debug_install.sh --flavor leanback --abi arm64-v8a --serial 192.168.50.3:5555' (overwrite install, same debug signature): (1) fresh database => FATAL=0, boot OK; (2) same versionCode with mismatching identity_hash, which crashed with FATAL IllegalStateException before the change => FATAL=0, boot OK, 'integrity check failed, rebuilding tv' logged, broken file preserved as tv.incompatible-20261011130235/132735 and it still contains the pre-recovery Keep row; (3) v45 database with user data migrating to the new build => FATAL=0, guard NOT triggered, all rows preserved (Keep 2 / History 60 / Config 1 / Site 1 / Track 1 / Device 1 / TmdbSeasonProgress 1 / PlaybackDeleteTombstone 1); (4) negative control with a non-identity corrupt database (random bytes) => handled by androidx.sqlite itself ('Corruption reported by sqlite ... deleting the database file'), app booted, guard did NOT intervene (recovery log count 0, no incompatible file), proving the guard stays limited to Room's identity failure.

Task-Guard: C60
2026-10-11 13:30:41 +08:00
Silent1566
d8c9bdc650 fix(update): 只有严格更新的构建才提示升级,切断 stable/beta 互相覆盖安装死循环
用户反馈 Android 9 盒子装完即闪退并反复下载 update.apk 覆盖安装。核实日志确认
targetSdk 本来就是 28;日志里的 FragmentManager$FragmentLifecycleCallbacks /
ValueAnimator$DurationScaleChangeListener CNFE 来自 me.jessyan.autosize 的可选探测,
被记录后跳过(同一镜像 leanback 包每次启动都有这 16 行且正常启动),
androidx.window.extensions 在清单中 required=false,均非崩溃原因。

真实死循环根因:Update.hasUpdate() 用 code != VERSION_CODE || !isCurrent(name) 判定,
把"不同"当成"更新"。本次 stable(v5.6.0-202609221328) 与 beta(v5.6.0-beta-202610091755)
清单 versionCode 同为 560,而 getPreferredUpdate() 优先选 stable,于是 beta 装机时被推荐
安装更旧的 stable 包,装完又反过来推荐 beta,两个渠道无限互相覆盖。

改为仅严格更新才算更新:更高 versionCode 才算更新,更低 versionCode 永不当作更新,
同 versionCode 时按发布标签尾部的构建时间戳(yyyyMMddHHmm[ss])判新旧,相同或缺少可比
时间戳时不更新(缺时间戳沿用历史语义,以免本地/非标构建失去升级能力)。

Verification: JVM focused unit tests: ./gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests AppVersionBuildStampTest --tests UpdateOrderingTest --tests UpdateChannelTest --tests UpdaterFutureTest => BUILD SUCCESSFUL, 21 tests / 0 failures / 0 errors / 0 skipped. Regression locked with the real channel manifest literals v5.6.0-202609221328 (stable) vs v5.6.0-beta-202610091755 (beta): an older manifest with the same versionCode is no longer reported as an update, a newer one still is. Root cause evidence: both channel manifests carry code=560 while their APKs ship different Room VERSION (45 vs 48), so the previous 'different means newer' rule made the two channels overwrite each other in a loop.

Task-Guard: C59
2026-10-11 13:07:31 +08:00
Silent1566
00ad80114f
Merge pull request #435 from Silent1566/dev2
merge: dev2 合并 beta 最新代码(PR#434 主题焦点环族)并复评已提交未推送的「退出播放页必定停播」修复
2026-10-11 12:21:34 +08:00
Silent1566
2275a50e71 docs(c58): 记录 dev2 交付坐标(合并提交 8f20fab65a / recovery tag / beta tip 3148f030df)
Verification: 文档坐标补记:合并提交 8f20fab65a(双父 c27f34b950 + 3148f030df)、阶段二 recovery tag、beta tip 3148f030df 已复核未再前进;本轮无生产代码改动,无需重新构建

Task-Guard: C58-beta-merge-dev2-coordinates
2026-10-11 11:58:15 +08:00
Silent1566
8f20fab65a merge: 合并 origin/beta 最新代码(PR#434 dev1 主题焦点环族 11 提交)并复评 dev2 已提交未推送的「退出播放页必定停播」修复
Verification: 以新 beta tip 重新合入:git merge --no-commit --no-ff origin/beta 0 冲突(MERGE_HEAD=3148f030df),索引树 c681b54c == git merge-tree --write-tree 自动合并结果树;净差异仅 dev2 自身 3 路径、--diff-filter=D 为空;零复活:47 条 beta 真移除路径在合并结果树命中 0、66 条与净差异交集 0、关键词命中 0;dev2 两改动文件 blob 逐字节未变;双 flavor 编译 4/4 BUILD SUCCESSFUL;全量 JVM 套件 leanback 4174/8 失败/7 类、mobile 4989/7 失败/6 类,失败类集合与文档基线同族(0 新增失败);PlaybackOwnershipSourceTest 双 flavor 18/18 通过(含两条新增契约用例)

Task-Guard: C58-beta-merge-dev2-beta434
2026-10-11 11:57:39 +08:00
Silent1566
c27f34b950 docs(c58): 复评 dev2 已提交未推送提交(退出播放页必定停播)并确认无需合并,双 flavor 全量验证通过
Verification: 本轮无需合并(origin/beta tip fb4fd7d99c 即 dev2 HEAD 97c7ae68ab 的父提交,git merge-base --is-ancestor origin/beta dev2 EXIT=0);双 flavor 编译 4/4 BUILD SUCCESSFUL;双 flavor 全量 JVM 套件 leanback 4150/8 失败、mobile 4985/7 失败,失败类集合与 C57 文档基线逐类一致(0 新增失败,+2 例为本次新增契约测试);PlaybackOwnershipSourceTest 双 flavor 18/18 通过(含两条新增用例);零复活:66 条 beta 移除路径与本次 2 个 PR 路径交集 0、关键词命中 0、diff-filter=D 为空;git diff --check 退出码 0;推送为快进 34a93e243f..97c7ae68ab

Task-Guard: C58-beta-merge-review-dev2
2026-10-11 11:48:16 +08:00
Silent1566
e0c067cba7 merge: 合并 beta 最新代码并复评 dev4 屏显对比度改动
Verification: origin/beta=fb4fd7d99ca1547ea624f3d5f1b7f3cd81b880a5 unchanged on final fetch; merge-tree equals index tree b52c5aedfbb06824fa36f8d920a59e646e170c30; zero conflicts; net diff app/docs only; CustomCsp theme fix and regression tests; leanback/mobile focused tests; dual flavor Java and AndroidTest Java compilation; diff check; Gradle stop and clean successful

Task-Guard: beta-sync-review-dev4-20261011
2026-10-11 11:39:32 +08:00
Silent1566
3148f030df
Merge pull request #434 from Silent1566/dev1
merge(dev1→beta): 合并 beta 最新代码(PR#432 电池块 / PR#433 加载圈族)并复评 dev1 全部已提交未推送的主题改动
2026-10-11 11:36:50 +08:00
Silent1566
5ad9025f93 docs(c58): 记录 dev1 合并评审交付坐标(合并提交 2ee22ce21 双父 / recovery tag / PR #434)
回填 docs/C58-beta-merge-review-dev1-20261011.md 的「交付坐标」与「闭环记录」:
- 合并提交 2ee22ce21655791e87164468c3c94d4db761601c,双父(649275b76 合并前 dev1 + fb4fd7d99 beta tip)
- recovery tag recovery/C58-beta-merge-review-dev1-20261011/20261011112617-2ee22ce21655
- 推送 913ae81d1..2ee22ce21,origin/dev1 == 本地 HEAD
- PR #434 已创建(base beta / head dev1,只创建未合并):state=OPEN、mergedAt=null、mergeable=MERGEABLE、changedFiles=83
- PR 文件集与 git diff --name-only origin/beta HEAD 逐项一致(83/83)
- 提交后严格复活行数 0;MERGE_HEAD 已清除;工作区干净

Verification: 文档回填:交付坐标(合并提交 2ee22ce216 双父 / recovery tag / 推送结果 / PR #434)+ 闭环记录(beta tip 未前进复核、双父结构验证 git rev-list --parents -n1 = 2ee22ce21 649275b76 fb4fd7d99、PR state=OPEN mergedAt=null mergeable=MERGEABLE changedFiles=83、PR 文件集与 git diff --name-only origin/beta HEAD 逐项一致 83/83);本轮为 docs-only 改动,无代码变化

Task-Guard: C58-beta-merge-review-dev1-20261011-coords
2026-10-11 11:27:48 +08:00
Silent1566
2ee22ce216 merge: 合并 origin/beta 最新代码(PR#432/#433:mobile 电池块 + 加载圈族修复)并复评 dev1 全部已提交未推送的主题改动
合并:origin/beta tip fb4fd7d99(13 个提交,含 PR#432 dev4 播放器控制栏电池块、PR#433 dev2 加载圈族 4 个 fix 与 OSD 重缓冲计数修复),0 冲突。合并结果相对 beta tip 净差异 82 路径,全部来自 dev1 自身(TV 焦点环统一 + 播放页单一高亮色)。

零复活三层证据:beta 侧 13 个提交无任何删除/回退(--diff-filter=DR 为空);对 beta 历史上 be1b02e06「revert: remove dynamic theme color system」的 2388 条删除行与 dev1 新增行求交(31 行通用脚手架)后逐行核对是否在 beta tip 任何文件中都不存在,严格复活行数 0;关键移除目标(assets/themes、mobile 主题对话框、主题测试)在合并结果中均不存在。注意 5682f2b05「剔除 PR #353」不在 beta 历史(仅 remove-pr353-theme-changes 分支),同样检查复活行数 0。

零丢失:beta 侧 15 个变更文件中 13 个完整保留,2 个自动合并点(TmdbDetailActivity.java / VideoActivityLayoutTest.java)逐 hunk 复核为 dev1 改动与 beta 内容共存,无 beta 内容丢失。唯一删除 selector_cache_button_focus.xml 是 dev1 自己的主题环改造(合并后全仓库零悬空引用)。

复评(3 轮)dev1 的 11 个未推送提交与 82 路径净差异,修复 3 类真实缺陷:
- F1 错别字「深箭玻璃」应为「深靛玻璃」,8 处(ThemeTokens/ThemeResolver/attrs/webhtv_tokens/colors/values-night tokens/leanback styles/selector_control_sheet_button),与仓库既有 13 个文件的「深靛」用词统一。
- F2 attrs.xml 与 webhtv_attrs.xml 的注释声称环色经「视频亮场景 + 固定玻璃两类极端背景夹取」,与 ThemeResolver.PLAYER_RING_BACKDROPS 的实际实现(仅 3 档固定深玻璃、注释明确写纯白视频刻意不在约束里)互相矛盾,2 处,已改写为与实现一致。
- F3 TmdbDetailActivity 悬空 javadoc:focusStroke() 的文档被新方法 focusRingWidthPx() 插入后成为孤立注释,已把文档移回 focusStroke() 正上方。

观察项(按 AGENTS.md §2 只报告不擅自扩大范围):TmdbPersonActivity 仍写死 FOCUS_STROKE_DP=3(宽度未接线 token)、EpisodeDetailDialog 同类悬空 javadoc;二者与 beta tip 逐字节相同,不在本轮净差异内,属 beta 侧既存状态。

Verification: 双flavor Java + 双flavor AndroidTest Java 编译 BUILD SUCCESSFUL;双flavor 全量 JVM 单测 leanback 4172 tests/0 failures、mobile 4987 tests/0 failures(与修复前逐项一致,增量相对 C56 基线 +26/+7 可解释);UI_TOKEN_STATUS PASS(violations=1 零新增、contrast failures=0);零复活严格判据(beta 回退提交 be1b02e06 删除行 ∩ dev1 新增行 31 行逐行核对,复活行数=0);零丢失(beta 侧 15 个变更文件全部保留,2 个自动合并点逐 hunk 复核为共存);git diff --cached --check 退出码 0、冲突标记 0

Task-Guard: C58-beta-merge-review-dev1-20261011
2026-10-11 11:26:17 +08:00
Silent1566
649275b762 fix(theme): 播放页只留一种高亮色(芯片宿主分层修正 + 深色表视频层环取近白)
Verification: testLeanback: TvFocusRingContractTest(17 条)+NativeEnhancedPlaybackStyleFocusTest 通过;testMobile: VideoActivityLayoutTest 通过;com.fongmi.android.tv.theme.* 通过(含 51 色 XML/frozen 色板一致性);scripts/check_ui_tokens.sh PASS(violations=1 基线, contrast failures=0);Robolectric 探针证明玻璃覆盖层:对话框子树 ?attr/tvFocusRing=#FF447BF5、页面内容区=#FF0B57D0、对话框内芯片栅格化描边=#FF447BF5;dev1 实机(深色表, 覆盖安装)数组分段芯片焦点环=#F5F7FF(旧 #7695C5 像素 0)、选集芯片=#F5F7FF、当前分段=#2CC56F、画面框=#F5F7FF

Task-Guard: video-playback-ring-unify
2026-10-11 10:50:34 +08:00
Silent1566
97c7ae68ab fix(playback): 退出播放页必定停播,堵住「退回详情页还有声音/通知栏仍显示播放中」
- stopPlayback(): 服务在而本页非归属方时改由服务侧 suspend() 停播并撤掉前台通知。旧实现只走
  mController.stop():控制器未连上时整次调用是空操作,播放器 IDLE(取流中退出)时会被会话
  stop 直接忽略,且这条路径从不撤前台通知。
- startPlayer(): 已决定退出(返回/通知栏停止)的播放页拒绝再次起播,避免迟到的换源/取流链路
  在服务里重新播放并挂上前台通知。
- 新增两条源码契约测试锁定上述语义。

Verification: mobile arm64_v8a debug 编译通过(compileMobileArm64_v8aDebugJavaWithJavac);:app:testMobileArm64_v8aDebugUnitTest --tests PlaybackOwnershipSourceTest 全类通过(含新增 exitingThePlayerPageAlwaysStopsThroughTheService / anExitingPlayerPageNeverStartsPlaybackAgain);ReaderPlaybackRoutingSourceTest#readerResultNeverFallsThroughToVideoPipeline 通过。ReaderPlaybackRoutingSourceTest 的两处 reader.html 契约失败与 PlayerPlaybackRegressionSourceTest 的 CRLF 多行字面量断言均为改动前既有失败(本机工作区为 CRLF 检出),与本次改动无关。

Task-Guard: EXIT-STOP-01
2026-10-11 10:06:39 +08:00
Silent1566
6be5c5ee40 feat(theme): 默认配色向上游对齐(TV 默认深色 + 白色焦点环 + 白色加载指示器)
用户要求:「默认主题配色靠近上游 Silent1566 分支,比如默认选中边框是白色、播放器加载中
效果是白色等,手机和电视版都注意一下」;随后补充:「电视版应该默认深色更好吧?」

上游基线(clone Silent1566 实测):TV 主题是 Theme.Material3.Dark.NoActionBar(强制深色、
windowBackground=#000000、colorPrimary=#FFFFFF),焦点环与播放器加载指示器都是 @color/white。
上游能用白环正因为它是深色底(21:1);本仓库 TV 是 DayNight 且默认落浅色表,
白环对 #F8FAFD 只有 1.05:1,会直接消失并被 ThemeContrast 的 focus/surface>=3:1 门拒绝。

改动:
- ThemeController.applyNightModeToApp():follow-system 分支按 flavour 分流 —— leanback 默认
  MODE_NIGHT_YES(与上游一致:电视多在暗环境观看,浅色亮底夜间刺眼;深色底上白环 17-18:1
  才成立),mobile 保持 MODE_NIGHT_FOLLOW_SYSTEM。显式浅色/深色仍然优先。
- values-night/webhtv_tokens.xml + ThemeTokens.dark():webhtv_color_focus -> #F5F7FF(近白)。
  不用纯白是因为 ThemeBinder 按「颜色->语义角色」精确匹配,而 webhtv_on_wallpaper 两张表
  恒为 #FFFFFF;焦点若取纯白会被 ThemeColorIndex 映射到 FOCUS,用户切主题时壁纸白字会被
  改写成焦点色(ThemeBaseWiringTest 实测抓出 replacementFor(#FFFFFF, light) 由 null 变 #0B57D0)。
  #F5F7FF 视觉上与纯白无异,对深色 surface 仍 17.29:1,且不与任何 dark 角色同值。
- {leanback,mobile}/res/layout/view_progress.xml:indicatorColor -> @color/white(与上游一致,
  两处都叠在视频上)。
- 顺带修掉本轮暴露的连带缺陷:追更页 primary/secondary 按钮的 backgroundTint 在
  focused/pressed 时取 following_button_focus(=webhtv_color_focus),而文本写死白色 ——
  浅色表下凑巧可读(6.39:1)掩盖了深色表下的 1.72:1(TV 默认深色后必然显形)。
  文本改为配对 on-色(webhtv_color_on_primary):浅色 6.39:1 / 深色 7.50:1。

新增契约测试(ThemeControllerContractTest 11/11):
- tvDefaultsToDarkWhileMobileKeepsFollowingTheSystem
- darkFocusRingIsNearWhiteButNeverPureWhite(防 binder 碰撞回归)
- followingButtonTextPairsWithItsFocusFill

验证:
- ThemeControllerContractTest 11/11
- :app:testLeanbackArm64_v8aDebugUnitTest 4169 tests / 0 failures
- :app:testMobileArm64_v8aDebugUnitTest 4984 tests / 0 failures
- scripts/check_ui_tokens.sh:violations=1(=基线)、hex_colors=0、contrast failures=0
- dev1 实机(覆盖安装,系统 cmd uimode night no 即白天):焦点环实测 #F5F7FF = 45484px、
  浅色表 #0B57D0 = 0px,确认 TV 在白天也默认走深色表

Verification: TV 默认深色(leanback follow-system -> MODE_NIGHT_YES);深色表 focus -> #F5F7FF(近白、对 surface 17.29:1、避开 binder 纯白碰撞);两版播放器加载指示器 -> @color/white;追更页按钮文本改配对 on-色(深色表 1.72:1 -> 7.50:1);新增 3 条契约测试;leanback 4169 + mobile 4984 tests 0 failures;check_ui_tokens violations=1(=基线);dev1 实机白天焦点环 #F5F7FF 45484px、浅色环 0px

Task-Guard: upstream-default-palette-align

Verification: TV 默认深色:ThemeController.applyNightModeToApp 的 follow-system 分支按 flavour 分流(leanback→MODE_NIGHT_YES,mobile→FOLLOW_SYSTEM),dev1 实机在系统 night=no(白天)下焦点环实测 #F5F7FF=45484px、浅色表 #0B57D0=0px;深色表 focus→#F5F7FF(近白,对 surface 17.29:1,且避开与 webhtv_on_wallpaper 纯白碰撞导致的 binder 误改写);两版 view_progress indicatorColor→@color/white;追更页两个文本 selector 由写死白改为 webhtv_color_on_primary(深色表 1.72:1→7.50:1);新增 ThemeControllerContractTest 3 条契约(11/11);leanback 4169 + mobile 4984 tests 0 failures;check_ui_tokens violations=1(=基线) hex_colors=0

Task-Guard: upstream-default-palette-align
2026-10-11 09:32:12 +08:00
Silent1566
fec5af7aa5 test(tv): 锁定屏显入口对比度链路
Verification: 用户截图链路已核对为 PlayerOsdDialog/adapter_player_osd;PlayerDisplaySettingSyncTest 7/7;leanback Java 编译与最终 Debug APK 构建 BUILD SUCCESSFUL;5561 adb install -r 成功且屏显弹窗源码契约与焦点节点复核通过;旧 display_option 误改已撤销;git diff --check 通过;Gradle、build 输出与临时资源已清理

Task-Guard: TV-DISPLAY-DIALOG-CONTRAST-20261011
2026-10-11 09:28:18 +08:00
Silent1566
bd78d7b528 fix(tv): 全面修复设置列表与焦点控件对比度
Verification: PlayerSettingsContrastTest 5/5;leanback theme/contrast/focus contracts BUILD SUCCESSFUL;mobile DialogGlassChipStyleTest 与 AdRuleManageDialogLayoutTest BUILD SUCCESSFUL;leanback/mobile Java compile BUILD SUCCESSFUL;最终 leanback Debug assemble BUILD SUCCESSFUL;5561 adb install -r 成功,屏显设置与播放器按钮截图实测文字/焦点/图标可读,核心对比度 10.48:1、7.93:1、7.04:1;git diff --check 通过;既存 UI token strict 唯一违规为未改动 item_following.xml;构建与临时资源已清理

Task-Guard: TV-PLAYER-SETTINGS-CONTRAST-20261010
2026-10-11 00:52:08 +08:00
Silent1566
8ba33b11fc revert: 全屏播放画面不加焦点环(用户纠正:控制栏需要,整个播放界面不需要)
用户纠正:「全屏播放器内部不需要选中效果呀」「控制栏需要但是怎么整个播放界面套了一个?」

上一提交 0209015d6 把内缩焦点环套在整个全屏播放界面上,属于对「看不出选中效果」
的过度解读。实际需要焦点环的是控制栏/OSD 按钮(那些本就有且已统一),
而全屏画面是沉浸观看态、不是可选控件,加环反而干扰观感。

本次完整 revert 0209015d6:
- VideoActivity.enterFullscreen() 恢复 mBinding.video.setForeground(null)
- TmdbDetailActivity.updatePlayerPanelFocus() 全屏/PiP 分支恢复清零描边后返回
- 同步移除同批次新增的两条契约测试(避免它们反过来要求错误行为)
- 4 个源文件与 7948c8e97 逐字节一致(git diff --quiet 验证)

分层结论(已写入 docs §16):
- 全屏播放画面:不需要焦点环
- 控制栏/OSD 按钮:需要(§14 已统一宽度 + 主题派生色)
- 内嵌(非全屏)播放面板:需要(§13 已对齐卡片圆角)

验证:TvFocusRingContractTest + TmdbDetailChipFillTest 通过;leanback 4166 + mobile 4981
tests 0 failures;check_ui_tokens violations=1(=基线);dev1 实机全屏播放环色像素 = 0
(确认画面无环),控制栏环不受影响

Task-Guard: revert-fullscreen-player-ring

Verification: git revert 0209015d6 完整回退:VideoActivity.enterFullscreen 恢复 setForeground(null)、TmdbDetailActivity 全屏/PiP 分支恢复清零描边;4 个源文件与 7948c8e97 逐字节一致(git diff --quiet 验证);新增两条契约测试已随之移除;TvFocusRingContractTest + TmdbDetailChipFillTest 通过;leanback 4166 + mobile 4981 tests 0 failures;check_ui_tokens violations=1(=基线);dev1 实机全屏播放 #0B57D0 像素 = 0(确认画面无环),控制栏 selector_video_item 环仍为 token 宽度 + tvPlayerRing 主题色

Task-Guard: revert-fullscreen-player-ring
2026-10-10 23:44:12 +08:00
Silent1566
0209015d62 fix(theme): 全屏播放不再失去焦点环,改为画面内侧内缩环
用户实机报告(附大窗形态播放截图):「这里没有选中效果,或者说看不出来」。

根因(像素级证据):用户截图 817x483 中播放面板四边完全没有描边色——边界从黑直接
过渡到紫灰壁纸(仅 1px #232228 抗锯齿),候选环色(#447BF5/#7695C5/#A8C7FA/白)
全部 0 像素,面板四角为直角。排查确认两个全屏入口都在无条件移除焦点环,而宿主在
这两种形态下仍然 focusable:
- VideoActivity.enterFullscreen(): mBinding.video.setForeground(null)
  全屏后 @id/video 仍 focused="true"(uiautomator dump 实测),环却被移除;
- TmdbDetailActivity.updatePlayerPanelFocus(): inlineFullscreen || inlinePiPLayout
  分支 setStrokeColor(0)+setStrokeWidth(0) 后 return,面板仍 focusable=true。
即「可以聚焦但零视觉反馈」。

修法(方案 A):两个入口都改为在画面内侧画一圈内缩焦点环,用 InsetDrawable 把描边
推离屏幕物理边缘,避免全屏时被切掉一半、也不侵入画面中央:
- 宽度取 @dimen/webhtv_focus_ring_width(全 TV 唯一来源)
- 颜色取 ThemeController.focusRingColor()(= ?attr/tvPlayerRing 同一来源,视频层主题派生色)
- 内缩 12dp / 圆角 8dp
新增 VideoActivity.fullscreenVideoRing() 与 TmdbDetailActivity.insetFocusRing()。

新增防回归契约:
- TvFocusRingContractTest.fullscreenPlayerPathsKeepAnInsetFocusRing(断言两个入口不得再
  用 setForeground(null)/清零分支移除环,且新环必须走 token 宽度 + 主题色 + InsetDrawable)
- TmdbDetailChipFillTest.fullscreenPlayerPanelDrawsAnInsetThemeFocusRing(栅格化全屏面板:
  焦点态必须画出内缩主题环、非焦点态无环、环不得触及屏幕物理边缘)

验证:
- TvFocusRingContractTest 19/19;TmdbDetailChipFillTest 3/3
- :app:testLeanbackArm64_v8aDebugUnitTest 4168 tests / 0 failures
- :app:testMobileArm64_v8aDebugUnitTest 4981 tests / 0 failures
- scripts/check_ui_tokens.sh:violations=1(=基线)、hex_drawables=0、contrast failures=0
- dev1 实机(覆盖安装同签名 Debug 包,全屏播放态):环色像素 0 → 17256;环 bbox
  x 24..1895 / y 24..1055(内缩 24px = 12dp);左/上边界剖面 #0B57D0 环带清晰可见

Verification: VideoActivity.enterFullscreen 与 TmdbDetailActivity 全屏/PiP 分支由「移除环」改为「画面内侧内缩主题环」(宽度 token + ThemeController.focusRingColor + InsetDrawable 12dp/8dp);新增 2 条防回归契约;leanback 4168 + mobile 4981 tests 0 failures;check_ui_tokens violations=1(=基线);dev1 实机全屏播放环像素 0→17256、bbox 内缩 24px

Task-Guard: inline-player-panel-focus-ring

Verification: 两个全屏入口由移除焦点环改为画面内侧内缩环:VideoActivity.enterFullscreen 用 fullscreenVideoRing()(InsetDrawable 12dp 内缩 + @dimen/webhtv_focus_ring_width + ThemeController.focusRingColor),TmdbDetailActivity 全屏/PiP 分支用 insetFocusRing();新增 TvFocusRingContractTest.fullscreenPlayerPathsKeepAnInsetFocusRing(19/19)与 TmdbDetailChipFillTest.fullscreenPlayerPanelDrawsAnInsetThemeFocusRing(3/3,栅格化断言焦点态有环/非焦点态无环/环不触物理边缘);leanback 4168 + mobile 4981 tests 0 failures;check_ui_tokens violations=1(=基线);dev1 实机全屏播放环色像素 0→17256、bbox x24..1895 y24..1055(内缩 24px=12dp)、边界剖面 #0B57D0

Task-Guard: inline-player-panel-focus-ring
2026-10-10 23:30:07 +08:00
Silent1566
7948c8e979 fix(theme): 修复对话框主题缺 tv* 属性导致的黑色边框,并收口代码挂环的硬编码宽度
用户实机报告两点:
1. 「现在选中播放器看不出选中的效果,好像是黑色的边框,不应该是统一的选中边框颜色吗?」
2. 「还有个性推荐等卡片的边框粗细没有改小没有统一」

缺陷一(上一轮引入的回归):selector_control_sheet_button(播放器控制面板按钮)的焦点环
从写死半透明白改成 ?attr/tvPlayerRing / ?attr/tvPlayerCurrentRing,但这两个属性当时只在
Theme.Base(= Theme.App)里绑定,而播放器控制面板(ControlDialog,BottomSheet)用的是
Material 默认 bottomSheetDialogTheme、Alert 对话框用的是 Theme.Material3.DayNight.Dialog.Alert
—— 两者都不继承 Theme.WebHTV。属性解析不到时 ?attr/xxx 静默退化为黑色/透明且不崩溃,
这就是「黑色边框」的来源(Robolectric 实测:Theme.WebHTV.Dialog / bottomSheetDialogTheme
下 tvPlayerRing = UNRESOLVED,修复后 = #FF447BF5)。

修法:Theme.WebHTV(基底)/ Theme.WebHTV.Dialog / ThemeOverlay.WebHTV.Dialog 三处都绑定
全部五项 tv* 属性;selector_control_sheet_button 补上缺失的 state_activated 分支
(部分入口用 ArrayAdapter.setActivated 表达「当前生效」,缺该分支就完全看不到选中环)。

缺陷二:前几轮统一的是 drawable 层的环宽,而一批控件是代码挂环(直接 setStrokeWidth /
GradientDrawable.setStroke),全部漏掉:TmdbRecommendationPresenter(FOCUS_WIDTH_DP=3)、
TmdbCardFocusHelper(FOCUS_STROKE_DP=3)、TmdbEpisodeAdapter(3)、TmdbVideoAdapter(2)、
InlineEpisodeAdapter(2)、TmdbDetailActivity(11 处 FOCUS_STROKE_DP=3)。
修法:全部改为从 @dimen/webhtv_focus_ring_width 读像素;TmdbCardFocusHelper.foregroundBorder
参数语义由 dp 改为 px(避免再次隐式写死);TmdbRecommendationPresenter 环色改走
ThemeController.focusRingColor(原来直接取颜色常量,绕过主题覆写通道)。

新增防回归契约:
- ThemeContractTest.everyReferencedTvFocusAttrIsAssignedByATheme:扫描全部 ?attr/tv* 引用,
  断言每个都被至少一个主题赋值,且基底主题自带全部五项(这类回归不崩溃、只有人眼能发现)
- TvFocusRingContractTest.noJavaCodeHardcodesAFocusRingWidth:扫描 9 个 Java 源,
  断言「按焦点分支设描边宽度」的真分支不含 dp 字面量/ *_DP 常量(实测扫到 12 处;
  实施中真的抓出一处漏改:TmdbDetailActivity.applyInlineEpisodeModeButtonState)
- TvFocusRingContractTest.noJavaCodeHardcodesAFocusRingColour

验证:
- ThemeContractTest 全绿;TvFocusRingContractTest 18/18
- :app:testLeanbackArm64_v8aDebugUnitTest 4166 tests / 0 failures
- :app:testMobileArm64_v8aDebugUnitTest 4981 tests / 0 failures
- scripts/check_ui_tokens.sh:violations=1(=基线)、hex_drawables=0、contrast failures=0
- dev1 实机(覆盖安装同签名 Debug 包):选集项焦点环实测 3px #447BF5(此前 3dp 写死色);
  Robolectric 复核 dialog_control#player 的 focus/selected/activated 三态分别为
  #FF447BF5 / #FF00A95A / #FF00A95A

Verification: 三个对话框主题补绑 tv* 五项 + control_sheet_button 补 state_activated 分支(黑框回归消失,实测 dialog_control#player activated 由无描边变为 #FF00A95A);6 个 Java 挂环位置改读 @dimen/webhtv_focus_ring_width(个性化推荐等卡片宽度统一);新增 3 条防回归契约;leanback 4166 + mobile 4981 tests 0 failures;check_ui_tokens violations=1(=基线);dev1 实机选集项环 3px #447BF5

Task-Guard: tv-ring-dialog-attr-recommendation

Verification: Theme.WebHTV/Theme.WebHTV.Dialog/ThemeOverlay.WebHTV.Dialog 三处补绑 tv* 五项(Robolectric 实测 dialog 主题 tvPlayerRing 由 UNRESOLVED→#FF447BF5);selector_control_sheet_button 补 state_activated 分支(dialog_control#player activated 由无描边→#FF00A95A);6 个 Java 代码挂环位置(TmdbRecommendationPresenter/TmdbCardFocusHelper/TmdbEpisodeAdapter/TmdbVideoAdapter/InlineEpisodeAdapter/TmdbDetailActivity 11 处)改读 @dimen/webhtv_focus_ring_width;新增 3 条防回归契约(tv 属性必被主题赋值、Java 不得写死环宽/环色);leanback 4166 + mobile 4981 tests 0 failures;check_ui_tokens violations=1(=基线) hex_drawables=0;dev1 实机选集项焦点环 3px #447BF5

Task-Guard: tv-ring-dialog-attr-recommendation
2026-10-10 21:56:12 +08:00
Silent1566
c3bb6a63d9 fix(theme): 播放页焦点/当前态环统一宽度并统一为受主题控制的派生色
用户实机报告(dev1):播放页按钮的边框太粗,按钮和播放器选中后的边框颜色也不统一。

根因(实机像素实测,非推测):
- selector_video_item(播放页 11 个文本按钮)焦点环写死 3dp,而同一屏的芯片族
  selector_chip(26+14 个 Control 按钮)是 1.5dp → 实机量到 6px vs 3px,「太粗」即 3dp 那族;
- 环色三套并存:芯片族写死 @color/white、文本按钮族 ?attr/tvFocusRing、当前态写死绿
  #2CC56F;且 shape_chip_activated/round_activated 完全没有描边。

关键约束(为什么不能直接换 ?attr/tvFocusRing):播放页控制条直接叠在视频上(背景
transparent,播放态黑色渐变被视频遮住),另一类宿主是固定深靛玻璃。实测白环对纯白视频
1.00:1、浅色表 FOCUS 深蓝 #0B57D0 对玻璃 1.83:1、写死绿对亮视频 2.25:1 —— 没有任何
调色板原色能同时在两类极端背景上达到 3:1。

方案(本轮经用户确认的「方案 A」):
- 新增 ThemeTokens.colorPlayerFocusRing / colorPlayerCurrentRing,由 ThemeResolver 用
  已有的 readableAccent 在 PLAYER_RING_BACKDROPS = {纯白视频 + 三档玻璃} 上夹取到
  MIN_PLAYER_RING_CONTRAST = 3.0(WCAG 2.2 SC 1.4.11 非文本门槛,与 outline/focus 同口径),
  保持用户槽的色相与彩度、只走明度;
- 新增主题属性 ?attr/tvPlayerRing / ?attr/tvPlayerCurrentRing(attrs.xml 声明 + 两个 flavor
  Theme.Base 绑定);
- 「当前播放/当前生效」环色提升为独立用户可编辑槽位 playerCurrent(ThemeProfile.SlotSet /
  ThemeEditor.Slot.PLAYER_CURRENT / Validator / ThemePreviewView 新增「播放器」分组 /
  ThemePresets 同步),满足用户「当前态独立一个设置,主题色彩中没有就添加」;
- 分层固化:视频层 → tvPlayerRing/tvPlayerCurrentRing;应用表面(含与详情页/手机版共用的
  selector_episode_card、selector_tmdb_*)→ tvFocusRing/tvCurrentRing。

派生值(编译期默认,写入 webhtv_tokens.xml):
  light  playerFocus #447BF5  playerCurrent #00A95A
  dark   playerFocus #7695C5  playerCurrent #00A95A

验证:
- TvFocusRingContractTest 16/16(新增播放页环「统一宽度 + 主题色」契约,以及
  playerRingColoursClearNonTextContrastOnVideoAndGlass 的实测对比度断言 +
  「调色板原色不可行」的回归钉子)
- NativeEnhancedPlaybackStyleFocusTest / ThemeContractTest / ThemeEditor* / ThemePresets* 全绿
  (色板资源数 49→51、新增槽位已全链路接线)
- :app:testLeanbackArm64_v8aDebugUnitTest 4163 tests / 0 failures
- :app:testMobileArm64_v8aDebugUnitTest 4980 tests / 0 failures
- scripts/check_ui_tokens.sh:violations=1(=基线)、hex_drawables=0、contrast failures=0
- dev1 实机(覆盖安装同签名 Debug 包)实测:焦点环带 6px(3dp)→3px(1.5dp)、当前态 3px(1.5dp);
  焦点环色 #447BF5、当前态 #00A95A,与派生值逐字节一致

Verification: 9 个播放页环 drawable 统一到 @dimen/webhtv_focus_ring_width 与 tvPlayerRing/tvPlayerCurrentRing;ThemeResolver 按「纯白视频+三档玻璃」夹取 ≥3:1 派生(light #447BF5/#00A95A、dark #7695C5/#00A95A);新增 playerCurrent 用户槽位全链路;TvFocusRingContractTest 16/16、leanback 4163 + mobile 4980 tests 0 failures、check_ui_tokens violations=1(=基线);dev1 实机覆盖安装后环带 6px→3px、环色与派生值逐字节一致

Task-Guard: tv-player-ring-unified

Verification: 9 个播放页环 drawable 统一宽度 token + tvPlayerRing/tvPlayerCurrentRing;ThemeResolver 在「纯白视频 + 三档玻璃」上 readableAccent 夹取 ≥3:1(light #447BF5/#00A95A、dark #7695C5/#00A95A,与 webhtv_tokens.xml 逐字节一致);新增 playerCurrent 用户槽位(Profile/Editor/Validator/PreviewView/Presets 全链路);TvFocusRingContractTest 16/16(含两类极端背景对比度断言与「调色板原色不可行」回归钉子)、NativeEnhancedPlaybackStyleFocusTest/ThemeContractTest/ThemeEditor*/ThemePresets* 全绿;leanback 4163 + mobile 4980 tests 0 failures;check_ui_tokens violations=1(=基线)、hex_drawables=0;dev1 覆盖安装实机:焦点环带 6px(3dp)→3px(1.5dp)、当前态 3px,环色 #447BF5/#00A95A 逐字节吻合

Task-Guard: tv-player-ring-unified
2026-10-10 20:17:47 +08:00
Silent1566
fb4fd7d99c
Merge pull request #433 from Silent1566/dev2
merge: dev2 合并 beta 最新代码(PR#427-#432)并复评加载圈族 6 个已提交未推送提交
2026-10-10 19:45:30 +08:00
Silent1566
34a93e243f docs(c57): 记录 dev2 交付坐标(合并提交 df7f7ff580 / recovery tag / beta tip 819003db8f) 2026-10-10 19:42:26 +08:00
Silent1566
df7f7ff580 merge: 合并 origin/beta 最新代码(PR#427-#432:小说朗读/去广告总时长/搜索下拉对比度/站点注入整页/mobile 电池块)并复评 dev2 加载圈族 6 个已提交未推送提交
Verification: 合并 origin/beta 819003db8f(0 冲突,MERGE_HEAD=beta tip);合并结果树 cd5c370c == git merge-tree --write-tree 自动合并结果树;零复活三层证据通过(严格复活数 0,--diff-filter=D 空,47 条 beta 真移除路径命中 0,检测器自检通过);双 flavor Java 编译 + 双 flavor AndroidTest Java 编译 BUILD SUCCESSFUL;双 flavor 全量 JVM 套件失败集与未触碰基线 29d52ab238 逐类一致(leanback 4148/8 失败 7 类、mobile 4983/7 失败 6 类,均既有 CRLF/Windows renameTo 环境问题,0 新增失败);定向 VideoActivityLayoutTest 155/155、TmdbDetailDirectPlayTransitionSourceTest 4/4、PlayerDisplaySettingSyncTest 7/7、PlaybackOwnershipSourceTest 16/16 全绿;git diff --check 与 --cached --check 退出码 0,冲突标记 0;构建后 gradlew clean 完成

Task-Guard: C57-beta-merge-review-dev2-20261009
2026-10-10 19:41:55 +08:00
Silent1566
819003db8f
Merge pull request #432 from Silent1566/dev4
dev4:合并 beta 评审通过,推送影视原生播放器控制栏电池电量+时间
2026-10-10 17:34:03 +08:00
Silent1566
c8764048c5 docs(c57): 记录 dev4 合并评审交付坐标(beta tip 已在祖先链,复评 2 个已提交未推送的电池提交,零复活三层证据 + mobile 全量验证)
Verification: 双flavor Java/AndroidTest编译通过; mobile全量单测4981项0失败(JBR21.0.10); VideoActivityLayoutTest 155项0失败; 零复活三层证据通过(含可证伪自检); git diff --check通过; 无冲突标记

Task-Guard: C57-beta-merge-review-dev4-20261010
2026-10-10 17:30:54 +08:00
Silent1566
c442601fbd feat(mobile): 影视原生播放器控制栏电池块补齐当前时间(同构沉浸融合 batteryInfo 竖排容器,时间复用 Formatters.TIME)
Verification: testMobileArm64_v8aDebugUnitTest VideoActivityLayoutTest 155 passed 0 failed + 8 affected classes BUILD SUCCESSFUL; assembleMobileArm64_v8aDebug BUILD SUCCESSFUL; ViewControlVodBinding batteryInfo/battery/batteryTime generated; adb install -r emulator-5560 Success; launch no FATAL

Task-Guard: ui-battery-time-20261010
2026-10-10 16:21:00 +08:00
Silent1566
8db36f33a6 feat(mobile): 影视原生播放器控制栏新增电池电量图标(对齐沉浸融合模式,全屏未锁定时显示,BatteryUtil 档位复用)
Verification: testMobileArm64_v8aDebugUnitTest VideoActivityLayoutTest 155 passed 0 failed; assembleMobileArm64_v8aDebug BUILD SUCCESSFUL; ViewControlVodBinding.battery generated; adb install -r emulator-5560 Success; launch no FATAL

Task-Guard: ui-battery-20261010
2026-10-10 15:17:09 +08:00
Silent1566
b8581afc41 fix(theme): 详情页/播放页焦点环圆角对齐宿主,并修正卡片集数名颜色
用户实机报告两点:
1. 播放页、详情页估计都有和首页一样的焦点环圆角问题;
2. 卡片下方「集数名称」一栏字体颜色不对、看不清(手机版和剧名同色)。

根因(与 §12.2 同一几何规则,只是宿主不同):环作为卡片 foreground 时,环外边界圆角
半径 = 声明值 + w/2。§12 只修了首页 selector_vod,同类实例在详情页/播放页还有 5 处仍
写「声明值 = 宿主圆角」,宿主圆角因此露出环外:
- selector_episode_card(8dp 宿主)8dp -> 6dp
- selector_tmdb_media_focus(8dp 宿主)8dp -> 6dp
- selector_tmdb_cast_focus(12dp 宿主)12dp -> 9.5dp
- shape_episode_photo_focused(8dp 宿主)8dp -> 6dp
- shape_video_focused(播放页画面框是直角 FrameLayout)8dp -> 0dp

cache 管理弹窗按钮另有一类更本质的问题:M3 按钮圆角来自
ShapeAppearance.M3.Comp.Button.Small.Container.Shape.Round -> Corner.Full = 50%(52dp 高
按钮 = 26dp 胶囊),而 foreground 环画在整个 view 上、按钮填充被 4dp inset(实测填充
bbox y12..143、环 bbox 0..155),任何固定 dp 都无法对齐。改为 Material 自带描边通道
(app:strokeColor + app:strokeWidth,与 dialog_about/item_following 同一做法),由同一个
ShapeAppearanceModel 同时画填充与描边;环色按既有规则取配对 on-色:tonal 走
focus_ring_secondary(13.18:1 / 7.24:1),text 走 focus_ring_primary(4.91:1)。
selector_cache_button_focus.xml 删除,CacheManagementDialog 两个工厂方法改用
applyFocusRing()。

集数名:adapter_vod 的 @id/remark 由 ?attr/colorOnSurfaceVariant(#44474F)改为
?attr/webhtvColorOnWallpaper(#FFFFFF),与剧名同色、与手机版一致;实机对比度
1.35:1 -> 7.26:1。

验证:
- TvFocusRingContractTest 14/14(新增前景环圆角规则 + 集数名可读性两项契约)
- TvAppSurfaceFocusRingDeviceTest 9/9(新增真实宿主 inflate + 真实环栅格化,四档密度
  hdpi/xhdpi/xxhdpi/xxxhdpi 逐像素判定:旧值 26/34/61/123 溢出,新值 0/2/0/2)
- :app:testLeanbackArm64_v8aDebugUnitTest 4161 tests / 0 failures
- :app:testMobileArm64_v8aDebugUnitTest 4980 tests / 0 failures
- scripts/check_ui_tokens.sh:violations=1(=基线)、hex_drawables=0、contrast failures=0
- dev1 实机(覆盖安装同签名 Debug 包):首页集数名全白 7.26:1;详情页选集卡环外
  「无法用 环色↔背景 混合解释」的像素 0;播放页画面框白环收成方角、黑角不再被切

Verification: 5 个前景环声明圆角改为 宿主圆角−w/2(8dp→6dp、12dp→9.5dp、直角→0dp);cache 按钮改走 MaterialButton stroke 通道并删除 selector_cache_button_focus;adapter_vod remark 改 webhtvColorOnWallpaper(1.35:1→7.26:1);TvFocusRingContractTest 14/14、TvAppSurfaceFocusRingDeviceTest 9/9(真实宿主四档密度 0 溢出)、leanback 4161 + mobile 4980 tests 0 failures、check_ui_tokens violations=1(=基线);dev1 实机三页截图复核通过

Task-Guard: tv-focus-ring-detail-player

Verification: 5 个前景环圆角改为 宿主圆角−w/2(8dp→6dp、12dp→9.5dp、直角→0dp);cache 按钮改走 MaterialButton stroke 通道(删 selector_cache_button_focus,CacheManagementDialog 走 applyFocusRing);adapter_vod remark 改 webhtvColorOnWallpaper 实测 1.35:1→7.26:1;TvFocusRingContractTest 14/14、TvAppSurfaceFocusRingDeviceTest 9/9(真实宿主 inflate + 真实 drawable,四档密度旧值 26/34/61/123 → 新值 0/2/0/2);leanback 4161 + mobile 4980 tests 0 failures;check_ui_tokens violations=1(=基线)、hex_drawables=0;dev1 覆盖安装实机复核:首页集数名 7.26:1、详情页选集卡环外异常像素 0、播放页画面框白环收成方角

Task-Guard: tv-focus-ring-detail-player
2026-10-10 13:56:58 +08:00
Silent1566
540d70b046 fix(theme): 应用表面焦点环改回 1.5dp 细环并让卡片环圆角对齐卡片 8dp 圆角
用户实机报告(dev1 192.168.50.3:5555)两点:颜色统一后 3dp 环比原来「白色那一族」的
1.5dp 粗;卡片焦点环在圆角处比卡片自身圆角大,卡片圆角露出环外一段米色月牙。

根因(实机像素复核 + Robolectric 栅格化扫描,非推测):
- 3dp 来自视频层样板 selector_video_item 的字面量,§9 把它套到应用表面后整族由 1.5dp 变粗;
- shape_vod_focused 是卡片的 foreground,而卡片可见表面另有自己的 8dp 圆角(图片走
  @style/Vod.Grid 上圆角、标题块走 shape_vod_name 下圆角、列表型宿主走 shape_vod_list
  四角)。描边型 <shape> 把描边中心线画在「内缩 w/2、圆角半径仍取声明值」的路径上,
  于是环外边界圆角 = 声明值 + w/2 = 8+1.5 = 9.5dp > 卡片 8dp,沿角落对角线卡片弧反而
  更靠外约 1px,即实机上那条 1–2px 米色月牙(m1.png: 环外边界 x=396.5 处出现 (221,207,194)
  这类卡片表面色像素)。

修复(生产改动仅 2 行):
- webhtv_focus_ring_width:3dp → 1.5dp(= 用户认可的原「白色那一族」宽度);
- shape_vod_focused 声明圆角:8dp → 7dp(宿主 8dp − w/2 = 7.25dp,再留描边按整数像素向上
  取整的余量;7dp 在 density 1.5/2/3/4 下实测溢出均为 0)。6 个 selector_vod 宿主表面都是
  8dp,单环仍同时对齐全部宿主。视频层 selector_video_item 保持 3dp/2dp/1dp 自成档位。

验证:
- TvAppSurfaceFocusRingDeviceTest 8/8(新增:真实 selector_vod 焦点分支与 8dp 卡片表面
  栅格化后卡片 0 像素落在环外;四档密度;变异检验用旧值 8dp+3dp 必须报出溢出)
- TvFocusRingContractTest 12/12(新增:卡片环圆角 ≤ 宿主圆角 − w/2 且不低于理论值 0.5dp)
- :app:testLeanbackArm64_v8aDebugUnitTest 4158 tests / 0 failures
- :app:testMobileArm64_v8aDebugUnitTest 4980 tests / 0 failures
- scripts/check_ui_tokens.sh:violations=1(= 基线)、hex_drawables=0、contrast failures=0
- dev1 实机(scripts/build_arm64_debug_install.sh --flavor leanback 覆盖安装同签名 Debug 包):
  环带 6px → 3px(功能按钮与内容卡片同宽、同色 = 主题 FOCUS 槽 #0B57D0);
  卡片圆角外侧「无法用 环色↔壁纸 混合解释」的像素数 25 → 0(16× 放大图人工比对确认)

Verification: dimen 3dp→1.5dp + shape_vod_focused 圆角 8dp→7dp;栅格化溢出 hdpi/xhdpi/xxhdpi/xxxhdpi = 0/5/0/0(旧值 25/52/114/181,变异检验可抓);TvAppSurfaceFocusRingDeviceTest 8/8、TvFocusRingContractTest 12/12;leanback 4158 + mobile 4980 tests / 0 failures;check_ui_tokens violations=1(=基线);dev1 实机覆盖安装后环带 6px→3px、卡片圆角外侧米色像素 25→0

Task-Guard: tv-focus-ring-app-surface-thin-corner

Verification: dimen 3dp→1.5dp + shape_vod_focused 圆角 8dp→7dp;Robolectric 栅格化卡片圆角溢出 hdpi/xhdpi/xxhdpi/xxxhdpi = 0/5/0/0(旧值 25/52/114/181,变异检验抓得到);TvAppSurfaceFocusRingDeviceTest 8/8、TvFocusRingContractTest 12/12;leanback 4158 + mobile 4980 tests 0 failures;check_ui_tokens violations=1(=基线);dev1 覆盖安装实机:环带 6px→3px(按钮与卡片同宽同色 #0B57D0)、卡片圆角外侧米色像素 25→0

Task-Guard: tv-focus-ring-app-surface-thin-corner
2026-10-10 12:22:04 +08:00
Silent1566
d8f958671a fix(theme): 收口剩余 TV 焦点环宽度并修正选集弹窗不可见焦点
接上一提交(统一 21 个应用表面焦点环)后,全库扫描仍发现 8 个 leanback 焦点态描边
没有引用唯一宽度 token,本轮收口:

- selector_control_sheet_button(2dp)、selector_episode_dialog_item(2dp)、
  selector_episode_dialog_page(3dp 字面量)、selector_danmaku_result_item(2dp)、
  selector_danmaku_search_action(2dp)、shape_video_item_focused(1.5dp)
  → 一律改为 @dimen/webhtv_focus_ring_width
- 两个文件按理由豁免:selector_video_item 是规范样板文件(字面量 3dp 与 token 同值,
  且被 NativeEnhancedPlaybackStyleFocusTest 当基准逐字断言);
  shape_audio_action_icon_focused 是播放页音频按钮的 inset 图标内描边环(40dp/17dp),
  角色与尺寸不同,套 3dp 会把图标糊成一团

顺带修掉扫描时暴露的「焦点完全看不见」缺陷:选集弹窗(固定深色面板 #DD111820)
的环色取自填充色的近似色,与自身填充只有 1.47:1 / 1.32:1,两个 selected 态更是
环与填充同色 = 1.00:1;改白后为 3.12:1 / 3.17:1,达到 WCAG 2.2 SC 1.4.11 的 3:1 门槛,
并与所有其它固定深色宿主的白环规则一致。同时把 state_focused 提到 state_selected
之前——原顺序会让「正在播放 + 获得焦点」的行命中 selected 分支而吞掉焦点环。

新增 TvFocusRingContractTest.noLeanbackFocusRingHardcodesItsWidth:扫描整个
leanback/res/drawable,断言每个焦点态描边都引用唯一 token(白名单式断言无法覆盖
新增文件,这条反过来做)。变格检验:把 selector_episode_dialog_page 改回 2dp 后
该测试与 appSurfaceFocusRingsShareOneWidthTokenAndAThemeColour 两项转红。

未完成:dev1 模拟器整轮离线(ping 100% 丢包 / ARP FAILED),未能做 D-pad 实机复核。

Verification: 剩余 8 个 leanback 焦点态描边收口:6 个改引 @dimen/webhtv_focus_ring_width(2 个豁免各有理由);选集弹窗环色改白,环/填充对比度由 1.00:1/1.32:1/1.47:1 提升到 3.12:1/3.17:1;修正 selected 先于 focused 导致焦点环被吞的分支顺序;新增全库不变量 noLeanbackFocusRingHardcodesItsWidth(变格检验:改回 2dp 后 2 项转红);leanback 4155 + mobile 4980 tests / 0 failures;TvFocusRingContractTest 11/11;TvAppSurfaceFocusRingDeviceTest 6/6;check_ui_tokens hex_drawables=0 violations=1(=基线)

Task-Guard: tv-focus-ring-app-surface-2
2026-10-10 09:53:22 +08:00
Silent1566
d07a8454f7 fix(theme): 统一 TV 端应用表面焦点环并接线到主题色板
用户报告(两张实机截图):同一电视首页上,搜索/历史功能按钮是蓝色边框、
最近观看内容卡片是白色边框,且粗细也不同。

根因:应用表面焦点环分裂成三族,各写各的取值——
shape_item_focused 等写 1.5dp ?attr/colorPrimary(蓝,且 colorPrimary 在 TV 上
同时驱动约 100 个布局的聚焦文字色);shape_vod_focused 等写
1.5dp~2dp @color/white(与调色板无关的常量,改主题纹丝不动);
shape_config_history_item_focused 等又写 2dp。

统一:宽度一律 @dimen/webhtv_focus_ring_width;宿主为调色板表面的 7 族改用
?attr/tvFocusRing,自带主题色填充的 4 族取焦点态填充的配对 on-色。
宿主固定明暗或属视频层的 10 个 drawable 只统一宽度、环色保持与调色板无关——
逐对计算 WCAG 对比度后确定边界(深色玻璃上 ?attr/tvFocusRing 仅 1.91:1,
浅色表深蓝在固定浅色面板上仅 1.6~2.8:1)。

验证:像素级栅格化证据(TvAppSurfaceFocusRingDeviceTest 6/6)、变格检验
(把 shape_vod_focused 改回 1.5dp 白后 3 项断言转红)、静态契约
(TvFocusRingContractTest)、leanback 4154 + mobile 4980 tests 全绿、
UI token 门槛回到基线。

未完成:dev1 模拟器本轮全程离线(ping 100% 丢包 / ARP FAILED),
未能做 D-pad 实机复核;证据改为 Robolectric 真实栅格化逐像素度量。

Verification: drawable 焦点环统一:21 个文件改为 @dimen/webhtv_focus_ring_width + 主题环色/配对 on-色;TvFocusRingContractTest 新增 2 项契约;TvAppSurfaceFocusRingDeviceTest 6/6 像素级栅格化证据(含 day/night 跟随 + 变格检验);leanback 4154 + mobile 4980 tests / 0 failures;check_ui_tokens hex_drawables=0 violations=1(=基线);assembleLeanbackArm64_v8aDebug SUCCESSFUL

Task-Guard: tv-focus-ring-app-surface
2026-10-10 09:24:09 +08:00
Silent1566
b6b8e7a375 docs: 记录加载圈系列在 5557 的实机验证证据与未定证部分
Verification: docs-only: records 5557 device verification evidence - debug leanback arm64 build installed via overwrite (signature SHA-256 identical to local debug keystore, artifact hash d5953c0c matches device base.apk), A/B against the pre-change APK (73c6567a) reproduced the identical ijk-smoke.mp4 timeout/freeze so it is not a regression, sintel-trailer.mp4 plays to the end with no stuck progress node after 3 seeks, 3 rounds of repeated VIEW intents exercising setPlayer(null) keep playing with no stuck spinner, crash buffer empty. Honestly records what was NOT provable on device (IJK rebuffer readout, same-episode replay semantics) and the prefs overwrite observation. No code change; no re-run of already-green tests.

Task-Guard: SWEEP-DEVICE-EVIDENCE-5557-20261009
2026-10-09 20:17:33 +08:00
Silent1566
e6413efd1e
Merge pull request #431 from Silent1566/dev1
fix(csp): 站点注入统一全屏整页(修竖屏按钮被压扁 / 横屏“没有全屏”)+ 合并 beta 最新代码
2026-10-09 19:46:54 +08:00
Silent1566
913ae81d19 docs(c56): 记录 dev1 交付坐标(合并提交 fe4d02e07 / recovery tag / 推送 / PR #431)
补记本任务闭环坐标与闭环记录:合并提交 fe4d02e076d781f5158d791c6aae8e0d216f07fb(两个父:
40ba21ed87 + beta tip 841a5707d5)、
合并结果树与合并前 HEAD 树同为 79182bfdb509d1a53102fa72ea839d79f205ab87、recovery tag
recovery/C56-beta-merge-review-dev1/20261009193053-fe4d02e076d7、origin/dev1 已推送、PR #431
(dev1 -> beta)已创建且未合并;并记录 PR 文件集与本地净差异逐项一致的校验结果。

Verification: 仅文档改动,无代码/构建/运行时变更;git status --porcelain 提交前仅含本文件,
提交后工作区干净;PR #431 state=OPEN、mergedAt=null、mergeable=MERGEABLE、changedFiles=4,
文件集与 git diff --name-only origin/beta HEAD 逐项一致;收尾复核 beta tip 仍为 841a5707d。

Task-Guard: C56-beta-merge-review-dev1-coords

Verification: docs-only 交付坐标闭环: 合并提交 fe4d02e076 两父(40ba21ed8+841a5707d)已校验, recovery tag recovery/C56-beta-merge-review-dev1/20261009193053-fe4d02e076d7, origin/dev1=fe4d02e07 已推送, PR #431 OPEN/mergedAt=null/MERGEABLE/changedFiles=4 且文件集与 git diff --name-only origin/beta HEAD 逐项一致, beta tip 复核仍 841a5707d

Task-Guard: C56-beta-merge-review-dev1-coords
2026-10-09 19:32:37 +08:00
Silent1566
fe4d02e076 merge: 合并 origin/beta 最新代码(PR#430)并复评站点注入整页链路(含已提交未推送的 CSP 修复)
远端 beta tip 841a5707d 的两个父提交(7e51768d8 / 67d930ed6)都已是本分支祖先,
故 beta 侧增量为 0 条内容:git merge --no-ff --no-commit origin/beta 零冲突、零文件变化,
合并结果树与合并前 HEAD 树逐字节相同(79182bfdb),MERGE_HEAD = 841a5707d。
零复活三层证据:严格复活行 0、--diff-filter=D 为空、beta 独有文件 0、关键移除能力关键词 0 命中。

复评对象:净差异 3 路径(CustomCspDialog.java 修改 + LayoutTest 新增 + SITE-INJECT 文档新增)
与 2 个已提交未推送提交(22348293b 竖屏整页、40ba21ed8 横屏整页)。

生产代码无缺陷,本轮未改动一行生产代码(sha256 前后一致)。
测试代码发现 1 个真问题并修复:
- 旧横屏“变异检验”是恒真算式((int)(W*0.76f) 与 Math.round 比较、W-(int)(W*0.76f)>0),
  不调用生产代码、无法转红;且 Robolectric 只装在 leanback 测试变体且默认竖屏,
  第 1 轮“横屏按朝向挑 0.76×0.98”分支在 JVM 层永远不可达——负对照 A 实测:
  回退该实现并用修复前测试文件跑 = 5 例全绿,缺陷可原样复发而不被发现。
- 修法:横屏负对照改为按旧窗口尺寸对同一面板链实测(root 宽<屏宽、高<可用高度),
  并对当前实现断言窗口宽高必须 MATCH_PARENT;新增 @Config(qualifiers=w686dp-h386dp-land)
  的 landscapeOrientationQualifierAlsoFillsTheWholePage()(自带 qualifier 自检),
  锁死真实横屏配置下的“铺满 + 按钮区 40dp 贴底”。负对照 B:同一变异下转红
  (真实横屏下 root 高度必须 MATCH_PARENT expected:<-1> but was:<378>);撤销变异 6/6 全绿。

改动仅测试与文档:CustomCspDialogLayoutTest 5→6 例 + 新增 C56 文档 + 订正 SITE-INJECT 文档
(变异检验口径与用例数、补充恢复 tag)。

Verification: 双 flavor Java 编译 + 双 flavor AndroidTest Java 编译 + 双 flavor 全量 JVM 套件
单次 Gradle 调用 BUILD SUCCESSFUL in 1m24s:leanback 651 suites/4146 tests/0 failure/0 error/2 skipped
(基线 4145,+1 = 新增加用例且可解释),mobile 726/4980/0/0/2(与基线一致)。
UI token 门禁 UI_TOKEN_STATUS PASS(violations=1、contrast min=4.28,与 C50-C55 基线逐项一致)。
git diff --check 退出码 0,冲突标记 0,未使用 git reset(合并状态完整保全)。

Task-Guard: C56-beta-merge-review-dev1

Verification: 合并 origin/beta(841a5707d) 内容零变化且零复活(严格复活行0/D过滤空/beta独有文件0/合并树==合并前HEAD树); 复评净差异3路径+2个已提交未推送提交: 生产代码无缺陷, 测试发现F1(横屏变异检验为恒真算式且真实横屏配置无回归锁)已修复并双向负对照(变异A: 修复前5例全绿->修复后6例转红 expected:<-1> but was:<378>, 撤销变异6/6绿); 生产文件sha256 311c8e09...1eeb前后一致; 双flavor编译+AndroidTest编译+全量JVM套件 BUILD SUCCESSFUL(leanback 651 suites/4146 tests/0 failure/0 error/2 skipped, mobile 726/4980/0/0/2, 差值+1可解释); UI_TOKEN_STATUS PASS(violations=1与基线逐项一致); git diff --check退出0, 冲突标记0

Task-Guard: C56-beta-merge-review-dev1
2026-10-09 19:30:53 +08:00
Silent1566
40ba21ed87 fix(csp): 站点注入横屏也铺满整页,修“没有全屏”
第 1 轮只把竖屏改成整页,横屏仍保留旧的 0.76x0.98 居中比例弹窗。
用户在 1920x1080 横屏设备复测看到的正是这一版:窗口 1459x1058 居中、
root [230,42][1689,1080],左右各空出 230px。

本次两种朝向统一铺满整页,删除 land/屏宽屏高三个参数,不再有任何按屏
比例的手算:窗口 MATCH_PARENT + ADJUST_RESIZE、root 与 Material 弹窗面板链
一并 match_parent、滚动区 height=0/weight=1/maxHeight=0,固定高按钮区
永远 40dp 并贴底,正文左右不贴边由 XML 既有 20dp padding 保证。

测试同步重写:竖屏 5 种比例 + 横屏 3 种比例都断言铺满与按钮 40dp、
窗口参数必须 MATCH_PARENT、短内容同样铺满、极短窗口按钮不被压缩,
并保留变异检验(回退第 1 轮实现后 4/5 失败)。

Verification: 用户复测反馈横屏未全屏已修复. 设备实测(dev1 192.168.50.3:5555 横屏 1920x1080@280): 窗口属性由 (1459x1058) gr=CENTER 变为 fill x fill + sim={adjust=resize}, root 由 [230,42][1689,1080] 变为 [0,42][1920,1080] 铺满整页, 取消/确定 70px=40dp 贴底, 列表 viewport 493px 且不与按钮区重叠; 竖屏(1080x2160@440, 5 条注入)仍整页, 按钮区 110px=40dp, viewport 1238px. JVM: CustomCspDialogLayoutTest 5/5 通过(竖屏5比例+横屏3比例均断言铺满与按钮40dp; 短内容; 极短窗口按钮不压缩; 变异检验回退第1轮实现后 4/5 失败); 全量单测 leanback 4145 项/0 failure/0 error/2 skipped, mobile 4980 项/0 failure/0 error/2 skipped. 设备状态已回收(registry md5 bce0777921bc78ec1991f4d89f1b3d87 一致, wm size/density reset, accelerometer_rotation=1, 应用 force-stop).

Task-Guard: SITE-INJECT-FULLPAGE-20261009
2026-10-09 19:05:57 +08:00
Silent1566
22348293b8 fix(csp): 站点注入竖屏铺满整页,底部按钮区不再被内容压扁
竖屏原本按屏幕比例手算高度(窗口 WRAP_CONTENT + 滚动区 wrap_content +
0.58H 上限 + 无 weight),条目一多就把底部按钮区挤出窗口下沿:设备实测
取消/确定 由 40dp 变 34.9dp、末张卡片操作行只剩 9.1dp。

改为竖屏铺满整页:窗口 MATCH_PARENT + ADJUST_RESIZE、root 与 Material
弹窗面板链一并 match_parent、滚动区 height=0/weight=1/maxHeight=0,
固定高按钮区永远保住自然高度并贴底,列表转为滚动视口。横屏保持
0.76x0.98 比例弹窗行为不变。

尺寸策略抽成静态 applyPageSizing 以便 JVM 测量测试锁定该契约。

Verification: 设备实测(dev1 192.168.50.3:5555, 1080x2160@440 竖屏, 5 条注入): 取消/确定由 34.9dp 恢复 40dp、第3卡操作行由 9.1dp 恢复到滚动视口内; 另测 1080x1920@480 与 1080x1800@440 竖屏按钮区均 40dp、横屏 1920x1080@280 窗口仍 0.76x0.98 且按钮区 40dp、文本模式/编辑面板/空搜索结果页面均铺满且按钮区贴底. JVM: CustomCspDialogLayoutTest 4/4 通过(含变异检验: 回退参数后 3/4 失败); dialog+bean 定向测试通过; 全量单测 leanback 4144 项/0 failure/0 error/2 skipped, mobile 4980 项/0 failure/0 error/2 skipped. 设备状态已回收(registry md5 bce0777921bc78ec1991f4d89f1b3d87 一致, wm size/density/reset, 应用 force-stop).

Task-Guard: SITE-INJECT-FULLPAGE-20261009
2026-10-09 18:43:23 +08:00
Silent1566
839809ee1e docs: 收口加载圈遗漏分支扫荡(4 项修复坐标、族闭合证明、有意契约与残留风险)
Verification: docs-only closure: records the four committed omission fixes (4319403502, 7809ff4952, d7b9439201, f2ccce36c9) with their recovery tags, proves the player-spinner write/clear sites are now closed (single VISIBLE/GONE write per flavor), and records with file:line evidence the deliberately-pinned contracts left unchanged (Exo-only stall watchdog from 60ad0d0969 + PlayerManagerLifecycleSourceTest, C47 seek gate clause, PlaybackOwnershipSourceTest isOwner/isEmpty). No code or build change in this session; no re-run of already-green verification.

Task-Guard: LOADING-SPINNER-OMISSION-SWEEP-20261009
2026-10-09 18:14:08 +08:00
Silent1566
f2ccce36c9 fix(osd): 屏显诊断重缓冲计数按引擎正确分派,IJK 不再恒为 0
PlayerOsdController.getDiagnostics() 的分派条件写成了「是否 MPV」:
  int rebufferCount = player.isMpv() ? player.getRebufferCount() : snapshot.rebufferCount();
而真实前提是「有没有 Exo analytics 快照」(即是否 Exo)。非 Exo 时 snapshot 为
Snapshot.empty(),于是 IJK 落到快照分支,重缓冲次数与总时长恒为 0。

PlayerManager.getRebufferCount()/getRebufferTotalMs() 本身是引擎无关的
(PlaybackBufferingTracker + MPV disc 计数),非 Exo 早有可用取数器,只是 IJK 没走到。
改为按 isExo() 分派:Exo 行为不变,IJK/MPV 读引擎无关追踪器。这是排查工具读数错误而非
播放缺陷——本系列排查「转圈」时曾据该读数给出无效建议,修掉后诊断面板对三种内核都可信。

Verification: :app:compileMobileArm64_v8aDebugJavaWithJavac 成功;:app:testMobileArm64_v8aDebugUnitTest
定向跑 PlayerDisplaySettingSyncTest 7/7(含新增 playerOsdDiagnosticsReadRebufferCountPerEngine)、
VideoActivityLayoutTest 154/154、TmdbDetailActivityLayoutTest 130/130 全绿。新断言对 base 源码必然
变红(base 表达式含 player.isMpv() 而不含 player.isExo())。过程中自查 git diff 发现并修回了新增
用例时误将签名与首行拼接成一行的问题。

Task-Guard: OSD-REBUFFER-ENGINE-BRANCH-20261009

Verification: compileMobileArm64_v8aDebugJavaWithJavac OK; PlayerDisplaySettingSyncTest 7/7 (incl. new playerOsdDiagnosticsReadRebufferCountPerEngine), VideoActivityLayoutTest 154/154, TmdbDetailActivityLayoutTest 130/130 all green; new assertion provably red at base (expression used player.isMpv()); accidental joined-line regression found via diff review and repaired before verification

Task-Guard: OSD-REBUFFER-ENGINE-BRANCH-20261009
2026-10-09 18:11:09 +08:00
Silent1566
d7b9439201 fix(playback): 被作废的取流请求必须释放加载态,消除永久转圈与同集无法再播
修复两处同族遗漏分支(上一次修复 7809ff4952 的延续):

① leanback VideoActivity.setPlayer(null) 早退不释放守卫。
   原写法 `if (result == null || isFinishing() || isDestroyed()) return;` 让三条路径共用一次早退,
   而 result == null 是可达的:SiteViewModel.cancelPlayerContent() 会向 PLAYER LiveData 写 null。
   4 个调用点中 onRefresh 的路径真会漏:cancelPlayerContent() 之后
   `if (mFlagAdapter.getItemCount() == 0) return;` 提前返回、不再重新置位,守卫残留使
   onStateChanged(READY) 的 break 与 hidePlaybackProgressIfStale() 同时为假,圈再无清除路径。
   修法:把 null 视为「请求已作废、不会再有结果回调」,在该分支释放守卫。

② 详情页 inlinePlaybackPending 缺归属判定。applyTmdbResultNow()(TMDB 详情异步装载完成重排剧集)
   与 onResume→refreshSelectionAfterExternalPlayback() 会重设选择面但不自增代际,在途回调因
   isInlinePlaybackRequestCurrent() 为假直接返回,标记再无释放路径 ⇒ 圈永久留存,且
   isSamePendingInlinePlayback() 恒真使同一集无法再次起播。
   修法:新增 inlinePlaybackPendingGeneration 记录置位代际,回调失效分支按归属释放
   (避免旧回调清掉新请求的标记),两处显式作废点改用无条件重载。任务被取消时回调不执行,
   故显式作废点的同步释放仍必要,二者互补。

Verification: 双 flavor Arm64 Debug Java 编译成功;:app:testMobileArm64_v8aDebugUnitTest 定向跑
VideoActivityLayoutTest 154/154、TmdbDetailDirectPlayTransitionSourceTest 4/4、PlaybackOwnershipSourceTest
16/16 全绿(含本任务新增/扩展断言)。新断言对 base 源码必然变红(逐方法体字符串校验:修复前无
独立 null 分支、三个方法与 helper 均不含 releaseInlinePlaybackPending*)。过程中自查 git diff 发现
并修回了首轮批量编辑误删的 `float speed = player().getSpeed();` 与一处空行。

Task-Guard: ABANDONED-REQUEST-LOADING-RELEASE-20261009

Verification: compileLeanback/Mobile Arm64 debug Java OK; VideoActivityLayoutTest 154/154, TmdbDetailDirectPlayTransitionSourceTest 4/4, PlaybackOwnershipSourceTest 16/16 all green; new assertions provably red at base commit via per-method string check; accidental deletion of float speed line and a blank line found via diff review and repaired before verification

Task-Guard: ABANDONED-REQUEST-LOADING-RELEASE-20261009
2026-10-09 18:06:17 +08:00
Silent1566
841a5707d5
Merge pull request #430 from Silent1566/dev1
合并 beta 最新代码并复评缓存改动:补齐「临时文件清理 / 长按一键全清」的调用点级回归锁(C55)
2026-10-09 17:55:55 +08:00
Silent1566
67d930ed63 docs(c55): 记录 dev1 交付坐标(合并提交 447934222b / recovery tag / 推送 / PR #430)
Verification: 坐标回填:合并提交 447934222b88934720b387225d7d561e781108ac(rev-list --parents 实测两个父 92ad7c69d + 7e51768d8,确为 merge commit,第二父 = origin/beta tip 7e51768d881a725754dd68321ab802680ea30e1c);recovery tag recovery/C55-beta-merge-review-dev1/20261009175202-447934222b88(annotated,0s);git push origin dev1 6bb920372..447934222,推送后 origin/dev1 == dev1 == 447934222;gh pr create 返回 https://github.com/Silent1566/webhtv/pull/430,gh pr view 430 实测 state=OPEN mergedAt=null mergeable=MERGEABLE mergeStateStatus=CLEAN base=beta head=dev1 changedFiles=14 additions=1140 deletions=49;gh api pulls/430/files 合计 14 个文件名与 git diff --name-only origin/beta HEAD 逐项一致(diff 无输出);PR 只创建未合并。文档改动为纯 docs(本任务文档交付坐标与闭环记录)。

Task-Guard: C55-beta-merge-review-dev1-coordinates
2026-10-09 17:53:14 +08:00
Silent1566
447934222b merge: 合并 origin/beta 最新代码(PR#429 dev4 搜索下拉对比度修复)并复评缓存临时文件/一键全清调用点回归锁
- 合并 origin/beta tip 7e51768d8(dev4 深色模式搜索分组下拉对比度修复 + PR#429):0 冲突,合并树 0d68f909,
  第二父 7e51768d8。四向结构证据 + 行级严格判据(复活行数 0)证明合并树里每个字节只可能来自 beta tip
  或本分支自身 13 个路径,故远端已移除/回退的内容零复活,beta 增量与 dev1 改动均零丢失。
- 复评 dev1 已提交未推送的缓存改动(13 路径 +776 -49):确认无功能缺陷。
- F1(真问题,已修复):两处缓存缺陷的调用点级回归锁缺失。负对照实测:把 executeCleanup 的
  case TEMP_FILES 调用点改回 TEMP_RETENTION_MS(用户报告的缺陷原形)后缓存包 71/71 仍全绿,
  缺陷可在单测全绿的状态下复发。按仓库既有源码守卫约定新增
  CacheFullCleanupTest.everyCleanupCallSiteUsesTheSharedRule(),锁定「FULL 分派」「六个模块共享保留期
  规则」「调用点不得写窗口字面量」三类不变量,并以两向变异验证会转红(B1 命中 CacheFullCleanupTest.java:206,
  B2 命中 FULL 分派断言)。
- 验证:双 flavor Java/AndroidTest 编译 + 双 flavor 全量 JVM 套件通过(leanback 650/4140、mobile 726/4980,
  failures=0 errors=0,差值逐项可解释);UI token 门禁 PASS(violations=1,与基线逐项一致);
  git diff --check 退出 0、无冲突标记、生产文件 sha256 字节还原。

Verification: 结构四向闭合:git diff 92ad7c69d <合并树> = 仅 beta 的 5 路径(dev1 改动零丢失);git diff origin/beta <合并树> = 仅 dev1 自身 13 路径且 --diff-filter=D 为空(beta 零丢失);git ls-tree 对比 beta 有而合并树缺的文件 = 0。行级严格判据复活行数 0(beta 4330 提交 / 139 移除类单亲提交 / 27862 删除行 vs 540 新增行;43 处表面命中全部为 beta tip 自身存在的脚手架行)。0 冲突,MERGE_HEAD=7e51768d881a725754dd68321ab802680ea30e1c,合并树 0d68f90990fb6d9c3d03c5f523f9ffe22567863c(重置后重做合并复现同一树)。复评 13 路径 +776 -49 确认无功能缺陷;F1 调用点级回归锁缺口已修复:负对照 A(case TEMP_FILES 调用点改回 TEMP_RETENTION_MS)缓存包 suites=14 tests=71 failures=0 证明缺口真实,补 everyCleanupCallSiteUsesTheSharedRule 后 B1 转红(失败点 CacheFullCleanupTest.java:206)、B2(FULL 分派改为 false &&)转红并报 FULL must select the full-clean path,生产文件 sha256sum -c 字节还原 f7540b763c584e1551ba2d7080768998cbfd6ffd3a11e7f16c8d0945063d9a64。6 个目标任务全部实际执行 BUILD SUCCESSFUL in 1m 23s:leanback 650/4140、mobile 726/4980,failures=0 errors=0 skipped=2(差值 +1 / +1本轮 +2beta 的 SearchScopePopupLayoutTest 2->4 例)。UI_TOKEN_STATUS PASS violations=1 与 C50/C52/C53/C54 基线逐项一致。git diff --check 退出 0,冲突标记命中 0,fetch 后 beta 未前进。

Task-Guard: C55-beta-merge-review-dev1
2026-10-09 17:52:02 +08:00
Silent1566
92ad7c69d1 docs(c54): 记录上游 Silent1566 合并交付坐标(合并提交 45bffbb860 / recovery tag / 设备收尾)
上游 webhtv/webhtv Silent1566 的 4 个提交已合并入 dev1,双 flavor 编译与全量单测零新增回归、UI token 零新增违规、mobile/leanback 双形态实机通过;本次仅补记交付坐标并同步功能设计文档,未改动任何代码。

Verification: docs-only closure:写入交付坐标(合并提交 45bffbb860 / recovery tag recovery/C54-upstream-sync-silent1566/20261009170756-45bffbb860dc / 未推送 / 设备装置已还原并覆盖安装 mobile flavor);无代码、构建、测试或运行时行为变化;git diff --check=0

Task-Guard: C54-deliver-coordinates
2026-10-09 17:09:43 +08:00
Silent1566
45bffbb860 merge: 合并上游 webhtv/webhtv Silent1566 缓存临时文件清理修复与长按一键全清
上游 4 个提交(f9f47c24ae/92f1844342/f0da126a2c/814935cee)全部纳入:修复临时文件行显式清理无效(显式请求忽略后台保留期)、补回拆分前语义的长按一键全清(CacheCleanupMode.FULL + 残留扫描,不弹面板、Toast 报结果)、修正缺陷回归锁与结果文案语言路径。
唯一冲突为 CacheManagementDialog import 块:取上游 Setting.wrapLanguage 依赖,保留本地 WebHtvAlertDialogBuilder 主题契约,拒绝带回 MaterialAlertDialogBuilder。
本地改动零丢失;双 flavor 全量单测零失败;UI token 零新增违规;mobile 与 leanback 双形态实机通过(含全清后冷启动)。

Verification: 上游 tip 814935ceea 成为合并提交第二父(MERGE_HEAD 核验);1 个冲突(CacheManagementDialog import 块:取上游 Setting + 保留本地 WebHtvAlertDialogBuilder,未带回 MaterialAlertDialogBuilder)解决后 0 未合并路径、0 冲突标记;零丢失四层证据:上游独占 5 个代码/测试路径与上游 tip 逐字节相同、合并净差 +586/-49/12 路径(=上游 +585/-49 + 本任务文档 1 行)、585 行上游新增逐行在上游 tip 命中(外来内容 0)、本地新增行丢失 0、无路径被删除;双 flavor 编译+androidTest 编译+全量单测 BUILD SUCCESSFUL(2m12s):leanback 650 suites/4139 tests、mobile 726/4977,failures=0 errors=0 skipped=2(较 C53 基线 +8 例/flavor = 上游新增用例);UI token 门禁与基线逐项一致(layouts=385/violations=1 既有 item_following.xml/contrast pairs=38 failures=0 min=4.28,零新增);git diff --check=0;负对照:explicitRequest 退回 MODULE 时 CacheFullCleanupTest 7 例 1 失败(锁有效),仅调用点回退 24h 窗口时缓存包单测仍全绿(调用点级锁缺失,已记录);实机 192.168.50.3:5555(LIO-AN00/Android 9,证书 SHA-256 一致,install -r 覆盖安装、未卸载):mobile 版临时文件行清理『清理完成 释放 3 MB · 删除 1 个文件』且 js/stray 保留、长按不弹面板 Toast『清理完成 释放 43.3 MB · 删除 470 个文件』、cache 23 项/45M→仅余运行中加载器 jar/624K、单击仍开面板、全清后冷启动无 FATAL/ANR 且缓存自动重建;leanback 版同类『释放 2 MB · 删除 1 个文件』『释放 7.4 MB · 删除 162 个文件』、14 项/8.3M→1 项/624K、单击仍开面板、无 FATAL/ANR;journal 4 条 MODULE/FULL COMPLETED 与 UI 文案逐条一致;设备 home_button 测试装置已还原为 0,8,6,1,2,3,4,7 并删除临时 prefs 备份;未推送、未创建 PR、未打正式包

Task-Guard: C54-upstream-sync-silent1566
2026-10-09 17:07:56 +08:00
Silent1566
7809ff4952 fix(detail): 作废内联取址请求时释放其加载标记,消除永久加载圈
现象:详情页内播(沉浸融合/详情直放)偶发加载圈一直转但不影响播放;同一集还会表现为无法再次起播。

根因:playInline() 先置 inlinePlaybackPending=true 再自增代际,而回调里
`inlinePlaybackPending = false;` 写在 isInlinePlaybackRequestCurrent(...) 检查**之后**。
于是凡是「只自增代际、不接替请求」的作废点,回调直接 return,标记再无释放路径:
① updateInlineLoading() 的可见条件首项即该标记 ⇒ 圈永久留在屏上;
② isSamePendingInlinePlayback() 恒真 ⇒ selectInlineEpisode 早退,同一集无法再次起播。
实测该类作废点两处:refreshAndSwitchInlinePlayer()(选其它内核,8993 调用)与
cancelPendingInlinePlayerSwitch()(选当前已生效内核 8998 / 打开外部播放器 9295)。
其余自增点(resetDetailState / cancelPendingInlinePlayback / closeDetailFullscreenPlayer)
均已释放,属置位点的 playInline 本身正常。

修法:两处作废点在自增代际后、投递新请求前同步释放标记并刷新面板。必须在作废点同步
释放而非仅在回调里释放:任务若在运行前被取消,submit 的 lambda 根本不执行,回调式释放
永不触发。切内核自身的缓冲显示由 updateInlineLoading 的引擎条件兜住,不依赖该标记。

Verification: :app:compileMobileArm64_v8aDebugJavaWithJavac 成功;:app:testMobileArm64_v8aDebugUnitTest 定向 TmdbDetailDirectPlayTransitionSourceTest 4/4(含新增断言);同批显式列出 77 个 Tmdb/Inline/PlaybackOwnership 用例类共 610 项,1 项失败经字节取证为既有 CRLF 检出环境问题(断言要求裸 \n,而 leanback VideoActivity.java 为 i/lf w/crlf、裸 LF 0 处;且该断言读的文件本次未改动),与本改动无因果。新断言对修复前源码必然变红(逐方法体字符串校验)。

Task-Guard: INLINE-LOADING-RELEASE-20261009

Verification: compileMobileArm64_v8aDebugJavaWithJavac OK; TmdbDetailDirectPlayTransitionSourceTest 4/4 pass (incl. new cancellingAnInlinePlaybackRequestReleasesItsLoadingFlag); wider 610-test Tmdb/Inline/PlaybackOwnership sweep: 609 pass, 1 failure (TmdbSourceOnlyInteractionTest line 42) proven pre-existing CRLF checkout artifact (bare LF sequence absent, CRLF variant present, file unmodified by this change); new assertion provably red before the fix

Task-Guard: INLINE-LOADING-RELEASE-20261009
2026-10-09 17:07:35 +08:00
Silent1566
7e51768d88
Merge pull request #429 from Silent1566/dev4
dev4:合并 beta 最新代码并复评手机版搜索下拉面板对比度修复
2026-10-09 16:52:48 +08:00
Silent1566
4319403502 fix(tv): 详情未就绪丢弃播放结果时释放加载守卫,消除永久转圈
现象:leanback 播放页偶发加载圈一直转、但不影响播放。

根因:setPlayer(Result) 在守卫持有期间的 6 条退出路径中,!canApplyPlayerResult()
这条早退分支不释放 mPlaybackRequestActive/mPlaybackPlayerStarted。命中后
onStateChanged(STATE_READY) 的 `if (mPlaybackRequestActive && !mPlaybackPlayerStarted) break;`
与 hidePlaybackProgressIfStale() 的同一条件同时被挡下——而这两处是加载圈仅有的清除
路径,于是旧内容继续播放、圈永久留存。与 C42 已修的同族分支同源(commit 3374def641)。

修法:该分支放弃本次结果时一并释放两个守卫,并补注释说明;mobile VideoActivity 没有
这套守卫字段(grep -c = 0),不涉及。范围仅 leanback VideoActivity 与该契约的源级用例。

Verification: :app:compileLeanbackArm64_v8aDebugJavaWithJavac 成功;:app:testMobileArm64_v8aDebugUnitTest 定向跑 VideoActivityLayoutTest 154/154、PlaybackOwnershipSourceTest 16/16 全绿(含本任务新增断言)。同批 ReaderPlaybackRoutingSourceTest 的 2 项失败经取证为既有 CRLF 检出环境问题(reader.html 为 i/lf w/crlf、裸 LF 0 处,断言要求裸 \n),与本改动无因果,已在任务文档记录。

Task-Guard: PLAYBACK-LOADING-GUARD-20261009

Verification: compileLeanbackArm64_v8aDebugJavaWithJavac OK; testMobileArm64_v8aDebugUnitTest: VideoActivityLayoutTest 154/154 pass (incl. new guard-release assertion), PlaybackOwnershipSourceTest 16/16 pass; ReaderPlaybackRoutingSourceTest 2 failures proven pre-existing CRLF checkout artifact (reader.html i/lf w/crlf, bare LF count 0), unrelated to this change

Task-Guard: PLAYBACK-LOADING-GUARD-20261009
2026-10-09 16:38:45 +08:00
Silent1566
27fe5c6927 docs(c54): 记录 dev4 交付坐标(合并提交 08d4b66118 / recovery tag / 推送 / PR #429)
- 合并提交 08d4b6611802db1f3349aff828df9d08b42c5d69,第二父 = origin/beta tip 35a5a63f8a
- recovery tag recovery/C54-beta-merge-review-dev4/20261009083551-08d4b6611802(已推送)
- origin/dev4 61e483011e → 08d4b661180(fast-forward)
- PR #429(base=beta、head=dev4、OPEN、mergedAt=null、MERGEABLE/CLEAN),文件集 5 路径与净差异及 compare API 三者一致;仅创建未合并

Task-Guard: C54-dev4-doc-coordinates

Verification: 仅文档改动:doc 记录合并提交 08d4b66118 / recovery tag recovery/C54-beta-merge-review-dev4/20261009083551-08d4b6611802 / origin/dev4 推送 61e483011ed→08d4b661180 / PR #429(state=OPEN、mergedAt=null、base=beta、head=dev4、5 路径与 git diff origin/beta dev4 及 compare API 一致);无代码改动,无需重跑构建与测试

Task-Guard: C54-dev4-doc-coordinates
2026-10-09 16:37:24 +08:00
Silent1566
08d4b66118 merge: 合并 origin/beta 最新代码(PR#427 小说朗读 / PR#428 去广告总时长)并复评 dev4 搜索下拉对比度修复
- 合入 beta tip 35a5a63f8a5(11 提交 / 36 路径,0 冲突、0 删除);合并树相对 beta 的净差异只有本分支既有改动(3 个 mobile 文件 + 1 个文档)与本任务文档
- 零复活:resurrected = 0(revert 集 109 提交 / 725 路径;可证伪自检注入 5 条「在 revert 集且不在 beta 树」的路径全部被报出);零漂移:36/36 带入路径 blob 与 beta 逐字节一致;零丢失:beta 树 − 合并树 = 0
- 复评本地未推送提交 42b03f028e9(手机版搜索下拉对比度):面板 colorSurfaceContainer × item 文字 colorOnSurface 同源配对;ThemeResolver.enforceBase 对 surfaceContainer 有 4.5:1 门(surfaceChanged = hasSurface || hasContainer || hasContainerHigh),自定义主题下不会重现;无失效导入、无行为外改动
- 复评 beta 增量:AndroidManifest 新服务 exported=false + foregroundServiceType=mediaPlayback(FGS/NOTIFICATIONS 权限既有,targetSdk=28);新增 13 个 reader_tts_* 键三语占位符逐条一致;PR#428 三条 HLS 通道文案统一到 HlsAdblockNotice.message(先归整到 0.1 秒再分档,与 AdBlockTimeFormatter 口径一致,0/负/非有限值回退片段数)
- 验证:双 flavor Java + 双 flavor AndroidTest Java 编译 BUILD SUCCESSFUL;双 flavor 全量 JVM 单测 mobile 4971 / leanback 4131,failures=0 errors=0 skipped=2(含 SearchScopePopupLayoutTest 4、Tts* 30、HlsAdblock* 17);check_ui_tokens --strict contrast failures=0、violations=1(既存 item_following.xml,零新增);git diff --cached --check 干净
- 环境:JAVA_HOME 为 Microsoft JDK 21.0.12 时 2 个既有 MPV 测试因 File.renameTo 语义差异确定性失败;本轮统一使用项目 provisioned JBR 21.0.10,非本次引入,按约定仅记录不修改

Task-Guard: C54-beta-merge-review-dev4

Verification: 合并 beta tip 35a5a63f8a5(11 提交 / 36 路径,0 冲突 0 删除)后:git diff origin/beta 净差异仅本地 4 路径 + 本任务文档;resurrected=0(109 revert 提交 / 725 路径 + 5 条已知回退路径注入自检全部报出);36/36 带入路径 blob 与 beta 一致(零漂移);beta 树 − 合并树 = 0(零丢失);双 flavor Java + AndroidTest Java 编译 BUILD SUCCESSFUL;双 flavor 全量 JVM 单测 mobile 4971 / leanback 4131,failures=0 errors=0 skipped=2;check_ui_tokens --strict contrast failures=0 / violations=1(既存 item_following.xml);git diff --cached --check 干净;JDK=项目 provisioned JBR 21.0.10

Task-Guard: C54-beta-merge-review-dev4
2026-10-09 16:35:51 +08:00
Silent1566
814935ceea fix(cache): 修复临时文件缺陷回归锁缺失与结果文案语言回退
Verification: 缓存包单测 71/71 通过(0 failures/0 errors; CacheFullCleanupTest 5->7 例, 新增 onlyExplicitRequestsIgnoreTheBackgroundRetentionWindow 与 explicitRequestStillOnlyClaimsTheReportedFamily, 并把两条临时文件用例改为向 explicitRetention 取窗口); :app:compileMobileArm64_v8aDebugJavaWithJavac 与 :app:compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; git diff --check 无空白问题

Task-Guard: cache-full-cleanup-review
2026-10-09 16:23:44 +08:00
Silent1566
35a5a63f8a
Merge pull request #428 from Silent1566/dev1
去广告成功提示补充总广告时长(三条 HLS 通道统一文案 + 时长格式化边界修复)
2026-10-09 16:06:24 +08:00
Silent1566
6bb9203722 docs(c53): 记录交付坐标(修复提交 926b9d50c / tag / 推送 / PR #428)
Verification: 交付坐标文档提交(仅 docs/C53-beta-merge-review-dev1-20261009.md):记录任务起始 HEAD 72cdec02b、origin/beta tip fe1f725a1(已为 HEAD 父提交,merge --no-ff 返回 Already up to date)、复评修复提交 926b9d50c、recovery tag recovery/C53-beta-merge-review-dev1/20261009160214-926b9d50ca66、推送 e44dadffd..926b9d50c dev1->dev1(推送后 origin/dev1==dev1)、PR #428(OPEN/未合并/mergeStateStatus=CLEAN),PR 文件集 9 路径与 git diff --name-only origin/beta HEAD 逐项一致,PR 变更规模 +485 -11。

Task-Guard: C53-beta-merge-review-dev1-coordinates
2026-10-09 16:03:34 +08:00
Silent1566
926b9d50ca fix(adblock): 复评修正广告时长跨单位进位与显示粒度边界(F1/F2)
复评 72cdec02b(去广告提示补充总广告时长)时在时长格式化上发现 2 个真实缺陷:

- F1 时长跨单位边界自相矛盾:原 durationText 用未舍入原值分档,
  [59.95, 60) 会被 %.1f 格式化成「60.0 秒」,与仓库既有约定
  AdBlockTimeFormatter.formatSeconds(先舍入后进位,测试锁定
  59.96 -> 00:01:00.0)冲突。改为先按 0.1 秒粒度舍入成 tenths 再分档,
  秒档条件 tenths < 600,进位只发生一次。
- F2 低于显示粒度的时长仍显示「0.0 秒」:AD-NOTICE-01 验收标准第 4 条
  要求不出现「0.0 秒」误导,但 0 < d < 0.05 的正值仍走秒档并被 %.1f
  格式化成「0.0 秒」。改为显示粒度以下(含 0/负数/非有限值)返回空串,
  message 退回只显示条数,最小可显示值为 0.1 秒。

新增 HlsAdblockNoticeTest 两条锁定用例(6 -> 8 项),既有文案断言全部保持。
本提交同时记录 C53 合并判定与复评结论(docs/C53-...),并更新
AD-NOTICE-01 任务文档的复评补充与下一动作。

Verification: 复评修复 F1/F2:durationText 先按 0.1 秒粒度舍入再分档(59.96 -> 1 分钟,消除自相矛盾的 60.0 秒),显示粒度以下返回空串、message 退回只显示条数(0.04 -> 无时长,消除 0.0 秒)。负对照按复评前原文逻辑实跑得 old(59.96)=60.0 秒 / old(0.04)=0.0 秒,证明两条新增用例可证伪。验证:双 flavor 定向单测 34 项(17+17)failures=0 errors=0;双 flavor Java/AndroidTest 编译 + 全量 JVM 套件 BUILD SUCCESSFUL(leanback 4131 / mobile 4969,failures=0 errors=0,较 C52 基线 +6/+6);UI_TOKEN_STATUS PASS(violations=1 为基线既有的 item_following.xml,零新增违规);git diff --check=0、冲突标记 0。合并判定:git merge --no-ff origin/beta = Already up to date(beta tip fe1f725a1 已是 HEAD 父提交,merge-base --is-ancestor 为真);零复活严格判据 0 行(139 个 revert 类单亲提交 / 26786 distinct 删除行 vs diff 新增 134 行,表面重合 13 行全为通用脚手架行且大量存在于 beta tip),--diff-filter=D 为空(零丢失)。

Task-Guard: C53-beta-merge-review-dev1
2026-10-09 16:02:14 +08:00
Silent1566
42b03f028e fix(mobile): 修复深色模式搜索分组下拉面板对比度不足
手机版搜索页与搜索结果页的范围/分组下拉面板由代码写死 Color.WHITE,
而同一面板的 item 文字取主题语义角色 colorOnSurface,深色表该角色是近白色
#E2E2E9 —— 前景与底色来自两张不同的表,深色模式下就是「近白字压在白底上」。
设备实测(192.168.50.3:5561,uimode night)搜索页与搜索结果页两个下拉均为
#FFFFFF × #E2E2E9 = 1.29:1,与用户照片采样一致。

面板改用与 item 文字同表的 M3 菜单容器角色 colorSurfaceContainer:
SearchFragment.getScopePopupBackground() 与 CollectFragment.getGroupPopupBackground()
各一行行为改动,圆角/水波纹/条目高度/宽度计算与选中回调均不变。
ThemeResolver 对 onSurface 本就有 enforceBase(surface, surfaceContainer,
surfaceContainerHigh) ≥ 4.5:1 的修复通道,自定义主题下不会再重现该缺陷。

Verification: JVM SearchScopePopupLayoutTest 4/4 通过(新增源码守卫 + 直接解析两套 token
表算对比度的守卫,并经变异检验转红);check_ui_tokens --strict contrast failures=0、
violations=1(既存,与基线一致);assembleMobileArm64_v8aDebug 成功并 -r 覆盖安装
(签名 4dbe7298);设备四组合实测 深色 12.14:1 / 浅色 14.73:1(修复前 1.29:1)。

Verification: JVM: testMobileArm64_v8aDebugUnitTest --tests SearchScopePopupLayoutTest => 4/4 pass (BUILD SUCCESSFUL); 变异检验:SearchFragment 面板改回 Color.WHITE 后 mobileScopePopupsTakeTheirSurfaceFromThePalette FAILED(AssertionError:70) 并转红,恢复后 4/4 通过; scripts/check_ui_tokens.sh --strict => contrast failures=0, violations=1(既存 item_following.xml,与基线一致,无新增); assembleMobileArm64_v8aDebug BUILD SUCCESSFUL; adb 192.168.50.3:5561 覆盖安装(install -r, 签名 4dbe7298 一致) Success; 设备像素实测四组合:深色搜索页下拉/深色分组下拉 #1F2428 × #E2E2E9 = 12.14:1,浅色两组 #ECEEF4 × #1A1C1E = 14.73:1(修复前 #FFFFFF × #E2E2E9 = 1.29:1,与用户照片 #FFFFFF 面板一致); 测试后已把 cmd uimode night 恢复为 no,未改动应用数据/主题/站源配置

Task-Guard: MOBILE-SEARCH-DROPDOWN-CONTRAST-20261009
2026-10-09 15:30:28 +08:00
Silent1566
f0da126a2c fix(cache): 长按一键全清不再打开缓存管理面板
Verification: 长按一键全清改为不弹面板(用户指正)。设备实测(5563/leanback arm64 debug 覆盖安装 com.silent.android.webhtv,versionText=5.6.0-202610091513,预置 2MB zip+64KB js+256KB bin,设置行 2.3MB/702MB): ①只发一次长按(不预先点击)后 mResumedActivity 仍为 SettingActivity 且无障碍树无任何面板节点; ②长按后1秒抓屏同时可见设置页在前台+缓存行自动刷新为 无/702MB+Toast「清理完成 释放 2.3 MB · 删除 7 个文件」; ③journal 末条 {mode:FULL,reason:shortcut,bytesBefore:2437024,after:0,deletedFiles:7,COMPLETED,warnings:[]} 与 Toast 一致; ④短按仍正常打开面板(无回归),logcat 无 FATAL/ANR; ⑤代码: showFullCleanup 三重载/ARG_CLEANUP_MODE/shortcutStarted/isFullCleanupShortcut 已删除,结果文案抽为静态 describe 共用,新增串 cache_cleanup_full_started 三语齐备; 构建 BUILD SUCCESSFUL; 未改缓存包纯函数故未重跑单测; build.gradle 已按 git hash-object+sha256 双指纹字节还原

Task-Guard: full-cleanup-no-panel
2026-10-09 15:28:04 +08:00
Silent1566
72cdec02b2 feat(adblock): 去广告成功提示补充总广告时长,三条 HLS 通道统一文案
原提示文案在三处内联且以 structured 为条件,legacy 兜底通道即使已算出
removedDurationSec 也只显示条数,用户因此只看到「总切片数」。

- HlsAdblockNotice 新增 message(...)/durationText(...) 作为唯一文案入口,
  只要时长 > 0 就追加「总广告时长 X」,不再依赖 structured 判据;
  < 60s 用秒、>= 60s 用分钟,避免长广告块显示成 185.0 秒。
- nano /m3u8 代理、Exo ExoHlsAdblockDataSource、MPV/IJK MpvHlsProxy
  三条通道统一改用该入口,消除三份重复实现。
- 补测:文案/时长/分钟进位、结构化与 legacy 双通道含时长断言、
  三通道必须走共享入口的源码契约测试;Exo 弱断言改为精确文案。

验证:mobile+leanback 定向单测 30 项全通过;设备实测 legacy 兜底
提示为「已跳过 2 个广告片段,总广告时长 45.5 秒」。仅改文案构造,
不触碰净化判据、删除阈值、回退闸门与统计写入。

Verification: 去广告提示补充总广告时长:mobile+leanback 双 flavor 定向单测各 15 项全通过(HlsAdblockNoticeTest 6 / HlsAdblockPipelineTest 6 / ExoHlsAdblockDataSourceTest 3,failures=0 errors=0),新增三通道共享入口契约测试锁死回归;设备 192.168.50.3:5555 (LIO-AN00/Android 9) 覆盖安装 mobile debug 后经真实 /m3u8 去广链路实测系统 Toast 队列原文:修复前 '已跳过 2 个广告片段'(无时长),修复后 legacy 兜底 '已跳过 2 个广告片段,总广告时长 45.5 秒'(removed=2 structured=false legacy=true,时长 20.0+25.5 秒吻合)与 '已跳过 1 个广告片段,总广告时长 7.0 秒',调用栈为 Notify.show(Notify.java:55) <- M3u8.recordAndNotify(M3u8.java:120);结构化通道由单测断言 '已跳过 1 个广告片段,总广告时长 7.0 秒' 覆盖;安装后 APK dex 仅含新文案、旧 '已跳过 %d 个广告片段(%.1f 秒)' 已消失,进程存活且 FATAL EXCEPTION=0

Task-Guard: AD-NOTICE-01
2026-10-09 15:21:51 +08:00
Silent1566
92f1844342 docs(cache): 记录长按一键全清与临时文件修复的设备实测证据
Verification: 设备实测(5563/leanback arm64 debug 覆盖安装 com.silent.android.webhtv,apksigner 证书 SHA-256 与既有包一致无需卸载,versionText=5.6.0-202610091450): ①临时文件模块行清理=清理完成 释放 3 MB·删除 1 个文件(新建 webhtv-sync-test1.zip 被删,js/zz-test-idle.js 与 unknown-probe.bin 保留);②长按缓存行=清理完成 释放 6.6 MB·删除 119 个文件,总量变共无,cache 目录清空;③journal: mode=MODULE bytesBefore=3145728 deletedFiles=1 COMPLETED warnings=[] 与 mode=FULL bytesBefore=6911351 deletedFiles=119 COMPLETED warnings=[];④设置行自动刷新为 无/702MB,确认框默认聚焦取消;⑤全清后冷启动无 FATAL/ANR 且 chaquopy/WebView 缓存自动重建;app/build.gradle 已按 git hash-object+sha256 双指纹字节还原

Task-Guard: cache-full-cleanup-device-verify
2026-10-09 15:03:36 +08:00
Silent1566
f9f47c24ae fix(cache): 修复临时文件显式清理无效并补回长按一键全清
Verification: 缓存包单元测试 69/69 通过(新增 CacheFullCleanupTest 5 例覆盖显式请求删新临时包、后台保留期仍保留、进行中传输豁免、残留扫描删无主残留与闲置插件脚本、保留播放缓存根/传输/恢复文件;CachePolicyEngineTest 新增 fullPlanCoversEveryModuleExactlyOnce);:app:compileMobileArm64_v8aDebugJavaWithJavac 与 :app:compileLeanbackArm64_v8aDebugJavaWithJavac 通过;git diff --check 干净;设备端长按手势实测未执行(待申请模拟器)

Task-Guard: cache-full-cleanup-and-temp-fix
2026-10-09 14:30:35 +08:00
Silent1566
fe1f725a12
Merge pull request #427 from Silent1566/dev1
merge: 合并 origin/beta 最新代码(PR#425 C47 seek 加载圈窗口 / PR#426 首页菜单键弹窗对比度)并复评小说朗读补全
2026-10-09 14:12:57 +08:00
Silent1566
e44dadffd6 docs(c52): 记录交付坐标(合并提交 86e1bfe3c / 修复提交 32202da2d / tag / PR #427)
文档收口提交:补齐任务文档的提交、recovery tag、推送与 PR 坐标,记录 PR 文件集与本地净差异
的逐项一致性校验结果,并记录两条既有静态扫描告警(picNonce 的 Math.random、图片缓存键 MD5)
不在本次改动范围内的判定依据。

Verification: docs-only 收口:仅回填交付坐标与逐项校验结果(PR 文件集 27 == 本地净差异 27),
无代码改动、未再次构建或验证;该净差异此前已通过双 flavor Java 与 AndroidTest Java 编译、
双 flavor 全量 JVM 套件(leanback 4125 / mobile 4963,0 失败)、UI token 门禁与真机
ReaderTtsDeviceTest OK (6 tests)

Task-Guard: C52-beta-merge-review-dev1-coordinates

Verification: docs-only 收口:仅回填交付坐标(合并提交 86e1bfe3c / 修复提交 32202da2d / recovery tag / PR #427)与逐项校验结果(PR 文件集 27 == 本地净差异 27),另记录两条既有扫描告警的归属判定;无代码改动、未再次构建或验证;该净差异此前已通过双 flavor Java 与 AndroidTest Java 编译、双 flavor 全量 JVM 套件(leanback 4125 / mobile 4963,0 失败)、UI token 门禁与真机 ReaderTtsDeviceTest OK (6 tests)

Task-Guard: C52-beta-merge-review-dev1-coordinates
2026-10-09 14:10:59 +08:00
Silent1566
32202da2de fix(reader-tts): 修复停播通知残留、暂停中改语速出声与朗读桥线程,复评朗读补全并合并 beta
合并远端 beta 最新代码(PR#425 C47 seek 加载圈窗口 / PR#426 首页菜单键弹窗对比度),
零冲突;零复活三层证据复活行数 0,beta 增量逐 blob 一致、零丢失。

复评未推送的朗读功能(NOVEL-TTS)后修复三处真实缺陷:
- 停播后通知栏残留:onTtsState 在 stopForegroundCompat 撤掉通知后仍无条件
  updateNotification,把通知贴回来且标题为「正在朗读」,服务销毁也不清理。
  改为 stopped 不再重贴,并在 onDestroy 兜底 cancel。
- 暂停中改语速出声:MediaPlayer.setPlaybackParams 用非零速度等价 start(),
  暂停中拖动语速会把音频拖响(界面仍显示「已暂停」)。改为暂停中只更新
  options,resume 后在 start() 之后补 applyRate。
- 朗读桥线程:@JavascriptInterface 跑在 WebView 的 JavaBridge 线程,12 个
  朗读桥直接改控制器状态会与主线程引擎回调并发(含 size/get 竞态与非主线程
  startForeground/通知更新)。统一经 postToService 回主线程。

同时修正 NOVEL-TTS 文档中「音频焦点 GAIN 自动恢复」的不实描述(实现只让出、
不自动恢复),并为上述修复补齐真机断言与源码契约测试。

Verification: 双 flavor Java 与 AndroidTest Java 编译通过;双 flavor 全量 JVM 套件
leanback 4125 / mobile 4963 用例 0 失败 0 错误;UI token 门禁 PASS 且相对基线零新增
违规;真机 192.168.50.3:5555 ReaderTtsDeviceTest OK (6 tests);F1/F2 修复前在真机上用
同一套断言各自复现(暂停中改语速把音频拖响、停播后通知栏残留);零复活三层证据复活
行数 0、beta 增量逐 blob 一致

Task-Guard: C52-beta-merge-review-dev1

Verification: 双 flavor Java + AndroidTest Java 编译通过;双 flavor 全量 JVM 套件 leanback 4125 / mobile 4963 用例 0 失败 0 错误;UI token 门禁 PASS(相对基线零新增违规);真机 192.168.50.3:5555 ReaderTtsDeviceTest OK (6 tests);F1/F2 修复前同机负对照各自复现缺陷(暂停中改语速出声、停播后通知残留);零复活三层证据复活行数 0、beta 增量逐 blob 一致

Task-Guard: C52-beta-merge-review-dev1
2026-10-09 14:09:47 +08:00
Silent1566
86e1bfe3ce merge: 合并 origin/beta 最新代码(PR#425 C47 seek 加载圈窗口 / PR#426 首页菜单键弹窗对比度)并复评小说朗读补全 2026-10-09 13:39:00 +08:00
Silent1566
db04c4c8bc docs(novel-tts): 更正朗读测试用例计数为 29(含音调映射)
Verification: 文档更正:JVM 用例计数与第二个会话新增的音调映射用例对齐(29 用例),无代码改动

Task-Guard: NOVEL-TTS-3
2026-10-09 13:23:14 +08:00
Silent1566
1ebec24a65 fix(reader): 朗读音调接入微软 Edge SSML 并按音源隐藏无效控件,路由丢失暂停可验证
Verification: 补强验证:定向 JVM 测试(含音调→Hz 映射及越界夹取)与 mobile/leanback 编译通过;arm64 Debug 重新打包安装通过;真机 192.168.50.3:5555 上 ReaderTtsDeviceTest 6/6 通过,阅读页端到端用例新增覆盖「音频路由丢失自动暂停并可继续朗读」

Task-Guard: NOVEL-TTS-2
2026-10-09 13:22:58 +08:00
Silent1566
8539a450f2 feat(reader): 补全小说模式朗读(系统/百度/Edge/自定义在线引擎 + 后台通知控制)
Verification: 定向 JVM 测试 28 用例通过(TtsTextSplitter/TtsHttpRule/ReaderTtsBridgeSource);mobile+leanback arm64 Java 编译与 arm64 Debug 打包安装通过;真机 192.168.50.3:5555 上 ReaderTtsDeviceTest 6/6 通过(百度与微软 Edge 引擎真实合成+播放推进、系统引擎无引擎时给出可操作错误、定时到点真的停播、阅读页面板按钮→JS 桥→前台服务→引擎端到端含后台继续朗读/通知栏控制/章末续读/停止);自定义在线引擎经临时夹具验证 POST+body 模板+自定义 Header 生效(服务端日志)后已移除夹具

Task-Guard: NOVEL-TTS
2026-10-09 13:14:15 +08:00
Silent1566
29d52ab238
Merge pull request #426 from Silent1566/dev2
merge: 合并 origin/beta 最新代码(PR#425 C47 seek 加载圈窗口)并复评 dev2 首页菜单键弹窗对比度修复
2026-10-09 11:48:22 +08:00
Silent1566
7543959f3e docs(c51): 记录 dev2 交付坐标(合并提交 1f8fc83369 / recovery tag / PR #426)
文档收口提交:补齐任务文档的提交、recovery tag、推送与 PR 坐标,并记录 PR 文件集与本地净差异的逐项一致性校验结果。
无生产代码改动。

Verification: docs-only 收口:仅记录交付坐标(合并提交 1f8fc83369 / recovery tag / PR #426)与 PR 文件集校验结果,无代码改动、未再次构建或验证;该净差异此前已通过双 flavor Java 与 AndroidTest Java 编译、mobile 与 leanback 定向用例(175 项 0 失败)与 UI token 门禁(与基线一致)

Task-Guard: C51-dev2-doc-coordinates
2026-10-09 11:43:45 +08:00
Silent1566
1f8fc83369 merge: 合并 origin/beta 最新代码(PR#425 C47 seek 加载圈窗口)并复评 dev2 首页菜单键弹窗对比度修复
把 origin/beta tip 75735b58f6(PR#425 from dev4)合入 dev2,0 冲突;远端已移除/回退的内容零复活,
beta 增量零丢失,日志与验证记录见 docs/C51-beta-merge-review-dev2-20261009.md。

beta 增量(5 提交 / 7 路径,全部纳入):
- 23e5280f37 修复拖拽进度时加载中/网速大概率不显示:seek 期间开最小可见 1200ms 窗口,
  并用 canHideSeekProgress() 统一约束网速 ticker 兜底、READY 回调与控制器晚绑定补发;
  两个 VideoActivity 的实现与断言与 beta 逐字节一致。
- 1a4110178f / 61e483011e 两份 C47/C49 任务记录,8dd441b1cc 与 75735b58f6 两个合并提交。

本分支自有改动(相对 beta 净差异 4 路径):首页菜单键弹窗焦点条目文字改用
colorOnPrimary(home_menu_text.xml + adapter_home_menu.xml + 对比度回归测试 + 任务文档)。

复评:3 轮。第 2 轮发现 1 个必修问题——任务文档把「上游设计本意」误记为
「adapter_home_menu.xml 曾直接写 @color/white」,实际该布局由 e95b90d3a8(2026-08-23) 创建即用
共享选择器、从未写过 @color/white;真实根因链是 0503f8e3cf 把焦点文字改为 ?attr/colorPrimary
(当时填充仍是硬编码 #2F6FED)、1bb72bd709 再把填充改为 ?attr/colorPrimary,
两者叠加才成为同一角色。已改写为可逐条复现的表述并新增 §8 更正与再验证,未改任何代码或测试。

Verification: 合并 origin/beta tip 75735b58f6(0 冲突;MERGE_HEAD 即该 tip)。零复活:182 个单亲 revert/回退类提交 x 852 触碰路径,beta 已移除 671 路径在合并树中复活 0(含 ISSUE_TEMPLATE 注入自检)。零丢失:beta 增量 7 路径逐 blob 与 beta 逐字节一致,本分支 4 路径逐 blob 与 c5912111a4 逐字节一致,beta 树 4980 路径在合并树中缺失 0。3 轮评审:第 1 轮 0 问题,第 2 轮发现并修复 1 个必修问题(任务文档 §2 历史引用不实,改为可逐条复现的准确表述并新增 §8 更正记录,代码/测试零改动),第 3 轮复核通过。验证:双 flavor 主代码与 AndroidTest Java 编译成功;mobile 定向 C47SeekLoadingProgressSourceTest 5/5 + VideoActivityLayoutTest 154/154;leanback 定向 theme.* 162 + HomeMenuDialogContrastTest 3/3 + HomeMenuDialogSourceTest 5/5 + C47SeekLoadingProgressSourceTest 5/5,合计 175 项 0 失败 0 错误;对比度独立复算 6.39/7.50 与 14.73/12.14,旧写法 1.00;check_ui_tokens.sh 与合并前基线逐项相同(violations=1 hex_layouts=1 pairs=38 failures=0 min=4.28 PASS);git diff --check 与 --cached --check 均为 0,11 个改动路径无冲突标记;提交前再次 fetch 确认 origin/beta 未前进;末次构建后已执行 gradlew --no-daemon clean 回收资源;未使用模拟器(净差异 4 路径 blob 与已实测提交相同,重复实机验证不产生新信息)

Task-Guard: C51-beta-merge-review-dev2-20261009
2026-10-09 11:38:46 +08:00
Silent1566
c5912111a4 fix(tv): 首页菜单键弹窗焦点条目文字改用 colorOnPrimary
HomeMenuDialog 条目的焦点底色是 shape_config_history_item_focused 的
colorPrimary 实心填充,而文字走共享的 config_history_text,其焦点色同样是
colorPrimary —— 同一角色压在自身之上:默认表 1.00:1,设备自定义主题下
ThemeBinder 只改写文字色状态列表、不改写 StateListDrawable 内的填充,实测
#006876 on #0B57D0 = 1.01:1。

新增 app/src/leanback/res/color/home_menu_text.xml(焦点/按下 colorOnPrimary,
常态 colorOnSurface)并接线到 adapter_home_menu.xml;共享的 config_history_text
保持不变,因为它还服务 dialog_history 中焦点底色为 primaryContainer 的描边按钮。

验证:HomeMenuDialogContrastTest 解析两套 token 表并计算对比度(焦点 6.39:1 /
7.50:1,常态 14.73:1 / 12.14:1,旧写法 1.00:1 钉为回归断言);leanback 测试与
theme.* 全族通过;覆盖安装后在 192.168.50.3:5557 实测焦点条目 1.01:1 -> 6.39:1,
其余 10 项保持 14.71:1,焦点移动后新焦点项同样 6.39:1。

Task-Guard: TV-HOME-MENU-CONTRAST-20261009

Verification: leanback JVM: HomeMenuDialogContrastTest 3/3 + HomeMenuDialogSourceTest 5/5 + theme.* 全族通过; assembleLeanbackArm64_v8aDebug 成功并 adb install -r 覆盖安装 192.168.50.3:5557; 设备实测焦点条目 #FFFFFF on #0B57D0 = 6.39:1(修复前 #006876 on #0B57D0 = 1.01:1), 其余 10 项 14.71:1

Task-Guard: TV-HOME-MENU-CONTRAST-20261009
2026-10-09 11:07:53 +08:00
Silent1566
75735b58f6
Merge pull request #425 from Silent1566/dev4
merge: 合并 origin/beta 最新代码(PR#421/422/423/424)并复评 C47 seek 加载圈窗口修复
2026-10-09 10:46:28 +08:00
Silent1566
61e483011e docs(c49): 记录 dev4 交付坐标(合并提交 8dd441b1cc / recovery tag / PR #425)
文档收口提交:补齐任务文档的提交、recovery tag、推送与 PR 坐标,并记录 PR 文件集与本地净差异的逐项一致性校验结果。
无生产代码改动。

Verification: 文档收口:仅记录交付坐标(合并提交 8dd441b1cc / recovery tag / PR #425)与 PR 文件集校验结果,无代码改动;该 7 路径净差异此前已通过双 flavor Java/AndroidTest 编译、双 flavor 全量单测(mobile 4933 / leanback 4092 零失败)与 UI token 门禁 PASS

Task-Guard: C49-dev4-doc-coordinates
2026-10-09 10:41:12 +08:00
Silent1566
8dd441b1cc merge: 合并 origin/beta 最新代码(PR#424 实验室配置源弹窗)并复评 C47 seek 加载圈窗口
- 以最终 origin/beta tip 7df98107be 合入 dev4(含 PR#422 自动下一集黑屏修复、PR#423 历史集数行对比度、PR#424 实验室配置源弹窗),0 冲突,合并树 df59dec76 与 merge-tree 预测逐字节一致
- 远端已移除/回退内容零复活:净差异 6 路径 == 本地自身改动集;行级扫描 54 个 revert 提交 / 36 removed 行 → 复活 0 行;并以 AdAudioConsumer.java 注入自检证明检测器不空转(正例 170 命中、负例 0)
- 复评 C47「拖拽 seek 时加载中/网速不显示」窗口修复:订正文档两处事实错误(onSeekStarted 的第二个调用点 PlaybackActivity.seekTo(477);leanback onControllerReadyReconciled 系本次新增而非既有改走闸门)
- 新合入 PR#424 实验室弹窗增量逐点复核无必修缺陷(内容与面板同源、语义 token、8 用例契约测试两 flavor 通过)
- 验证:双 flavor Java/AndroidTest 编译通过;全量单测 mobile 4933 / leanback 4092 零失败;UI token 门禁 PASS

Verification: 双 flavor Java 编译 + 双 flavor AndroidTest Java 编译 BUILD SUCCESSFUL;双 flavor 全量 JVM 单测 mobile 4933 / leanback 4092,failures=0 errors=0 skipped=2;UI token 门禁 PASS(violations=1 为预先存在的 item_following.xml,相对基线零新增);零复活三层证据 resurrected=0 且可证伪自检 PASS(哨兵 AdAudioConsumer.java 正例 170 命中/负例 0);beta 增量 31 路径零丢失零漂移、dev4 自身 6 路径零丢失零改写;31 条已暂存路径与索引 blob 零内容差异;合并树 df59dec76 与 git merge-tree 预测逐字节一致

Task-Guard: C49-beta-merge-review-dev4
2026-10-09 10:38:08 +08:00
Silent1566
7df98107be
Merge pull request #424 from Silent1566/dev1
merge: 合并 origin/beta 最新代码(PR#423 手机历史集数行对比度修复)并复评实验室配置源弹窗修复
2026-10-08 23:59:21 +08:00
Silent1566
cb8022bf77 docs(c50): 记录交付坐标(提交 a8b30e09d / 1e83bdc25 / tag / PR #424)
Verification: docs-only 收口:记录交付坐标(合并 a8b30e09d / 文档 1e83bdc25 / tag / PR #424);PR #424 base=beta head=dev1 state=OPEN mergedAt=null MERGEABLE,PR 文件集 7 项与 git diff --name-only origin/beta HEAD 逐项一致;dev1 已推送且 0 ahead/0 behind;未新增构建或验证

Task-Guard: C50-beta-merge-review-dev1
2026-10-08 23:52:50 +08:00
Silent1566
1e83bdc25a docs(c50): 复评实验室配置源弹窗修复并记录 beta 合并评审,修正下拉列表主题跟随的不实描述
Verification: 双 flavor Java + AndroidTest 编译与全量 JVM 套件 BUILD SUCCESSFUL(leanback 4087 / mobile 4928 用例,0 失败 0 错误);check_ui_tokens.sh PASS(violations=1 为既存 item_following.xml,相对基线零新增);零复活三层校验 resurrected=0(139 个单亲 revert 类提交 / 761 文件版本 / 236817 删除行)、beta 增量 3 路径逐 blob 与 beta 一致、净差异仅 dev1 自身 6 路径;设备复测(192.168.50.3:5555)复现弹窗与下拉对比度 14.87:1 / 12.64:1 并验证网络 URL 切换功能流;冲突标记 0、9 个 strings.xml 无重复键、改动 XML 良构

Task-Guard: C50-beta-merge-review-dev1
2026-10-08 23:50:56 +08:00
Silent1566
a8b30e09dc merge: 合并 origin/beta 最新代码(PR#423 历史集数行对比度修复)并复评实验室配置源弹窗修复 2026-10-08 23:33:46 +08:00
Silent1566
60e657c074 fix(lab): 配置源下拉列表改用弹窗主题上下文与语义 item 布局,修复白字浅底
Verification: dev1 192.168.50.3:5555 最终 APK 像素实测:配置源下拉弹出列表 item 文字 #1A1C1E 对比 #F3EDF7 面板 = 14.87:1(修复前 1.19:1),深色模式 12.64:1;弹窗正文 6.11-11.24:1,主题色=鸢尾紫时面板/描边/图标/按钮全部跟随;check_ui_tokens --strict 仅剩 HEAD 既存 item_following.xml 违规;testMobileArm64_v8aDebugUnitTest + testLeanbackArm64_v8aDebugUnitTest BUILD SUCCESSFUL;LabSettingsDialogThemeTest 8/8 通过并经变异检验转红

Task-Guard: LAB-CONFIG-DIALOG-20261008-DROPDOWN
2026-10-08 23:27:19 +08:00
Silent1566
765dce2e2a
Merge pull request #423 from Silent1566/dev2
merge: 合并 origin/beta 最新代码(PR#422 自动下一集黑屏修复)并复评 dev2 历史集数行对比度修复
2026-10-08 22:48:28 +08:00
Silent1566
a7154d35e4 fix(lab): 实验室配置源弹窗内容与面板同源,修复默认主题下白字看不清并让主题色生效
Verification: dev1 192.168.50.3:5555 像素实测:默认主题弹窗正文 6.11-11.24:1(修复前 1.05:1)、深色 5.71-7.53:1、主题色=鸢尾紫面板 #C6D2EA→#EBDEF3 且描边/图标/按钮 #0B57D0→#8400F0;check_ui_tokens --strict 仅剩 HEAD 既存 item_following.xml 违规(stash 验证无关);testMobileArm64_v8aDebugUnitTest + testLeanbackArm64_v8aDebugUnitTest BUILD SUCCESSFUL;新增 LabSettingsDialogThemeTest 6/6 通过并经变异检验转红

Task-Guard: LAB-CONFIG-DIALOG-20261008
2026-10-08 22:13:26 +08:00
Silent1566
a36a1e9c8f docs(c49): 记录交付坐标(提交 a51e6f366f / tag / PR #423)
Verification: 文档-only 收口提交;PR #423 base=beta head=dev2 state=OPEN mergedAt=null,PR 文件集 3 项与 git diff --name-status origin/beta HEAD 逐项一致;dev2 已推送且 0 ahead/0 behind;未再次构建或验证。
2026-10-08 22:10:09 +08:00
Silent1566
a51e6f366f merge: 合并 origin/beta 最新代码(PR#422 自动下一集黑屏修复)并复评 dev2 历史集数行对比度修复
Verification: 合并 origin/beta tip 4174c65ea3(0 冲突;MERGE_HEAD 即该 tip);零复活:182 个单亲 revert/移除提交 x 852 触碰路径,beta 已移除 671 路径在合并树中复活 0(含 ISSUE_TEMPLATE 注入自检);零丢失:beta 增量 4 路径逐 blob 与 beta 一致,dev2 自身 2 路径逐 blob 保留,合并树路径集合 == beta 路径集合;2 轮评审发现并修复 1 个必修问题(新增注释含裸色值使 UI token 门禁违规 1→2,改写注释后回到基线 1);git diff --check 与 --cached --check 均 0;check_ui_tokens.sh violations=1 legacy=0 contrast failures=0 PASS;双 flavor 全量单测 mobile 4920/0/0 leanback 4079/0/0 BUILD SUCCESSFUL;双 flavor Java 与 AndroidTest Java 编译成功;PlaybackOwnershipSourceTest 16/16 x2、HistoryAdapterTest 3/3;未使用模拟器(本任务零生产行为改动)。

Task-Guard: C49-beta-merge-review-dev2
2026-10-08 22:08:02 +08:00
Silent1566
1a4110178f docs(c47): 记录模拟器验收证据与精度边界
把 C47 的实机(模拟器)验收过程与结论补入任务文档,不涉及代码变更。

- 环境阻塞与排除:安装 dev4 后 App 启动即崩 Room IllegalStateException
  (Migration didn't properly handle: History)。只读排查确认是设备预存状态——
  user_version=37 但 History 仅 17 列,缺 MIGRATION_36_37 应加的 5 列
  (typeName/area/actor/director/year),且 tv 文件时间戳早于本次安装;C47 提交
  只动 2 个 VideoActivity + 测试 + 文档,不触碰 db/,故与本次改动无关。为完成
  验收临时移开 databases/tv 让 Room 重建,验收后已完整还原。
- 判据纠正:dumpsys activity top 报的 V/G 是视图自身标志,对 overlay 容器不可靠
  (根 app:id/progress 为 G 时其子 app:id/traffic 仍可能显示 V)。判定覆盖层可见
  性必须用根节点。
- 路径辨析:mobile 的手势横滑不经过本次改动——onSeeking() 会主动 hideProgress(),
  且 onTouchEnd/onSeekEnd 不调用 onSeekStarted()(唯一调用者是 CustomSeekView,
  即进度条)。用户所述「拖拽进度」对应进度条路径,验收须用 app:id/timeBar 实测
  绝对坐标 [178,597][794,675]。
- 进度条拖拽实测:设备端紧循环采样(单次 30-160ms)显示 基线 G(隐藏)→ 抬手
  瞬间 V(出现)→ 持续约 1.0-2.0s 后干净回到 G;越过缓冲区拖拽时窗口更长。与
  「最小可见 1.2s + 闸门」预期一致,未复现「抬手即被收圈」。
- 精度边界(如实记录):该实例 loadavg≈9-12(x86_64 + ARM 转译 + 软件渲染),
  uiautomator dump 约 3.9s、dumpsys 取时戳滞后 150-800ms,无法逐帧分辨 1.2s
  窗口,traffic 文本也不落 mText,故不宣称网速观感已逐帧确认。
- 设备已还原:原 APK 重新覆盖安装(sha256 315e7d03… 与安装前一致)、databases/tv
  还原为 user_version=37/17 列、移除全部临时文件;未打正式包。

Task-Guard: C47-device

Verification: 仅文档变更:把 C47 的模拟器验收证据写入 docs/C47-seek-loading-progress.md(设备 192.168.50.3:5561 = emulator-5560 HD1910,mobile/arm64-v8a Debug 覆盖安装)。证据:① App 启动崩溃经只读排查定性为设备预存 Room 迁移不匹配(user_version=37 但 History 仅 17 列,缺 MIGRATION_36_37 的 5 列;C47 不触碰 db/),为验收临时移开 databases/tv 并在验收后完整还原;② 纠正判据:dumpsys 的 V/G 对 overlay 容器不可靠,须用根节点 app:id/progress;③ 辨析路径:mobile 手势横滑不经 onSeekStarted(onSeeking 会 hideProgress),用户所述拖拽对应进度条路径,须用 app:id/timeBar;④ 进度条拖拽实测(设备端紧循环采样 30-160ms):基线 G → 抬手瞬间 V → 持续约 1.0-2.0s 后干净回到 G,未复现「抬手即被收圈」;⑤ 如实记录精度边界:该实例 loadavg≈9-12,uiautomator dump 约 3.9s、dumpsys 取时戳滞后 150-800ms,无法逐帧分辨 1.2s 窗口,也不宣称网速文本已逐帧确认;⑥ 设备已完整还原(原 APK sha256 315e7d03… 一致、DB 还原为 37/17、临时文件清除)。未执行 gradle(本次仅改 markdown,无代码变更),git diff --check clean

Task-Guard: C47-device
2026-10-08 22:02:38 +08:00
Silent1566
4174c65ea3
Merge pull request #422 from Silent1566/dev1
merge: 合并 origin/beta 最新代码(PR#421 站点注入搜索与手机缓存排版)并复评自动下一集黑屏修复
2026-10-08 20:45:22 +08:00
Silent1566
0a0bed4208 docs(c48): 记录交付坐标(提交 87380b5cf / tag / PR #422)
Verification: 文档-only 收口提交;PR #422 已创建且 mergedAt=null,PR 文件集(gh api 分页 4 项)与 git diff --name-only origin/beta HEAD 逐项一致;dev1 已推送且 0 ahead/0 behind

Task-Guard: C48-beta-merge-review-dev1
2026-10-08 20:42:39 +08:00
Silent1566
87380b5cf7 merge: 合并 origin/beta 最新代码(PR#421 站点注入搜索与手机缓存排版)并复评自动下一集黑屏修复
把远端 beta 最新 7 个提交合入 dev1(3 个实质增量:站点注入搜索、搜索空状态与排序刷新回归修复、手机缓存管理排版修复),
并复评 dev1 全部已修改代码(含已提交未推送的 5096941a4 自动下一集黑屏修复)。

合并:0 冲突,第二父为 origin/beta tip 46917be79。
零复活:104 个单亲 revert/移除类提交 x 137 文件版本扫描复活行数为 0;dev1 与 beta 共有 4970 路径中 4968 路径 blob 逐字节相同,只在 beta 存在的路径数为 0。
零丢失:beta 增量 17 路径与 beta tip 逐 blob 一致;dev1 自身 3 路径全部保留。
净差异收敛为 dev1 自身 3 路径。

复评 3 轮(本任务 app/src 零改动):
- 第 1 轮复核 5096941a4:根因(preparePlayer 回调后清 spec / attachSurface 只判空)成立;新判断为旧语义超集,无功能回退;PlayerView.setPlayer 同实例早退,无解绑-绑定循环;已 release 引擎走 isCommandAvailable=false 分支,无空指针风险;单测可证伪。
- 第 2 轮复核 beta 增量:站点注入搜索的 visibleIndices 索引层、越界守卫、筛选期禁排序、局部通知保留、空状态同步、搜索字段覆盖、主题 token、三语言资源均通过;手机缓存排版竖排/横排分支、焦点链、主题契约、双 flavor 布局 id 一致性均通过。
- 第 3 轮最终复核:净差异、零复活、空白、冲突标记、重复键、编译、单测、门禁全部通过。

Verification: 见 commit trailer 与 docs/C48-beta-merge-review-dev1-20261008.md

Task-Guard: C48-beta-merge-review-dev1

Verification: 合并提交 b8adf3cc1 第二父为 origin/beta tip 46917be79(0 冲突 / 0 冲突标记);零复活:104 个单亲 revert/移除类提交 x 137 文件版本扫描,复活行数 0,且 dev1 与 beta 共有 4970 路径中 4968 路径 blob 逐字节相同、只在 beta 存在的路径数 0;零丢失:beta 增量 17 路径与 beta tip 逐 blob 一致(差异 0),dev1 自身 3 路径全部保留;净差异收敛为 dev1 自身 3 路径(PlaybackActivity.java +9 -2 / PlaybackOwnershipSourceTest.java +26 / docs/AUTONEXT-01-…md +169);3 轮评审全部通过无必修问题(本任务 app/src 零改动);双 flavor Java 编译 BUILD SUCCESSFUL (1m18s EXIT=0);双 flavor AndroidTest Java 编译 BUILD SUCCESSFUL (27s EXIT=0);双 flavor 全量单测 BUILD SUCCESSFUL (2m6s EXIT=0) leanback 4079 tests / mobile 4920 tests failures=0 errors=0 skipped 各 2,定向 PlaybackOwnershipSourceTest 16/16 双 flavor、CustomCspDialogTest 5/5、CustomCspSettingTest 3/3;UI token 门禁 UI_TOKEN_STATUS PASS violations=1 与基线一致(零新增);三套 strings.xml 无重复键、两个 flavor dialog_cache_management.xml id 集合相同、全部 XML 解析通过;git diff --check 含 --cached 退出码 0;未使用模拟器(本任务零生产代码改动)

Task-Guard: C48-beta-merge-review-dev1
2026-10-08 20:40:55 +08:00
Silent1566
0582c72221 fix(mobile): 修复历史记录集数行在壁纸上的对比度不足
手机版历史卡片信息区底色是 shape_vod_name(#33000000) 压在壁纸上,与明暗调色板无关,
但集数行(adapter_vod.xml 的 @+id/remark)在 f63b0a81bc 统一 token 时从 @color/white_70
改成了 ?attr/colorOnSurfaceVariant,日间表解析为 #44474F:压在默认(梦想紫)壁纸上实测
对比度仅 1.4:1,历史记录的集数信息几乎不可读。

改回与标题行同一套与调色板无关的角色 ?attr/webhtvColorOnWallpaper(#FFFFFF),两种明暗
表取值相同;实机实测同一底上由 1.4:1 提升到 7.42:1。HistoryAdapterTest 增加守卫,
固定集数行与标题行必须使用该角色。

Verification: unit: :app:testMobileArm64_v8aDebugUnitTest runs 4919 tests; HistoryAdapterTest(3) incl. the new episode-line foreground-role guard passes; the only 5 failures are in 4 unrelated source-scan suites (TmdbSourceOnlyInteractionTest, PlayerPlaybackRegressionSourceTest, ReaderPlaybackRoutingSourceTest, WebThemeTokenSourceTest) that read files absent from this diff, so they are pre-existing. device: mobileArm64_v8aDebug APK overwrite-installed (-r, no uninstall) on 192.168.50.3:5557 where the signature hash 4dbe7298 matches the local debug key; HistoryActivity episode line measured white #FFFFFF on the info fill over the dream-purple default wall = 7.42:1 (old ?attr/colorOnSurfaceVariant #44474F = 1.40:1; user screenshot 1.40:1), and 9.4-11.2:1 on the emulator's as-found aurora-glass wall. Emulator wall pref restored to the as-found value 10.

Task-Guard: HIST-REMARK-CONTRAST-20261008
2026-10-08 20:39:43 +08:00
Silent1566
23e5280f37 修复:拖拽进度时加载中/网速大概率不显示
现象:拖动播放进度条后,加载圈(转圈+网速)基本不出现或一闪即没。

根因是本地 seek 专属进度控制与加载圈兜底之间的时序竞态:
CustomSeekView.onScrubStop -> seekListener.onSeekStarted() 先亮圈,showProgress()
随即 App.post(mR2/mR3, 0) 立刻投递网速 ticker,而 seek 命令要等这一跳之后才提交。
ticker 第一跳执行 hidePlaybackProgressIfStale(),此时引擎仍报 READY,于是把刚亮起的
圈收掉。次生缺口:onStateChanged(STATE_READY) 与 onControllerReadyReconciled() 也会
无条件收圈,引擎在 seek 生效前的那次 READY 读数同样收圈。

上游取证:webhtv/Silent1566 与 main 等全部分支均无 seek 专属窗口(onSeekStarted /
SeekListener / mSeekProgressFallback 命中数 0),上游的圈纯由 BUFFERING 状态驱动,
且上游没有本地 2839bb40955/4e303d30d34 这两个提交。故不照搬上游(无对应代码可摘,
回退到纯状态驱动会丢掉已交付的本地 seek 进度控制与加载圈兜底),而是按上游语义在本地
补上窗口这一环。

修法:seek 期间打开「最小可见 + 确实可收」窗口,无新增生产类。
- 新增 SEEK_PROGRESS_MIN_VISIBLE_MS = 1200L 与 mSeekProgressPending/StartedAtMs。
  取 1200ms 而非 500ms:网速读数由 PlaybackSpeedMeter 相邻两次采样的字节差算出,
  setTraffic 每秒一跳且每次 hideProgress 都 Traffic.reset() 重设基线,首跳必然空白,
  窗口盖不过一跳则圈虽停留、网速仍渲染不出来,只修到诉求的一半。
- onSeekStarted() 先开窗口再 showProgress()(顺序关键,后者会立刻投递 ticker),
  最后挂最小可见计时器。
- 新增 canHideSeekProgress():未开窗口放行;开着则要求已过最小可见时长、播放器有效、
  READY 且不在真实加载阻塞中(!isLoading() || isPlaying())。
- hidePlaybackProgressIfStale()(症状的直接执行者)、onStateChanged(READY)、
  onControllerReadyReconciled()、hideSeekProgressIfReady() 统一走该闸门。
- hideProgress() 同时关窗口(音频舞台路径借此自动收口);showProgress() 在窗口开着时
  不摘掉最小可见计时器。
- 曾写 200ms 重试后移除:网速 ticker 在圈可见期间本就每秒重判,自发重试会在播放器长期
  报 loading 时变成每秒 5 次的空转唤醒,故窗口收尾只保留计时器 + ticker 两条路径。

有意接受的代价(测试已锁定):缓冲内瞬时 seek 也会多显示约 0.7s 的圈,方向偏向「显示」
而非「不显示」,与诉求一致,不涉及功能或数据。

范围:仅两个 flavor 的 VideoActivity 与测试;未改 native/AAR/依赖/构建配置。
TmdbDetailActivity 内嵌播放器未覆写 onSeekStarted、加载态纯由 isLoading() 驱动,
不存在同款竞态,故未纳入。

Verification: 双 flavor arm64 Debug Java 编译 BUILD SUCCESSFUL;C47SeekLoadingProgressSourceTest 5x2=10、VideoActivityLayoutTest 154、PlaybackOwnershipSourceTest 15x2=30,合计 194 次用例执行 0 failure 0 error 0 skipped(真实执行);两处变异精确命中并已按 md5 恢复(删 hidePlaybackProgressIfStale 的 seek 闸门 -> '网速 ticker 不得收掉挂起的 seek' 变红;最小可见时长 1200->500 -> '须盖过一个网速采样间隔' 变红);git diff --check clean;全仓测试源集无其它用例钉住旧 500ms 形状;未做真机观感验收(本工作区分配的口 192.168.50.3:5561 多次连接被拒,其余端口属其它工作区未占用);未打正式包

Task-Guard: C47
2026-10-08 19:50:03 +08:00
Silent1566
b8adf3cc1c merge: 合并 origin/beta 最新代码(PR#421 站点注入搜索与手机缓存排版)并复评自动下一集黑屏修复 2026-10-08 19:48:17 +08:00
Silent1566
5096941a40 修复自动下一集黑屏:Surface 未绑定到重建后的引擎
自动下一集换集时 PlayerManager.clear() 先把 spec 置空,preparePlayer() 随后重建
引擎并回调 onPlayerRebuild();此时 isOwner() 为 false,setRender() 整段被跳过,
render 值不变。attachSurface() 原来只按“PlayerView.getPlayer() 是否为空”判断,
而 View 上仍挂着上一集已 release() 的旧引擎(非空),于是新引擎从未拿到 Surface:
解码器能初始化、能到 READY、音频正常,但零视频帧 —— 表现为有声音、全黑无画面,
必须手动切换播放核心或软硬解才恢复(两者都会重新走 onPlayerRebuild → setRender
→ detach+attach,顺带完成缺失的重绑)。

改为按引擎实例身份判断并强制重绑:同引擎跳过(未重建时的正常换集行为完全不变),
不同引擎先 setPlayer(null) 再 setPlayer(next)。

用户设备修复前日志确证:换集边界 render=0/target=0 使重绑分支被跳过、无
"attach after setPlayer"、READY 后长时间无 first-frame;随后一次偶然的引擎重建
重新绑定后立刻出现首帧。该偶然重建来自已被 56ebd07f1 整体删除的音频指纹去广告
rebuildAudioPipeline(),这正是缺陷在近期才频繁出现的原因。

Verification: PlaybackOwnershipSourceTest 16/16 通过(新增 kernelRebuildBindsTheNewEngineToThePlayerViewByInstanceIdentity);revert 生产改动后该用例 FAILED,证明确实锁定本次修复;双 flavor Java 编译 BUILD SUCCESSFUL;scripts/build_arm64_debug_install.sh --flavor mobile --serial 192.168.50.3:5555 构建成功并 install -r 覆盖安装(未卸载),启动与界面渲染正常、logcat 无 FATAL EXCEPTION

Task-Guard: autonext-black-screen

Verification: PlaybackOwnershipSourceTest 16/16 passed (new kernelRebuildBindsTheNewEngineToThePlayerViewByInstanceIdentity); falsified by stashing the production change => that test FAILED; compileMobileArm64_v8aDebugJavaWithJavac + compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; build_arm64_debug_install.sh --flavor mobile --serial 192.168.50.3:5555 built and install -r succeeded, app booted/rendered with zero FATAL EXCEPTION

Task-Guard: autonext-black-screen
2026-10-08 19:40:31 +08:00
Silent1566
46917be796
Merge pull request #421 from Silent1566/dev2
merge: 合并 origin/beta 最新代码(音频指纹移除与 T3→T4 本机网关)并复评站点注入搜索/手机缓存排版
2026-10-08 18:56:40 +08:00
Silent1566
1a4d70410c merge: 合并 origin/beta 最新代码(音频指纹移除与 T3→T4 本机网关)
Verification: origin/beta tip 24265785a3 成为合并提交第二父(0 冲突);零复活:beta 上 845 个 revert 类提交触碰路径中 187 个在当前 beta 树已删除,这 187 个在合并树 904ca58f 中复活数 0;被移除的 100 个路径在 dev2 侧本地改动丢失数 0;悬空引用 0(8 个已移除类与 5 个已移除字符串引用数均为 0);路径集合闭合(合并树 vs dev2 并 beta 复活 0 丢失 0);beta 关键文件 build.gradle/libs.versions.toml/Nano.java/PlaybackActivity.java/PlayerManager.java 与 beta 逐字节一致;净差异 16 路径 0 删除;git diff --check 退出码 0;双 flavor Java 与 androidTest Java 编译 BUILD SUCCESSFUL;双 flavor 全量单测 mobile 4919/7 leanback 4078/8 与基线失败集合逐条一致(零新增回归);定向契约测试 204 用例 failures=0 errors=0(含 VodApiTest 31/31);UI token 门禁 violations=1 hex_layouts=1 hex_drawables=0 与基线完全一致(零新增违规);双 flavor debug APK 打包 BUILD SUCCESSFUL;TV 192.168.50.3:5557 实机覆盖安装启动 FATAL=0,首页与去广告页正常且音频检测分组已移除无空分组,设备内真实 HTTP 调用验证 T3 到 T4 网关 ac=config 返回 200 与完整 type4 站点信封且 key 正确 URL 编码、ac=detail 返回苹果CMS 列表信封、OPTIONS 返回 204、相邻端点未被误路由;手机实机启动 FATAL=0,缓存弹窗模块竖排且窄屏 720x1600+320dpi+1.3x 字体下底部策略行与三档清理按钮刷新确定完整可见;设备 wm size/density/font_scale 已恢复;两轮评审均未发现必修问题

Task-Guard: C47-beta-merge-review-dev2
2026-10-08 17:59:32 +08:00
Silent1566
74c39d6dea docs(c46): 记录 dev2 合并 beta 与站点注入搜索/手机缓存排版的评审与验证
Verification: origin/beta tip d8daedf86c 为 HEAD 第一父(合并完整);零复活:267 个 revert 类提交 x 742 路径中 55 个 beta 已删除路径在 HEAD 复活数 0,6 个净差异新增路径在 beta 全历史条目数均为 0;beta 增量零丢失(5054 路径 0 缺失);净差异 15 路径 0 删除;冲突 0 / 冲突标记 0;git diff --check 退出码 0;双 flavor Java 与 androidTest Java 编译 BUILD SUCCESSFUL;双 flavor 全量单测 mobile 5220/7 leanback 4379/8,与基线 d8daedf86c 失败集合逐条一致(零新增回归);定向契约测试 326 用例 failures=0 errors=0;UI token 门禁 violations=1 hex_layouts=1 hex_drawables=0 与基线完全一致(零新增违规);TV 192.168.50.3:5557 实机覆盖安装启动 FATAL=0、站点注入搜索过滤生效(2 条到 1 条)、无匹配空状态可见、缓存弹窗保持 TV 横排;手机实机模块竖排且窄屏 720x1600+320dpi+1.3x 字体下底部控件完整可见;设备 wm size/density/font_scale 已恢复;两轮评审均未发现必修问题

Task-Guard: C46-beta-merge-review-dev2
2026-10-08 17:11:01 +08:00
Silent1566
24265785a3
Merge pull request #419 from Silent1566/dev3
dev3:合并 beta 最新代码(PR#417/#418)并复评 T3→T4 本机网关
2026-10-08 16:59:49 +08:00
Silent1566
6d11b98128
Merge pull request #420 from Silent1566/dev1
移除音频指纹去广告与语音广告识别(含 C46 复评补齐两处遗留缺口)
2026-10-08 16:57:03 +08:00
Silent1566
2283e15d13 docs(c46): 修正 revert 检测口径与净差异路径计数
上一版把检测器写成 git log --grep='回退|revert'(未锚定),
grep 会匹配提交正文,而本任务 5d4a7e8e18 的正文恰好含
「258 个 revert-like 提交、1144 条被触及路径」,其中一行以 revert 开头,
导致本任务自己的提交被误判为 revert 提交,进而把本任务文档
docs/C46-…md 误报为「复活路径」。

修正:
1. 检测器改为只匹配主题行(%s)并锚定开头,得到 109 个 revert 提交、
   725 条被触及路径、601 条不在 beta 树、在 HEAD 树存在 = 0;
2. 记录两次误报(MSYS 路径转换、本提交正文被 grep 命中)的成因与排除方式;
3. 把过时的「净差异 5 个路径」订正为 6 个(5 个功能路径 + 本任务文档);
4. 修正标题层级以满足 markdownlint。

仅文档改动,未修改生产代码。

Verification: documentation-only correction: replaced the over-broad revert-commit detector (git log --grep matched commit bodies, so this task's own merge message containing the word 'revert' was misclassified) with a subject-anchored definition (109 revert commits / 725 touched paths / 601 absent from beta tree / 0 revived); cross-checked by excluding this task's 3 commits (0 revived) and confirmed the flagged file never existed on origin/beta; also corrected stale '5 paths' to '6 paths' net-diff figures and heading level for markdownlint

Task-Guard: C46-revival-count-correction
2026-10-08 16:55:50 +08:00
Silent1566
c2761b2ff6 docs(c46): 记录交付坐标(提交 cd88c907d / tag / PR #420)
- 提交 cd88c907d9
- recovery tag recovery/C46-beta-merge-review-dev1/20261008164917-cd88c907d912
- dev1 已推送至 origin/dev1(0 ahead / 0 behind)
- PR #420 dev1 -> beta:OPEN、未合并(merged=false、merged_at=null)、MERGEABLE、122 文件 +323 -24844
- PR 文件集经 gh api 分页校验与 git diff --name-only origin/beta dev1 逐项一致

Task-Guard: C46-beta-merge-review-dev1-doc-closure

Verification: 纯文档收口:仅记录已确认的提交、recovery tag、推送与 PR#420 坐标(merged=false / merged_at=null / MERGEABLE / 122 文件),无代码或行为变更;PR 文件集经 gh api 分页校验与本地净差异逐项一致

Task-Guard: C46-beta-merge-review-dev1-doc-closure
2026-10-08 16:51:24 +08:00
Silent1566
5e4cc37ba5 docs(c46): 记录 Nano 资源回退分支的可证伪区分证据
前一条记录只写了「回退资源与内置 asset 逐字节一致」,但设备上
/storage/emulated/0/WebHTV/EnvFiles/T4Proxy.js 已被 LabActivity 同步存在,
该结果无法区分命中新增回退分支还是原有 Local 外置文件分支。

补充移走外置文件的可证伪验证:
- 外置存在 -> 由 Local 分支服务(sha256 同);
- 移走外置 -> 由新增回退分支服务内置 asset,sha256 字节一致(改动前该 URL 为 404);
- 写入哨兵内容 -> 返回哨兵原文,用户脚本未被覆盖;
- 恢复外置文件 -> 设备状态复原。

仅文档改动,未修改生产代码。

Verification: documentation-only follow-up commit recording the Nano fallback-branch discriminating evidence (moved the external /storage/emulated/0/WebHTV/EnvFiles/T4Proxy.js away and confirmed HTTP 200 serving the built-in asset byte-identically, plus sentinel test proving user scripts are not overwritten); no production code changed

Task-Guard: C46-fallback-evidence
2026-10-08 16:49:38 +08:00
Silent1566
cd88c907d9 docs(c46): 复评 beta 合并后的 C45 交付并补齐两处遗留缺口
合并状态:origin/beta tip d8daedf86 已是 dev1 祖先(dev1..origin/beta 计数为 0),
无需合并操作;dev1 = origin/beta tip + C45 单个提交,线性关系。

远端已移除内容零复活(三层证据):
1. 结构性证明:dev1 相对 beta 的 120 个改动路径与 C45 提交改动路径完全一致,
   改动集之外的 4935 个文件 blob 与 beta 100% 相同;
2. 行级扫描:beta 历史 22 个单亲 revert/移除类提交、1686 行被删内容,
   10 行原始命中经甄别全部为 beta 自身后续合法重新引入(三文件与 beta tip 逐字节相同);
3. 净差异核对:dev1 独有且 beta 不含的非空行仅 20 行,全部来自 C45 自身改写。

复评发现并修复两处真实缺陷(均属 C45 完成度缺口):
- scripts/pull-ad-audio-log.sh:只服务被删功能的孤儿脚本,其依赖的 3 个偏好键
  与 10 个诊断标记在源码中引用数均为 0,上游 Silent1566 亦无此文件 → 删除(-53 行);
- 智能去广-设计文档.md:C45 声明的「决策 F 改写」实际执行成整节删除,而该节描述的
  PlaybackMediaSignalHub/PipelineLease/generation 门控仍被实时字幕使用
  (RealtimeSubtitleController 是现存唯一生产 consumer)→ 以「决策 E」恢复该架构决策。

第 3 轮订正我自己在第 2 轮的超范围恢复:「当前明确不承诺」4 条全部指向已删功能,
C45 删除它是正确的,已撤销恢复。

生产代码零改动:app/src 下本次无任何新增/修改/删除。

Verification: compileLeanbackArm64_v8aDebugJavaWithJavac + compileMobileArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; testLeanbackArm64_v8aDebugUnitTest 4039 tests + testMobileArm64_v8aDebugUnitTest 4885 tests, 0 failures/0 errors; scripts/check_ui_tokens.sh UI_TOKEN_STATUS PASS (violations=1 为预先存在的 item_following.xml,不在改动集内); scripts/build_arm64_debug_install.sh --flavor mobile --serial 192.168.50.3:5555 BUILD SUCCESSFUL + 覆盖安装 Success; 设备冷启动无 FATAL/ClassNotFound/InflateException,设置页摘要「总开关、规则、片头片尾」,去广告子页仅基础/规则与统计/片段跳过三组,ad_audio*/speech_ad* 偏好键计数 0 而 adblock/ai_ad_detection/auto_skip_intro_outro 保留; git diff --check clean

Task-Guard: C46-beta-merge-review-dev1

Verification: 双 flavor Java 编译 BUILD SUCCESSFUL;双 flavor 全量单测 4039+4885 全通过 0 失败 0 错误;UI token 门禁 PASS 相对基线零新增违规;零复活三层程序化校验通过(改动集外 4935 文件 blob 与 beta 100% 相同,复活行 0);功能层残留引用 0;mobile 覆盖安装成功且冷启动无崩溃、设置页无音频识别项、ad_audio*/speech_ad* 偏好键计数 0

Task-Guard: C46-beta-merge-review-dev1
2026-10-08 16:49:17 +08:00
Silent1566
5d4a7e8e18 merge: 合并 origin/beta 最新代码(PR#417/#418)并复评 T3→T4 本机网关
纳入 beta 唯一实质增量 6e667a236f:gradle/gradle-daemon-jvm.properties
从已下架的 JetBrains JBR 21 硬钉改为「Java 21、任意厂商」并重新生成
10 个有效下载地址,修复 CI 在测试前即中断的问题(旧 toolchainVendor=JETBRAINS
已移除,实测 ./gradlew --version 显示 Compatible with Java 21, any vendor)。
0 冲突、0 冲突标记。

复评 dev3 全部已修改代码(含已提交未推送的 c388619629 T3→T4 本机网关),
4 轮评审全部通过,未发现必修问题,本任务未修改任何生产代码。

远端已移除/回退内容零复活:全 refs 258 个 revert-like 提交、1144 条被触及
路径逐条比对,合并索引树相对 origin/beta 复活数 = 0;净差异 5 个路径与
revert 路径集交集为空。检测器已用「注入伪造树」反证有效(能正确报出
ISSUE_TEMPLATE),故 0 复活非空转结论。

验证:
- VodApiTest 31 tests / 0 skipped / 0 failures / 0 errors;
- 双 flavor Java 与 AndroidTest 编译 BUILD SUCCESSFUL;
- node --check、git diff --check、冲突标记扫描均通过;
- UI token 门禁 violations=1,为既有 item_following.xml,相对 beta 零新增;
- 实机 192.168.50.3:5559 端到端:170 个站源、真实站源搜索/详情/播放/分类
  与 base64 ext 筛选全部正常,内部 site/tmdb 字段未泄漏;
  /file/WebHTV/EnvFiles/T4Proxy.js 回退资源与内置 asset 逐字节一致。

净差异(相对 origin/beta)仅 5 个路径,全部为 dev3 自身改动:
T4Proxy.js、Nano.java、VodApi.java、VodApiTest.java、docs/C45-t4-api-gateway.md。

Verification: merge origin/beta d8daedf86c (0 conflicts, 1 path: gradle/gradle-daemon-jvm.properties); zero revived remote-removed paths (1144 revert-touched paths scanned, detector self-test with injected fake tree reports REVIVED; net diff vs origin/beta disjoint from revert set); beta increment and dev3 changes lossless (per-path SAME); VodApiTest 31 passed 0 failures; dual-flavor Java+AndroidTest compile BUILD SUCCESSFUL; gradlew --version shows 'Java 21, any vendor'; node --check passed; UI token gate relative to beta zero new violations; device 192.168.50.3:5559 e2e: 170 sites, real-source search/detail/play/category/ext filter, asset fallback byte-identical; git diff --check clean; no conflict markers

Task-Guard: C46-beta-merge-review-dev3-20261008
2026-10-08 16:43:37 +08:00
Silent1566
56ebd07f10 移除:删除音频指纹去广告与语音广告识别功能,向 Silent1566 上游靠齐
删除 app/src/main/java/com/fongmi/android/tv/ad/audio 全部 45 个类与其 37 个测试类、
AdSkipPromptPresenter、SpeechRecognitionFactory、RealtimeSubtitleSpeechRecognitionFactory,
以及 speech_ad_rules_v1.txt、probe-rules-v1-community.json 和 11 个只服务于这两个功能的文档。

清理接线:PlayerManager 的 AdAudioRuntimeController/AdAudioPlaybackPort/管线重建预算与
refreshAdAudioRuntime 调用;PlaybackActivity 的 AdSkipPromptPresenter 绑定;Backup 的
SpeechAdSetting.sanitizePreferences 通道与三个 speech_ad 偏好键;PlaybackMediaSignalHub
的 AD_AUDIO consumer 种类;RealtimeSubtitleRecognizer 的 AD_AUDIO 执行 profile。

设置页删除音频识别分组(指纹开关、自动跳过、社区规则源、语音开关/关键词/规则/时长/模式),
三套 strings.xml 各删 66 条键并把去广告摘要改为“总开关、规则、片头片尾”。
移除仅被签名规则包使用的 Tink 依赖。

保留接口/HLS/AI 规则去广告、广告统计、片头片尾区间跳过与实时字幕;PCM Hub 与
rebuildAudioPipeline 仍由实时字幕使用,故未删除。

Verification: compileLeanbackArm64_v8aDebugJavaWithJavac + compileMobileArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; testLeanbackArm64_v8aDebugUnitTest 4039 tests + testMobileArm64_v8aDebugUnitTest 4885 tests, 0 failures/0 errors (--rerun-tasks, full re-run); targeted bean/setting/player.audio/subtitle suites 0 failures; scripts/build_arm64_debug_install.sh --flavor mobile --serial 192.168.50.3:5555 BUILD SUCCESSFUL + overwrite install Success; device cold start no crash/no FATAL/no ClassNotFound, Ad settings page shows only Basic/Rules+stats/Segment-skipping, prefs ad_audio*/speech_ad* count 0 while adblock/ai_ad_detection/auto_skip_intro_outro intact; residual AdAudio|SpeechAd|AudioFingerprint|ProbeRule|AdSkipCoordinator|SpeechRecognitionFactory references 0 in app/src and docs/; git diff --check clean

Task-Guard: C45-remove-ad-audio-fingerprint-speech
2026-10-08 14:53:45 +08:00
Silent1566
c388619629 feat(server): add local T3-to-T4 gateway with HTTP contract coverage
Verification: VodApiTest: 31 passed, 0 failures (real NanoHTTPD-to-Spider fixture); Node HTTP contract: 10 scenarios passed; node --check, scoped Java LSP and git diff --check passed; single-use build/test servers closed; app/build and build cleaned; no APK/device testing

Task-Guard: C45-t4-api-gateway
2026-10-08 14:52:04 +08:00
Silent1566
ce01b45c3c merge: 合并上游 webhtv/webhtv Silent1566 站点注入搜索与手机缓存排版修复
上游 3 个提交(58ebb653ff/7357b2c37d/05469a967a)全部纳入:站点注入弹窗搜索与空状态、手机窄屏缓存管理排版修复。

解决 CacheManagementDialog 唯一 import 冲突:取上游 Util(mobile 分支必需),拒绝带回 MaterialAlertDialogBuilder,保留本地 WebHtvAlertDialogBuilder 主题契约。

按强制评审结论做最小本地适配:6 处硬编码颜色改为本地语义 token(?attr/colorOnSurface / colorOnSurfaceVariant / colorSurfaceContainerHighest),上游测试主题基座改为本地 Theme.WebHTV.Dialog(M2 主题缺 colorOutline 会导致布局 inflate 失败)。

Verification: 上游 tip 05469a967a 成为第二父;1 冲突(import)解决且 0 残留冲突标记;上游 14 路径全部落地(6 新增/8 修改/0 删除,6 条缺失行均为评审结论下的语义 token 与测试主题基座有意改写);本地既有改动零丢失(7 交集路径逐行核对,2 行为 mobile 参数化预期改写);双 flavor Java 与 androidTest Java 编译 BUILD SUCCESSFUL;双 flavor 全量单测相对合并前 worktree 基线零新增失败(leanback 基线 9→8、mobile 5217/7→5220/7 失败集合逐条一致);定向 27 类 176 用例 failures=0;UI token 门禁 violations=1/hex_layouts=1/hex_drawables=0 与基线完全一致(零新增);TV 192.168.50.3:5557 实机覆盖安装启动无 FATAL、设置页缓存行与缓存弹窗 TV 横排正常、站点注入搜索框渲染且过滤生效、无匹配空状态可见;手机实机缓存弹窗模块竖排、窄屏 720x1600+320dpi+1.3x 字体下详情占满宽度且底部策略与清理按钮滚动后完整可见、模块级确认框正常;设备 wm size/density/font_scale 已恢复;git diff --check 通过;全部路径在 app/ docs/ scope 内

Task-Guard: C45-upstream-sync-silent1566
2026-10-08 13:58:40 +08:00
Silent1566
d8daedf86c
Merge pull request #418 from Silent1566/dev1
fix(ci): 修复守护进程 JVM 供给失败导致流水线在测试前中断
2026-10-08 12:13:54 +08:00
Silent1566
6e667a236f fix(ci): 修复守护进程 JVM 供给失败导致流水线在测试前中断
流水线 run 37649723583 在「Run release-critical tests」步骤、尚未执行任何
测试前即失败:

  Unable to download toolchain matching the requirements
  ({languageVersion=21, vendor=JetBrains, implementation=vendor-specific,
  nativeImageCapable=false}) ... Received status code 400 from server: Bad Request

根因:gradle/gradle-daemon-jvm.properties 把守护进程 JVM 硬钉在 JetBrains
JBR 21,而该包已从 foojay 下架。该文件记录的 10 个 api.foojay.io ID 现已
全部失效(逐个返回 HTTP 400「Package id not found」),且 foojay 当前根本
不再发布 JetBrains JDK 21(仅有 11 与 17)。

文件上次生成于 5 月;上一次发布成功是因为 runner 本地缓存中恰好仍有 JBR,
待缓存转冷后才暴露该问题——因此「上次成功/本次失败」之间仓库侧零改动
(同一 blob 5c34300fa6)。

修复:
1. 用官方 ./gradlew updateDaemonJvm --jvm-version=21 重新生成该文件,
   10 个下载地址恢复有效(均 HTTP 301,实测可下载真实产物)。
2. 移除 toolchainVendor=JETBRAINS 条件,使 Gradle 接受 setup-java 安装的
   Temurin 21,而不再尝试供给已不存在的 JBR。构建逻辑不依赖 JetBrains 厂商,
   这也正是 Gradle 文档记载的默认行为(默认不生成 vendor 条件)。

验证:
- 以无 jdks/ 缓存的 GRADLE_USER_HOME 在真实仓库复现出与 CI 逐字相同的报错;
- 重新生成的文件与 updateDaemonJvm 规范输出逐字节一致;
- 10 个新地址均解析到真实产物(Linux x86_64 实测 206 + gzip 魔数 1f8b);
- ./gradlew --version 显示「Compatible with Java 21, any vendor」,且未创建
  jdks/(即无需下载);
- 流水线两条 Gradle 命令原样执行均 BUILD SUCCESSFUL:移动端 73 个测试
  (17 个 XML)0 失败 0 错误,Leanback 10 个测试 0 失败;
- git diff --check 干净;仅改动 gradle/gradle-daemon-jvm.properties。

Task-Guard: FIX-CI-DAEMON-JVM-20261008
2026-10-08 12:09:52 +08:00
Silent1566
05469a967a fix: 修复站点注入搜索空状态与排序刷新回归
Verification: 原提交及修复已复评;Robolectric与匹配测试8/8通过(修复前2失败);Leanback Debug Java/资源编译通过;变更Java文件LSP clean;git diff --check通过;一次性daemon已退出

Task-Guard: site-injection-search-review
2026-10-08 11:43:00 +08:00
Silent1566
6e3037c569
Merge pull request #417 from Silent1566/dev3
dev3:合并 beta 最新代码(PR#414-#416)并复评修复 LightDialog 外壳透明度双重施加
2026-10-07 23:30:11 +08:00
Silent1566
f85398d359 merge: 合并 origin/beta 最新代码(PR#414-#416)并复评修复 LightDialog 外壳透明度双重施加
- 合并 origin/beta tip 2fafb302cc(PR#414 短剧源禁用、PR#415 episode 并发、PR#416 弹窗 tint 泄漏),0 冲突
- 按 beta 当前权威树处置历史回退项:SpiderCrashBreadcrumb 等 3 个路径不复活
- LightDialog 抽出唯一外壳形状源 shell(),外壳保持不透明,dialogOpacity 只由
  ThemeBinder.bindWindowBackground 施加;修复 0.70 被算成 0.49,以及半透明填充
  失去基线角色匹配导致 surfaceContainerHigh 用户覆写无法到达该外壳
- 新增 LightDialogShellOpacityTest(2 用例,反证可失败)与 ThemeBinderContractTest 反预乘断言

Verification: C44 dev3 beta 合并复评:合并 0 冲突(第二父=origin/beta tip 2fafb302cc);beta 增量 13 路径零丢失、dev3 既有 12 路径零丢失(三方逐文件一致);远端已回退内容零复活(341 路径扫描 0 复活,检测器自检可报 REVIVED,含 790d678c9e 的 SpiderCrashBreadcrumb 3 路径未带回);第 1 轮发现并修复 LightDialog 外壳 dialogOpacity 被应用两次(0.70 -> 0.49)且半透明填充失去角色匹配导致 surfaceContainerHigh 覆写失效,新增 LightDialogShellOpacityTest 2 用例并反证可失败;第 2/3 轮复评无新增必修问题;leanback 4371 用例/6 失败、mobile 5217 用例/5 失败,失败集合与本机 CRLF 基线的既有集合逐条一致且涉及 23 个源文件相对 origin/beta 与合并前 HEAD 均逐字节一致;双 flavor Java + AndroidTest Java 编译 BUILD SUCCESSFUL;check_ui_tokens --strict violations=1 为既有 mobile/item_following.xml 零新增;git diff --check 0;设备 192.168.50.3:5559 leanback 覆盖安装(未卸载)实测显式深色与显式浅色两种模式:ChoiceDialog 聚焦 6.39/5.23、常规 12.14/14.73、面板标题 10.48/13.99,LightDialog 外壳跟随当前模式;设备偏好已逐键还原(296 键仅 2 个运行时缓存键自然变化)

Task-Guard: C44-beta-merge-review-dev3-20261007
2026-10-07 23:21:27 +08:00
Silent1566
7357b2c37d feat: add site injection search
Verification: CustomCspSettingTest passed; :app:testMobileArm64_v8aDebugUnitTest passed with mobile Java/resource compilation; git diff --check passed; regenerated app/.cxx remains protected and uncommitted

Task-Guard: site-injection-search
2026-10-07 21:49:46 +08:00
Silent1566
2fafb302cc
Merge pull request #416 from Silent1566/dev2
merge: 合并 origin/beta 最新代码(PR#412-#415)并修复弹幕接口弹窗 tint 泄漏
2026-10-07 19:54:09 +08:00
Silent1566
fb5f2a03df merge: 合并 origin/beta 最新代码(PR#412-#415)并复评弹幕弹窗 tint 泄漏修复
Verification: 合并 origin/beta(056200cda0) 0冲突;回退内容零复活(124移除类提交x361路径 + 5682f2b05x70路径 blob级比对=0);beta 5045路径零丢失;dev2 4路径零丢失;三方合成文件0;net-diff 4路径全为dev2自身改动;mobile 5216用例/5失败=基线5199/5零新增回归;leanback 4365/6=基线4351/6零新增回归;双flavor androidTest编译成功;定向171用例0失败;设备192.168.50.3:5557 DanmakuApiDialogDeviceTest OK(3 tests) 且RED反证1 failed;UI token violations=1=基线零新增;git diff --check退出0

Task-Guard: C44-beta-merge-review-dev2
2026-10-07 19:49:18 +08:00
Silent1566
fb85759b03 fix(tv): 主题模式解析与选项弹窗跟随活动调色板
- ThemeController 使用显式 theme_mode 解析,显式浅/深色覆盖系统
- ChoiceDialog 选项按钮与弹窗根背景改用 ThemeController.current() token
- 自定义 Dialog 入口补 ThemeController.bindDialog
- 新增 AppearanceRowThemeTest#choiceItemsUseTheActivePaletteInBothModes 契约

Verification: TV-THEME-MODE-DARK-20261007: 定向 JVM 测试通过(leanback com.fongmi.android.tv.theme.* + ui.dialog.* 共 210 用例,唯一失败 DialogRoundedCornerSourceTest 为 beta 侧既有 CRLF 脆弱断言,与本改动零交集);ChoiceDialog 选项按钮 token 化对比度实测 浅/深 = 聚焦 6.39/7.50、选中 12.57/7.04、常规 14.73/12.14,禁用 2.12/2.67(与修复前 2.37 同口径);已按 beta 侧 C33/C35/C37/C39 既有记录处置,不扩界修改

Task-Guard: TV-THEME-MODE-DARK-20261007
2026-10-07 19:36:13 +08:00
Silent1566
10daccd631 fix(tv): honor explicit dark and light theme modes
Verification: ThemeController now resolves explicit theme_mode before system mode; ThemeDialogModeTest and ThemeControllerContractTest passed; full Leanback theme suite passed; leanback arm64 Debug built and adb install -r succeeded on 192.168.50.3:5559; temporary verification files removed

Task-Guard: TV-THEME-MODE-DARK-20261007
2026-10-07 17:30:50 +08:00
Silent1566
7169eddc35 fix(tv): theme appearance dialog shell
Verification: Leanback theme suite 159 tests passed; leanback arm64 Debug built successfully; adb install -r succeeded on 192.168.50.3:5559; rebuilt app launched stably; temporary screenshots removed; diff check passed

Task-Guard: TV-THEME-DIALOG-20261007
2026-10-07 16:04:35 +08:00
Silent1566
756f164ab3 fix(tv): persist theme editor light and dark mode
Verification: Leanback theme suite passed; focused mode persistence and flavor contract tests passed; leanback arm64 debug built and adb install -r succeeded on 192.168.50.3:5559; worktree diff check passed

Task-Guard: TV-THEME-MODE-20261007
2026-10-07 15:19:08 +08:00
Silent1566
a10322a75c docs(danmaku): 记录提交 7596ebabb7 与恢复标签 2026-10-07 14:27:20 +08:00
Silent1566
7596ebabb7 fix(dialog): 弹窗改用主题覆盖层,阻止控件 tint 泄漏到系统编辑菜单
BaseAlertDialog.builder() 此前把圆角面板控件样式 MaterialAlertDialog_WebHTV_Rounded
作为 themeResId 传入,使其 backgroundTint 成为对话框上下文级属性,并被系统浮动编辑菜单
(复制/粘贴)的 AppCompat ImageButton 继承。vivo 绘制类在 mutate() 该背景时抛出
ClassCastException,导致弹幕接口输入处长按即崩溃。

圆角本已由 ThemeOverlay.WebHTV.Dialog 的 alertDialogStyle 指向同一控件样式,
面板外观不变;改用主题覆盖层后 tint 不再泄漏。

验证:
- 新增设备回归 DanmakuApiDialogDeviceTest(tint 不泄漏、面板样式仍为 Rounded、
  复制/剪切/粘贴、浮动工具栏可创建、保存与取消、IME 完成),旧代码 RED、新代码 OK (3 tests)。
- DialogRoundedCornerSourceTest 收紧为断言主题覆盖层,mobile/leanback 均通过。

限制:本地为 Android 9 模拟器,未在 vivo V2307A / Android 14 真机复测。

Verification: 手机源码回归 testMobileArm64_v8aDebugUnitTest DialogRoundedCornerSourceTest 2/2 通过;TV 源码回归 testLeanbackArm64_v8aDebugUnitTest 同用例 2/2 通过;192.168.50.3:5557 设备回归 DanmakuApiDialogDeviceTest OK (3 tests);旧代码上两项均 RED(源码断言命中 theme overlay,设备断言命中 panel backgroundTint leaked into system text actions);覆盖安装未清除应用数据

Task-Guard: FIX-DANMAKU-VIVO-20261007
2026-10-07 14:26:36 +08:00
Silent1566
056200cda0
Merge pull request #415 from Silent1566/dev4
合并 beta 最新代码并修复剧集列表并发修改崩溃
2026-10-07 14:24:03 +08:00
Silent1566
2b10824361 merge: 合并 origin/beta 最新代码并修复 episode 列表并发
Verification: 合入 origin/beta 6ff6a4458cf;无冲突;PR 净差异仅 SourceEpisodeSeasonCache.java、TmdbEpisodeSorter.java、SourceEpisodeSeasonCacheTest.java 与 C43 文档;beta 已删除的 SpiderCrashBreadcrumb.java、其测试及 SPIDER-CRASH-DIAG 文档均未复活;最新 beta(含 PR#414)移动/Leanback 定向 JVM 测试、双 flavor Java/AndroidTest Java 编译 BUILD SUCCESSFUL;gradlew.bat clean BUILD SUCCESSFUL;git diff --cached --check 通过

Task-Guard: C43-beta-merge-review-dev4
2026-10-07 14:14:22 +08:00
Silent1566
6ff6a4458c
Merge pull request #414 from Silent1566/dev1
merge: 合并 origin/beta 最新代码(PR#412/#413)并复评补齐短剧源禁用契约
2026-10-07 13:08:54 +08:00
Silent1566
d1b7e2750e docs(c42): 记录 dev1 交付坐标并订正合并树与提交树
合并结果树 de7921410 是任务文档写入前用于逐字节比对的索引树;提交树含文档故为 4351c0f。补记提交、recovery tag、推送与 PR #414 坐标。

Task-Guard: C42-beta-merge-review-dev1
2026-10-07 13:07:18 +08:00
Silent1566
84d76f4c59 merge: 合并 origin/beta 最新代码(PR#412/#413)并复评补齐短剧源禁用契约
## 合并

- 合并 origin/beta tip 2a2139beb(Merge PR #413 from dev3),纳入 10 个提交
  (PR#412 播放加载圈守卫、PR#413 TV 固定深色面板对比度回归修复)
- 0 冲突、0 冲突标记,19 路径自动合入,合并树 de7921410
- beta 增量 19 路径逐字节一致、dev1 既有改动 3 路径逐字节一致

## 远端已移除内容零复活(用户核心关注点)

- 116 个 revert/移除类单亲提交零复活:550 文件版本、117040 行扫描、复活 0 行
- 远端剔除提交 5682f2b05(剔除 PR #353 主题系统改动)触碰的 70 个路径
  与 origin/beta 逐字节一致,旧主题系统文件在两侧同为缺席

## 评审修复(第 1 轮发现,第 2/3/4 轮订正后通过)

- 补齐短剧源规则契约的第三个站点:ShortDramaSourceDialog.showSiteManage()
  曾用 tempEnabledRules.isEmpty() 回填默认关键词,用户清空规则后打开一次
  「站点管理」即被重新勾选写回,用户报告的「短剧源无法禁用」从该路径复现,
  而原测试只钉住了 show() 与 resetToDefault() 两处,抓不到
- 改为钉语义不变量:空规则判定式与 defaultRulesText() 必须彻底消失、
  defaultRules() 合法入口不超过「未配置初始值」与「恢复默认」两处
- 按仓库既有约定加 codeOnly() 剥注释,避免契约匹配到说明文字
- 本任务未改动任何生产源码:两个生产文件与 6414185af 及合并索引逐字节一致

## 验证

- 双 flavor Java 与 androidTest 编译 BUILD SUCCESSFUL
- 全量单测 mobile 5214 / leanback 4365,零失败零错误
- 三次注入反证(第三处 defaultRules() 兜底、isEmpty+defaultRulesText 旧写法、
  去掉 isConfigured() 初始值)均使契约 FAILED,证明非空转,注入已完全回滚
- UI token 门禁 violations=1(既有 item_following.xml,不在本次净差异)

Verification: 合并 origin/beta 2a2139beb(PR#412/#413)0 冲突;beta 增量 19 路径零丢失、dev1 增量 3 路径零丢失;116 个 revert/移除类提交零复活(550 文件版本/117040 行扫描);远端剔除提交 5682f2b05 触碰的 70 路径与 origin/beta 逐字节一致(零带回);双 flavor Java+androidTest 编译 BUILD SUCCESSFUL;全量单测 mobile 5214 / leanback 4365 零失败零错误;ShortDramaConfigTest 双 flavor 各 3 tests 通过,三处注入反证均 FAILED 后已完全回滚(生产源码与 6414185af 逐字节一致);UI token 门禁 violations=1 且唯一违规 item_following.xml 不在本次净差异中(相对基线零新增);git diff --check 退出码 0、无冲突标记

Task-Guard: C42-beta-merge-review-dev1
2026-10-07 13:05:22 +08:00
Silent1566
2a2139bebc
Merge pull request #413 from Silent1566/dev3
merge: 合并 origin/beta 最新代码(PR#410/#411/#412)并复评修复 TV 固定深色面板对比度回归
2026-10-07 11:35:34 +08:00
Silent1566
12f2639b8d merge: 合并 origin/beta 最新代码(PR#410/#411/#412)并复评修复 TV 固定深色面板对比度回归
Verification: 合并 origin/beta tip 53b54d1b6(PR#410/#411/#412)0 冲突,合并树 3005b6f2a 与 merge-tree 预测一致;26 个 beta revert 提交移除内容零复活;复评发现并修复合并引入的 TV 固定深色面板对比度回归(5 处:dialog_exit_confirm/dialog_display/dialog_disc_menu/dialog_audio_comment/adapter_tmdb_recommendation_landscape,实测 1.02-3.10:1 -> 6.39-19.80:1);新增 TvFixedDarkSurfaceContrastTest 4 用例(含合成探针,反证可失败);全量 JVM leanback 4362 / mobile 5211 零失败;check_ui_tokens.sh --strict violations=1 legacy=0 hex_colors=0 相对基线零新增;设备 192.168.50.3:5559 日间模式复现缺陷并确认修复(主按钮 2.68:1 -> 6.39:1),设备 base.apk md5 1e4dcde4ac3b3deb3513843960f5ecfb 与本地构建产物一致;git diff --check 退出码 0

Task-Guard: C42-beta-merge-review-dev3-20261007
2026-10-07 11:31:58 +08:00
Silent1566
9555806b70 fix: prevent episode list concurrent modification crash
Verification: 定向单测通过::app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.helper.SourceEpisodeSeasonCacheTest --tests com.fongmi.android.tv.utils.TmdbEpisodeSorterTest;9+3 tests passed,Java compile passed;git diff --check passed

Task-Guard: fix-source-episode-concurrency
2026-10-07 11:20:06 +08:00
Silent1566
53b54d1b62
Merge pull request #412 from Silent1566/dev2
dev2:合并 beta 最新代码(PR#411)并复评修复播放加载圈守卫泄漏
2026-10-07 10:55:35 +08:00
Silent1566
3374def641 merge: 合并 origin/beta 最新代码(PR#411)并复评修复播放加载圈守卫泄漏
- 合并 origin/beta tip 9b168a1f38(PR#411),0 冲突;远端 18 个回退提交移除内容零复活
- 复评 dev2 未推送提交 f77c4a0236 时发现真实缺陷:setPlayer 的重复结果早退分支
  未释放 mPlaybackRequestActive,导致 onStateChanged 与 hidePlaybackProgressIfStale
  两个收圈路径同时被挡下,加载圈在「同一结果已在播」时永不清除;改为在该分支内释放守卫
- 同步订正 ReaderPlaybackRoutingSourceTest 中被钉死的单行写法为语义断言,保留「重复结果不得重复起播」口径
- 双 flavor Java/androidTest 编译通过;双端全量 JVM 套件相对合并前基线零新增回归

Verification: :app:compile{Leanback,Mobile}Arm64_v8aDebugJavaWithJavac and :app:compile{Leanback,Mobile}Arm64_v8aDebugAndroidTestJavaWithJavac BUILD SUCCESSFUL; :app:testMobileArm64_v8aDebugUnitTest 5200 tests/6 failed and :app:testLeanbackArm64_v8aDebugUnitTest 4351 tests/7 failed, both failure sets identical to the pre-merge baseline worktree @ f77c4a0236 (mobile 5195/6, leanback 4349/7) => zero new regressions (CRLF-only multi-line source assertions + one load-sensitive flake, all target files byte-identical to origin/beta); 18 beta revert commits x 48 file-versions x 795 removed lines => 0 resurrected; 6 merged paths byte-identical to origin/beta, 2 dev2 paths preserved; merge tree 8d0126b93e == git merge-tree prediction; fix falsification: reverting the duplicate-result guard branch => VideoActivityLayoutTest 154 tests/1 failed (leanbackPlaybackLoadingIgnoresStaleReadyBeforeNewPlayerStarts), and deleting the mobile guard => reclaimedOrRepeatedPlayerResultsAreNotStartedTwice FAILED; check_ui_tokens.sh --strict violations=1 legacy=0 (pre-existing mobile/item_following.xml, zero new) and UI_TOKEN_CONTRAST pairs=38 failures=0; git diff --check clean

Task-Guard: C42-beta-merge-review-dev2
2026-10-07 10:50:03 +08:00
Silent1566
790d678c9e feat(crash): 崩溃页显示最后加载的蜘蛛源,便于定位外部 JAR 缺陷
Verification: leanback+mobile unit tests 7/7 pass; assembleLeanbackArm64_v8aDebug EXIT=0; aapt2 confirms crash_details_spider in 3 locales; dexdump confirms CrashActivity.read() + crash_details_spider ref and JarLoader.record() x1; device 5557 overwrite-install writes cache/spider-last.txt

Task-Guard: SPIDER-CRASH-DIAG
2026-10-07 01:39:37 +08:00
Silent1566
f77c4a0236 fix(tv): keep playback loading visible until new player starts
Verification: :app:compileLeanbackArm64_v8aDebugJavaWithJavac and VideoActivityLayoutTest 154/154 passed; assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL; install -r on 127.0.0.1:5557 succeeded; gradle clean BUILD SUCCESSFUL

Task-Guard: native-enhanced-loading-spinner
2026-10-07 01:36:29 +08:00
Silent1566
6414185aff 修复短剧源规则无法禁用
Verification: 聚焦单元测试 :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.bean.ShortDramaConfigTest 通过;LSP 变更文件无诊断;mobile arm64 debug 包构建成功并覆盖安装到 192.168.50.3:5555;真实 UI 删除 [短] 与 短剧后保存并重新打开仍为空;bash ./gradlew clean 成功

Task-Guard: short-drama-rules-disable
2026-10-06 23:31:31 +08:00
Silent1566
5ff8037e58 docs(tv): 补充浅色模式路径的设备验证结果
TV flavor 只编译深色 token 表,浅色模式是独立的解析路径,单独在设备上验证
「主题模式=浅色」下的外观与语言对话框:10 个文字区域对比度 6.43-10.49:1,
全部达到 WCAG AA 4.5:1。同时记录测量方法(字形取 max 而非百分位,行带由
纵向扫描实测),避免抗锯齿边缘导致的假阴性。

Verification: 设备实测 192.168.50.3:5559 将主题模式改为浅色后重开外观与语言, 10 个文字区域对比度 6.43-10.49:1 全部达标(WCAG AA 4.5:1); 验证后已把主题模式恢复为跟随系统、界面大小恢复为稍大

Task-Guard: TV-APPEARANCE-LIGHTMODE-VERIFY-20261006
2026-10-06 23:22:11 +08:00
Silent1566
009d708422 docs(tv): 记录最终提交后的设备重新验证结果
清理提交 8624f6c0d 后从最终提交重新打包并覆盖安装,确认设备产物 md5 与构建
产物一致,并重跑完整路径。11 个文字区域实测对比度 6.43-10.49:1,全部达到
WCAG AA 4.5:1 阈值(修复前最差 1.00:1)。

Verification: 从最终提交 8624f6c0d 重新打包并覆盖安装: build_arm64_debug_install.sh --flavor leanback --serial 192.168.50.3:5559 BUILD SUCCESSFUL + install Success; 设备 base.apk md5 691103eb120cacdf17404fa711f8ccae 与构建产物一致; 设备复现首页->设置->外观与语言->界面大小, 11 个文字区域对比度 6.43-10.49:1 全部达到 WCAG AA 4.5:1 (修复前最差 1.00:1)

Task-Guard: TV-APPEARANCE-FINAL-VERIFY-20261006
2026-10-06 22:59:06 +08:00
Silent1566
58ebb653ff fix: repair mobile cache management layout
Verification: Baseline mobile layout instrumentation 4/4 failed with module detail width collapsing to 16px at 320dp/2x font; fixed arm64 mobile APK and AndroidTest built successfully; adb install -r coverage on emulator-5560; fixed CacheManagementDialogLayoutTest 4/4 passed across narrow portrait, 1.3x/2x Chinese font, and English landscape; real 720x1600/320dpi/1.3x UIAutomator and screenshots confirmed full-width module details, readable action rows, and complete bottom actions after scroll; emulator display settings restored; LSP diagnostics clean; diff --check clean.

Task-Guard: mobile-cache-layout
2026-10-06 22:57:32 +08:00
Silent1566
9b168a1f38
Merge pull request #411 from Silent1566/dev1
dev1:合并 beta 最新代码(PR#410 真正打通 TV 浅色模式)并复评线路/选集 chip 底色与浅色光影剧幕中间层
2026-10-06 22:50:37 +08:00
Silent1566
92f9b020f2 docs(c41): 订正净差异路径数为实测 6 并区分是否含本任务文档
Verification: 文档数值订正:C41 记录中「合并结果净差异」原写 5 个路径,实测为 6 个(第 6 个是本任务文档 docs/C41-beta-merge-review-dev1-20261006.md 自身);已改为区分「本任务文档创建前 5 个」与「含文档自身 6 个」,与 git diff --name-only origin/beta HEAD 的实测输出一致。仅改动文档,无代码/资源/行为变更,无需重跑构建与单测。

Task-Guard: C41-doc-precision
2026-10-06 22:42:27 +08:00
Silent1566
8d03e86d82 merge: 合并 origin/beta 最新代码(PR#410 真正打通 TV 浅色模式)并复评
合并 origin/beta @ 414babc1b(PR#410)进 dev1:0 冲突,65 路径自动合入(58 个 leanback 布局、
leanback webhtv_tokens.xml 删除、双 flavor ThemeDialog、ThemeController、webhtv_styles.xml、
LeanbackForegroundContrastTest)。26 个 beta 回退提交零复活(143 文件版本 / 1377 行被移除行逐行扫描)、
beta 增量 65 路径零丢失、dev1 未推送的 5c95aa19c 五路径零丢失。

复评 dev1 全部已修改代码(含已提交未推送的 chip 底色 + 浅色剧幕中间层修复)2 轮,未发现需修改缺陷:
- setChipState 已走 setBackgroundTintList 持久通道;全文件剩余 7 处 setBackgroundColor 均非 chip 填充;
- cinemaLightBackdropShade() 与用户所要求的历史实现(7e42054de^)逐字节一致;
- applyLightCinemaCopyPlate / LightCinemaCopyPlateDrawable 全仓零引用,删除的 7 个 graphics import
  在全文件出现 0 次,未使用 import 数与 origin/beta 持平(均为既有 2 项);
- detailInfo 无程序化 padding/background 写入点,删除羽化底板后无残留副作用;
- beta 新引入的 405 处 leanback 前景迁移逐类核对最近绘制背景:379 处 webhtvColorOnWallpaper 压在
  壁纸/玻璃面板/black_20|black_40 形状上、4 处 player_control_muted 与 12 处 white_70/80/90 全在
  播放器 OSD 内部,两种配色下均为深底,未发现白底白字;
- LeanbackForegroundContrastTest 门禁可失败性已反证(注入 ?attr/colorOnSurfaceVariant 后
  lightCardRowsKeepThePaletteFollowingRole 与 constantLightForegroundsStayOnDarkSurfacesInDayMode
  立即失败),探针已完全回滚。

已记录不修改的边界:LeanbackForegroundContrastTest 的常量浅色前景表未登记 beta 新引入的
white_70/80/90,该测试属 C40/dev2 侧契约,扩大其覆盖面超出本任务范围;实测这 12 处当前全部正确。

Verification: 合并 origin/beta @ 414babc1b 进 dev1:0 冲突、0 冲突标记;26 个单亲 revert 提交 × 143 文件版本 × 1377 行被移除行逐行扫描,复活 0 行 0 文件;beta 增量 65 路径逐文件与 414babc1b 字节一致;dev1 未推送 5c95aa19c 的 5 路径逐文件字节一致;净差异 git diff --name-only origin/beta HEAD 仅 5 路径且全为 dev1 自身改动;revert 补丁可应用性与 origin/beta 同集合(HEAD-only=∅、beta-only=∅)。评审 2 轮未发现需修改缺陷:setChipState 已改走 setBackgroundTintList 且全文件剩余 7 处 setBackgroundColor 均非 MaterialButton chip 填充;cinemaLightBackdropShade() 与 7e42054de^ 历史实现逐字节一致;applyLightCinemaCopyPlate/LightCinemaCopyPlateDrawable 全仓零引用、删除的 7 个 graphics import 全文件出现 0 次、未使用 import 数与 origin/beta 持平;detailInfo 无程序化 padding/background 写入点;beta 新引入 379 处 webhtvColorOnWallpaper + 4 处 player_control_muted + 12 处 white_70/80/90 逐类核对最近绘制背景均为深底,未发现白底白字;LeanbackForegroundContrastTest 门禁可失败性已反证(注入 colorOnSurfaceVariant 后 2 个用例失败,探针已回滚且 diff 为空)。验证:双 flavor Java 与 androidTest 编译 BUILD SUCCESSFUL;:app:testMobileArm64_v8aDebugUnitTest 5199 tests 0 failures 0 errors 2 skipped;:app:testLeanbackArm64_v8aDebugUnitTest 4351 tests 0 failures 0 errors 2 skipped;check_ui_tokens.sh --strict violations=1 legacy=0 相对基线零新增(唯一违规为既有 mobile/item_following.xml);git diff --check 退出码 0。设备 192.168.50.3:5555(API 28 leanback arm64,覆盖安装未卸载,经 HomeActivity 正常路径进入详情页):浅色光影剧幕白色中间层可见且深色文字可读;线路 chip 填充实测 (234,240,245) vs 相邻空白 (168,180,193),选中项蓝光线路绿色 tint+描边 (29,143,90);选集第 1 季 chip 填充实测 (248,245,247) vs 相邻空白 (174,184,197)。

Task-Guard: C41-beta-merge-review-dev1
2026-10-06 22:37:06 +08:00
Silent1566
5c95aa19cd fix(detail): 修复线路/选集 chip 无底色并恢复光影剧幕浅色白色中间层
用户报告两个问题:
1. 电视版「线路」「选集」chip 只剩描边、没有底色,压在亮色剧照上看不清;
2. 「光影剧幕」浅色模式要改回早之前有白色中间层的版本,和深色模式差不多。

chip 无底色:setChipState 用 MaterialButton#setBackgroundColor,该方法只 tint 当前那个
MaterialShapeDrawable 实例,不更新 helper 的 backgroundTint 字段;Material 之后在 inset/
圆角/尺寸变化时用该字段重建背景,填充因此丢回全透明。同一页的「第 N 季」按钮走
setBackgroundTintList(写持久字段)所以一直有底色,构成截图里的天然 A/B 对照。
改用 setBackgroundTintList。

浅色剧幕:cinemaBackdropShade() 是夹在剧照与内容之间的全屏渐变中间层,深色一直是黑幕,
浅色那层此前被改成全透明,内容直接浮在剧照原图上。恢复 cinemaLightBackdropShade() 并接回
浅色分支,删除为绕开该缺失而引入的局部羽化底板 applyLightCinemaCopyPlate()/
LightCinemaCopyPlateDrawable。同时 tintTmdbSectionTitles() 原先不分深浅一律强制白字,
在白色中间层上会看不见,改为仅深色剧幕强制白字、浅色剧幕使用调色板深色。

新增契约测试:TmdbDetailChipFillTest 用真实渲染证明两条通道在背景重建后的差异;
TmdbCinemaLightReadabilityTest 从生产源码读取中间层 alpha 做 WCAG 对比度断言。

Verification: TmdbDetailChipFillTest 2/2 passed (real-render proof: tint channel keeps chip fill through Material background rebuild; legacy setBackgroundColor degrades to 0x00000000); TmdbCinemaLightReadabilityTest 4/4 passed (reads shipped middle-layer alphas from production source; WCAG contrast >=4.5:1 for title/body; mutation check weakening alpha 0x99->0x1A drops title contrast to 2.95:1 and fails 3 assertions); TmdbDetailActivityLayoutTest 130/130; TmdbCinemaThemeTest 4/4; :app:testMobileArm64_v8aDebugUnitTest 5196 tests 0 failures; :app:testLeanbackArm64_v8aDebugUnitTest 4348 tests 0 failures; leanback arm64 debug APK built (196M) and disassembly confirms setChipState calls MaterialButton.setBackgroundTintList with no setBackgroundColor, cinemaLightBackdropShade present, LightCinemaCopyPlateDrawable removed. Device verification NOT obtained: dev1 emulator 192.168.50.3:5555 was offline for the whole task (port closed; 5557/5559/5561 belong to other devs and were not taken; no local emulator/AVD and no SSH/docker/virsh path to restart it).

Task-Guard: CINEMA-LIGHT-CHIP-FILL-20261006
2026-10-06 19:27:59 +08:00
Silent1566
414babc1b5
Merge pull request #410 from Silent1566/dev2
merge: 合并 origin/beta 最新代码(PR#407-#409)并复评修复 TV 浅色迁移的日间白底白字回归
2026-10-06 19:27:21 +08:00
Silent1566
990f8039d7 merge: 合并 origin/beta 最新代码(PR#407-#409)并复评修复 TV 浅色迁移的日间白底白字回归
合并 origin/beta @ cb82755ae8(PR#407-#409:主题编辑器安全区、缓存确认框主题化、详情页追更就地取消),
0 冲突;25 个 beta 回退提交零复活、beta 增量零丢失。

评审 dev2 未推送的「真正打通 TV 浅色模式」时发现:405 处 leanback 前景整文件批量迁移未区分该前景
压在什么表面上,其中 4 个布局的行底是固定浅色卡片,迁移后变成白底白字(日间 1.05:1 / 1.16:1 /
1.29:1,低于 WCAG 非文本 3:1 与正文 4.5:1)。已把这 4 个布局的前景回退为 ?attr/colorOnSurface /
?attr/colorOnSurfaceVariant(与 mobile 同名文件语义一致,日间 16.21:1)。

新增 LeanbackForegroundContrastTest:遍历全部 129 个 leanback 布局,按标签树解析每个调色板无关
浅色前景最近的绘制背景并判定其日间取值,防止同类迁移回归;已反证可失败。

Verification: 合并 origin/beta @ cb82755ae8 进 dev2:0 冲突;25 个 beta revert × 171 文件版本 × 1334 行行级比对零复活、PR 方向零回带;beta 增量 13 路径零丢失;dev2 增量 66 路径中 65 路径逐行完整,8 行为有意修正。第 1 轮评审发现 dev2 未推送的 TV 浅色迁移把 4 个布局(adapter_device/adapter_player_osd/adapter_recommendation_feedback/adapter_tmdb_item)的行前景整文件替换为调色板无关浅色常量,而它们的行底是固定浅色卡片,日间实测 1.05:1 / 1.16:1 / 1.29:1(白底白字,低于 WCAG 非文本 3:1 与正文 4.5:1),已回退为 ?attr/colorOnSurface / ?attr/colorOnSurfaceVariant(日间 16.21:1);新增 LeanbackForegroundContrastTest 把该口径固化为遍历全部 129 个 leanback 布局的结构性检查,反证可失败(回退修复后 tests=3 failures=2,命中 6 处并复现实测数字)。第 2/3 轮复评通过。双 flavor Java 与 androidTest 编译 BUILD SUCCESSFUL;全量 JVM 套件相对合并前基线零新增回归(mobile 10→6、leanback 10→7,失败集合 comm -13 双向为空);check_ui_tokens.sh --strict violations=1 相对基线零新增;git diff --check 退出码 0;设备 127.0.0.1:5557(API 28 leanback arm64,覆盖安装未卸载)日间/夜间各一遍通过,屏显设置弹窗与设置页卡片行文字实测深字压浅卡,缓存管理/DoH/AI 服务页渲染与焦点环正常。已记录非本任务引入的既有夜间缺陷(同 4 个布局的固定浅色卡片无 drawable-night 变体,beta 上同样存在,刻意不断言为通过)。

Task-Guard: C40-beta-merge-review-dev2
2026-10-06 11:02:58 +08:00
Silent1566
8624f6c0d7 refactor(tv): 删除 AppearanceRowTheme 中未被引用的三个辅助方法
refresh()/textColors()/color() 在引入后没有任何调用点(实际调用只有
apply()),属于本任务不应保留的推测性接口面。删除它们并移除随之无用的
ColorStateList/ContextCompat 导入;同时把外观行测试中对 apply() 方法体的
边界从 refresh( 改为 shape(,不再依赖被删除的符号。

Verification: AppearanceRowThemeTest 7 tests + ThemeControllerContractTest 8 tests 通过(0 failures/0 errors); leanback+mobile arm64 Debug Java 编译通过

Task-Guard: TV-APPEARANCE-ROWTHEME-CLEANUP-20261006
2026-10-06 08:27:57 +08:00
Silent1566
663ac5e7da fix(tv): 修复外观与语言二级选择弹窗标题不可读
ChoiceDialog 的标题写死 #202124、消息写死 #5F6368,而它的面板是
shape_shell_proxy_dialog (?attr/colorSurfaceContainerHigh),在 TV 上编译为深色
#2A2F34。设备实测(192.168.50.3:5559) 从「外观与语言」行点入的「界面大小」选择
弹窗标题对比度仅 1.00:1,消息 2.23:1,与用户报告的看不清文字属同一路径上的
同类缺陷。

标题改用 ThemeController.current().colorOnSurface()、消息改用
colorOnSurfaceVariant()。选项行未动:它们画在自有的不透明白色卡片上,实测
常态 16.10:1、选中 6.85:1、焦点 4.51:1,均达标。

设备实测:标题对比度 1.00:1 -> 12.06:1。

Verification: AppearanceRowThemeTest 7 tests + ThemeControllerContractTest 8 tests 全部通过(0 failures/0 errors); leanback+mobile arm64 Debug Java 编译通过; scripts/build_arm64_debug_install.sh --flavor leanback --serial 192.168.50.3:5559 覆盖安装成功且设备 base.apk md5 与构建产物一致(59156f9453f3791500224e1490a50a1c); 设备实测 192.168.50.3:5559 首页->设置->外观与语言->界面大小 二级选择弹窗标题对比度由 1.00:1 提升到 12.06:1

Task-Guard: TV-CHOICEDIALOG-TITLE-CONTRAST-20261005
2026-10-06 08:10:22 +08:00
Silent1566
35a97894a2 feat(theme): 真正打通 TV 浅色模式
删除 leanback 恒深色 token 遮蔽表,使 TV 与手机一样按 day/night 解析色板;
Theme.WebHTV.TV 不再硬编码 isLightTheme=false;405 处 leanback 前景从
?attr/colorOnSurface* 迁移到与手机一致的调色板无关角色
(webhtvColorOnWallpaper / player_control_muted / white_80_90_70);
ThemeDialog 预览模式改为按 ThemeController.isNight 打开,不再在 TV 上恒以深色预览。

缺陷证据:改动前 theme_mode=0 与 =1 截图逐字节相同(297612B),即 TV 忽略浅色选择。
改动后同一设置页在系统浅/深下像素差 8.29%,浅色下 primary 由 #A8C7FA 变为 #0B57D0;
显式浅色可覆盖系统深色且跨重启持久。

Verification: 改动前 theme_mode=0 与 =1 截图逐字节相同(297612B, md5 5cfbd8eb0d77)证明缺陷真实;改动后以系统 night 驱动 A/B,no vs yes 各 3 帧帧内稳定且两态 md5 不同(ea1e269b vs 4d11f57e),像素差 8.29%;主色量化:旧构建浅色选中 light#0B57D0=0/dark#A8C7FA=3281(缺陷),新构建浅色 light=3281/dark=0;用户原路径端到端(UI 点 外观与语言→主题模式→浅色)theme_mode 落盘 0,force-stop 重启且系统设为 night=yes 后仍渲染浅色帧 f9ceed4f(与系统浅色下的浅色帧逐字节相同),证明显式浅色覆盖系统深色且跨重启持久;编辑器 uiautomator 显示 浅色 selected=true/深色 selected=false(改动前恒深色);构建 bash scripts/build_arm64_debug_install.sh --flavor leanback --serial 127.0.0.1:5557 BUILD SUCCESSFUL 并 adb install -r 覆盖安装成功(未卸载);:app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.* --tests TvFocusRingContractTest 150 tests / 1 failed,唯一失败 ThemeBinderContractTest.materialAlertDialogsAreBuiltThroughTheThemedBuilder 经证为既有(纯静态扫描,测试与被告发文件 CacheManagementDialog.java 均未被本单元改动,用法由 ae0b26e8ee 2026-09-25 引入晚于测试方法 2c2712e55d 2026-09-24);scripts/check_ui_tokens.sh --strict violations=1 legacy=0,唯一违规为既有 mobile item_following.xml,零新增;65 文件 +448/-517;设备 prefs 已复原(wall=10/wall_type=0/wall_color=-13922613/theme_color=-16732991,删除注入的 theme_mode,chown+restorecon,系统 night 复位 no),临时脚本与截图已清理,gradlew clean 完成并停止守护进程

Task-Guard: TV-LIGHT-THEME-20261005
2026-10-05 22:40:30 +08:00
Silent1566
57362eb4f2 fix(tv): 修复外观与语言对话框行文字不可读
TV 版「外观与语言」的每一行把固定浅色 selector_git_cloud_card(#F8F9FA) 作为
底色,而行文字已跟随 ThemeController.current()。leanback 编译的是深色 token
表,于是 #E2E2E9 / #C4C6D0 被画在 #F8F9FA 上,设备实测对比度仅 1.09:1 / 1.24:1,
用户报告「看不清文字」;同一固定卡片也让自定义主题 profile 无法生效。

新增 AppearanceRowTheme:用当前语义 token 生成行背景 StateListDrawable
(常态 surfaceContainerHigh + outlineVariant,焦点/按下 primaryContainer +
2dp primary 环) 并同时设置两个文字色。焦点行不用 primary 实心填充,因为行文字
在焦点时并不改色,onSurface 压 primary 仅 1.3-2.4:1。两个 flavor 的 addRow()
改调该渲染器,并在 onStart()/onThemeProfileApplied() 重绘已建行。

设备实测(192.168.50.3:5559):常态行 label 10.48:1、value 7.93:1;焦点行
label 7.09:1、value 5.31:1;焦点环对填充 5.32:1、对面板 7.86:1;切换内置主题
松林绿后行填充变为 #242F1F、焦点环变为 #A9D291,证明样式随 profile 变化。

Verification: AppearanceRowThemeTest(5)+ThemeControllerContractTest/ThemeEditorContractTest/ThemeBinderContractTest/ThemeContractTest 通过; leanback+mobile arm64 Debug Java 编译通过; scripts/build_arm64_debug_install.sh --flavor leanback --serial 192.168.50.3:5559 覆盖安装成功; 设备实测 192.168.50.3:5559 外观与语言 5 行对比度由修复前 1.09-1.36:1 提升到 4.08-12.06:1, 焦点环对填充 5.32:1/对面板 7.86:1, 切换到内置主题松林绿后行填充由 #2A2F34 变为 #242F1F 证明样式随 profile 变化

Task-Guard: TV-APPEARANCE-DIALOG-CONTRAST-20261005
2026-10-05 22:30:10 +08:00
Silent1566
cb82755ae8
Merge pull request #409 from Silent1566/dev4
dev4: 合并 beta 最新代码(PR#407 缓存管理 P0-P4 / PR#408 主题编辑安全区)并复评修复缓存确认框主题化门禁回归
2026-10-05 20:20:55 +08:00
Silent1566
3e6b8dc390 merge: 合并 origin/beta 最新代码(PR#408 主题编辑安全区)并解决缓存确认框等价修复冲突
beta 在 PR #409 创建后前进到 c94e5548be1(Merge PR #408 from dev3),使 PR 报
CONFLICTING。本次把该增量合入 dev4:

- 唯一冲突 app/src/main/java/com/fongmi/android/tv/ui/dialog/CacheManagementDialog.java:
  dev3 在 PR #408 里独立做出了与本分支第 1 轮完全相同的修复(三处确认框由裸
  MaterialAlertDialogBuilder 改为 WebHtvAlertDialogBuilder)。双方改动等价,仅本分支
  多两行中文注释,故取 beta 版解决,使该文件与 origin/beta 逐字节一致,不把纯注释
  差异带进 PR。
- 第 1 轮的门禁修复因此被 beta 超集覆盖,最终净差异回到纯 dev4 自身改动 + 本文档。
- beta 本轮还带入 dev3 的主题编辑安全区修复(ThemeDialogLayout.safeArea + 两 flavor
  ThemeDialog 按系统栏 insets 重定尺寸),与 dev4 追更就地取消零文件交集。

复核:零复活 0 行/0 文件(含 PR 方向反向校验);beta 增量 8 路径 0 缺失且逐字节一致;
双 flavor Java/androidTest 编译通过;全量 JVM 相对任务前基线零新增回归(mobile
5191/6、leanback 4346/7,失败集合逐条相同);UI token 门禁零新增违规。

Verification: second merge of origin/beta c94e5548be into dev4 after PR #409 reported CONFLICTING (base advanced via PR #408); 1 conflicted path CacheManagementDialog.java where dev3 independently made the identical MaterialAlertDialogBuilder->WebHtvAlertDialogBuilder fix - resolved by taking the beta version so the file is byte-identical to origin/beta; 0 conflict markers; zero-resurrection rerun 28 reverts/194 file-versions/1773 lines = 0 lines 0 files, PR direction 0/0; beta round-2 delta 8 paths 0 missing lines/files, 8/8 byte-identical to origin/beta; net diff vs origin/beta = dev4-only 4 paths + this doc (first-round CacheManagementDialog fix superseded by beta superset); both flavors Java + androidTest compile BUILD SUCCESSFUL; full JVM mobile 5191/6-fail leanback 4346/7-fail = zero new regressions vs pre-task baseline (5122/6, 4277/7) and identical failure sets; ThemeBinderContractTest 26/0; check_ui_tokens --strict violations 1 (baseline 2, zero new); git diff --check clean

Task-Guard: C39-beta-remerge-dev4-20261005
2026-10-05 20:05:13 +08:00
Silent1566
ae09751eed docs(c39): 订正净差异路径数为最终实测 6 并区分合并时点与 PR 时点
Verification: doc-only correction: net-diff figure updated from the pre-fix merge state (4 paths) to the final PR state (6 paths = 4 dev4 paths + CacheManagementDialog fix + this doc), measured by git diff --name-status origin/beta dev4; markdown clean; no code change

Task-Guard: C39-doc-closure-dev4
2026-10-05 19:45:34 +08:00
Silent1566
b19ccde879 merge: 合并 origin/beta 最新代码(PR#407 / 缓存管理 P0-P4)并复评修复缓存确认框主题化门禁回归
把 origin/beta tip 03c239e32e 合入 dev4(0 冲突),并复评 dev4 已提交未推送的
详情页追更就地取消改动。复评发现合并带入 1 个真实回归并修复:

- CacheManagementDialog 的 3 处清理确认框(轻度/标准、单模块、深度两步)原先直接用
  裸 MaterialAlertDialogBuilder,其 create() 不经过 ThemeController.bindDialog,自定义
  主题下面板与文字仍停留在编译期调色板,使 ThemeBinderContractTest 由合并前 26/26 通过
  变为 26 项 1 失败(纯 origin/beta 树同样失败,CI 不跑该门禁)。现统一改用
  WebHtvAlertDialogBuilder,与 BaseAlertDialog 同源;该构造器在默认主题下走
  baseline.equals(active) 短路,零行为差异。

- 远端已移除(回退)内容零复活:28 个单亲 revert × 194 文件版本 × 1773 行程序化比对
  0 复活行 / 0 复活文件;PR 方向(会不会把已删内容带回 beta)同样 0 行 / 0 文件。
- beta 增量零丢失:76 路径 0 缺失,且 76/76 逐字节等于 origin/beta;dev4 侧 4 路径
  逐字节等于 a455e5a6cce;双方改动路径零交集。
- 净差异恰好 4 个路径,全部为 dev4 自身改动。
- 双 flavor Java/androidTest 编译通过;全量 JVM 相对合并前真实 worktree 基线零新增回归
  (mobile 5185/6、leanback 4340/7,失败集合逐条相同且均为既有 CRLF 环境问题)。
- 设备覆盖安装实测:详情页就地取消用例 OK(1)、追更墓碑/迁移用例 OK(3)、缓存管理面板与
  三条确认框路径正常且焦点落在「取消」。

Verification: merge origin/beta 03c239e32e into dev4 (0 conflicts); zero-resurrection 28 reverts/194 file-versions/1773 lines = 0 lines 0 files (also PR-direction: 0); beta delta 76 paths 0 missing lines/files, 76/76 byte-identical to origin/beta; dev4 4 paths byte-identical to a455e5a6cc; net diff vs beta = exactly 4 dev4-only paths; full JVM mobile 5185/6-fail leanback 4340/7-fail = zero new regressions vs real-worktree baseline a455e5a6cc (5122/6, 4277/7), identical failure sets (pre-existing CRLF-env multiline source assertions, targets byte-identical to both sides); fixed merge-introduced ThemeBinderContractTest regression (CacheManagementDialog 3 alert builders -> WebHtvAlertDialogBuilder; 26/1 -> 26/0), CI does not run that gate; both flavors Java + androidTest compile BUILD SUCCESSFUL; check_ui_tokens --strict violations 1 vs baseline 2 (zero new); git diff --check clean; device 127.0.0.1:5561 over-install (install -r -t, signer 95e4b2e7...7e4d): TmdbDetailFollowingCancelDeviceTest OK(1) 24.6s, TmdbDetailFollowingTombstoneDeviceTest+FollowingMigrationDeviceTest OK(3), cache management panel + ChoiceDialog + light-confirm + deep two-step confirm all render with focus on Cancel, no new crashes

Task-Guard: C39-beta-merge-review-dev4-20261005
2026-10-05 19:43:47 +08:00
Silent1566
c94e5548be
Merge pull request #408 from Silent1566/dev3
merge: 合并 origin/beta 最新代码(PR#407 缓存管理上游同步)并复评修复主题契约与安全区 insets 缺陷
2026-10-05 18:20:45 +08:00
Silent1566
bbaf19b972 merge: 合并 origin/beta 最新代码(PR#407 缓存管理上游同步)并复评修复主题契约与安全区 insets 缺陷
Verification: 合并 origin/beta tip 03c239e32(0 冲突,合并树 5b0ad6bb5 与 merge-tree 预测一致);16 个 beta revert 提交移除内容零复活(54 路径差异 0);4 轮评审修复 2 处真实缺陷:CacheManagementDialog 裸用 MaterialAlertDialogBuilder 违反主题契约门禁(改 WebHtvAlertDialogBuilder)、ThemeDialog 全零 insets 未回退致竖屏导航栏按钮裁 48px(新增 isEmpty + 三级 insets 回退,修复后余量 60px);全量 JVM mobile 5191/leanback 4346 零失败,双 flavor androidTest 编译通过;check_ui_tokens.sh --strict violations=1 既有项零新增;5559 实机 mobile 竖屏+导航栏与 leanback 横屏窗口 [48,48][1872,1032] 与基线一致;设备状态已恢复

Task-Guard: C38-BETA-MERGE-REVIEW-DEV3-20261005
2026-10-05 18:16:21 +08:00
Silent1566
3e4e49331a fix(theme): fit editor dialog inside system bar safe area
Verification: mobile 310/310 与 leanback 191/191 定向测试通过;5559 竖屏导航栏场景保存按钮完整可见且余量83px;leanback窗口/按钮无回归;设备偏好与显示设置已恢复

Task-Guard: THEME-EDITOR-INSET-20261005
2026-10-05 16:46:50 +08:00
Silent1566
03c239e32e
Merge pull request #407 from Silent1566/dev2
dev2: 合并 beta 最新代码(PR#403-#406)并复评修复焦点环注释裸 hex 门禁回归
2026-10-05 13:28:55 +08:00
Silent1566
2162548799 merge: 合并 origin/beta 最新代码(PR#403-#406)并复评修复焦点环注释裸 hex 门禁回归
本次改动:

1. 合并远端 beta 最新代码
   - origin/beta tip 79244f274d(PR#403-#406 之后)合入 dev2,0 冲突(42 路径自动合入);
     合并提交第二父即 79244f274d;
   - 合并树 vs origin/beta 净差异 74 路径,全部为 dev2 自身改动(缓存管理 P0-P4 + 评审文档),
     beta 侧内容零改动。

2. 远端已移除的回退提交零复活
   - beta 上 25 个单亲 revert 提交,按行级程序化比对被移除内容
     (removed(R,f) = lines(R^:f) - lines(R:f)):4215 行扫描 → 0 行复活、0 文件复活;
   - beta 增量零丢失(546 条新增行逐行校验 0 缺失);
   - dev2 本地零丢失(113 路径三分法:dev2-only 丢失 0、beta-only 未应用 0、both 丢失 0)。

3. 复评发现并修复 1 个真实缺陷(UI token 门禁回归)
   - 文件:app/src/main/res/color/focus_ring_error.xml(beta PR#404 新增);
   - 根因:该文件 XML 注释中含裸十六进制 #93000A / #690005 / #FFDAD6(仅用于解释
     "为何配对 on_error_container 而非 on_error"),而 check_ui_tokens.sh 按整行正则匹配裸 hex,
     导致 --strict 由 violations=1 变为 violations=2;
   - 修复:注释改写为文字描述(保留全部对比度数值 1.40:1 / 7.24:1),仅注释变更、零运行时行为影响;
   - 修复后:violations=1(与合并前基线完全一致,同为既有 item_following.xml)、
     hex_colors=0、contrast failures=0 min=4.28 → 零新增违规。

4. 随合并带入的 beta 增量(原样纳入,未改动)
   - TV 焦点环统一为主题色边框环(ThemeController.focusRingColor 成为唯一代码取值源,
     替换 5 处 Java 内写死的 0xFFFFD166;新增 3 个环色选择器与 webhtv_focus_ring_width);
   - 详情页追更墓碑判定修复(isFollowed 排除墓碑行 + 内联播放中就地处取消,不再跳转追更页);
   - 直播线路回退修复(线路播放失败只在本频道线路间轮换,仅节目列表拉取失败才切下一个直播接口,
     并补齐绕圈耗尽保护与 reload 失败反馈)。

Verification: beta tip 79244f274d 已合并(0 冲突,42 路径自动合入);25 个 beta 回退提交的 1146 行被移除内容行级校验零复活(0 复活行/0 复活文件);beta 546 条新增行零丢失;dev2 113 路径零丢失;合并结果 vs origin/beta 净差异 74 路径全部为 dev2 自身改动;第1轮评审发现并修复 focus_ring_error.xml 注释裸 hex 导致 UI token 门禁新增 1 违规(修复后 violations=1 与基线一致、hex_colors=0);第2轮复评:双 flavor Java+androidTest 编译 BUILD SUCCESSFUL,全量 JVM mobile 5185/9 失败、leanback 4340/10 失败,均为合并前基线(11/12)的严格子集、零新增回归且修好 2 个,定向 cache.*+直播回退+3 Layout 契约 0 失败;第3轮复评通过;git diff --check 退出码 0

Task-Guard: C38-beta-merge-review-dev2
2026-10-05 13:22:30 +08:00
Silent1566
a455e5a6cc fix(following): 详情页追更按钮统一为就地取消并立即生效
详情页 TmdbDetailActivity 此前在未内联播放时点「已追更」会跳转 FollowingActivity,
把一步取消变成两步,且在播放中会打断播放。现移除 isInlineFollowingPlaybackSurface
分支,与播放页同源:点「已追更」一律写墓碑、取消 one-shot 检查、立即刷新按钮并提示
「已取消追更」,全程停留在当前页;列表页仍是追更记录的唯一管理入口。

新增设备用例 TmdbDetailFollowingCancelDeviceTest 用 ActivityMonitor 锁定「不跳页 +
立即落库 + 按钮立即刷新」三条契约;FollowingUiSourceTest 增加源码级断言并把多行片段
断言改为 CRLF 归一化,避免 Windows 检出(core.autocrlf=true)下的假失败。

Verification: JVM: FollowingUiSourceTest 15/15 both flavors (mobile/leanback arm64_v8aDebug); device 127.0.0.1:5561 (API28, arm64-v8a): TmdbDetailFollowingCancelDeviceTest OK (1 test, 24.3s) — real click writes tombstone, button returns to 加入追更, FollowingActivity monitor hits=0, activity not finishing; over-install (adb install -r -t) with matching signer 95e4b2e7…7e4d

Task-Guard: FOLLOW-DETAIL-CANCEL
2026-10-05 13:16:44 +08:00
Silent1566
79244f274d
Merge pull request #405 from Silent1566/dev4
dev4: 合并 beta 最新代码(PR#402/#403)并复评直播线路回退修复
2026-10-05 10:57:57 +08:00
Silent1566
8724d665fb
Merge pull request #406 from Silent1566/dev3
dev3: 合并 beta 最新代码(PR#403/#404)并复评修复详情页追更墓碑判定
2026-10-05 10:57:13 +08:00
Silent1566
c6307261f7 merge: 合并上游 webhtv/webhtv Silent1566 缓存管理(P0-P4)并适配本地主题语义 token
Verification: upstream tip d042cd542b (58 commits / 72 paths) fully merged with 0 unresolved conflicts; 5 union conflicts resolved keeping local theme-semantic tokens (no hardcoded @color/white, #FFFFFFFF, #33FFFFFF); cache package + tests + design doc byte-identical to upstream; compileMobileArm64_v8aDebugJavaWithJavac + compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; 13 cache test classes 64 cases 0 failure 0 error; check_ui_tokens.sh --strict shows zero new violations vs pre-merge baseline (violations=1 is pre-existing item_following.xml); leanback emulator-5556 and mobile emulator-5560 real-device verification: dialog renders 15 modules at 90%x90%, all controls focusable, cancel-focused confirm, real module cleanup freed 13.4MB/6 files and 12.5MB/5 files matching cache_mgmt_cleanup_history, settings row auto-refreshed 203.4->190MB and 54->40.8MB via RefreshEvent.CACHE; git diff --check clean; no FATAL EXCEPTION

Task-Guard: C37-upstream-sync-silent1566
2026-10-05 00:18:15 +08:00
Silent1566
b1ce6f88d4 docs(c37): 订正净差异路径数为实测 11 并补 PR 文件集比对证据
Verification: C37 doc-only correction: the committed dev3 net diff vs origin/beta is 11 paths (9 modified + 2 added: TmdbDetailFollowingTombstoneDeviceTest.java and this task doc), not 9; 9 was the pre-commit tracked-only count while the 2 new files were still untracked. Corrected three places in the task record. Verified: git diff --name-only origin/beta HEAD = 11, and gh pr view 406 files set is byte-identical to that list (MATCH). No code, test, or build artifact changed; no re-verification needed.

Task-Guard: C37-beta-merge-review-dev3-20261004-doc
2026-10-05 00:10:00 +08:00
Silent1566
7673a81411 merge: 合并 origin/beta 最新代码(PR#403/#404)并复评修复详情页追更墓碑判定
Verification: C37: merged latest origin/beta (tip 6d152c4a24, PR#403 zero-content + PR#404 24 files) with 0 conflicts and 0 reverted-content revival (fd29d76ee 21/21 paths identical to beta, PR#353 symbols=0, net diff vs beta = 9 task-owned paths). Review round 1 found and fixed a real defect in the unpushed commit aa5345c83: TmdbDetailActivity judged 'already followed' with 'existing != null', but FollowingStore.resolveTmdb deliberately returns the tombstone row (C9 anti-revival guard), so after unfollowing the button stayed on 已追更 and re-following was impossible on inline playback; added isFollowed(item) = item != null && !item.isDeleted() at both decision sites. Evidence: FollowingUiSourceTest 15/15 both flavors; full JVM mobile 5117 tests 0 failures 0 errors, leanback 4273 tests 0 failures 0 errors; assembleMobileArm64_v8aDebug + assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL; device 192.168.50.3:5559 (overwrite install) TmdbDetailFollowingTombstoneDeviceTest + FollowingMigrationDeviceTest = 3 tests OK; guard proven non-vacuous by re-falsification on the final merged tree (pre-fix code FAILS with expected:<加入追更> but was:<已追更>, fixed code OK); check_ui_tokens.sh --strict identical on merged tree and pure origin/beta tree (violations=2, zero new); git diff --check clean

Task-Guard: C37-beta-merge-review-dev3-20261004
2026-10-05 00:07:21 +08:00
Silent1566
03dd0e76b9 merge: 合并 origin/beta 最新代码(PR#402/#403)并修复直播线路回退的缓冲超时、回退耗尽与 reload 反馈缺陷
Verification: LiveActivityLayoutTest 10/10、LiveSourceFallbackPolicyTest 9/9、LiveActivitySourceFallbackSourceTest 5/5(testMobileArm64_v8aDebugUnitTest,共 24 用例 0 failures/0 errors;LiveActivityLayoutTest 同时断言 mobile+leanback 两变体源码);leanback 变体 LiveSourceFallbackPolicyTest 9/9;:app:compileLeanbackArm64_v8aDebugJavaWithJavac 与 :app:compileMobileArm64_v8aDebugJavaWithJavac 均 BUILD SUCCESSFUL;leanback Live* 102 用例 0 失败、mobile Live* 118 用例 0 失败;合并树 vs origin/beta 净差异恰好 7 路径(全为 dev4 自身改动);beta 上 16 个 revert 提交涉及 33 文件,与净差异交集仅 1 文件且被回退符号 updateStatusBarInset 在合并后源码中 ABSENT(零复活);git diff --check 通过

Task-Guard: C37-beta-merge-review-dev4-20261004
2026-10-05 00:06:13 +08:00
Silent1566
6d152c4a24
Merge pull request #404 from Silent1566/dev1
dev1:合并 beta 最新代码(PR#399-#403)并统一电视版焦点高亮为主题色边框环
2026-10-04 23:28:37 +08:00
Silent1566
f1c64ca0c8 merge: 合并 origin/beta 最新代码(PR#399-#403)并复评统一 TV 焦点环为主题色边框环
- 合并 origin/beta tip e72239063(PR#399-#403 共 19 提交,0 冲突),beta 侧删除项零复活
- 复评未推送提交 94b7fba36:修复 TV 详情页写死 #FFD166 与主题焦点槽并存的两种环色
- 新增唯一取值源 ThemeController.focusRingColor(),6 个 Java 消费点与 shape_episode_photo_focused 改走 ?attr/tvFocusRing
- 回退「代码路径动态跟随主题」的尝试:?attr 为编译期静态资源,binder 不改写前景选择器,单独跟随会重新造出两种环色
- 新增设备端契约测试 TvFocusRingResolutionDeviceTest 钉死单一取值源
- 浅色详情页焦点环对比度由 1.14-1.35:1 提升到主题 FOCUS 槽的 5.03-5.99:1

Verification: 合并 origin/beta e72239063(19 提交,0 冲突);JVM 双 flavor 单测 9390 项 0 失败;双 flavor 编译门通过;UI token 门 PASS;leanback arm64 Debug 打包并 install -r 覆盖安装到 192.168.50.3:5555;设备端 Instrumentation TvFocusRingResolutionDeviceTest 3/3;实机冷启动无 FATAL 且 HomeActivity resumed;首页像素采样主题焦点色 #A8C7FA 命中 390px;beta 侧 3 个删除项零复活;净差异 14 路径

Task-Guard: C36-beta-merge-review-dev1
2026-10-04 23:16:48 +08:00
Silent1566
aa5345c835 fix(following): 播放页追更按钮改为就地取消,不再跳转追更页
已追更时再次点击播放页(mobile/leanback VideoActivity 与内联播放中的
TmdbDetailActivity)的追更按钮,此前会跳转到追更列表页打断播放。现在改为
就地取消:写墓碑、取消该条 one-shot 检查、刷新按钮为“加入追更”并提示
“已取消追更”,全程停留在当前播放页。未播放的详情页保持原有
“已追更→打开追更详情”约定。

Verification: FOLLOW-PLAYBACK-CANCEL: playback-page 追更 button is now an in-place toggle. mobile/leanback VideoActivity and inline-playing TmdbDetailActivity write the tombstone, cancel the per-item one-shot check, refresh the button back to 加入追更 and toast 已取消追更 without navigating. Evidence: :app:testMobileArm64_v8aDebugUnitTest and :app:testLeanbackArm64_v8aDebugUnitTest FollowingUiSourceTest = 15 tests / 0 failures each; :app:assembleLeanbackArm64_v8aDebug and :app:assembleMobileArm64_v8aDebug BUILD SUCCESSFUL; scripts/build_arm64_debug_install.sh --flavor mobile --serial 192.168.50.3:5559 installed over the existing package; device tap on the followed show's playback page stayed on VideoActivity (mResumedActivity unchanged) with deleted_at=1791123588777/enabled=0 and the screenshot shows the 已取消追更 toast plus the button reverted to 加入追更; non-playing TmdbDetailActivity still opens FollowingActivity as documented; git diff --check clean

Task-Guard: FOLLOW-PLAYBACK-CANCEL
2026-10-04 22:27:38 +08:00
Silent1566
d042cd542b fix(cache): 评审修复死代码、测试可移植性与繁体翻译缺失
评审分支相对 origin/main 的全部未合入改动后修复三处问题:

1. EpgParser.clearExpiredCache 为死代码:P2 引入 CacheRetentionManager
   后调用者已被替换,删除该无引用方法。
2. CachePathSafetyTest.finalComponentSymlinkIsStillDetected 在
   File.getCanonicalFile() 不解析符号链接的主机 JVM(如 Windows)上
   必然失败:该检测依赖 Linux/Android 的 canonical 语义,现于断言前
   用 Assume 跳过不遵循链接的平台,测试意图在设备语义上保持不变。
3. zh-rTW 缺失 cache_module_diagnostic_logs 与 cache_module_unclassified
   两条字符串,且 legacy 模块文案与 EN/CN 的「遗留路径」不一致:补齐
   翻译并统一术语,三语 cache_* 键 61/61/61 完全对齐。
   另修正 CacheManagementDialog.moduleButton 一处 } 后语句排版。

验证:缓存包独立 JUnit 63/63 通过;双形态
compileMobileArm64_v8aDebugJavaWithJavac 与
compileLeanbackArm64_v8aDebugJavaWithJavac 均 BUILD SUCCESSFUL。

Verification: cache JUnit 63/63 OK (independent runner; CachePathSafetyTest symlink case platform-guarded); :app:compileMobileArm64_v8aDebugJavaWithJavac + :app:compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; zh-rTW strings 61 keys aligned with EN/CN

Task-Guard: cache-mgmt-review-fix
2026-10-04 20:56:47 +08:00
Silent1566
e72239063b
Merge pull request #403 from Silent1566/dev3
dev3: 合并 beta 最新代码(PR#399-#402)并复评手机版深色表面文字对比度修复
2026-10-04 20:20:27 +08:00
Silent1566
96531595dc fix(live): 直播线路失效优先在本频道线路间轮换,仅节目列表拉取失败才切换下一个直播接口
Verification: LiveSourceFallbackPolicyTest 9/9 与 LiveActivityLayoutTest 9/9(testMobileArm64_v8aDebugUnitTest,0 failures/errors,断言覆盖 mobile+leanback 两变体源码)与 Leanback 变体 LiveSourceFallbackPolicyTest 9/9(testLeanbackArm64_v8aDebugUnitTest)均通过;两变体 Java 编译成功;:app:assembleLeanbackArm64_v8aDebug 构建成功;git diff --check 通过;设备 install -r 因全部在线设备为异签名旧包且按约定不卸载而放弃,不作为回归声明

Task-Guard: live-line-fallback-first
2026-10-04 19:57:52 +08:00
Silent1566
08d6cca51e docs(c36): 订正 dev3 净差异路径数为实测 23 并补程序化比对证据
Verification: 文档数值订正:C36 评审文档中 dev3 相对 origin/beta 净差异路径数由笔误 22 改为实测 23(3 个 Java 对话框 + shape_mpv_action_menu.xml + selector_tmdb_flag_item_dark.xml + 12 个手机版 dialog_* 布局 + docs/ui-token-allowlist.txt + 2 个测试 + 3 个文档),并补充 gh pr view 403 --json files 与 git diff --name-only origin/beta HEAD 程序化比对 MATCH 的证据;同步修正 beta 增量提交数(9→11)与主题面隔离表述。校验:git diff --name-only origin/beta HEAD | wc -l = 23;PR#403 files 集合与之一致;纯文档改动无代码影响。

Task-Guard: C36-beta-merge-review-dev3-20261004-doc
2026-10-04 19:56:54 +08:00
Silent1566
06f9af4037 merge: 合并 origin/beta 最新代码(PR#399-#402)并复评 dev3 未推送的主题对比度改动
Verification: 合并 origin/beta faac13dd64b(PR#399-#402,32 文件 0 冲突;合并树重放 git write-tree=abc7eae3c 与提交树逐字节一致);两轮复评通过:dev3 未推送两提交(手机版深色面板文字对比度 + Java 对话框语义前景)无缺陷,dev3 净差异 vs beta 恰好 22 路径、与 beta 增量路径交集 0;被回退内容零复活(mobile/res/values/webhtv_tokens.xml 在 beta 与合并树均不存在,19 个还原为上游字面量的 drawable 抽检仍为裸色,fd29d76ee/5682f2b05 仍在祖先链);beta 横幅删除正确带入且无存活引用;12 个深色面板布局前景属性程序化扫描零遗漏,最不利合成底色对比度 6.34:1 / 4.76:1 均达标;check_ui_tokens.sh --strict 三树对照(纯 beta / 合并前 / 合并后)输出完全一致 violations=1 为既有 item_following.xml,零新增违规;JVM 套件 leanback 176 + mobile 5117 仅 1 项既有 flaky(MultiThreadProxyRouteTest,隔离重跑 4 次全过、文件与 beta 逐字节一致)其余 0 失败 0 错误;mobile arm64 debug 覆盖安装到 192.168.50.3:5559 成功,冷启动 HomeActivity 正常 resume,FATAL EXCEPTION/ANR=0

Task-Guard: C36-beta-merge-review-dev3-20261004
2026-10-04 19:54:16 +08:00
Silent1566
94b7fba363 统一电视版焦点高亮为主题色边框环
Verification: 契约测试 TvFocusRingContractTest 7/7 + NativeEnhancedPlaybackStyleFocusTest 14/14 + FollowingUiSourceTest 15/15 共36项全绿;leanback arm64 Debug 构建并覆盖安装到 dev1 192.168.50.3:5555;实机像素验证焦点环随焦点出现/消失,环/填充对比度 检查更新6.09:1、全部已读6.09:1、取消追更6.41:1(修正前为1.33:1/1.40:1);mobile arm64 编译门通过

Task-Guard: tv-focus-ring-unified
2026-10-04 17:39:29 +08:00
Silent1566
c39e5748a5 fix(ui): Java 构建对话框的前景色改用语义角色,修复夜间深底深字
DebugLogDialog / MpvConfigDialog / PlaybackPerformanceDialog 把前景写死为
Material 灰蓝(#5F6368/#202124/#174EA6 等),但容器 shape_shell_proxy_dialog 的填充是
?attr/colorSurfaceContainerHigh —— 跟随调色板(日间#E7E8EF / 夜间#2A2F34)。
写死的前景只能在其中一种模式下正确:夜间实测仅 1.19-2.23:1。

改为统一从 ThemeController.current() 取语义角色(onSurface / onSurfaceVariant /
primary / primaryContainer / outline / error / surfaceContainerLowest),
使面板与前景成对跟随调色板:日间浅底深字、夜间深底浅字。
与既有范式一致(LightDialog 早已如此,仓库内 13 处先例)。

连带修复 shape_mpv_action_menu.xml:该弹出菜单原本写死白底,
若只改文字会在夜间变成近黑底+近黑字。同步迁移为语义角色,
并从 ui-token-allowlist.txt 移除其豁免条目(文件已无裸色)。

新增 DialogForegroundTokenTest 三项守卫,防止再次写死前景。

Task-Guard: dialog-foreground-semantic-tokens

Verification: DialogForegroundTokenTest 3项全过; theme+setting+ui.dialog+ui.style 322项0失败; check_ui_tokens.sh --strict 相对HEAD无新增违规(allowlist回到191); mobile arm64 debug 覆盖安装到192.168.50.3:5559成功; 实机FATAL EXCEPTION=0; 日间播放性能对话框 标题16.35:1/Tab7.20-8.70:1/行16.35:1; 夜间 标题16.54:1/Tab11.30-11.32:1/行16.54:1(修复前夜间1.19-2.23:1); 守卫逻辑在HEAD源码上全部报违规、修复后全通过; 设备已恢复日间模式并force-stop

Task-Guard: dialog-foreground-semantic-tokens
2026-10-04 16:59:51 +08:00
Silent1566
ef8bed60b5 fix(ui): 修复手机版默认主题下深色表面文字看不清
0af2340d4 把 12 个播放器玻璃子面板的 @color/white* 前景改成 ?attr/colorOnSurface,
但手机版日间调色板把该角色解析为近黑 #1A1C1E,而面板是恒定深色渐变
(#E62F315E→#CC303463)。leanback 同样写法没问题只因其 token 表在所有档位都是深色。
e1ea7ab34 同样把 TMDB 深色页线路芯片的实色换成跟随调色板的浅色表面,
变成浅底+近白字,即用户红圈处。

修复改用三张 token 表取值完全相同的既有语义角色
(webhtvColorOnWallpaper / webhtv_color_player_control_muted / webhtv_color_overlay_light),
不新增资源、不扩大范围、不触发豁免;TMDB 深色页芯片恢复恒定半透明填充。

新增 3 项守卫测试防回归。

Task-Guard: mobile-dark-surface-text-contrast

Verification: ThemeBaseWiringTest 13项全过(含3项新守卫); 相邻套件(theme/ui.style/ui.dialog/VideoActivityLayoutTest/EpisodeAdapterTest)480项0失败; check_ui_tokens.sh --stage B violations=0 legacy=0; --strict 相对HEAD无新增违规; mobile arm64 debug 覆盖安装到192.168.50.3:5559成功; 实机日间模式 dialog_timer 面板实测5.85-7.41:1(修复前反事实1.23-1.50:1)

Task-Guard: mobile-dark-surface-text-contrast
2026-10-04 16:18:42 +08:00
Silent1566
faac13dd64
Merge pull request #402 from Silent1566/dev4
dev4: 合并 beta 最新代码(PR#399-#401)并复评 TV 横幅按密度输出(含 CRLF 断言归一化)
2026-10-04 14:48:57 +08:00
Silent1566
3400cd7b0a merge: 合并 origin/beta 最新代码(PR#399-#401)并复评 dev4 未推送的 TV 横幅按密度输出改动(含 CRLF 断言归一化修复)
Verification: 双 flavor 编译 BUILD SUCCESSFUL;leanback 全量 4259 用例仅 11 失败、mobile 全量 5111 用例仅 10 失败,且失败集合与纯 origin/beta 参照工作树(同机同配置独立构建)完全一致,零个由本任务引入;净差异内测试全通过:AppBrandingContractTest 13/13、EpisodeVersionIdentityTest 17/17、HistoryPlaybackTest 63/63、TmdbConfigCustomRouteModeTest 5/5、following.* 34/34、TmdbSourceDialogInflationContractTest 修复 CRLF 归一化后双 flavor 通过;横幅 PNG 5 档实测 16:9 且字形真实渲染;合并树 vs origin/beta 净差异恰为 255059d48b 文件集,132 个被回退文件零差异

Task-Guard: C35-beta-merge-review-dev4-20261004
2026-10-04 14:38:50 +08:00
Silent1566
255059d48b fix(leanback): TV 横幅改为按密度输出 16:9 位图,移除抢占密度的 anydpi-v26 自适应图标
Verification: bash ./gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.setting.AppBrandingContractTest => 13 tests, 0 failures, 0 errors (新增 tvBannerIsProvidedPerDensityAtFullResolution / tvBannerUsesBitmapDensitiesNotAdaptiveIcon 均执行); 重跑 scripts/gen_app_icon.py 后 5 个密度 ic_banner.png md5 与改动前一致(生成幂等),banner 实测尺寸 160x90/240x135/320x180/480x270/640x360 全为 16:9

Task-Guard: tv-banner-density
2026-10-04 02:10:38 +08:00
Silent1566
13f07c8cba
Merge pull request #401 from Silent1566/dev3
TMDB 线路自定义支持(API/图片线路下拉自定义选项 + D-pad 交互)并合并 beta 最新代码
2026-10-03 17:30:14 +08:00
Silent1566
8847705081 merge: 合并 origin/beta 最新代码(PR#395-#398)并复评 dev3 未推送的 TMDB 线路自定义改动
Verification: 合并 origin/beta b056747894cff64fb34062d4f4a1ac416f4206a0(PR#395-#398,16 文件 0 冲突);两轮复评通过:beta 集多版本消歧与 dev3 TMDB 线路自定义 4 提交均无问题,含音频队列/跨线路续播回归核对;被回退内容(fd29d76ee 21 文件与 5682f2b05)未带回;合并树定向单测 Leanback 90 项 + Mobile 258 项 0 失败(含 EpisodeVersionIdentityTest/TmdbConfigCustomRouteModeTest/TmdbSourceDialogInflationContractTest)

Task-Guard: C34-beta-merge-review-dev3-20261003
2026-10-03 17:27:47 +08:00
Silent1566
e224f1b8cd
Merge pull request #400 from Silent1566/dev1
dev1: 合并 beta 最新代码(集多版本消歧修复)并复评启动配置偏好选择修复
2026-10-03 17:16:25 +08:00
Silent1566
f22e24f5f2 merge: 合并 origin/beta 最新代码(PR#397/#398 集多版本消歧修复)并复评启动配置偏好选择修复(含 C34 复评)
Verification: 定向单测 EpisodeVersionIdentityTest+HistoryPlaybackTest(leanback)与 VideoActivityLayoutTest+VideoActivityHistoryTitleTest(mobile)全通过;bean/db 契约 leanback 266+mobile 262 项 0 失败;双flavor arm64 Java编译通过;mobile arm64 debug 覆盖安装 192.168.50.3:5555 冷启动冒烟:猫源就绪后 vod-config-loaded success=1 cost=5021ms,无'配置取得失败'无FATAL,进程存活;三层回退核对(文件级diff唯一差异Config.java/5682f2b05非祖先/revert删除文件反查全部absent)通过

Task-Guard: C34-beta-merge-review-dev1
2026-10-03 17:10:13 +08:00
Silent1566
490218a66e
Merge pull request #399 from Silent1566/dev4
追更取消跨设备同步(墓碑方案)+ 合并 beta 最新代码并复评
2026-10-03 16:48:54 +08:00
Silent1566
62527bec42 merge: 合并 origin/beta 最新代码(PR#394-#397)并复评 dev4 未推送的追更墓碑同步
Verification: merge clean (563 files, beta 8e4dac7606a..59d481e7264); revert fd29d76ee7 的 21 个文件与 origin/beta 逐字节一致(不复活); C9 tombstone 复评通过(迁移SQL与schema 3.json 一致); JVM following 34/34 leanback+34/34 mobile; 双flavor编译通过; 设备 following androidTest 17例16过(唯一失败 FollowingActivityDeviceTest 为历史遗留,纯beta参照worktree复现实证无关); FollowingUiSourceTest 2 失败为 CRLF 环境脆弱断言(三方基线均失败,探针实证),非本任务引入

Task-Guard: beta-merge-review-20261003
2026-10-03 16:44:25 +08:00
Silent1566
e2304fc1d6 fix(task-guard): 分批执行 staged 空白校验,规避 Windows argv 上限
大合并场景下 check_staged_whitespace 一次性把全部 staged 路径传给
git diff --cached --check,563 文件约 42KB 参数超过 Windows
CreateProcess 32KB 上限,报 'Argument list too long' 使 guard finish
误判为空白校验失败。改为每批 100 路径分批校验,语义不变。

验证: bash -n 通过;对 564 staged 文件实际运行 git diff --cached --check
exit 0 零输出(无真实空白违规)。

Task-Guard: MAINTENANCE-TASK-GUARD-ARGV-20261003
2026-10-03 16:39:42 +08:00
Silent1566
f2cd006b01 fix(config): 启动时按 config_<type> 偏好选上次使用的接口,不再盲选 time 最新行
用户反馈:最新版点播 http 开头接口启动显示配置失败,重装或切换其他接口后恢复。

根因:Config.vod()/live()/wall() 用 ORDER BY time DESC 取最新行作为启动配置。
一旦某行(典型是猫源)成功加载过一次,其 time 永远最新;此后每次冷启动都优先
重拉它——猫源需重启 :node 子进程,就绪探测超时后报'配置取得失败',而用户实际
在用的 http 接口根本没被加载。失败不更新 time,无法自愈:切换接口(bump 另一行)
或重装(清表)才能逃出,与反馈完全吻合。

修复:update() 本就持久化 config_<type>=url 偏好;启动选择先按该偏好精确找行,
缺失或行已删除时回退原 time DESC 行为。设备端构造'猫源行time最新+偏好指向http接口'
场景验证:修复后冷启动直接加载 http 接口成功,无 cat-source 分支与超时。

Verification: 双flavor arm64 Java编译通过(compileMobile/LeanbackArm64_v8aDebugJavaWithJavac);定向单测(*Backup*/*Config*相关)0失败;测试包mobile arm64 debug经scripts/build_arm64_debug_install.sh覆盖安装至192.168.50.3:5555;构造猫源行time最新+config_0偏好指向http接口的复现场景后冷启动两次:诊断日志csp-warmup schedule reason=vod-config-loaded config=4(buye-0 http接口)加载成功,无cat-source分支、无3分钟就绪超时、无'配置取得失败';复现前同状态日志显示启动进cat-source分支253457ms后报配置取得失败\n猫源启动失败;设备DB已恢复原状并复验通过

Task-Guard: fix-startup-config-selection
2026-10-03 15:37:18 +08:00
Silent1566
bfbd26bb98 修复 TMDB 线路 Picker 电视端 D-pad 焦点无法进入列表项导致选自定义无效
Verification: 修复 TMDB 线路 Picker 电视端 D-pad 无法选中列表项的根因: AlertDialog 单选列表弹出后焦点停在 listview 容器,DOWN 直接跳到取消按钮,用户选自定义实际取消了 Picker,配置保持 itv666. 修复为 show 后 setSelection(checked)+listview.requestFocus. 纯 D-pad 实测: Picker 打开焦点在 checked 项,DOWN 一次选中自定义,CENTER 确认后显示自定义输入框并聚焦,保存重开回显自定义+值,恢复自动配置也通过 D-pad 完成. 单测(leanback+mobile)通过

Task-Guard: tmdb-picker-dpad-focus
2026-10-03 14:09:15 +08:00
Silent1566
b056747894
Merge pull request #398 from Silent1566/dev2
dev2: 合并 beta 最新代码并修复集多版本消歧误伤跨线路续播
2026-10-03 13:29:55 +08:00
Silent1566
5e6af9e540 fix(tv): 修复集多版本消歧误伤跨线路续播并合并 origin/beta 复评
评审发现 dd37a13fc5 的『季号已知即按 URL 消歧』误伤跨线路/跨源续播
与源站刷新契约(换线路后 URL 必然不同 → 被判换集 → 进度从 0 开始、
选中高亮错回第一集)。改为按『历史 URL 能否定位到当前线路条目』分层:

- Episode.matchesPlayback(Episode) 恢复纯集号容错语义
- 新增 matchesPlayback(Episode, boolean versionAware):同线路内才启用
  版本消歧,保住『点第二版本不再被当作第一版本』的原始修复
- Flag.containsEpisodeUrl:消歧启用判据(与 find 的 URL 优先定位同构,
  含 TMDB 位置冲突防护)
- leanback/mobile 调用方按判据传入 versionAware;leanback
  getSelectedEpisodePosition 增加 URL 优先命中(多版本选中由 URL 锁定)
- TmdbDetailActivity.isHistoryEpisode 同样分层,炫彩内联换线路不再误停

Verification: 合并 b6877389da(dd37a13fc5 × bf9e6bcb19)无冲突;净差异与 dd37a13fc5 逐字节一致(结构性排除被回退内容夹带,5682f2b054/fd29d76ee7 负向核对通过);四轮评审发现并修复 dd37a13fc5 版本消歧误伤跨线路续播回归(versionAware 分层);定向单测 EpisodeVersionIdentityTest 17 + HistoryPlaybackTest 63、源码断言 VideoActivityLayoutTest 153 + VideoActivityHistoryTitleTest 4 全通过;bean/history/playback 契约 404 项 0 失败;修复后定向 248 通过(仅 ReaderPlaybackRoutingSourceTest 2 失败为纯合并树上同样复现的 beta 预存失败,与任务无关)

Task-Guard: C33-beta-merge-review-dev2
2026-10-03 13:11:04 +08:00
Silent1566
9d0deec460 修复 TMDB 线路选官方直连保存后重开误显示为自定义
Verification: 修复 isCustomDisplay 误判: 旧逻辑对任何非自定义文本都返回 true,导致选官方直连保存后重开误显示自定义. 改为 display 未命中任何内置选项标签才判为自定义. 模拟器实测: 选官方图片(直连)保存重开正确回显'官方图片(直连)', 自定义 URL 保存重开回显'自定义'+值, 单测 leanback+mobile 通过

Task-Guard: tmdb-route-custom-fix3
2026-10-03 11:56:58 +08:00
Silent1566
699635fca7 fix(following): propagate unfollow across one-key sync via tombstones
取消追更原为物理删除, 备份快照无法携带删除意图, TV 端 union 合并永远保留该行,
导致手机取消追更后 TV 仍需手动再删一次。

改为墓碑表方案:
- Following 增加 deleted_at 列 (v2->v3 全量重建迁移, v2 时代 enabled=0 行一并转墓碑)
- 取消追更改写墓碑 (markDeleted), 备份捕获/合并均含墓碑行, 删除意图随一键同步传播
- 合并策略墓碑分支: 双墓碑取较新; 取消后重追 (createdAt>deletedAt) 复活胜出; 旧版快照不复活
- 活行查询 (list/find/findDue/unreadCount) 过滤墓碑; 重追经 saveNew 复活并重置 createdAt
- 防复活: TMDB 身份迁移遇墓碑目标直接返回; 订阅导入跳过墓碑候选

验证: JVM Following 套件 34/34; 设备测试 16/17 (唯一失败为 HEAD 历史遗留,
stash 实证与本任务无关); 迁移/复活/防复活设备用例全绿 (emulator-5561 覆盖安装)。

Verification: JVM 34/34 (FollowingMergePolicyTest 7 用例全绿含 5 新墓碑用例); 设备 16/17 (唯一失败 FollowingActivityDeviceTest 为 HEAD 历史遗留, stash 实证无关); 迁移 v1->v3 与 v2->v3 数据保留+enabled=0转墓碑验证通过; 重追复活/订阅导入防复活/旧快照合并防复活设备用例通过

Task-Guard: C9-following-tombstone-sync
2026-10-03 11:55:48 +08:00
Silent1566
024c13b5f9 TMDB 自定义线路输入框移至对应线路下方; 线路 Picker 改为确认键弹出(不再聚焦自动弹出)
Verification: Leanback 模拟器(5559)实测: 1)apiCustomInput 紧跟 apiHostInput 正下方(y:665->792), imageCustomInput 紧跟 imageHostInput 下方, 顺序正确; 2)焦点移到线路下拉框不再自动弹 Picker, 按 DPAD_CENTER/点击才弹出, picker 含自定义选项且选中后输入框显示并自动聚焦, D-pad 焦点链 apiHost<->apiCustom<->imageHost<->imageCustom 全部可达; 单测 leanback+mobile 通过

Task-Guard: tmdb-route-custom-fix2
2026-10-03 11:14:34 +08:00
Silent1566
59d481e726
Merge pull request #397 from Silent1566/dev1
merge: 合并 beta 最新代码(PR#396)并修复影视原生模式播放页简介按钮不显示
2026-10-03 09:44:18 +08:00
Silent1566
41ab47182c TMDB API/图片线路支持下拉自定义选项与自定义输入框(电视版 D-pad 交互)
Verification: Leanback 模拟器(192.168.50.3:5559)实测: API/图片线路 Picker 含自定义选项,选中后自定义输入框显示并自动聚焦,可输入 URL,D-pad 焦点链(apiHostInput<->apiCustomInput<->imageHostInput<->imageCustomInput)可达,保存后重开弹窗回显自定义模式与值,切回内置线路后输入框隐藏;修复重开弹窗 NPE 崩溃;单测 TmdbSourceDialogInflationContractTest(leanback+mobile) 通过

Task-Guard: tmdb-route-custom
2026-10-03 09:38:45 +08:00
Silent1566
a24c85f548 Merge remote-tracking branch 'origin/beta' into dev1 2026-10-03 09:27:14 +08:00
Silent1566
dfd4e5b3f3 fix(tv): 修复影视原生模式播放页简介按钮不显示
934e831de 在隐藏 leanback 追更(following)按钮时,第一个 diff hunk 误命中了
同区块的简介(content)按钮,给它加上了 android:visibility="gone"。影视原生
(DIRECT)模式不运行 TMDB 富集路径(applyTmdbDetailFields),没有任何运行时代码
会把这个按钮恢复可见,导致简介入口永久消失。

移除该误加的 visibility=gone 恢复默认可见;新增
VideoActivityDetailShellSourceTest.directPlaybackKeepsSynopsisButtonVisibleByDefault
回归测试锁定契约。设备实测:简介按钮可见且弹窗正常展示简介。

Task-Guard: fix-direct-content-button

Verification: leanback 全量单测 4226 项 0 失败;设备 192.168.50.3:5555 实测影视原生模式播放页简介按钮可见(text=简介, bounds[896,420][1088,495])且点击后 ContentDialog 弹出完整简介文本

Task-Guard: fix-direct-content-button
2026-10-03 08:18:26 +08:00
Silent1566
b6877389da Merge remote-tracking branch 'origin/beta' into dev2 2026-10-02 23:26:59 +08:00
Silent1566
76a923fcce
Merge pull request #396 from Silent1566/dev1
fix(detail): 详情直放模式跟随光影剧幕主题
2026-10-02 23:05:28 +08:00
Silent1566
6288fd4bd1 fix(detail): 详情直放模式跟随光影剧幕主题
d964faca5 修复炫彩详情的 cinema 主题委托时遗漏了详情直放模式:
PlayerDetailController 未覆写 isCinemaStyle(),导致设置选光影剧幕后
详情直放页仍固定使用清透流彩模板。补上 host.isCinemaStyle() 委托,
并新增 DetailModeControllerTest 测试锁定该行为。

Verification: DetailModeControllerTest 13/13 通过(新增 playerDetailController_followsSelectedCinemaTheme);leanback arm64 debug 覆盖安装到 192.168.50.3:5555;运行时验证:详情直放(detail_open_mode=4)+光影剧幕(detail_theme_mode=1)下 heroSpacer=56px/28dp、title bounds [68,110][924,227]、contentPanel 全宽 radius 0(cinema 模板特征);切清透流彩(detail_theme_mode=0)后 heroSpacer=204px/102dp(profile 模板特征),两主题在直放模式下可正确区分;无崩溃;模拟器配置已恢复原始值

Task-Guard: FIX-DIRECT-CINEMA-THEME
2026-10-02 21:23:17 +08:00
Silent1566
dd37a13fc5 fix(tv): 同一 TMDB 集多版本按季号置信度消歧
Verification: verified: ./gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.bean.HistoryPlaybackTest --tests com.fongmi.android.tv.bean.EpisodeVersionIdentityTest => BUILD SUCCESSFUL, 77 tests (15+62), 0 failures

Task-Guard: fix-episode-version-identity
2026-10-02 20:28:02 +08:00
Silent1566
bf9e6bcb19
Merge pull request #382 from Silent1566/dev3
merge: 合并 origin/beta 最新代码(PR#388–#394)并复评 dev3 全部未推送改动
2026-10-02 17:34:37 +08:00
Silent1566
4d434d2c0a
Merge pull request #395 from Silent1566/dev1
fix(tv): 恢复原生增强播放页当前线路绿色高亮描边
2026-10-02 17:32:02 +08:00
Silent1566
125837036d Merge remote-tracking branch 'origin/beta' into dev3 2026-10-02 16:34:11 +08:00
Silent1566
c92ada1015 fix(tv): 恢复原生增强播放页当前线路绿色高亮描边
Verification: leanback FlagAdapter 绑定 state_activated(绿色 2dp tvCurrentRing 描边)恢复当前播放线路高亮

Task-Guard: flag-current-highlight
2026-10-02 16:20:09 +08:00
Silent1566
f33710935c
Merge pull request #394 from Silent1566/dev1
合并 beta 最新代码并交付猫源启动超时修复(dev1)
2026-10-02 15:25:36 +08:00
Silent1566
279c16c125 merge: 增量合并 origin/beta PR#393(dev4 播放器按钮接线与默认隐藏)并复评
创建 PR 时发现远端 beta 前进至 8e4dac760(PR #393 dev4 播放器多线程/硬解
按钮接线 + 多线程按钮默认隐藏种子逻辑),增量合入 dev1 无冲突;第 3 轮评审
通过:mobile/leanback 两 flavor 按钮方法名各自匹配无错绑,HIDDEN_SEEDED
一次性种子注入与 reset 清除核对一致;净差异仍仅猫源链路 7 文件,被剔除提交
未带回。

Verification: 增量合并树 Leanback 定向单测 73 项 0 失败(含 MultiThreadProxyPlayerUiSourceTest 5 项 0 失败)

Task-Guard: C31-beta-merge-review-dev1-20261001-b

Verification: 增量合并树 Leanback 定向单测 73 项 0 失败(含 MultiThreadProxyPlayerUiSourceTest 5 项);PR#393 播放器按钮接线/默认隐藏种子逻辑核对通过;5682f2b05 仍非祖先

Task-Guard: C31-beta-merge-review-dev1-20261001-b
2026-10-02 15:17:37 +08:00
Silent1566
1fb2b558ec merge: 合并 origin/beta 最新代码(PR#391 菜单扩容 / PR#392 站点批量焦点)并复评猫源启动超时修复(含 import 整理)
合并 origin/beta 45d340418(PR #391 首页菜单追更/站点注入 + PR #392 站点批量
动作 D-pad 焦点可达修复)无冲突;两轮评审 dev1 全部未推送改动:65d709a47
猫源启动超时修复整体通过,仅修复 NodeService import 顺序错乱与 4 处 TimeUnit
全限定名冗余(无行为变化);PR #391 菜单扩容 12 项与 PR #392 enabled/clickable
语义解耦契约核对通过;无任何远端已回退内容被顺带提交(5682f2b05/59f438285/
c900dac20 均非祖先)。

Verification: 合并树 Leanback 定向单测 98 项 0 失败;Mobile 定向单测 61 项 0 失败;5555 设备覆盖安装冷启动冒烟无 FATAL/ANR

Task-Guard: C31-beta-merge-review-dev1-20261001

Verification: 合并树 Leanback 定向单测 98 项 0 失败(含 SiteDialogActionFocusTest 3 项) + Mobile 定向单测 61 项 0 失败 + 5555 设备覆盖安装冷启动冒烟无 FATAL/ANR;两轮评审通过(含 PR#391/PR#392 契约核对);被剔除提交(5682f2b05/59f438285/c900dac20)均非祖先未带回

Task-Guard: C31-beta-merge-review-dev1-20261001
2026-10-02 14:41:56 +08:00
Silent1566
8e4dac7606
Merge pull request #393 from Silent1566/dev4
merge: 合并 beta 最新代码(PR#385–#392)并复评 dev4 播放器按钮修复
2026-10-02 14:41:15 +08:00
Silent1566
475abef28c merge: 合并 beta 增量 PR#392(站点批量动作焦点修复)并复评
Verification: r2 merge clean (3 files, PR#392 SiteDialog focus fix); SiteDialogActionFocusTest 3/3 + MultiThreadProxyPlayerUiSourceTest 5/3 pass on merged tree; no revert commits in increment window; r1 evidence carried: mobile 449/0, dual-flavor compile OK

Task-Guard: beta-merge-review-r2-20261002
2026-10-02 14:28:17 +08:00
Silent1566
56f479b1d2 merge: 合并 origin/beta 最新代码(PR#385-#391)并复评 dev4 未推送的播放器按钮修复
Verification: mobile 449 tests/0 failures (MultiThreadProxyPlayerUiSourceTest 5/5), dual-flavor javac compile BUILD SUCCESSFUL, leanback 4077 tests 11 failed all pre-existing CRLF-checkout (incl. beta-new NativeEnhancedPlaybackStyleFocusTest, LF-literal vs CRLF worktree, beta LF env 9/9 documented), zero revert commits in merge window, staged tree f5c67c7144 verified

Task-Guard: beta-merge-review-20261001
2026-10-02 13:58:01 +08:00
Silent1566
45d340418a
Merge pull request #392 from Silent1566/dev2
dev2: 合并 beta 最新代码并修复站点批量动作遥控焦点可达
2026-10-02 13:26:19 +08:00
Silent1566
410b76a4ca docs: 记录 C31 dev2 合并 beta 与站点批量动作焦点修复复评
Verification: 合并 origin/beta 8ba23ac144 无冲突(HEAD..origin/beta=0;带入 diff 与 beta 侧逐字节一致;回退坐标 5682f2b054 非祖先、被删文件仍不存在、净差异仅 2 文件全为批量动作焦点修复);四轮评审 2bf29bc4c8 无需修复;合并树定向单测 SiteDialogActionFocusTest 3 用例 + HomeMenuDialogSourceTest 5 用例 BUILD SUCCESSFUL 0 失败;git diff --check 通过

Task-Guard: C31-beta-merge-review-dev2
2026-10-02 13:10:00 +08:00
Silent1566
01f14fc8ea merge: 合并 origin/beta 最新代码(PR#389/#390/#391)并复评 dev3 全部未推送改动 2026-10-01 23:55:51 +08:00
Silent1566
1b04e7a33b Merge remote-tracking branch 'origin/beta' into dev2 2026-10-01 23:27:14 +08:00
Silent1566
f912cd41ee feat(theme): add draft inline theme editor
Verification: 主题定向测试:mobile 137、leanback 139,零失败/错误/跳过;ThemeColorPickerPrecisionTest 2/2;mobile 与 leanback arm64 Debug 构建并在 192.168.50.3:5559 以 adb install -r 覆盖安装;设备验收通过默认首位、完整模板、浅深预览、草稿不落盘、精确 HEX、清空槽位、连续透明度拖动、取消不保存;任务开始偏好快照恢复后 SHA-256 校验一致;gradlew clean 成功。

Task-Guard: THEME-EDITOR-20261001
2026-10-01 23:22:36 +08:00
Silent1566
65d709a477 fix: extend cat source startup timeout and probe readiness efficiently
Verification: Targeted NodePortSelectionTest, NodeBundleInstallTest, and NodeLocalBundleTest passed via :app:testMobileArm64_v8aDebugUnitTest; mobile arm64-v8a debug APK assembled and covered-installed to 192.168.50.3:5555; device tests confirmed the supplied ghfast and catpaw sources reach 猫源已就绪 without configuration-load timeout; diff check and LSP diagnostics clean

Task-Guard: cat-source-load-timeout-20261001
2026-10-01 23:06:50 +08:00
Silent1566
2bf29bc4c8 fix(tv): keep site bulk actions reachable by remote
Verification: Robolectric SiteDialogActionFocusTest: 3 passed (2 failed before fix); SiteAdapterSelectionTest: 1 passed; assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL; emulator-5556 adb D-pad select/cancel focus verified by fresh UI XML and screenshots; test APK SHA256 7b4ef8c6a10e52d3f02cdce611961b474eaaaf68e2f093cbc0942598882d9c39; original APK and 18 app data files restored and hash-checked; temporary backups reclaimed; Gradle clean --no-daemon succeeded. Evidence: F:/temp/webhtv-site-focus

Task-Guard: tv-site-action-focus
2026-10-01 21:41:26 +08:00
Silent1566
d2853d20ad feat(tv): apply theme palettes to site dialog
Verification: 定向主题/站点 JVM 测试通过;leanback/mobile arm64 Java 与资源编译通过;leanback Debug 包成功并以 adb install -r 覆盖安装到 192.168.50.3:5559;设备实测主题编辑器 palette style 与 TV 站点弹窗动态主题通过;构建产物已清理;git diff --check 与 task_guard check 通过。

Task-Guard: THEME-TV-SITE-PALETTE-20261001
2026-10-01 20:41:32 +08:00
Silent1566
8ba23ac144
Merge pull request #391 from Silent1566/dev2
fix(tv): 首页菜单键新增追更/站点注入动作并同步 beta(含扩容注释复评修复)
2026-10-01 20:34:12 +08:00
Silent1566
de66627174 fix(tv): 同步首页菜单扩容注释并合并 origin/beta 最新代码
复评 b2e1174d76(菜单新增追更/站点注入动作)发现 4 处扩容后过时注释并修正:HomeMenuDialog Listener javadoc 1..9→1..11、GRID_COUNT 行数注释按 11 项 4 行更新、dialog_home_menu.xml tools:itemCount 9→11、测试英文文案长度 13→14 字符。
合并 origin/beta 890934423d(PR #390:dev1 猫源 node 崩溃恢复修复与 CatSpiderResolveTest、C29 dev1 文档),无冲突、未带回任何远端已回退内容。

Verification: 定向单测 HomeMenuDialogSourceTest 与 CatSpiderResolveTest 通过;git diff --check 通过

Verification: 合并 origin/beta 890934423d 无冲突(dev2..origin/beta=0;无回退内容带回:5682f2b05 非祖先、被删文件仍不存在、净差异仅 8 文件全为菜单改动);四轮评审 b2e1174d76,修复 4 处过时注释(HomeMenuDialog javadoc 1..11、GRID_COUNT 行数注释、tools:itemCount=11、测试英文文案 14 字符);定向单测 HomeMenuDialogSourceTest 与 CatSpiderResolveTest BUILD SUCCESSFUL;git diff --check 通过

Task-Guard: C30-beta-merge-review-dev2
2026-10-01 20:30:21 +08:00
Silent1566
14556212b1 Merge remote-tracking branch 'origin/beta' into dev2 2026-10-01 19:52:56 +08:00
Silent1566
d4b2f04b11 docs(player-button): record decisive device evidence that both wired buttons open their dialogs
Verification: docs-only: records the fourth-session decisive device evidence that closes the last outstanding acceptance item for player-button-wiring. Re-calibrated the device first — emulator-5556 had been re-installed by another worktree with a different mobile build (201,409,966 bytes; has onCodecCapabilityPanel but no player_button_hidden_seeded), so the fix build was re-installed via install -r -d and verified by md5 b1228ac8518d40eb344bab22f2db124b (matches local, same signing key 95E4B2E7). Corrects a prior misdiagnosis: the 0,0-0,0 bounds were parent-relative (bottom container origin 0,920), not an unlaid-out view; and app:id/action is GONE in window mode, so real landscape fullscreen is a prerequisite. With multiThreadProxy center (488,951) and codecCapability center (604,951): tapping 604,951 opened CodecCapabilityDialog (screenshot plus uiautomator dump showing 硬解能力 title, chip hardware qcom / board SM-N9700, four tabs, 当前媒体轨道 2/2, video/avc 854x480, decoder OMX.qcom.video.decoder.avc, 硬解查询 当前规格可硬解, 取消/复制); tapping 488,951 opened MultiThreadProxyDialog, proven by the dumpsys Added Fragments multi-thread-proxy tag count going 0 -> 4 (probe validity itself verified against the known report_fragment_tag) and confirmed by screenshot showing 多线程播放加速, 全局并行线程数 10, 全局分片数 256. No code or test change in this commit; markdown clean.

Task-Guard: player-button-wiring-verify4
2026-10-01 19:48:49 +08:00
Silent1566
b2e1174d76 feat(tv): add following and site injection home menu actions
Verification: 定向 Leanback 单元测试 HomeMenuDialogSourceTest 通过;覆盖首页菜单键 12 项资源、追更/站点注入动作映射、默认选项弹窗与三种语言资源;task_guard check 和 git diff --check 通过

Task-Guard: home-menu-following-site
2026-10-01 19:15:17 +08:00
Silent1566
890934423d
Merge pull request #390 from Silent1566/dev1
merge: 合并 beta 最新代码并复评修复猫源远端源误改写(猫源 node 死进程恢复 + C29 复评)
2026-10-01 19:08:05 +08:00
Silent1566
5decbb96bf merge: 合并 origin/beta 最新代码(PR#387/#388/#389)并复评修复猫源远端源误改写(含 C29 复评)
Verification: catnode dead-proc recovery + beta merge: 合并 origin/beta tip 6ee8a41dc(PR#387/#388/#389)零冲突,净差异只剩猫源修复;复评修复 CatSpider.resolve 远端 T4 api 误改写 127.0.0.1 的实质 bug(isLoopbackBase 收窄)+ NodeRuntime javadoc 与重复探活;新增 CatSpiderResolveTest 6 用例锁定契约;定向单测/双flavor编译 PASS,全量单测 --rerun-tasks 4075 项 0 失败,leanback debug APK 覆盖安装 5555 Success,冷启动 HomeActivityCurrent 无崩溃,合并清单 0 queries(Android 9 修复生效),dex 含修复字符串;被剔除提交 5682f2b05 非祖先,无远端删除文件带回

Task-Guard: C29-beta-merge-review-dev1-20261001
2026-10-01 18:58:49 +08:00
Silent1566
6ee8a41dc9
Merge pull request #389 from Silent1566/dev2
fix(tv): 搜索结果进入的播放页在详情为空时不再退出回搜索页
2026-10-01 18:28:06 +08:00
Silent1566
78c8aa4afa docs: 记录 C29 dev2 同步 beta 复评与搜索空详情修复交付
Verification: VideoActivityDetailShellSourceTest BUILD SUCCESSFUL in 2m 23s; git diff --check origin/beta dev2 passed; 2-round review passed with no code change required

Task-Guard: c29-beta-merge-review-dev2
2026-10-01 18:24:17 +08:00
Silent1566
02d81552ff docs(player-button): record third-session decisive device evidence and correct the pre-fix-APK root cause
Verification: docs-only: records the third-session decisive device evidence for player-button-wiring — (1) dex-level APK comparison proves emulator-5554/5558 were running pre-fix builds (player_button_hidden_seeded and onCodecCapabilityPanel absent), falsifying the prior sessions' implicit premise; (2) overwrite-install of the local mobile debug APK on emulator-5556 (same signing key 95E4B2E7, install -r -d, md5 b1228ac8518d40eb344bab22f2db124b) yields player_button_hidden=multi_thread_proxy plus seeded=true on real device, and View Hierarchy shows multiThreadProxy GONE / codecCapability VISIBLE by default and VISIBLE after the user enables it — three-state end-to-end proof for the default-hidden contract; (3) remaining §2.1 click->dialog evidence scope and reproduction recipe documented. No code or test change in this commit; markdown clean.

Task-Guard: player-button-wiring-verify3
2026-10-01 18:06:17 +08:00
Silent1566
0a1ca102e1 fix(tv): keep failed search detail on playback page
Verification: :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.VideoActivityDetailShellSourceTest passed; git diff --check passed; bash ./gradlew clean passed; Gradle daemon stopped

Task-Guard: tv-search-result-empty-detail
2026-10-01 16:43:31 +08:00
Silent1566
bba9e17dbb fix(tv): 猫源 node 子进程崩溃后无法感知与恢复(配置加载/搜索一直失败)
Verification: cat source dead-proc recovery: NodeRuntime now probes :node process liveness before reuse (serviceAlive), resets stale state on death; CatSpider resolves current NodeRuntime port per request and triggers restartIfDead on connection failures. Verified: Mira battle ground truth (search 404 matches user log then node dies), --rerun-tasks compile SUCCESSFUL 1m10s, 154 unit tests pass (Node 60 / Cat* 37 / SiteApi 16, 0 failures), fixed strings confirmed in dex, debug APK streamed install to 5555, app boots no crash.

Task-Guard: catnode-dead-proc-recovery
2026-10-01 16:10:58 +08:00
Silent1566
bbe7062f42
Merge pull request #388 from Silent1566/dev2
fix(tv):移除 Android 11 manifest queries,修复 Android 9 TV 启动
2026-10-01 14:55:32 +08:00
Silent1566
59f438285b merge: 合并 origin/beta 最新代码(含 PR#386/PR#387)并复评 dev3 全部未推送改动
- 合入 beta 最新 tip b184f7363:原生增强播放页统一焦点规范(tvFocusRing/tvCurrentRing/
  tvNormalStroke 三主题属性 + 4 个 selector)、纵向焦点链与海报行 rowHeight 修复、
  详情直放/沉浸融合切集旧位置回写守卫(inlinePlayerMediaReady)、历史卡片重复已看时间
  移除、融合/原生唤醒记住用户焦点修复(PR#387)。
- 保留 dev3 统一语义主题系统为存活方,删除 beta 旧 ThemeCatalog 主题系统落地文件,
  双 flavor Theme.Base 同时绑定 tv* 三属性以支撑共享 selector。
- 复评修复:演员卡焦点环圆角改用与卡片同一个尺寸 token(消除描边内缩/外溢);
  dev3 既有断言改随‘焦点环走前景 selector’新契约(消除双重描边);清理未用 import。
- 未把远端已移除/回退的提交带回(5682f2b05 非祖先,beta 回退删除文件均未恢复)。

Verification: 双flavor全量单测9235项0失败(leanback4186/mobile5049,各2跳过);双flavor Java编译通过;mobile与leanback Debug打包并覆盖安装到192.168.50.3:5559成功;设备实测设置→外观与语言→主题色彩→焦点色→精确十六进制全链路无崩溃、v2主题键完好;tvFocusRing/tvCurrentRing/tvNormalStroke三属性双flavor绑定闭环;git diff --cached --check通过、无冲突标记、无重复view id/资源名;MERGE_HEAD=b184f7363(beta tip)、0未解决冲突、被剔除提交5682f2b05非祖先、beta回退删除文件未带回

Task-Guard: C29-BETA-MERGE-REVIEW-DEV3-20260930
2026-09-30 19:30:52 +08:00
Silent1566
8cd77ba46e fix(tv): remove Android 11 manifest queries for Android 9
Verification: Android 9/API 28 emulator-5556: adb install -r leanbackArmeabi_v7a debug APK succeeded; app launched and mResumedActivity=HomeActivityCurrent; no Unknown element under manifest, FATAL EXCEPTION, or process death; merged manifest HAS_QUERIES=False; git diff --check passed

Task-Guard: tv-android9-update-crash-final
2026-09-30 18:35:56 +08:00
Silent1566
b184f7363c
Merge pull request #387 from Silent1566/dev2
fix(tv):修复控制栏唤醒焦点错位并同步 beta
2026-09-30 16:54:26 +08:00
Silent1566
7e0ae4af6f docs: 记录 dev2 合并 beta 与焦点修复复审
Verification: origin/beta 已完整合入且 dev2..origin/beta=0;合并后仅保留 3 个本地焦点修复的 4 文件净差异;远端已移除提交 5682f2b054 不在 dev2;Mobile 定向 298 项与 Leanback 定向 12 项共 310 项通过;assembleLeanbackArm64_v8aDebug 成功,emulator-5556 使用 adb install -r 覆盖安装并启动到 HomeActivityCurrent,无 FATAL EXCEPTION/ANR/Fatal signal;已执行 ./gradlew clean

Task-Guard: beta-merge-review-dev2-20260930
2026-09-30 16:47:15 +08:00
Silent1566
4cb165a0fc
Merge pull request #386 from Silent1566/dev1
fix(tv): 统一原生增强播放页 TMDB 焦点环并修复海报行与纵向焦点链(含合并 beta 最新代码)
2026-09-30 15:04:29 +08:00
Silent1566
fc36522d2e fix(tv): 统一原生增强播放页 TMDB 焦点环并修复海报行与纵向焦点链(含合并 origin/beta 最新代码)
Verification: C28: 合并 origin/beta@8f7376295 后 2 轮复评 dev1 净差异(3 个未推送提交/7 文件);修复 3 处中文文案错别字(塔陷->塌陷)与 1 处文件末尾缺换行;定向单测 TmdbRowFocusChainTest 10/10 + NativeEnhancedPlaybackStyleFocusTest 14/14 全通过,双 flavor Java 编译通过;Leanback 全量单测 4068 项 0 failure/0 error;leanback arm64 Debug 打包 196M 并以 adb install -r 覆盖安装成功(未卸载);设备 192.168.50.3:5555 端到端实测焦点链 剧照->海报->相关视频->主创->推荐->个性推荐 全部命中、向上反向完整、空行自动跳过,海报行 h=444(原塌陷),三处卡片焦点环像素扫描均为 6px=3dp #FFD166 矩形环,logcat 无 FATAL/ANR;5965b 与主题系统文件未随净差异带回(diff --diff-filter=D 为空)

Task-Guard: C28-beta-merge-review-dev1-20260930
2026-09-30 14:20:45 +08:00
Silent1566
c53fce8d61 fix(ui): 主题色彩弹窗保留主题面板,不再把窗口背景换成透明
Verification: dev3 192.168.50.3:5559 覆盖安装 mobile arm64 后真实点击路径(设置→外观与语言→主题色彩)截图确认面板恢复不透明主题圆角面板,外框 [28,48][1892,1032]=1864x984 与 4.32 实测一致(放大占比未回退),标题/预设芯片/8 行色槽/恢复默认-取消-应用 均可见,底层设置页不再透出;根因由 Material 1.14.0 MaterialAlertDialogBuilder.create() 源码(window.setBackgroundDrawable(insetDrawable(background, insets)))与 WebHtvAlertDialogBuilder 现有注释(面板即 window background)双向确认;:app:testMobileArm64_v8aDebugUnitTest 与 :app:testLeanbackArm64_v8aDebugUnitTest 的 ThemeDialogLayoutTest 各 5 项 0 failure/0 error,两个 flavor 的 ThemeDialog 均编译通过(两文件逐字节相同)

Task-Guard: THEME-EDITOR-DIALOG-PANEL-REGRESSION-20260930
2026-09-30 14:11:52 +08:00
Silent1566
c14d146f0b Merge remote-tracking branch 'origin/beta' into dev2 2026-09-30 14:04:37 +08:00
Silent1566
41c5e904a4 fix(ui): 主题色彩弹窗放大占比,对齐广告拦截统计弹窗窗口契约
Verification: leanback 1920x1080 设备实测主题色彩弹窗外框 [48,48][1872,1032]=1824x984 (95.0%x91.1%),mobile 实测 [28,49][1892,1073]=1864x1024 (97.1%x94.8%),与同设备参考弹窗 AdBlockStatsDialog 实测数值完全一致;13 色槽+3 透明度滑块+实时预览滚动可达,恢复默认/取消/应用三按钮完整可见;:app:testMobileArm64_v8aDebugUnitTest 5041 项、:app:testLeanbackArm64_v8aDebugUnitTest 4169 项,0 failure / 0 error;:app:compileMobileArm64_v8aDebugJavaWithJavac 通过

Task-Guard: THEME-EDITOR-DIALOG-SIZE-20260930
2026-09-30 13:55:38 +08:00
Silent1566
678a7d1483 fix(tv): 修复融合控制栏点击后唤醒焦点落错按钮
用户反馈:点击播放参数后再唤醒控制栏,焦点落在选集按钮上,而非上次点击的按钮。

两处同源缺陷:
1) setupInlineControl 只监听 OnFocusChange。鼠标/触摸点击不移动焦点,
   用户点过的按钮从未进入 inlineControlFocus;唤醒时只能落到默认候选,
   而候选里 next/fullscreen/prev/reset 被隐藏,第一个可见的正是选集按钮。
   补 OnTouchListener 在 ACTION_DOWN 记录被点击按钮,返回 false 不吞事件。
2) PlayerControlFocusHelper.ensureFocus 在 root 内已有焦点时提前返回,
   使调用方明确传入的目标被静默忽略。改为显式目标优先,其次沿用 root
   内已有焦点,最后回退 firstFocusable(root)。

手机端不挂触摸监听,触控语义不变。

验证:PlayerControlFocusIntegrationTest 10/10 通过。

Verification: 定向单测 :app:testMobileArm64_v8aDebugUnitTest --tests PlayerControlFocusIntegrationTest BUILD SUCCESSFUL,结果 XML tests=10 skipped=0 failures=0 errors=0(新增 tvControlClickIsRememberedAndPreferredFocusWinsOnWake,既有 9 项无回归),2026-09-30 12:26;assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL(4m49s)并 -r 覆盖安装到 127.0.0.1:5557(lastUpdateTime 2026-09-30 12:36:07,未卸载);设备实测未取证:模拟器窗口焦点反复漂移到 launcher/null(mCurrentFocus=null)、控制栏 10s 自动隐藏与 uiautomator dump 时序冲突,多次尝试后按契约停止该路线

Task-Guard: TV-fusion-click-focus-20260930
2026-09-30 13:22:15 +08:00
Silent1566
5026e2147f fix(tv): restore poster row height and TMDB row focus chain
Verification: leanback 全量单测 4068 tests/0 failures(含 TmdbRowFocusChainTest 10、NativeEnhancedPlaybackStyleFocusTest 14),构建 11:34:37 晚于最终编辑 11:32:18;设备 192.168.50.3:5555 端到端验证:播放页 TMDB 行向下链条 剧照->海报(缺相关视频行自动跳过)->相关视频->主创->推荐->个性推荐 全部命中,向上链条反向完整;海报行占位 y=907..1080(原塌陷为0)且卡片显示、聚焦有 3dp 黄环

Task-Guard: tv-poster-row-height-focus
2026-09-30 13:17:21 +08:00
Silent1566
cbe19f945a docs(player-button): append second-session device-verification findings and final evidence scope
Verification: docs-only: appends the second-session device-verification findings (double-tap pause works but dump still fails; fragment-name signal confirms the click does not reach MultiThreadProxyDialog; BACK exits fullscreen; show-bar tap is a toggle and backfires). No code or test change in this commit.

Task-Guard: player-button-wiring-verify2
2026-09-30 02:53:54 +08:00
Silent1566
5e88018541 docs(player-button): record device-verification blocker and exact action-row coordinates for the 多线程/硬解能力 fixes
Verification: docs-only: records the emulator-5556 device-verification blocker for the player-button-wiring fix (no code or test change in this commit); markdown lint clean

Task-Guard: player-button-wiring-verify
2026-09-30 02:26:35 +08:00
Silent1566
e82cdce5cf fix(tv): restore related video focus highlight and row focus chain
Verification: 全量 Leanback 单测 BUILD SUCCESSFUL;git diff --check 通过;设备 192.168.50.3:5555 实测:动画缩放恢复 1.0 后加载指示器三帧旋转角度不同(点→短弧→长弧)证明转圈动效恢复;uiautomator 焦点节点判定剧照行卡片[48,546][488,794]按向下可落到相关视频行卡片[37,552][611,876](16:9);像素检测相关视频卡焦点环为 rgb(255,209,102)=#FFD166 且构成长直边矩形环

Task-Guard: tv-related-video-focus-navigation
2026-09-29 20:04:34 +08:00
Silent1566
46505877d5 fix(ui): 可选中的壁纸条目恢复状态色表,修正选中态白字压亮底
修正 e66a7d3cb 引入的回退:该提交把 21 个布局一律改成恒亮
?attr/webhtvColorOnWallpaper,但这些条目里有 7 个的填充是
「随状态变化」的,单一颜色必然在其中一个状态上失配。

根因(移动版 selector_item):
    state_selected -> ?attr/colorSecondaryContainer @0.65 alpha  = 日间亮灰
    默认           -> @color/black @0.15 alpha                   = 深色
实测首页类型芯片:选中填充 #BAB9C5(原色 #DFE2E6 叠壁纸),未选填充
#645C74;白字在选中态仅 1.94:1(不可读),深字在未选态仅 2.71:1。
两边都错,必须用状态色表。

这不是新设计:统一改造 f63b0a81b 之前,dev3 这 7 个文件本来就用状态色
表,是那次改造把「文字这一半」压平成了单一 ?attr/colorOnSurface,既变暗
又丢掉了选中高亮。

修复 = 精确还原到 f63b0a81b^ 的文字色(已逐文件 diff 验证):
  adapter_collect / adapter_type            -> @color/selector_text
  adapter_episode_grid/group/hori/flag/quality -> @color/selector_video_text
  其中 5 个 textColor 与改造前逐字节一致;adapter_episode_grid 与
  _hori 的 overview 由 #E6FFFFFF 改为 ?attr/webhtvColorOnWallpaper
  (同义,且避免新增硬编码 hex 触发 check_ui_tokens 门禁)。

两者 dev3 都仍在且未被引用,本次重新启用:
  selector_text       : selected -> ?attr/colorOnSecondaryContainer, 默认 @color/white
  selector_video_text : selected -> @color/yellow_500,              默认 @color/white

保留恒亮 token 的 14 个布局其填充在所有状态下都是深的
(shape_live #47000000/#5C000000、shape_vod_list/shape_vod_name #33000000、
shape_video_episode_grid #99000000、shape_quick_search_item、
?attr/colorSurfaceContainer*),且 TypeAdapter/CollectAdapter 之外的适配器
经核查均不调用 setSelected(RecordAdapter/WordAdapter/HotWordAdapter/
QuickAdapter 均为 0 次),故无须状态色表。

Verification: 静态:7 个文件 textColor 与 f63b0a81b^ 逐文件 diff(5 个完全一致,2 个仅 hex->语义 token);全部 0 处残留 ?attr/colorOnSurface;check_ui_tokens.sh --strict 对比度 38 组 0 失败(min 4.28),唯一 violation item_following.xml 为改动前既有。单测::app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.* 与 ui.style.UiStyleSourceTest 全绿(含新增 selectableWallpaperItemsKeepAStateList 与 wallpaperStateListsAreLightByDefaultAndDistinctWhenSelected 两个门禁)。设备:dev3 192.168.50.3:5559 经 scripts/build_arm64_debug_install.sh --flavor mobile 覆盖安装;logcat InflateException/ResourceNotFound/FATAL EXCEPTION 均为 0(仅出现 WorkManager SystemJobService ANR 一次,与本次纯 textColor 改动无关)。实测像素:首页类型芯片选中「电影」填充 (186,185,197),文字实测 (27,27,27) -> 8.89:1(修复前同一处白字仅 1.94:1);未选「电视剧」填充 (100,92,116),白字 6.32:1。剧集/线路行 selector_video_text:填充为 black_30/50/60 叠暗底(实测约 (12,12,12)),白字 19.56:1、选中黄字 16.02:1(旧深字仅 1.14:1)。遗留:视频源解析失败(播放地址解析失败),未能进到剧集列表对话框内实测选中黄字;该项已由状态色表定义与静态对比度计算佐证。

Verification: 7 文件 textColor 与 f63b0a81b^ 逐文件 diff(5 完全一致);0 处残留 colorOnSurface;theme.* 与 UiStyleSourceTest 全绿(+2 门禁);check_ui_tokens --strict 对比度 38 组 0 失败;dev3 覆盖安装无布局异常;首页选中芯片实测 8.89:1(修前 1.94:1)、未选 6.32:1

Task-Guard: WALLPAPER-STATE-TEXT-20260929
2026-09-29 18:33:27 +08:00
Silent1566
593c04d052 fix(tv): 沉浸融合唤醒控制栏不再被片头/片尾抢占
融合模式 getInlineControlFocus() 把片头/片尾按钮当成合法「上次焦点」,
遥控端唤出控制栏时会被还原到片头/片尾,覆盖用户真正选中的按钮,
与影视原生模式已修的问题同源但判定不对称。

抽出 hasRememberedInlineControlFocus(),遥控端仅「用户真实选中过的普通
控件」算有效记忆焦点,片头/片尾一律排除;手机端保持原有触控语义不变。
落点回退仍为 playerNext,非片头。

验证:PlayerControlFocusIntegrationTest 9/9 通过(新增
fusionControlWakeKeepsUserChosenFocusOutsideTheIntroRange)。

Verification: 定向单测 :app:testMobileArm64_v8aDebugUnitTest --tests PlayerControlFocusIntegrationTest BUILD SUCCESSFUL;结果 XML tests=9 skipped=0 failures=0 errors=0(含新增 fusionControlWakeKeepsUserChosenFocusOutsideTheIntroRange 与既有 leanbackControlWakeKeepsUserChosenFocusOutsideTheIntroRange,无回归),2026-09-29 17:10;设备实测:emulator-5556(leanback 包)可进入 TmdbDetailActivity 内嵌播放器并 dump 到 playerControls/playerOpening/playerDanmaku 等控件,但注入 CENTER/DPAD_UP 无法稳定进入融合全屏控制栏(bar=hid),故融合模式端侧行为本次仍未取证

Task-Guard: TV-fusion-focus-20260929
2026-09-29 17:27:31 +08:00
Silent1566
e66a7d3cb4 fix(ui): 壁纸之上的页面列表文字统一为亮色
承接 59ea0fe6c(设置页 257 处),补完同类遗漏:列表项与叠加视图同样压在
深色壁纸/视频上,却仍用 ?attr/colorOnSurface —— 日间模式解析为 #1A1C1E,
深字压深底。

判定依据不是文件类型,而是「文字最近祖先背景 + 宿主是否对话框」:
  · 半透明黑或透明背景(shape_live #66000000、shape_item/shape_item_round
    /shape_search_hot_word → @color/selector_item 15%% 黑、shape_vod_list
    #33000000、shape_vod_name、shape_video_item → selector_video_item
    #80000000/#4D000000、shape_video_episode_grid #99000000)→ 需亮字
  · 不透明亮底(shape_light_dialog_item #FFFFFF、selector_custom_csp_item
    #FFFFFF/#E8F0FE、selector_git_cloud_card #E8F0FE、colorSurfaceContainer
    /Lowest)→ 深字本就正确,不动

上游权威证据(/home/maple/orca/webhtv):这些项目原本全是亮色 ——
@color/white、@color/white_60/80、@color/selector_video_text(默认项为白)。
dev3 的「统一为 colorOnSurface」才是回归。

关键结论:6 个双上下文布局无需分上下文着色。上游在 dialog_quick_search、
dialog_episode_list、dialog_receive 里复用的正是同一批 item 布局,且同样是
亮色;这两个对话框还是透明窗口 + #66FFFFFF 玻璃面板悬于视频之上。
因此单一恒亮角色在页面与对话框两侧都正确,无需给
ThemeOverlay.WebHTV.Dialog 加重映射(该 overlay 也只挂在 materialAlertDialogTheme
上,够不到 BottomSheetDialogFragment)。

改动:
  15 个仅页面布局 19 处 + 6 个双上下文布局 9 处,共 28 处 android:textColor
  由 ?attr/colorOnSurface 改为 ?attr/webhtvColorOnWallpaper,零结构改动。
  测试:ThemeBaseWiringTest 新增 WALLPAPER_ITEMS 清单(21 项,含上游依据注释),
  并让既有 wallpaperPagesUseTheWallpaperForegroundRole 同时覆盖页面与列表。

Verification: 静态:21 个布局残留 colorOnSurface=0、新引用=28(15 个 19 处 + 6 个 9 处);替换前逐处断言全部落在 android:textColor 上。check_ui_tokens.sh --strict 对比度 38 组 0 失败(min 4.28),唯一 violation item_following.xml 为动手前既有、不在本次变更集。单测::app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.* 与 ui.style.UiStyleSourceTest 全绿(含新清单)。设备:dev3 192.168.50.3:5559 经 scripts/build_arm64_debug_install.sh --flavor mobile 覆盖安装成功,无崩溃(logcat 无 AndroidRuntime/FATAL/InflateException)。实测像素(搜索页芯片 = shape_item_round,背景均匀最适合测量):芯片「中文字幕」底 (115,106,134),白字 5.08:1;芯片「兰香如故」底 (113,104,132),白字 5.23:1;两处若仍用 colorOnSurface(#1A1C1E) 仅 3.37:1 / 3.27:1,不达 WCAG AA 4.5。遗留未测:设备在验证中途掉线(No route to host),直播页 adpter_channel/group/epg_data 与 6 个双上下文布局在对话框内的呈现未取得设备截图;两者均已由上游同款布局+同款亮色与静态检查佐证,但未获本机实测像素。另一处已知同类残留(未处理、需另行授权):这些 item 里作次要文字用的 ?attr/colorOnSurfaceVariant 日间为 #44474F,同样偏暗;仓库无「壁纸上的次要前景色」token,上游对应值是 @color/white_60/80,需要新增语义角色才能修。

Verification: 21 布局残留 0 / 新引用 28;theme.* 与 UiStyleSourceTest 全绿;check_ui_tokens --strict 对比度 38 组 0 失败;dev3 覆盖安装无崩溃;搜索页芯片实测白字 5.08:1/5.23:1(改前口径 3.37:1/3.27:1)

Task-Guard: PAGE-LIST-FOREGROUND-20260929
2026-09-29 14:44:04 +08:00
Silent1566
4432937d2f fix(tv): highlight enhanced media focus
Verification: LSP diagnostics clean for both changed Java presenters; git diff --check passed; ./gradlew :app:assembleLeanbackArm64_v8aDebug --stacktrace completed BUILD SUCCESSFUL

Task-Guard: tv-enhanced-media-focus
2026-09-29 13:58:16 +08:00
Silent1566
8f73762950
Merge pull request #385 from Silent1566/dev1
fix(tv): 统一原生增强播放页焦点视觉并补全纵向焦点链
2026-09-29 13:06:13 +08:00
Silent1566
15b738cc83 fix(tv): 统一原生增强播放页焦点视觉并补全纵向焦点链
Verification: 已同步并确认 origin/beta@5cba7b00b 为最新基线;最终复评通过,git diff --cached --check 通过且无冲突标记,未夹带远端已移除或回退的独立提交;定向回归 NativeEnhancedPlaybackStyleFocusTest 与 VideoActivityLayoutTest BUILD SUCCESSFUL;Leanback 与 Mobile 全量回归共 4951 项,2 项失败、2 项跳过,其中两项 TmdbUIAdapterTest 失败已在 origin/beta@5cba7b00b 独立工作树按正确包名复现为 2/2 失败,确认属于 beta 基线既有问题

Task-Guard: NATIVE-ENHANCED-PLAYBACK-STYLE-FOCUS-BETA-REVIEW-20260929
2026-09-29 13:00:56 +08:00
Silent1566
c7fba76a46 fix(tv): 影视原生唤醒控制栏优先恢复用户记忆焦点
片头/片尾区间内按 DPAD_UP 唤醒控制栏时,焦点被无条件抢占到片头按钮,
覆盖用户上次选中的按钮(弹幕/字幕等),表现为「开完面板回来被拽回片头」。

抽出 hasRememberedFocus() 共享判定,仅在无可用记忆焦点时才走片头/片尾
快捷聚焦;getFocus2() 与之复用同一判定,语义与改动前严格等价。
上游「片头区间快捷聚焦」意图保留。

验证:PlayerControlFocusIntegrationTest 8/8 通过(新增回归用例
leanbackControlWakeKeepsUserChosenFocusOutsideTheIntroRange);
assembleLeanbackArm64_v8aDebug 构建通过。设备行为未取证。

Verification: 定向单测 testMobileArm64_v8aDebugUnitTest 的 PlayerControlFocusIntegrationTest 8 tests / 0 failures / 0 errors(含新增 leanbackControlWakeKeepsUserChosenFocusOutsideTheIntroRange),结果 XML 于 2026-09-29 09:31;assembleLeanbackArm64_v8aDebug BUILD SUCCESSFUL(APK 201,288,697 bytes,10:25);设备实测未完成:emulator-5556 覆盖安装 leanback 包成功(签名 95e4b2e7 一致,未卸载),但注入 CENTER 未能进入全屏,onKeyUp 唤醒路径未被触发,且原外网源 一瓯春 已失效(連接超时/这里什么都没有),故设备侧行为未取证

Task-Guard: TV-focus-return-20260929
2026-09-29 12:11:00 +08:00
Silent1566
59ea0fe6c8 fix(ui): 壁纸之上的页面文字改用专用前景色角色
页面与弹窗争用同一个 ?attr/colorOnSurface,是 9/28 两次返工的根因:
  mobile 页面画在深色壁纸(CustomWallView)上,弹窗画在浅色面板上。
  一张色表只能满足一边——亮表让页面近黑压深壁纸(实测 3.36:1,
  不达 WCAG AA),暗表让弹窗浅色卡片配亮字(1.24:1)。
修法:新增 webhtvColorOnWallpaper 角色(恒为 #FFFFFF),只给壁纸之上的页面用。

改动:
  tokens:三张表(light/night/leanback)各加 webhtv_on_wallpaper = #FFFFFF。
    命名避开 webhtv_color_ 前缀,与 webhtv_on_surface_20/70/80/90 同族,
    不进入 ThemeContractTest 冻结的 49 项 Material 角色契约。
  attrs/styles:声明 webhtvColorOnWallpaper 并在 Theme.WebHTV 赋值;
    弹窗(ThemeOverlay.WebHTV.Dialog)、卡片 drawable 一律未动。
  layouts:9 个设置页共 257 处 android:textColor 由 ?attr/colorOnSurface
    改为 ?attr/webhtvColorOnWallpaper,零结构改动、零 Variant 混入。
  test:ThemeBaseWiringTest 新增两个守门测试——壁纸页不得再用
    colorOnSurface;该角色须三表齐备且 ThemeBinder 永不重写它。

Verification: 静态:9 个设置页残留 colorOnSurface=0、新引用=257;全仓仅这 9 个布局使用新 attr,弹窗布局 0 处使用。check_ui_tokens.sh --strict 对比度 38 组 0 失败(min 4.28),唯一 violation item_following.xml 为动手前既有、不在本次变更集。单测::app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.* 与 ui.style.UiStyleSourceTest 共 117 项 0 失败(含两个新增守门测试;期间修正了一次断言错误——dark 表 0xFFFFFFFF 无任何 ThemeRole 承载,light 表由 4 个角色共享,两者均返回 null,故守门断言改为行为不变量而非机制)。设备:dev3 192.168.50.3:5559 经 scripts/build_arm64_debug_install.sh --flavor mobile 覆盖安装成功(未卸载)。实测像素:设置页与播放设置子页标签均为纯 #FFFFFF,白字 vs 行背景 6.47:1、vs 壁纸 5.09:1,修前深字 #1A1C1E vs 同壁纸仅 3.36:1;同屏弹窗仍为浅面板 #E1E2E9 + 深字,对比度 13.23:1,确认弹窗未被本次改动影响。遗留(不在本次范围,需另行授权):其余 mobile 页面(约 23 个 adapter_*、view_empty、view_progress)仍用 ?attr/colorOnSurface,同样压在壁纸上,存在同类低对比;dialog_* 布局属浅色面板,不可改。

Verification: 9 页 257 处替换+残留 0;theme/UiStyleSource 单测 117 项 0 失败;check_ui_tokens --strict 对比度 38 组 0 失败;dev3 覆盖安装实测 白字 6.47:1/5.09:1、弹窗 13.23:1

Task-Guard: PAGE-ON-WALLPAPER-ROLE-20260928
2026-09-29 11:14:24 +08:00
Silent1566
62670b6413 fix(player): wire mobile multi-thread and codec-capability buttons and hide multi-thread by default
Verification: MultiThreadProxyPlayerUiSourceTest 5/5 pass on mobile flavor (incl. new multiThreadButtonIsHiddenByDefaultButStillUserConfigurable); compileMobileArm64_v8aDebugJavaWithJavac + compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; mobile targeted set 413 tests/1 failed and leanback full 4045 tests/10 failed, all reproduced on clean baseline HEAD (pre-existing CRLF multi-line source-text assertions in untouched files)

Task-Guard: player-button-wiring
2026-09-29 09:54:53 +08:00
Silent1566
5cba7b00b0
Merge pull request #384 from Silent1566/dev4
fix(mobile): 移除手机版历史卡片重复的已看时间并清理死代码
2026-09-28 21:52:24 +08:00
Silent1566
fd29d76ee7 revert: 撤销把电视版暗色配色搬到手机版的两个提交
上游手机版与电视版配色本就不同,不应统一:
  mobile   Theme.Base parent = Theme.Material3.DynamicColors.DayNight
  leanback Theme.Base parent = Theme.Material3.Dark
上游弹窗面板 shape_shell_proxy_dialog 用 @color/white 硬编码,
弹窗行文字硬编码 #202124/#5F6368;上游无 values-night、无 webhtv_tokens。

问题根因:?attr/colorOnSurface 同时被「页面(深色壁纸)」与「弹窗(浅色面板)」
引用,单张色表无法同时满足两者。

撤销:
  a6a470c2f 为 mobile 新增暗色表(使弹窗面板变深)
  db793d5af 把 19 个白底卡片改为跟随主题(使卡片也变黑)

还原后弹窗恢复上游浅色观感:面板 #E7E8EF、卡片 #F8F9FA、文字 #1A1C1E。

Verification: 回退把电视版暗色表搬到手机版的两个提交,恢复上游「浅色弹窗 + 白卡片」正确观感。背景:12:04 a6a470c2f 给 mobile flavor 新增 values/webhtv_tokens.xml(内容=leanback 暗色表),使手机版弹窗面板由浅色变深色;16:16 db793d5af 又把 19 个白底卡片改成跟随主题,把卡片也弄黑。用户指出上游手机版弹窗本就是白底深字,暗色是电视版专属——证据:上游 mobile/res/values/styles.xml 的 Theme.Base parent=Theme.Material3.DynamicColors.DayNight.NoActionBar(DayNight),leanback=Theme.Material3.Dark.NoActionBar(固定深色);上游 shape_shell_proxy_dialog.xml 面板用 @color/white 硬编码,AppearanceDialog 行文字硬编码 #202124/#5F6368 深色;上游无 values-night、无任何 webhtv_tokens.xml。根因:?attr/colorOnSurface 同时被「页面(深色壁纸+15%黑行背景)」和「弹窗(浅色面板)」引用,单张色表无法同时满足两者——暗表修好页面却弄坏弹窗,亮表反之。实施:git revert --no-commit db793d5af a6a470c2f,精确还原到 a6a470c2f 之前的 9554195c9 状态(mobile 暗色表删除、19 个 drawable 恢复上游字面量)。验证:git diff 9554195c9 对全部 21 个变更文件为空(逐字节还原);单测 UiStyleSourceTest/ThemeBaseWiringTest/ThemeContractTest 共 17 项 0 失败;check_ui_tokens.sh --strict 对比度 38 组 0 失败 min=4.28(violations=1 为预存 item_following.xml,非本次引入);设备 dev3 192.168.50.3:5559 mobile arm64 debug 覆盖安装后冷启动,打开「外观与语言」弹窗实测面板 #E7E8EF、卡片 #F8F9FA、卡片文字 #1A1C1E(对比度 81.33:1),与上游一致。遗留(下一单元处理):页面侧 ?attr/colorOnSurface 仍解析为 #1A1C1E 近黑,压在深色壁纸上,即用户 11:17 首次投诉的「半黑半白」,需按上游 @color/white 白字语义单独修复。

Task-Guard: REVERT-TV-PALETTE-20260928
2026-09-28 21:12:21 +08:00
Silent1566
127d335951 docs: 收口观看历史任务状态与交付坐标
Recovery anchor 原记录停留在提交前状态(写有'不 push'与'由 guard finish 提交'),与已完成的推送和 PR 创建不一致;更新为已交付状态并记录 PR Silent1566/webhtv#384(base beta,保持 OPEN 不代为合并)。仅文档变更。

Verification: 文档收口修正:Recovery anchor 的当前状态与唯一下一动作已更新为已交付(dev4 已推送、PR Silent1566/webhtv#384 base beta 已创建且保持 OPEN 未合并),删除了误导性的'不 push'与'由 guard finish 提交'表述;仅文档变更,无代码/资源/依赖改动,不影响已在 PR#384 中验证的 3/3 定向单测与 TV 编译结果

Task-Guard: beta-merge-review-r3-dev4-doc-20260928
2026-09-28 21:09:06 +08:00
www.xueximeng.com
ff3f8d7045 fix(cache): 修复自动清理与临时文件清理的评审问题
评审发现并修复三项问题:

1. 临时文件"报告≠删除":清理候选此前按另一份手写前缀白名单匹配,
   与注册表统计集合(.apk/.zip/.tmp/.log 后缀)不一致,导致同步 zip、
   goProxy.log 等界面报告的临时文件永远清理不掉。现改为从注册表
   TEMP_FILES 模块定义派生候选集合(单一来源),保留在用传输保护。
2. 自动清理计划忽略 allowAutomaticCleanup 声明:设计 §13.2 要求"只对
   allowAutomaticCleanup 模块生成 plan",原实现仅排除诊断日志,导致
   GLIDE、WEBHOME_RAW 虽声明不可自动清理仍会进入自动 STANDARD 计划。
   现按 CachePolicyEngine.allowsAutomatic 过滤。
3. 自动清理开关关闭后进程内定时任务仍会清理:startup/periodic/limit
   三项检查现每次 tick 重新读取开关,与 cancelPersistent 取消平台作业
   的行为一致。

Verification: 63 cache 单元测试全通过(含新增 CacheAutoPlanAndTempFamilyTest 6 例);mobile/leanback 双形态编译 BUILD SUCCESSFUL

Task-Guard: review-cache-temp-and-auto-filter
2026-09-28 20:49:12 +08:00
Silent1566
b25377f169 fix(mobile): complete removal of duplicate watched time dead code
复评上一轮提交发现文档断言不实:HistoryProgressFormatter 的最后生产调用已由 TV 清理 7605a7d589 移除,上一轮删掉手机端调用后该类及其资源已无任何生产引用,属死代码。

- 删除 app/src/main/java/com/fongmi/android/tv/utils/HistoryProgressFormatter.java 及其单测
- 删除 values/values-zh-rCN/values-zh-rTW 三处已无引用的 history_watched_time
- HistoryAdapterTest 将单文件字符串断言升级为手机布局+双端适配器+三语言字符串的结构断言,并新增死代码回流防线
- 修正 docs/HISTORY-COVER-PROGRESS.md 中不实记载并补录复评修正与验证证据

Verification: 复评发现上一轮提交的文档断言不实:HistoryProgressFormatter 在合并基线的 TV 清理 7605a7d589 后已无生产调用,上一轮又删掉其最后调用者,故该类/其单测/history_watched_time(三语言)成死代码。修正为删除死代码并升级断言:JAVA_HOME='C:\Program Files\Android\Android Studio\jbr' bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.adapter.HistoryAdapterTest → BUILD SUCCESSFUL,JUnit XML tests=3 skipped=0 failures=0 errors=0(含新增 removedBelowPosterTimeHasNoLeftoverProductionReferences);判别力负向对照:把 @+id/historyProgress 节点插回手机布局后同一命令 3 tests completed, 2 failed(BUILD FAILED),还原布局后恢复通过;同批 :app:compileLeanbackArm64_v8aDebugJavaWithJavac → BUILD SUCCESSFUL;grep 确认非 build 路径下无 HistoryProgressFormatter/history_watched_time 残留

Task-Guard: beta-merge-review-r3-dev4-20260928
2026-09-28 20:42:08 +08:00
Silent1566
db793d5af6 fix(ui): 弹窗亮底卡片改用语义 token 消除亮字压亮底
19 个 drawable 硬编码 Google 亮色调色板(#F8F9FA/#E8F0FE/#DADCE0 等),
dev3 统一主题系统时只替换了文字色这一半(956 处 ?attr/colorOnSurface),
未替换背景那一半,导致单一 token 无法同时适配深色壁纸与浅色卡片:
亮表下页面文字全黑,暗表下弹窗浅底配浅字(实测 #E2E2E9 on #F8F9FA = 1.24:1)。

按仓库既有 dialog_card_bg.xml/dialog_card_stroke.xml 约定改为语义 token:
primary_container / primary / surface_container_low / surface_container_high /
surface_container / outline_variant / success_container / success / transparent。

纯字面量替换、零结构改动。亮暗双表最差配对 7.04:1;受影响界面:
外观与语言、Git 云备份、代理规则、播放器 OSD、MPV 配置、去广告规则、
退出确认、快捷搜索、Ad 统计。

Verification: 深色弹窗亮底卡片语义化,消除「亮字压亮底」失效配对。目的:用户反馈手机版默认场景下外观与语言弹窗观感变丑、与上游基准不符。根因:上游靠「硬编码成对」成立(页面深壁纸配 @color/white 白字;弹窗浅面板配 #F8F9FA 亮卡片 + #202124 深字),dev3 统一主题系统时只把「文字」这一半换成单一 token ?attr/colorOnSurface(956 处),未替换「背景」那一半——19 个硬编码亮底 drawable(selector_git_cloud_card/css_item/dialog_switch/mpv_icon_button 等),于是一字一色无法同时适配深壁纸与浅卡片:亮表下页面全黑字、暗表下弹窗亮字压亮底。A/B 方案否决:纯 revert a6a470c2f 会精确退回用户 11:17 投诉态且波及全部 956 处。实施:按仓库既有 dialog_card_bg.xml/dialog_card_stroke.xml 约定,把 19 个 drawable 的字面量替换为语义 token(#E8F0FE/D2E3FC/DCEAFF/F4F8FF/F1F6FF→webhtv_color_primary_container,#1A73E8/0B57D0→primary,#F8F9FA/FFFFFF/F3F5F7/FAFBFC/F8FAFD→surface_container_low,#FAFBFC/FBFCFF→surface_container_high,#F4F7FB→surface_container,#DADCE0/E1E5EA/E2E8F0/E5EAF3/E5E7EB/C7DBFF/AECBFA→outline_variant,#E8F5E9/EAF6EE→success_container,#4CAF50/B8DFC4→success,#00FFFFFF→transparent),纯字面量替换、零结构改动。静态验证:19 文件 XML 全部合法、残留裸 hex=0;scripts/check_ui_tokens.sh --strict 对比度 pairs=38 failures=0 min=4.28(与改动前一致,唯一 violations=1 为预存 item_following.xml,非本任务引入);自算新配对亮/暗双表最差 7.04:1(on_primary_container/primary_container),修复项由 #E2E2E9 on #F8F9FA 1.24:1 提升至 on #181C20 66.57:1。单元验证::app:testMobileArm64_v8aDebugUnitTest 定向 28 套件/391 项/0 失败/0 错误(UiStyleSourceTest、ThemeBaseWiringTest 等)。设备验证:dev3 192.168.50.3:5559 mobile arm64 debug 经 scripts/build_arm64_debug_install.sh 覆盖安装;用户新截图 QQ20260928-161219.png 实测弹窗卡片带由 #F8F9FA 变为 #181C20,5 张卡片文字与卡底对比度 25.11/42.89/47.16/—/42.42:1 均远超 WCAG AA 4.5:1,亮底 #F8F9FA 残留像素由 289004 降至 769;进程 pid=29138 FATAL EXCEPTION=0。

Task-Guard: DIALUIGHT-CARD-TOKENS-20260928
2026-09-28 16:16:03 +08:00
Silent1566
18fad44d66 fix(mobile): remove duplicate watched time below history cards
Verification: 手机版历史卡片下部“已看 mm:ss”重复行移除:HistoryAdapterTest 2/2 通过(新增 mobileHistoryCardDoesNotRepeatWatchedTimeBelowThePoster,且基线代码对该断言计数=2 证明有判别力),grep 确认 historyProgress 无残留,APK 覆盖安装到签名一致的 emulator-5560 后 dumpsys activity top 视图树中 historyProgress=0、history_info 仅剩 name+remark、playback 封面标签仍在,截图为证

Task-Guard: ui-remove-duplicate-history-watched-mobile
2026-09-28 16:03:10 +08:00
Silent1566
908774597a
Merge pull request #383 from Silent1566/dev4
fix: 修复切集加载期旧集残留位置回写新集进度(偶现根因) + 合并 beta 最新代码
2026-09-28 12:30:46 +08:00
www.xueximeng.com
e092453640 Merge remote-tracking branch 'origin/main' into Silent1566 2026-09-28 12:21:52 +08:00
Silent1566
a6a470c2fa fix(mobile): 统一深色语义表使设置页文字颜色一致
mobile flavor 的 values/webhtv_tokens.xml 编译的是亮色表,而 BaseActivity 为每个
Activity 插入全屏 CustomWallView 使页面背景始终为深色壁纸,导致 ?attr/colorOnSurface
解析为近黑 #1A1C1E,工具栏标题与矢量图标却仍是硬编码白色,同一页面两种文字色。

改为与 leanback flavor 及 main/res/values-night 逐字节同值的深色表,两个 flavor 由此
对称:前景角色统一为亮色,夜间与非夜间模式解析同一张表。

新增守门测试断言 mobile/leanback/night 三张表颜色值一致且等于深色表,防止未来只把
单个 flavor 改回亮色。设备实测:13 行文案像素全部为 #E2E2E9,与标题一致。

Verification: mobile 统一深色语义表修复设置页半黑半白。目的:设置页部分文字为亮色(标题/返回箭头/图标硬编码 white)而部分为近黑(?attr/colorOnSurface),同一页两种文字色不协调。根因:BaseActivity 为每个 mobile Activity 在 content 下插入全屏 CustomWallView,内置壁纸全部为深色,页面背景实际是壁纸而非 webhtv_color_surface;但 mobile flavor 的 values/webhtv_tokens.xml 仍是亮色表(main/res/values 的 #1A1C1E),而 leanback flavor 的 values/ 直接编译深色表故无此缺陷。修复:app/src/mobile/res/values/webhtv_tokens.xml 改为与 leanback 及 main/res/values-night 逐字节同值的深色表。单元验证::app:testMobileArm64_v8aDebugUnitTest ThemeBaseWiringTest 7/7 通过(含新增守门测试 bothFlavoursCompileTheSameDarkTableAsTheNightPalette)、ThemeContractTest 5/5 通过;反向验证把 mobile tokens 换回亮色表时新守门测试 FAILED(18s) 证明断言有效。设备验证:dev3 192.168.50.3:5559,mobile arm64 debug 经 scripts/build_arm64_debug_install.sh 覆盖安装,uimode night=no,冷启动进入设置页;uiautomator dump 13 行文案完整无崩溃,像素采样全部文字/值/图标为 #E2E2E9(亮度228.3) 与标题一致,修复前 '点播''薰衣水晶' 等为 #1A1C1E(亮度28)。

Task-Guard: MOBILE-DARK-TOKENS-20260928
2026-09-28 12:04:47 +08:00
www.xueximeng.com
e24d6db5cd fix(cache): refresh settings cache value right after cleanup
Verification: cache package independent JUnit 57/57 OK; :app:compileMobileArm64_v8aDebugJavaWithJavac + :app:compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; device 5563 leanback arm64 debug covered install (applicationId temporarily patched to com.silent.android.webhtv, build.gradle restored byte-identical md5 c6ad2a8176046335686bac30be2c9c45): settings row refreshed immediately after LIGHT cleanup with NO page switch and NO activity recreate (same ActivityRecord t31, same pid 20949, logcat shows no SettingActivity START/Displayed) - row 22.1 MB -> 14.2 MB matching the reopened dialog summary '14.2 MB'; journal bytesBefore=17825792 bytesAfter=0 deletedFiles=2 COMPLETED matching the two fixtures; mobile flavor covered install then row 23.5 MB -> 14.5 MB under same ActivityRecord t32/pid 22345 stayed on settings tab, matching dialog summary '14.5 MB', journal bytesBefore=9437184 deletedFiles=1 COMPLETED; test fixtures and temp cache/restore-legacy dir removed, cache entry list restored, leanback flavor reinstalled on device; no FATAL EXCEPTION

Task-Guard: CACHE-MGMT-01-settings-refresh-after-cleanup
2026-09-28 11:52:26 +08:00
Silent1566
9554195c98 docs(beta-sync): 记录 dev3 合并 beta 的提交、tag 与 PR #382
Verification: 文档收尾:记录合并提交 c900dac20 与双父提交、recovery tag、origin/dev3 推送结果与 PR #382 编号;仅改动 docs/BETA-SYNC-DEV3-20260927.md,未重新构建或重跑测试

Task-Guard: BETA-SYNC-DEV3-20260927-CLOSE
2026-09-28 11:06:02 +08:00
Silent1566
c900dac20e merge: 合并 origin/beta 最新代码并保留 dev3 统一主题系统
Verification: 双 flavor 全量单测 BUILD SUCCESSFUL(mobile 5032 项 / leanback 4160 项,0 failure 0 error);双 flavor Java 编译通过;git diff --cached --check 通过且无冲突标记残留;mobile arm64 debug 打包成功并以 adb install -r 覆盖安装;dev3 机位 192.168.50.3:5559 冷启动与主题编辑器实测无崩溃;被剔除提交 5682f2b05 经 merge-base --is-ancestor 判定非 origin/beta 祖先

Task-Guard: BETA-SYNC-DEV3-20260927
2026-09-28 11:04:41 +08:00
Silent1566
96a4546b77 Merge remote-tracking branch 'origin/beta' into dev4 2026-09-28 09:27:15 +08:00
Silent1566
b55d52bf64 修复切集加载期旧集残留位置回写新集进度(偶现根因)
Verification: 第二轮修复实测: 5561(用户偶现同款环境, 红果短剧详情直放)修复前抓到 plan ready resumeMs 跨集延续铁证(139218连两集/110396>本集时长91710/84595连两集); 根因为 ExoPlayer stop 后 getPosition() 返回旧集残留位置, 新集 READY 前 tick 经 inlineStartPositionApplied回退分支 写入新集 history; 引入 inlinePlayerMediaReady 媒体就绪守卫(stop前置false/STATE_READY置true/清空置true, 三处写入守卫升级为 settled&&mediaReady 双条件)后 testMobileArm64_v8aDebugUnitTest TmdbDetailActivityLayoutTest 129/129 通过; assembleLeanbackArm64_v8aDebug 编译成功; 5561 覆盖安装实测: 三轮手动快滑切集起播位置全部 positionMs=0(上一集71秒位置无污染), 自动连播 78563→0 连续推进无位置跳变事件

Task-Guard: fix-inline-switch-position-r2-20260928
2026-09-28 04:30:05 +08:00
Silent1566
163cdf037d
Merge pull request #381 from Silent1566/dev4
修复详情直放与沉浸融合模式切集沿用上一集播放进度
2026-09-27 21:46:34 +08:00
Silent1566
5306e1bce1 修复详情直放与沉浸融合模式切集沿用上一集播放进度
Verification: testMobileArm64_v8aDebugUnitTest: TmdbDetailActivityLayoutTest 129/129 通过(含新增回归测试 inlineEpisodeSwitchDoesNotCarryPreviousEpisodePosition); assembleLeanbackArm64_v8aDebug 编译通过; emulator-5556 覆盖安装实测: 详情直放手动切下一集从0开始(第3集6:29→第4集从0连续播放)、片尾自动连播从0开始、同集退出重进续播381946ms正常、切集窗口期进度/缓存写入守卫生效

Task-Guard: fix-inline-switch-position-20260927
2026-09-27 21:01:25 +08:00
www.xueximeng.com
5e59ef54d9 fix(cache): clean legacy paths by a versioned rule table
Verification: cache package independent JUnit 57/57 OK; :app:compileMobileArm64_v8aDebugJavaWithJavac + :app:compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; device 5563 leanback arm64 debug covered install (applicationId temporarily patched to com.silent.android.webhtv, build.gradle restored byte-identical md5 c6ad2a8176046335686bac30be2c9c45): legacy row no longer misreports live caches (was 'legacy/orphan 17MB/93 files', now 'diagnostic logs 13.3MB/5', 'unclassified 3.7MB/87' owner-managed, 'legacy paths 20 bytes/2'); legacy MODULE clean reported 20 bytes/2 files and both fixtures actually deleted (journal bytesBefore=20 bytesAfter=0 deletedFiles=2 COMPLETED); diagnostic logs MODULE clean released 13.3MB/3 files (journal 13987893->4048) with live segment recreated by owner; L1 deleted 0 files and L3 deleted 16 files without touching diagnostic logs or the 82 plugin-preheat files; no FATAL EXCEPTION, pid stable

Task-Guard: CACHE-MGMT-01-legacy-orphan-cleanup-fix
2026-09-27 19:07:38 +08:00
Silent1566
09f8882251
Merge pull request #380 from Silent1566/dev1
合并 dev1:站点注入首页按钮默认不开启并完成 beta 同步复核
2026-09-27 11:17:07 +08:00
Silent1566
dee7979040 fix(webhome): restore Eclipse token root scope
Verification: WebThemeTokenSourceTest passed: ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.web.WebThemeTokenSourceTest; git diff --check passed; no top-level --eclipse-color/--eclipse-detail-color declarations remain

Task-Guard: WEBHOME-ECLIPSE-CSS-20260927
2026-09-27 11:13:40 +08:00
Silent1566
08d6d3e0de docs: 补录 dev1 未推送提交组合回归验证证据
Verification: docs-only: 补录未推送提交组合回归证据(QuickAdapterSelectionTest 2/2 + SiteAdapterSelectionTest 1/1 + SiteInjectHomeButtonSourceTest 4/4 + SiteInjectHomeButtonDefaultStateTest 5/5,合计 12 项 0 failure/0 error,BUILD SUCCESSFUL in 36s)

Task-Guard: beta-merge-dev1-review-verify
2026-09-27 11:12:52 +08:00
Silent1566
b32406d7ad docs: 记录 dev1 合并 beta 后的复评与站点注入默认态实测
Verification: review: origin/beta@b5a110516 已是 dev1 祖先(dev1..origin/beta=0),净差异仅 2 提交/4 文件;被剔除提交 5682f2b05 非 dev1 祖先;2 轮复评无必须修改项;unit SiteInjectHomeButtonSourceTest 4/4 + SiteInjectHomeButtonDefaultStateTest 5/5 通过;compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL;build_arm64_debug_install.sh leanback/192.168.50.3:5555 打包 129 tasks 成功并 adb install -r 覆盖安装;启动冒烟无 FATAL/ANR;设备清空 home_button key 后首页功能按钮行 7 项不含站点注入;设备 prefs 已回滚 md5 一致

Task-Guard: beta-merge-dev1-review-20260927
2026-09-27 11:10:05 +08:00
Silent1566
3aae6d9e5b docs(tv): 记录站点注入默认不开启的设备实测
Verification: docs-only: 记录 2026-09-27 设备实测证据(新安装默认态首页功能按钮行 7 项不含站点注入;重置后个性设置->首页按钮摘要 6/10 且站点注入未勾选;手动勾选后 7/10 且首页出现 站点注入:开),设备 prefs 已按备份原样恢复并校验 md5 一致

Task-Guard: TV-CUSTOM-CSP-DEFAULT-OFF-doc
2026-09-27 10:39:08 +08:00
Silent1566
2072bf4d65 fix(tv): 站点注入首页按钮默认不开启
Verification: tv-device: 192.168.50.3:5555 leanback debug 覆盖安装后 个性设置→首页按钮 重置默认值摘要由 8/10 变为 6/10 且 站点注入 复选框未勾选,仍可手动勾选(7/10)并在首页出现「站点注入:开」;unit: SiteInjectHomeButtonSourceTest 4/4 + SiteInjectHomeButtonDefaultStateTest 5/5;regression: testLeanbackArm64_v8aDebugUnitTest 全量 4051 tests 0 failure 0 error;compile: compileLeanbackArm64_v8aDebugJavaWithJavac

Task-Guard: TV-CUSTOM-CSP-DEFAULT-OFF
2026-09-27 10:36:03 +08:00
Silent1566
b5a1105163
Merge pull request #379 from Silent1566/dev2
同步 beta 并复审 dev2 选源高亮
2026-09-27 10:08:35 +08:00
Silent1566
7465b91f0b docs: 记录 beta 合并与 dev2 选源高亮复审
Verification: .:app:testLeanbackArm64_v8aDebugUnitTest --tests QuickAdapterSelectionTest --tests SiteAdapterSelectionTest 通过;git diff --check origin/beta..HEAD 通过

Task-Guard: beta-merge-dev2-review-20260927
2026-09-27 09:55:45 +08:00
Silent1566
1a27828360 Merge remote-tracking branch 'origin/beta' into dev2 2026-09-27 09:16:35 +08:00
Silent1566
746c015908 docs(theme): record scrim pixel verification commit
Verification: 仅文档闭环:记录 THEME-SCRIM-PIXEL-20260927 的提交 401b65b83f 和 recovery tag;无代码/构建变更。

Task-Guard: THEME-SCRIM-PIXEL-DOC-20260927
2026-09-27 03:50:48 +08:00
Silent1566
401b65b83f test(theme): prove scrim opacity changes only the modal scrim
Verification: THEME-SCRIM-PIXEL-20260927: dev3 192.168.50.3:5559 上新增 debug-only 宿主和 instrumentation 像素测试;scrimOpacity 未设置遮罩采样 #ff9fbad6,最大值 0.85 为 #ffd5e0ed,弹窗面板中心均为 #fff4f7fa;测试 OK (1 test),无生产运行时行为修改。

Task-Guard: THEME-SCRIM-PIXEL-20260927
2026-09-27 03:46:58 +08:00
www.xueximeng.com
5f2277724c fix(cache): restore focus to cleanup trigger
Verification: 双形态 Java 编译通过;5563 覆盖安装实测确认框关闭后焦点回到原 Exo 清理按钮;git diff --check 通过

Task-Guard: CACHE-MGMT-01-CONFIRM-RETURN-FOCUS
2026-09-27 01:23:30 +08:00
Silent1566
e55f03f57f
Merge pull request #378 from Silent1566/dev1
合并 dev1:同步 beta 代理重定向修复并复评配置/站点主题改动
2026-09-27 01:03:07 +08:00
Silent1566
e55a3a3f78 docs(c25): record delivery coordinates
Verification: Docs-only closure: recorded commit 720d2bcbd3, recovery tag recovery/C25-beta-sync-review-dev1-20260926/20260927005542-720d2bcbd3d1 and PR https://github.com/Silent1566/webhtv/pull/378; no code, build or test change in this commit

Task-Guard: C25-beta-sync-review-dev1-20260926-doc-closure
2026-09-27 00:56:48 +08:00
Silent1566
720d2bcbd3 chore(tv): remove superseded config name drawables
Verification: Mobile targeted tests ProxyRedirectResponseTest 8/8, ProxyRangeResponsePolicyTest 4/4, SiteDialogThemeSourceTest 7/7, InterfaceEntryInteractionTest 10/10 passed; :app:compileLeanbackArm64_v8aDebugJavaWithJavac passed; leanback Arm64 debug APK assemble succeeded; adb install skipped because 192.168.50.3:5555 and all assigned emulators are offline; removed commits 5682f2b054 and 2faa1f37757f absent from merge result; superseded drawables have no remaining references

Task-Guard: C25-beta-sync-review-dev1-20260926
2026-09-27 00:55:42 +08:00
Silent1566
c1645a3745 merge: sync beta proxy redirect fixes (PR #377) 2026-09-27 00:49:41 +08:00
Silent1566
ec6730cc88
Merge pull request #377 from Silent1566/dev4
合并 dev4 最新修复到 beta
2026-09-26 23:08:50 +08:00
Silent1566
1e278a3a7f Fix short proxy responses and redirect tests
Verification: ProxyRedirectResponseTest and ProxyRangeResponsePolicyTest passed; git diff --check passed; second review passed

Task-Guard: beta-sync-review-20260926
2026-09-26 23:04:55 +08:00
Silent1566
83e0a3159b fix(tv): keep config name border visible
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.dialog.InterfaceEntryInteractionTest BUILD SUCCESSFUL; scripts/build_arm64_debug_install.sh --flavor leanback --serial 192.168.50.3:5555 build+install success; device verified normal/focused borders and DPAD_DOWN keeps name column, DPAD_RIGHT enters same-row edit column

Task-Guard: TV-CONFIG-NAME-BORDER-CONTRAST
2026-09-26 22:21:01 +08:00
Silent1566
e65243842b fix(tv): make selected site cards readable in site dialog
Verification: SiteAdapterSelectionTest passed; assembleLeanbackArm64_v8aDebug passed; 5557 pixel verification: selected site cell now 30129 px #381E72 fill with #FFFFFF text

Task-Guard: tv-site-dialog-contrast
2026-09-26 20:56:55 +08:00
www.xueximeng.com
689c2b5b37 feat(cache): migrate cache policy schema
Verification: CachePolicyStoreTest 3/3 通过;:app:compileMobileArm64_v8aDebugJavaWithJavac 与 :app:compileLeanbackArm64_v8aDebugJavaWithJavac 通过

Task-Guard: CACHE-MGMT-01-SCHEMA
2026-09-26 19:29:28 +08:00
Silent1566
67849819db Fix Hongguo redirect proxy responses
Verification: ProxyRedirectResponseTest passed; assembleLeanbackArm64_v8aDebug passed; device 127.0.0.1:5561 Hongguo proxy now returns 302 Location and target returns 200; app entered VideoActivity/ExoPlayer session

Task-Guard: fix-hongguo-302-proxy
2026-09-26 18:36:22 +08:00
www.xueximeng.com
45734f7b48 fix(cache): focus cancel by default in cleanup confirmations
Verification: 双形态 Java 编译通过;5563 覆盖安装实测:清理确认框打开后无障碍树 focused 立即为 android.widget.Button '取消'(修复前 focused 为空,按确认键无反应);预置 4 个过期 .tmp 后单击一次确认键,文件数清理前后均为 4、确认框关闭且未清理,证明单次确认键执行的是「取消」;深度清理第一步同样默认聚焦「取消」;测试文件已清理

Task-Guard: CACHE-MGMT-01-CONFIRM-FOCUS
2026-09-26 15:33:07 +08:00
Silent1566
677aca7156 docs(theme): refresh the plan status and its single next action
Brings the plan's authoritative summary back in line with what is actually committed. The status line now records that the remaining two matrix rows were closed (playback regression in 4.27, TV remote in 4.29, with WebHome left independent by the user's decision), that the TV theme channel's wholesale failure was located and fixed in 4.28 (the baseline was inferred from uiMode instead of the palette the flavour actually compiles), and that two real defects were then fixed: semantic attrs that were declared but assigned by no theme, which crashed several dialogs during inflation (4.30), and the standalone input style inheriting a container style, which left the fields borderless with clipped text (4.31). The single next action now states the real position: scrimOpacity is wired per the approved path 1 (task THEME-SCRIM-WIRE-P1-20260926, commit 350ea614b, implementation and acceptance recorded in docs/THEME-SCRIM-OVERLAY-20260925-design.md sections 6 and 13) - both dialogs keep their own scrim hue and only let the user drive alpha, a strict no-op while unset - with one device-level pixel confirmation still outstanding (compare the two extreme scrimOpacity values on a device that can open the TMDB person dialog and show a non-zero difference in the scrim region); overlayOpacity has no semantically correct native landing site and is now labelled web-theme-only in the editor; and the remaining uncovered areas are translucent-fill matching, StateListDrawable/RippleDrawable backgrounds and remote web theme pages. The stale task id and doc reference were corrected to the actual commit and standalone document.

Task-Guard: THEME-PLAN-REFRESH-20260926

Verification: 刷新主题计划的权威状态与下一步(仅文档,无代码变更)。当前状态行补充:4.7 设备矩阵其余两行已收口(播放回归 4.27、TV 遥控 4.29,WebHome 经用户决定独立)、TV 主题通道整体失效已定位并修复(4.28,根因是基线误用 uiMode 推断而非按 flavor 实际编译的调色板)、此后修复两个真实缺陷(4.30 声明但无主题赋值的语义属性导致多个弹窗 inflate 崩溃;4.31 独立输入框误用容器样式导致无边框且文字裁切)。下一步唯一动作改为本轮真实结论:scrimOpacity 已按用户批准的路径 1 接线完成(任务 THEME-SCRIM-WIRE-P1-20260926,提交 350ea614b,实现与验收记录在 docs/THEME-SCRIM-OVERLAY-20260925-design.md 第 6/13 节;两个弹窗保留自身遮罩色相、仅由用户控制 alpha、未设置时严格 no-op),仍缺一次设备级像素确认(需在有内容源且能打开 TMDB 人物详情弹窗的设备上取两极值比对遮罩区差异>0);overlayOpacity 经查无语义正确的原生落点,已在编辑器标注仅 Web 主题生效;其余未覆盖项为半透明填充匹配策略、StateListDrawable/RippleDrawable 背景、远程 Web 主题页面。原第二项引用的任务 ID 与文档链接已校准为实际提交号与独立文档路径。

Task-Guard: THEME-PLAN-REFRESH-20260926
2026-09-26 15:18:46 +08:00
Silent1566
6cdd491fed docs(theme): record the standalone input style fix
Documents 4.31, the record for 336c471b8 whose code and tests were already committed and verified; this commit only writes the acceptance record. It captures the root cause (the unification commit pointed these fields at Widget.WebHTV.Input, whose parent was a TextInputLayout container style, while none of the layouts contains a TextInputLayout, so nothing drew a border, and the style it replaced was the one carrying @drawable/selector_dialog_input plus the text metrics - together producing borderless fields with clipped text inside their fixed 44dp heights), the fix (Widget.WebHTV.Input is now a standalone field style drawing its own themed background and text colours, matching the bare AppCompatEditText fields already used correctly in dialog_ad_block_stats, with no new resources), the new UiStyleSourceTest guard, the device evidence from real taps on dev3 5559 for both the TMDB data configuration and AI general configuration dialogs, the style test result, and the rollback anchor.

Task-Guard: THEME-INPUT-STYLE-DOC-20260926

Verification: 文档记录 4.31(补 336c471b8 的验收记录,代码与测试已在上一提交完成并验证,本次只写文档)。内容:记录根因(0af2340d4 把输入框样式换成 Widget.WebHTV.Input,而后者父为 TextInputLayout 容器样式;这些布局无 TextInputLayout 故无边框,且被替换的旧样式自带 @drawable/selector_dialog_input 与文字样式,叠加导致固定 44dp 内文字裁切)、修复内容(Widget.WebHTV.Input 改为独立输入框样式,自带主题感知背景 ?attr/colorPrimary/colorOutline/colorSurfaceContainerHighest、textColor/textColorHint/cursor/14sp,与 dialog_ad_block_stats 既有正确做法一致,无新增资源)、守门测试 UiStyleSourceTest#standaloneInputStyleProvidesItsOwnBox、设备证据(dev3 5559 真实点击:TMDB 数据配置 与 AI 通用配置 两个弹窗输入框均恢复边框与完整文字,FATAL=0)、自动化证据(:app:testMobileArm64_v8aDebugUnitTest --tests 'com.fongmi.android.tv.ui.style.*' BUILD SUCCESSFUL)与回滚锚点。本任务仅文档,无代码或资源变更。

Task-Guard: THEME-INPUT-STYLE-DOC-20260926
2026-09-26 13:05:04 +08:00
Silent1566
336c471b8c fix(ui): give standalone dialog inputs their own box again
The unification commit moved the TMDB/AI input fields from Widget.WebHTV.LightDialog.Input to Widget.WebHTV.Input, whose parent was Widget.Material3.TextInputLayout.OutlinedBox - a TextInputLayout container style that draws the outline on the container and reserves space for a floating label. Two facts made that a total failure: none of those layouts contains a TextInputLayout (19 bare TextInputEditText fields across dialog_tmdb_source, dialog_ai_config, dialog_ai_prompt_config and dialog_group_rule_edit), so nothing drew a border at all; and the style it replaced was the one carrying android:background=@drawable/selector_dialog_input plus the text size, colour and cursor. The result was exactly what was reported - borderless fields whose text is squeezed and clipped inside their fixed 44dp heights. Widget.WebHTV.Input is now a standalone field style with no container parent, drawing @drawable/selector_dialog_input (whose focused and default states resolve through ?attr/colorPrimary, ?attr/colorOutline and ?attr/colorSurfaceContainerHighest) and setting ?attr/colorOnSurface text, ?attr/colorOnSurfaceVariant hints, the themed cursor drawable and 14sp - the same shape the tree already uses correctly elsewhere, such as the bare AppCompatEditText fields in dialog_ad_block_stats, with no new resources. UiStyleSourceTest#standaloneInputStyleProvidesItsOwnBox parses the style and fails if it regains a TextInputLayout parent or loses its own box and text colour. Verified on dev3 192.168.50.3:5559 by real taps: Settings > TMDB > TMDB data configuration now shows bordered fields with the full API key, language, API host and image host no longer clipped, and Settings > AI service > AI general configuration shows the same recovery - FATAL EXCEPTION stayed 0 in both, confirming a class-wide fix rather than a one-off. The style unit tests pass. Documentation for this fix lives in a separate commit because it falls outside this session's declared scope.

Task-Guard: THEME-INPUT-STYLE-REGRESSION-20260926

Verification: 修复 4.31 输入框无边框且文字裁切。根因(0af2340d4 引入):该提交把 TMDB/AI 等布局的输入框样式从 Widget.WebHTV.LightDialog.Input 换成 Widget.WebHTV.Input,而后者父样式是 Widget.Material3.TextInputLayout.OutlinedBox——这是给 TextInputLayout 容器用的样式,描边由容器绘制、内边距为浮动标签预留。但 (1) 这些布局里根本没有 TextInputLayout(dialog_tmdb_source/dialog_ai_config/dialog_ai_prompt_config/dialog_group_rule_edit 共 19 处全是裸 TextInputEditText),没有任何东西画边框;(2) 被替换掉的旧样式恰恰含 android:background=@drawable/selector_dialog_input(可见输入框外观)以及 textSize/textColor/cursor。两者叠加 = 无边框 + 文字在固定 44dp 高度里被挤压裁切,与用户截图一致。修复:Widget.WebHTV.Input 改为独立输入框样式(不再继承任何容器样式),自带 android:background=@drawable/selector_dialog_input(焦点/默认两态,颜色为 ?attr/colorPrimary / ?attr/colorOutline / ?attr/colorSurfaceContainerHighest)、android:textColor=?attr/colorOnSurface、android:textColorHint=?attr/colorOnSurfaceVariant、@drawable/shape_cursor_dialog、14sp;该写法与仓库既有正确做法一致(dialog_ad_block_stats 三套 source set 的独立输入框就是裸 AppCompatEditText + 同一 drawable),未新增资源。守门测试 UiStyleSourceTest#standaloneInputStyleProvidesItsOwnBox:解析 Widget.WebHTV.Input 定义,断言父样式不得含 TextInputLayout、必须自带 @drawable/selector_dialog_input、必须自带 android:textColor。设备验证(dev3 192.168.50.3:5559,覆盖安装,真实点击路径):设置→TMDB→「TMDB 数据配置」输入框恢复可见圆角边框,API Key 304ca56b1b7b57ca7a47d9b59946be94、语言 zh-CN、API 域名、图片域名均完整显示不再裁切,排版正常,FATAL EXCEPTION=0;设置→AI 服务→「AI 通用配置」同类输入框同样恢复边框与完整文字,FATAL=0,证明是整类修复而非单点。自动化::app:testMobileArm64_v8aDebugUnitTest --tests 'com.fongmi.android.tv.ui.style.*' BUILD SUCCESSFUL。注:本次提交只含代码与测试;配套文档 4.31 记录因超出本会话声明范围,已另存为 /tmp/doc_431.patch 待下个文档任务提交。

Task-Guard: THEME-INPUT-STYLE-REGRESSION-20260926
2026-09-26 12:41:03 +08:00
Silent1566
3ebc9ccd4e fix(tv): highlight current site in quick source switcher
Verification: QuickAdapterSelectionTest passed (2 tests, leanbackArm64_v8aDebugUnitTest); assembleLeanbackArm64_v8aDebug passed; Robolectric binding test proves current site is activated and others are not

Task-Guard: tv-site-selection-style
2026-09-26 12:38:01 +08:00
Silent1566
1e41863c18 fix(tv): show config name button border
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.dialog.InterfaceEntryInteractionTest (BUILD SUCCESSFUL); task_guard check; git diff --check

Task-Guard: TV-CONFIG-NAME-BORDER
2026-09-26 11:34:11 +08:00
Silent1566
1bb72bd709 fix(tv): keep config action focus columns
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.dialog.InterfaceEntryInteractionTest (BUILD SUCCESSFUL); task_guard check; git diff --check

Task-Guard: TV-CONFIG-ACTION-FOCUS
2026-09-26 11:06:05 +08:00
Silent1566
f7674024e9 fix(tv): restore readable VOD source colors
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.dialog.SiteDialogThemeSourceTest (BUILD SUCCESSFUL); git diff --check

Task-Guard: TV-VOD-SITE-COLOR
2026-09-26 10:10:13 +08:00
Silent1566
44a0509d62 fix(theme): stop crashing when a semantic attr has no theme assignment
Fixes the reported crash: tapping TMDB data configuration under Settings > TMDB killed the app. The exception was UnsupportedOperationException: 'Failed to resolve attribute at index 3: TypedValue{t=0x2/d=0x7f040665}' thrown from TypedArray.getColorStateList inside TextView.readTextAppearance during inflation. That resource id resolves to attr webhtvColorOnSurface, and all five TextAppearance.WebHTV.* styles set android:textColor to the custom webhtvColorOnSurface / webhtvColorOnSurfaceVariant attrs, which webhtv_attrs.xml declares but no theme ever assigns - Theme.WebHTV only maps the standard Material attrs onto the same tokens. Reading such a text appearance therefore throws inside the View constructor, so every dialog using Widget.WebHTV.Label, Label.Secondary, Helper or SliderLabel crashed on open: dialog_tmdb_source, dialog_ai_config, dialog_ai_prompt_config, dialog_speed and dialog_buffer, not just the one reported. The fix points those five styles at the standard attrs the theme does assign, ?attr/colorOnSurface and ?attr/colorOnSurfaceVariant, which Theme.WebHTV maps to exactly the same token colours, so nothing changes visually while the dependency on unassigned attributes is removed. ThemeContractTest#everyReferencedSemanticAttrIsAssignedByATheme now scans all ?attr/webhtv* references across main/mobile/leanback and requires each to have a matching <item> assignment - it failed before the fix naming exactly [webhtvColorOnSurface, webhtvColorOnSurfaceVariant] and passes after - so the declarations and their assignments can never drift apart again. Verified on dev3 192.168.50.3:5559 through the user's original path: the TMDB data configuration dialog now opens and renders its title, source description, API key, language, API and image hosts and both buttons, process alive, FATAL EXCEPTION = 0, where it previously crashed every time. Theme and dialog unit suites pass, as does the leanback javac compile. The defect came from the earlier theme-integration commits and predates the scrim work, which did not touch these files.

Task-Guard: TMDB-DIALOG-INFLATE-CRASH-20260926

Verification: 修复用户报告的「设置 → TMDB → TMDB 数据配置」一按即崩。根因链(每环有证据):(1) 崩溃异常为 UnsupportedOperationException: Failed to resolve attribute at index 3: TypedValue{t=0x2/d=0x7f040665},抛于 TypedArray.getColorStateList ← TextView.readTextAppearance ← MaterialTextView.<init>,即 inflate 读 textAppearance 时崩溃;(2) 0x7f040665 在 app/build/intermediates/runtime_symbol_list/mobileArm64_v8aDebug/.../R.txt 解析为 attr webhtvColorOnSurface;(3) app/src/main/res/values/webhtv_type.xml 的 5 个 TextAppearance.WebHTV.* 把 android:textColor 指向 ?attr/webhtvColorOnSurface(Variant);(4) webhtv_attrs.xml 只声明这些属性,全仓库无任何主题赋值(grep 'name="webhtvColor' 过滤掉 format="color" 后为空),Theme.WebHTV 仅将标准 Material 属性映射到同一批 token(colorOnSurface→@color/webhtv_color_on_surface、colorOnSurfaceVariant→@color/webhtv_color_on_surface_variant)。影响面大于单点:Widget.WebHTV.Label/Label.Secondary/Helper/SliderLabel 全部受影响,dialog_tmdb_source、dialog_ai_config、dialog_ai_prompt_config、dialog_speed、dialog_buffer 等弹窗均会崩。修复:webhtv_type.xml 5 处改为主题确实赋值的标准属性 ?attr/colorOnSurface / ?attr/colorOnSurfaceVariant,二者在 Theme.WebHTV 中指向完全相同的 token 色,故视觉零变化,仅移除对无人赋值属性的依赖。守门测试:ThemeContractTest#everyReferencedSemanticAttrIsAssignedByATheme 扫描 main/mobile/leanback 三套 res 的全部 ?attr/webhtv* 引用并要求每个属性在 values/ 中找到 <item name=...> 赋值;该测试在修复前失败并精确报出 [webhtvColorOnSurface, webhtvColorOnSurfaceVariant],修复后通过。设备验证(按用户原始路径,dev3 192.168.50.3:5559,覆盖安装):设置 → TMDB → 「TMDB 数据配置」弹窗正常打开,渲染出标题、数据源说明、API Key、语言 zh-CN、API 域名、图片域名与取消/确定按钮,进程存活 pid 32224,FATAL EXCEPTION = 0;修复前该操作必崩(用户截图 09:37 与我抓取的 09:36/09:37 崩溃栈一致)。自动化::app:testMobileArm64_v8aDebugUnitTest --tests 'com.fongmi.android.tv.theme.*' --tests 'com.fongmi.android.tv.ui.dialog.*' BUILD SUCCESSFUL;:app:compileLeanbackArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL。归属:该缺陷源自主题整合提交(webhtv_attrs.xml/webhtv_type.xml,6cdc7dd36 等),非本轮 scrim 工作引入,后者未触碰这两个文件。无数据格式、偏好键或资源替换类变更。

Task-Guard: TMDB-DIALOG-INFLATE-CRASH-20260926
2026-09-26 09:46:28 +08:00
www.xueximeng.com
37eb7f4f0d docs(cache): correct update blocker root cause
Verification: 修正文档中不准确的「设备网络不可用」表述:宿主机与设备分别请求 api.github.com/.../releases/latest 均 403 且响应为 API rate limit exceeded;CNB 发布镜像 403 且 errmsg 为 Repo fish2035/webhtv-release has been frozen,证明是发布端/配额问题而非设备网络;据此换取更准确的阻塞描述

Task-Guard: CACHE-MGMT-01-UPDATE-ROOTCAUSE
2026-09-26 09:35:26 +08:00
Silent1566
f35b8dafdd
Merge pull request #376 from Silent1566/dev1
合并 dev1:同步最新 beta 并完善光影剧幕与 TMDB 语言边界
2026-09-26 09:10:37 +08:00
Silent1566
5058aaab2e merge: sync latest beta and review theme changes
Verification: Mobile targeted tests: TmdbSourcePayloadTest/TmdbLanguagePolicyTest/TmdbSourceCapabilityPlannerTest/TmdbSourceMergerTest 20/20 passed; TmdbDetailActivityLayoutTest/DetailModeControllerTest/TmdbCinemaThemeTest 144/144 passed; Mobile+Leanback Arm64 Debug Java compilation passed; Debug APK overwrite-installed on 192.168.50.3:5555 and app launched without fatal errors; removed commits excluded; Gradle daemon stopped

Task-Guard: C24-beta-sync-review-dev1-20260926
2026-09-26 04:45:47 +08:00
www.xueximeng.com
225a3fc374 fix(cache): keep panel open when closing during cleanup
Verification: 双形态 Java 编译通过;5563 覆盖安装实测(预置最多 120000 个 2 天前 .tmp 使清理持续数秒):清理中点击面板底部「确定/关闭」按钮后弹窗保持打开(无障碍树仍可读到「缓存管理」,无任何弹窗按钮),未出现修改前的直接关闭;此前已验证清理中 BACK 只请求取消且状态显示「缓存清理已取消」、空闲时 BACK 正常关闭;测试文件已清理

Task-Guard: CACHE-MGMT-01-CLOSE-CANCEL
2026-09-26 02:56:33 +08:00
Silent1566
350ea614bb feat(theme): drive modal scrim alpha from scrimOpacity
Implements the approved path 1 (preserve the default look) and the agreed overlayOpacity editor label. The dialogs keep their own scrim colour - translucent white in light mode, translucent black in dark - and the user drives only its alpha, because substituting the token's colorScrim (translucent black in both modes) was measured to invert the light-mode scrim from lightening to darkening. ThemeController gains configuredScrimOpacity(boolean light), which reads the user's explicitly configured alpha and returns null when unset - the distinction from 'the value happens to equal the default' is what keeps the shipped rendering byte-identical - plus a pure applyScrimOpacity(int base, Float opacity) that returns the base untouched for null and otherwise replaces only the alpha byte. The mobile EpisodeDetailDialog and TmdbPersonDialog overlay literals now route through it, delegating to the shared maths, and the overlayOpacity label states it applies to the web theme only in all three locales. Automated evidence: theme unit tests pass with three new contracts - the dialogs must keep 0x99F4F7FA and must never reference colorScrim(), with arithmetic asserted directly (null is a strict no-op, 0f/1f map to 0x00/0xFF alpha, out-of-range clamps, 0.85f yields 0xD9F4F7FA); the accessor distinguishes unset from a value equal to the token baseline (0.322f still reports as a user value); and the three locale labels exist. Regression coverage over theme.*, ui.dialog.*, EpisodeAdapterTest, and TmdbDetailActivityLayoutTest - all of which reference the changed files - passes, as do the mobile and leanback javac compiles. Honest gap: no device screenshot was captured because both dialogs are only reachable through the TMDB detail flow and this device currently has no watch history or site configuration, and the earlier 'network blocked' reading was itself wrong (TMDB drops ICMP and returned 401 only for a keyless request; with the device's configured key search/person returns 200 with real data), so the runtime pixel effect still lacks device confirmation and is recorded as such with a follow-up procedure.

Task-Guard: THEME-SCRIM-WIRE-P1-20260926

Verification: 按路径 1(保默认外观)实现 scrimOpacity 接线 + overlayOpacity 编辑器标注。设计:不把 token 的 colorScrim 灌进弹窗,而是保留每个弹窗自己的遮罩色相,只让用户控制 alpha,从而默认外观逐字节不变。实现:ThemeController 新增 configuredScrimOpacity(boolean light)——读取 profile 中用户显式设置的 scrim alpha,未设置返回 null(关键:必须区分'未设置'与'恰好等于默认值'),并按调用方自己解析的 light/dark 选择槽集;新增纯函数 applyScrimOpacity(int base, Float opacity)——null 原样返回、否则只替换 alpha 字节保留 RGB。mobile EpisodeDetailDialog.java:289 与 TmdbPersonDialog.java:372 的 overlay 由字面量改为 applyScrimOpacity(light ? 0x99F4F7FA : <深色字面量>, light),本地 helper 委托共享函数。overlayOpacity 的 label 在三份 strings.xml 追加'仅 Web 主题生效'。自动化证据(全部通过):(1) :app:testMobileArm64_v8aDebugUnitTest --tests 'com.fongmi.android.tv.theme.*' BUILD SUCCESSFUL,新增 3 条契约——scrimOpacityKeepsTheDialogOwnColourAndOnlyMovesAlphaWhenSet 断言两弹窗保留 0x99F4F7FA、必须不出现 colorScrim()、必须委托共享函数,并直接断言算术行为(null 严格等于 0x99F4F7FA/0xB3000000、0f→0x00F4F7FA、1f→0xFFF4F7FA、-1f/2f 被夹取、0.85f→0xD9F4F7FA);configuredScrimOpacityReportsUnsetSeparatelyFromAnyValue 用反射注入 profile,断言未设置→null、仅设 light 时 dark 仍 null、设成 0.322f(等于 token 基线)仍报用户值而非未设置;overlayOpacityIsLabelledAsWebOnly 断言三语文案。(2) 回归:同任务覆盖 theme.*、ui.dialog.*、EpisodeAdapterTest、TmdbDetailActivityLayoutTest 全部通过(这些测试直接引用被改动文件)。(3) 双 flavor :app:compileMobileArm64_v8aDebugJavaWithJavac 与 :app:compileLeanbackArm64_v8aDebugJavaWithJavac 均 BUILD SUCCESSFUL。如实记录的验证缺口:未取得设备端截图证据——两弹窗仅能从 TMDB 详情路径进入,当前设备 192.168.50.3:5559 无观看历史、无站点配置,无法在合理成本内到达;同时排除了'网络阻塞'误判(TMDB 封 ICMP 且裸请求缺 api_key 才 401,用设备内配置 key 实测 search/person 返回 200 带真实数据),故该弹窗在有内容源设备上应可打开但本次未实测。因此 scrimOpacity 目前只有单元级行为证据、缺设备级像素确认,已在文档 13.3 节明确标注并给出后续补验步骤(设两极值打开 TmdbPersonDialog,遮罩区差异应>0、分隔线应 0 差异)。设备偏好已按字节还原为基线 /tmp/sc_base.xml。

Task-Guard: THEME-SCRIM-WIRE-P1-20260926
2026-09-26 02:55:25 +08:00
Silent1566
5e51a2c0eb Merge remote-tracking branch 'origin/beta' into dev1 2026-09-26 01:11:02 +08:00
Silent1566
b15b6cdfe0
Merge pull request #375 from Silent1566/dev4
合并远端 beta 并修正移动端全局历史点击
2026-09-26 00:56:52 +08:00
Silent1566
8da7bd6253 fix(detail): add light cinema text plate
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests TmdbDetailActivityLayoutTest passed (128 tests)

Task-Guard: CINEMA-LIGHT-TEXT-PLATE
2026-09-26 00:56:27 +08:00
www.xueximeng.com
f7d8327548 fix(cache): treat BACK as cancel request while cleanup runs
Verification: 双形态 Java 编译通过;5563 覆盖安装实测(预置 30000 个 2 天前 .tmp 使清理持续数秒):清理中按 BACK 面板不关闭、清理被取消且状态行显示「缓存清理已取消」;空闲时按 BACK 正常关闭弹窗回到设置页;测试文件已清理,cache 恢复 21MB

Task-Guard: CACHE-MGMT-01-BACK-CANCEL
2026-09-26 00:39:30 +08:00
Silent1566
4d0fb65e09 Merge remote-tracking branch 'origin/beta' into dev4 2026-09-26 00:03:17 +08:00
Silent1566
682a90cc99 docs(theme): record that the scrim replacement would regress the default look
Assessment-only follow-up, no code change. The corrected wiring path from the previous note - replacing the runtime overlay literals in mobile EpisodeDetailDialog and TmdbPersonDialog with ThemeController.current().colorScrim() - turns out to alter the default appearance and is therefore a regression that must not be applied as-is. Quantified: mobile EpisodeDetailDialog light uses 0x99F4F7FA (white, alpha 0.600) while the token baseline is 0x52000000 (black, alpha 0.322), differing in hue, brightness and strength; its dark value 0xB3000000 (alpha 0.702) differs in strength from 0x8A000000 (alpha 0.541); TmdbPersonDialog light likewise uses 0x99F4F7FA versus the black baseline; and its dark 0x8F000000 (0.561) is within 0.02 of the baseline. In other words these two dialogs use a translucent white scrim in light mode where colorScrim is translucent black, so a straight swap would turn a lightening overlay into a darkening one. Two viable paths are recorded instead: preserve the default look by keeping the literals as the default and only overriding when the user explicitly sets scrimOpacity (which requires exposing whether the slot was overridden, since colorScrim currently bakes the user value into alpha and cannot distinguish a user-set 0.322 from unset), or accept a deliberate visual unification onto the colorScrim baseline as a separately reviewed visual change. Conclusion: the real blocker for scrimOpacity is not finding a consumer but the mismatch between the current default scrim colour and the token baseline, so nothing should be implemented until a path is chosen; this task stays unimplemented. The plan's next-action line was updated to match and the document's section ordering was normalised to 10, 11, 12.

Task-Guard: THEME-SCRIM-DEFAULT-20260925

Verification: 进一步阻塞复核(仅文档,无代码变更)。第 11.3 节提出的修正路径——把 mobile EpisodeDetailDialog 与 TmdbPersonDialog 的运行时 overlay 字面量换成 ThemeController.current().colorScrim()——经色值量化后确认会改动默认外观,属回归,不能直接实施。逐项对照:mobile EpisodeDetailDialog 浅色现有 0x99F4F7FA(白,alpha 0x99=0.600)对 token 基线 0x52000000(黑,alpha 0x52=0.322),色相/明暗/强度全不同;深色现有 0xB3000000(alpha 0.702)对基线 0x8A000000(alpha 0.541),强度不同;TmdbPersonDialog 浅色现有 0x99F4F7FA 对 0x52000000,同样全不同;深色现有 0x8F000000(0.561)对 0x8A000000(0.541),仅差 0.02。即浅色下这两个弹窗用的是半透明白遮罩,而 colorScrim 基线是半透明黑,直接替换会把浅色弹窗由提亮变压暗。据此记录两条可选路径:路径1保默认外观——保留字面量作默认,仅在用户显式设置 scrimOpacity 时才覆盖,代价是需把'用户是否覆盖'暴露到 ThemeTokens(当前 colorScrim 已把用户值烘进 alpha,无法区分'用户设为 0.322'与'未设置');路径2接受外观统一——把默认遮罩统一到 colorScrim 基线,属有意的视觉变更,须独立评审。结论:scrimOpacity 接线的真正阻塞不是找消费点,而是现有默认遮罩色与 token 基线不一致;在选定路径前不应实施,本任务保持未实施状态。已同步修正主文档下一步描述。文档节序整理为 10→11→12,无重复编号。本任务未运行构建或设备测试(纯文档)。

Task-Guard: THEME-SCRIM-DEFAULT-20260925
2026-09-25 23:44:31 +08:00
Silent1566
8d07e76093 fix: unlock mobile global history setting taps
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.history.GlobalHistorySettingSourceTest BUILD SUCCESSFUL; git diff --check PASS

Task-Guard: global-history-resume-touch-lock
2026-09-25 23:28:06 +08:00
Silent1566
a47a9bf1bc
Merge pull request #374 from Silent1566/dev2
追更全量刷新与元数据展示
2026-09-25 23:03:52 +08:00
Silent1566
68df600518 docs(theme): falsify the scrim wiring premise and correct the plan
Assessment correction, no code change: while implementing, verification of where each dialog actually obtains its scrim colour disproved the premise the plan rested on, so the implementation was aborted and every code edit reverted (ThemeRole.java, ThemeBinder.java and the two layouts restored; SCRIM hits 0, webhtv:scrim hits 0, worktree otherwise clean). The three full-screen dialogs do declare android:background=@color/webhtv_color_scrim in XML, but all of them overwrite it at runtime with hardcoded literals: mobile EpisodeDetailDialog.java:290,297 sets root.setBackgroundColor(light ? 0x99F4F7FA : 0xB3000000), TmdbPersonDialog.java:372,383 sets view.setBackgroundColor(light ? 0x99F4F7FA : 0x8F000000), leanback EpisodeDetailDialog.java:316,322 sets root.setBackgroundColor(light ? 0xFFFFFFFF : 0xFF101214) which is an opaque page colour rather than a scrim, and TmdbVideoPlayerDialog.java:322 sets container.setBackgroundColor(Color.BLACK) in fullscreen. Those XML declarations are therefore dead code at runtime, and the only live webhtv_color_scrim uses are not scrims at all (two 1dp dividers plus an 88dp row background per source set in dialog_ad_block_stats, and a poster placeholder in dialog_receive), so no live scrim consumer driven by that token exists today. Consequently the original design - add ThemeRole.SCRIM, teach ThemeBinder about ColorDrawable, tag the layouts - would not have had any effect even if implemented. The corrected wiring is the four-line change that assigns the colour in the first place: replace the two runtime overlay literals with ThemeController.current().colorScrim(), which already carries the user's scrimOpacity because ThemeResolver writes withAlpha(base.colorScrim(), scrimOpacity). That touches two Java files outside this session's declared scope, so it is left for an approved follow-up. Recorded the falsification, the corrected path, the now-unnecessary binder work (cost drops from medium to low) and the lesson - evaluating a token's landing site must verify the colour's final effective source, not just its XML declaration - in the design document, and updated the plan's next-action line accordingly.

Task-Guard: THEME-SCRIM-WIRING-20260925

Verification: 实施前复核证伪设计前提,实施中止并全部回退,仅保留文档修正(无代码变更,工作树除两份文档外干净)。关键事实:设计文档第 3 节认定'三个全屏弹窗的 XML scrim 背景是活的遮罩、可直接接线',逐文件核对运行时取色后证伪——(1) mobile EpisodeDetailDialog.java:290,297 运行时 root.setBackgroundColor(overlay),overlay=light?0x99F4F7FA:0xB3000000 硬编码;(2) TmdbPersonDialog.java:372,383 运行时 view.setBackgroundColor(overlay),overlay=light?0x99F4F7FA:0x8F000000 硬编码;(3) leanback EpisodeDetailDialog.java:316,322 运行时 root.setBackgroundColor(background),background=light?0xFFFFFFFF:0xFF101214,是不透明全屏底色而非遮罩;(4) TmdbVideoPlayerDialog.java:322 全屏时 container.setBackgroundColor(Color.BLACK)。即 webhtv_color_scrim 在这四处 XML 声明均为运行时被覆盖的死代码。剩余'活着'的 webhtv_color_scrim 用法全部不是遮罩:dialog_ad_block_stats 三个 source set 各 2 处 1dp 分隔线 + 1 处 88dp 行背景,dialog_receive 为海报占位底。因此当前代码不存在任何由 webhtv_color_scrim 驱动的活遮罩消费点,原方案(新增 ThemeRole.SCRIM + ThemeBinder 支持 ColorDrawable + 布局打 tag)即使实施也不会生效。已回退本轮全部无效代码改动:git checkout 还原 ThemeRole.java、ThemeBinder.java、dialog_episode_detail.xml、dialog_tmdb_person.xml,复查 SCRIM 命中=0、webhtv:scrim 命中=0,git status 仅剩两份文档改动。文档修正:新增第 11 节记录证伪、正确接线点(把两处运行时硬编码 overlay 字面量改为 ThemeController.current().colorScrim(),因 ThemeTokens.colorScrim() 已含用户 scrimOpacity)与教训(评估 token 落点必须验证最终生效来源而非只看 XML);第 6 节建议改为指向该正确路径;第 7 节旧步骤标记为已废弃仅留追溯;第 5 节方案 B 描述同步更新(binder 契约扩展经复核不需要,工作量由中降为低);主文档下一步描述同步修正。正确路径涉及两份新的 Java 文件(mobile EpisodeDetailDialog.java、TmdbPersonDialog.java),超出本会话声明的路径范围,故不在本会话实施,留待用户批准新范围。

Task-Guard: THEME-SCRIM-WIRING-20260925
2026-09-25 22:59:15 +08:00
Silent1566
d7bebcf586 feat(following): refresh all items and show metadata
Verification: FOLLOW-2: 23 targeted unit tests passed; 2 device migration tests passed on 192.168.50.3:5557; mobile arm64 debug build+overwrite install passed; app launched without fatal crash

Task-Guard: FOLLOW-2
2026-09-25 22:37:48 +08:00
www.xueximeng.com
ae0b26e8ee fix(cache): notify cleanup result instead of asking to confirm it
Verification: 双形态 Java 编译通过;5563 覆盖安装实测:构造 2 天前 pushed-stale.apk 后执行轻度清理,仅出现 Toast「清理完成 释放 512KB · 删除 1 个文件」,无任何结果弹窗(4 秒后仍无确定/取消按钮),无障碍树 id/status=「清理完成 释放 512 KB · 删除 1 个文件」、id/summary 刷新为 19.3MB/478.9MB;回归确认深度清理执行前二次确认仍为第一步 取消/继续

Task-Guard: CACHE-MGMT-01-RESULT-TOAST
2026-09-25 21:02:49 +08:00
www.xueximeng.com
3ea56bc079 docs(cache): record update endpoint network blocker
Verification: 实测确认更新闭环的外部阻塞:设备请求 api.github.com/repos/fish2018/webhtv/releases/latest 与 cnb.cool 发布清单均返回 403,网络受限无法完成真实检查更新;已据实写入文档,不虚报完成

Task-Guard: CACHE-MGMT-01-UPDATE-NET
2026-09-25 19:11:35 +08:00
www.xueximeng.com
98f27f59fc docs(cache): finalize recovery anchor status
Verification: 最终审计:工作区干净、无未勾选验收项、43 个恢复标签;缓存包独立 JUnit 41/41 通过;恢复锚点更新为准确终态,明确两项非阻塞已知边界(应用更新完整闭环受环境限制、插件上限自动淘汰按安全设计暂不启用)

Task-Guard: CACHE-MGMT-01-FINAL-ANCHOR
2026-09-25 18:03:23 +08:00
www.xueximeng.com
5faf3d8b4d docs(cache): clarify update verification scope
Verification: 审查 Updater/HttpUpdateTransfer/OciUpdateTransfer 确认下载目标为 cache/update.apk 且属本计划缓存契约;该契约已实测通过(进行中 update.apk 被保护、过期临时包淘汰)。完整更新闭环受环境限制:ApkUrlPolicy 拒绝私网/HTTP、无线上 HTTPS 发布包、安装需系统安装器人工确认,故按 §24 第5条如实标注而未虚报

Task-Guard: CACHE-MGMT-01-UPDATE-SCOPE
2026-09-25 17:59:31 +08:00
www.xueximeng.com
d762e8ee21 docs(cache): document plugin limit safety decision
Verification: 审计插件模块上限并回退试改:directCleanupStatus 对 PLUGIN_SCRIPTS 恒返回 NOT_ALLOWED,UI 显示「由模块管理」并禁用清理,applyConfiguredLimits 不淘汰插件文件,属有意安全设计(运行中暂停插件加载未实现,后台删除 jar/py/js 可能致回归)。设备实测:80MB js 文件 + 64MB 插件上限,深度清理后该文件仍保留(仅释放其他模块 8.7MB/56 文件)。尝试改自动淘汰后已回退以避免回归,结论记录于 §20.6

Task-Guard: CACHE-MGMT-01-PLUGIN-LIMIT
2026-09-25 17:53:32 +08:00
Silent1566
83aca519ed docs(theme): assess scrim and overlay opacity wiring
Assessment-only: no code, resource, lock or runtime change. Produced docs/THEME-SCRIM-OVERLAY-20260925-design.md and registered the task plus next action in the theme plan. Every conclusion is traceable: ThemeRole has no SCRIM/OVERLAY role (0 hits), so neither slot has a native consumer and both editor sliders are dead controls. Their semantics differ from dialogOpacity - scrimOpacity and overlayOpacity are absolute alphas (withAlpha(base.colorScrim(), ...) / withAlpha(base.colorOverlayLight(), ...)) whose bases are 0x52000000 (light) and 0x8A000000 (dark/TV) for scrim, while dialogOpacity is a scale with a 1.0 baseline, so applyShellOpacity's opacity>=1 no-op rule must not be reused. The binder handles no ColorDrawable (0 hits) and even the explicit-tag path still requires baseline==current. scrimOpacity does have correct existing consumers - the full-screen overlay backgrounds of dialog_episode_detail, dialog_tmdb_person and dialog_tmdb_video_player - but webhtv_color_scrim is also used for 1dp dividers and an 88dp row in dialog_ad_block_stats (3 places per source set) plus a poster placeholder in dialog_receive, so a global swap is wrong and the wiring must be scoped per view. overlayOpacity's semantics do not hold: overlay_light's only use is a 1dp divider in dialog_quick_search while the real image veils use overlay_dark, which 4.23 froze. Official evidence confirms the intent - Material's mtrl_scrim_color is exactly #52000000, matching the project's light webhtv_color_scrim - and the plan's own slot table already defines scrimOpacity as the page/dialog outer scrim, making this an implementation gap rather than a design gap. A material risk is recorded: TmdbPersonDialog:133 and EpisodeDetailDialog:240 disable window dim (setDimAmount(0f)) but TmdbVideoPlayerDialog:326 keeps 0.72f, so a view scrim would stack with it; the first implementation should therefore wire only the two clean dialogs. The document compares no-change, the recommended scoped-wiring option, rebinding the veils and unfreezing overlayDark, window-level dim, and removing the slots from the editor, and states the recommendation, minimal implementation steps, acceptance criteria and rollback path, citing source excerpts instead of line numbers so references do not rot. No build or device test was run, as this task changes docs only.

Task-Guard: THEME-SCRIM-OVERLAY-20260925

Verification: 设计研究任务(仅文档,未改代码)。产出 docs/THEME-SCRIM-OVERLAY-20260925-design.md,并在主题主文档登记任务与下一步。所有结论均双向可回溯,已逐条复核:(1) ThemeRole 无 SCRIM/OVERLAY 角色(grep -c = 0),故两槽无原生消费点,编辑器滑块是死控件;(2) resolver 语义差异——scrimOpacity 是绝对 alpha(withAlpha(base.colorScrim(), scrimOpacity),ThemeResolver:168,184),基线 light=0x52000000(0.322)/dark与TV=0x8A000000(0.541),而 dialogOpacity 是缩放且基线 1.0,实施时不能套用 applyShellOpacity 的 opacity>=1 no-op 判定;overlayOpacity 派生自 colorOverlayLight(0x14FFFFFF);(3) binder 不处理 ColorDrawable(grep -c = 0),且 singleColor 的显式 tag 路径仍要求 baseline==current;(4) scrim 消费点清单——3 个全屏弹层遮罩(dialog_episode_detail/dialog_tmdb_person/dialog_tmdb_video_player)语义正确,但 dialog_ad_block_stats 三个 source set 各 3 处用于 1dp 分隔线/88dp 行底、dialog_receive 用于海报占位底,因此不能全局替换、必须按视图限定作用域;(5) overlay 语义错配已证实——overlay_light 唯一用法是 dialog_quick_search.xml:80 的 1dp 分隔线,真正的图片 veil 用被 4.23 冻结的 overlay_dark(shape_tmdb_episode_overlay/shape_tmdb_recommendation_info 的 centerColor);(6) 官方一手证据复核通过——Material Components mtrl_scrim_color 恰为 #52000000,与本项目浅色 webhtv_color_scrim 完全一致;(7) 设计文档原文(第 14 槽表格与 Dialog 处理清单)本就把 scrim 定义为 Dialog 外遮罩,故 scrim 属实现遗漏而非设计缺失;(8) 关键风险已记录——TmdbPersonDialog:133 与 EpisodeDetailDialog:240 设 setDimAmount(0f),但 TmdbVideoPlayerDialog:326 设 0.72f,视图 scrim 会与窗口 dim 叠加,故建议首版只接前两个弹窗。方案对比含保持现状/推荐方案B/改绑 veil 解冻 overlayDark/窗口级 dim/移出编辑器五条,并给出推荐、最小实施步骤、验收标准与回滚路径。引用采用原文片段而非绝对行号以避免漂移。本任务未运行构建或设备测试(纯文档,无代码变更)。

Task-Guard: THEME-SCRIM-OVERLAY-20260925
2026-09-25 17:45:41 +08:00
Silent1566
c22cc652a4
Merge pull request #372 from Silent1566/dev4
dev4:同步 beta 并修复转译环境接口重载闪退
2026-09-25 15:27:40 +08:00
Silent1566
27ce58a70f docs: record second beta sync review
Verification: 定向单测(GlobalHistorySettingSourceTest、NativeBridgeGuardTest、PlayerPlaybackRegressionSourceTest)通过;mobile/leanback arm64 Java 编译通过;git diff --check 与两轮净差异复评通过;origin/beta 为祖先且被移除主题提交不是祖先

Task-Guard: BETA-SYNC-REVIEW-DEV4-20260925
2026-09-25 15:19:43 +08:00
Silent1566
30912bac89 Merge remote-tracking branch 'origin/beta' into dev4 2026-09-25 15:11:37 +08:00
Silent1566
c5df062925 fix(loader): retain translated dex loaders to avoid houdini crash
Verification: NativeBridgeGuardTest passed; leanback arm64 debug build and overwrite install on 192.168.50.3:5561 passed; 3 real VOD interface reloads retained translated loaders (total 2/3/4) and left app process alive with empty crash buffer

Task-Guard: MIGRATE-HOUDINI-FIX-DEV4-20260925
2026-09-25 15:05:23 +08:00
www.xueximeng.com
c66f7f1874 docs(cache): record system quota enforcement evidence
Verification: 5563 读取系统缓存配额 478.9MB 并在设置页/缓存页展示;设备设置总上限 1GB(偏好 1073741824),有效上限由 CacheTotalLimitPolicy.effectiveLimit 收敛至配额(CacheTotalLimitPolicyTest 断言 min(1024,512)=512);验证后还原为不限(偏好 0)

Task-Guard: CACHE-MGMT-01-QUOTA-EVIDENCE
2026-09-25 15:01:02 +08:00
www.xueximeng.com
4089b4b91b docs(cache): record mobile flavor device verification
Verification: 5563 覆盖安装 mobileArm64_v8a debug 后实测:设置页显示 缓存管理 25.7MB/478.9MB,弹窗真实扫描 25.7MB/系统配额 478.9MB,三级清理/刷新/确定可见可点,轻度清理返回清理完成且无 FATAL EXCEPTION;验证后重装 leanback flavor 恢复 TV 形态

Task-Guard: CACHE-MGMT-01-MOBILE-EVIDENCE
2026-09-25 14:55:50 +08:00
Silent1566
8ee61fbdb7 docs(theme): record the TV remote focus/selection/back acceptance
Verification: closed the last row of the 4.7 device matrix on dev3 192.168.50.3:5559 (V1923A, API 28, light system) with the leanback arm64 debug build overwrite-installed and focus driven through adb shell input keyevent DPAD_*. Focus movement advanced one cell at a time across the home navigation row ((48,176)->(304,176)->(572,176)->(840,176)) and into the content grid, each step changing 265674-292954 pixels with the focus rect matching the focused=true node in the UI dump; 4-5 selected=true nodes coexisted and stayed distinguishable from the focused one; BACK from both the settings page and the exit dialog returned to HomeActivityCurrent without a stuck or leftover activity; the focused button ring measured #FFFFFF on the #FBFCFF panel (6.39:1, above the focus/surface >= 3:1 gate) and the focus band changed colours identically under the probe and Default, so focus colour is neither lost nor corrupted by theming; under the probe (primary=focus=#C62828, surface=#FFF8E1) the settings page differed from Default by 11474 px at every focus step while a focus move changed 292414 px, showing theming and focus coexist. Probe correction: focus and primary share the same frozen baseline in TV (#A8C7FA both), and the binder only rewrites a shared baseline colour when the roles agree, so an initial probe splitting them (primary=#C62828, focus=#7B1FA2) made the channel refuse every rewrite - a probe defect, not a product one. Two false leads were excluded by reading resources: the fixed translucent focus bar and brand colours (yellow_500 etc.) are deliberate exemptions, and the TV exit dialog is a hardcoded brand surface (shape_exit_confirm_dialog #FBFCFF/#E5EAF3, shape_exit_confirm_badge #E8F0FE/#C7DBFF, selector_exit_confirm_secondary #DCEAFF/#0B57D0/#F8FAFD) that the binder must skip, so the 4.28 fix introduced no regression. Scope stated honestly: 5559 is a tablet-characteristics device without android.software.leanback, so this validates theme-time focus/selection/disabled visibility under D-pad navigation, not real remote hardware. Device state restored byte-identically, night mode back to no, mobile flavour reinstalled, no leftover test files.

Task-Guard: TV-REMOTE-STATES-20260925

Verification: TV 遥控(焦点/选中/返回)三态验收完成,4.7 设备矩阵按设计项收口。设备 dev3 192.168.50.3:5559(V1923A,API28,浅色系统),leanback arm64 debug 覆盖安装,用 adb shell input keyevent DPAD_* 驱动焦点。子项证据:(1) 焦点移动通过——首页导航行 (48,176)->(304,176)->(572,176)->(840,176) 逐格推进,DPAD_DOWN 进入内容网格并继续移动,每步截图变化 265674-292954 像素,焦点框坐标与 UI dump 的 focused=true 节点逐一对应;(2) 选中态通过——UI dump 同时含 4-5 个 selected=true 节点(导航项+网格卡片),选中与焦点项可区分;(3) 返回通过——设置页 BACK 与退出弹窗 BACK 均回到 HomeActivityCurrent,无卡死或残留;(4) 状态可见无丢色——焦点按钮边框 #FFFFFF 对弹窗面板 #FBFCFF 对比度 6.39:1(设计门禁 focus/surface >= 3:1),焦点带在探针与 Default 下换色分布逐项相同(#5D566A->#464050 x246 等);(5) 主题与焦点共存——探针 primary=focus=#C62828/surface=#FFF8E1 下设置页 4 个焦点步各与 Default 相差 11474 像素(bbox (73,72)-(1847,445)),焦点移动步变化 292414 像素,两者互不干扰。探针构造修正:冻结调色板中 focus 与 primary 同色(TV 基线均 #A8C7FA),binder 规定共享基线色的角色取值一致才改写,首个 primary=#C62828/focus=#7B1FA2 的探针会让通道整体拒绝改写,属探针缺陷,改用同值后才有效。排除两个误判:首页/设置页焦点高亮条在探针与 Default 下完全相同,核对资源确认是刻意固定的半透明遮罩与品牌色(yellow_500 等,按设计豁免),真正消费 focus token 的 leanback 侧是 TmdbCastPresenter 与 main 共享的 selector_tmdb_card;TV 退出弹窗面板/按钮两组完全相同,核对 app/src/leanback/res/drawable 确认是硬编码字面量品牌样式(shape_exit_confirm_dialog #FBFCFF/#E5EAF3、shape_exit_confirm_badge #E8F0FE/#C7DBFF、selector_exit_confirm_secondary #DCEAFF/#0B57D0/#F8FAFD),binder 按契约本就跳过,因此 4.28 未引入回退。验收边界如实标注:5559 为 tablet 特性设备(无 android.software.leanback),本行验的是主题语境下焦点/选中/禁用态可见性,用 DPAD 代遥控导航,不等于真实遥控器硬件验证,后者仍无设备。环境还原:偏好按字节恢复(cmp 一致)、夜间模式 no、设备装回 mobile flavor(leanback launcher=0)、应用可正常启动、无残留测试文件。

Task-Guard: TV-REMOTE-STATES-20260925
2026-09-25 14:48:46 +08:00
www.xueximeng.com
53d1149c09 docs(cache): record karaoke module cache isolation evidence
Verification: 5563 实测:缓存页统计 K 歌轨道 256KB/1文件 与 歌词 128KB/1文件;K 歌行清理确认框指向「清理 K 歌轨道」,执行结果释放 6 bytes/删除 1 文件,仅删除 verify.generated.txt,保留非轨道文件与歌词文件,证明模块清理隔离

Task-Guard: CACHE-MGMT-01-KARAOKE-EVIDENCE
2026-09-25 14:47:29 +08:00
www.xueximeng.com
aec62ca27b docs(cache): record EPG live refresh verification
Verification: 5563 本地 HLS 直播源实测:GET /live.m3u 200、GET /epg.xml 200、HLS playlist 与全部 segNNN.ts 200(持续重取),PlaybackState state=3/buffered 30000ms;cache/epg/epg.xml 落盘 319 字节且内容为测试频道与验证节目;验证后 Config 恢复原始 3 行并删除测试 EPG 缓存

Task-Guard: CACHE-MGMT-01-EPG-REFRESH
2026-09-25 14:41:47 +08:00
Silent1566
0e4f4164c8
Merge pull request #371 from Silent1566/dev2
修复移动端全局历史设置触控与持久化
2026-09-25 13:43:49 +08:00
Silent1566
7e42054deb fix(detail): show light cinema backdrop without wash
Verification: profileBackdropKeepsHistoricalTransparencyOverPosterArt passed; mobile arm64 debug build installed over existing package on 192.168.50.3:5555; runtime Cinema light detail shows varied original artwork pixels (148,185,204, 82,63,65, 121,91,89) rather than washed F4F7FA gradient; Gradle daemons stopped

Task-Guard: CINEMA-LIGHT-ORIGINAL-BACKDROP
2026-09-25 13:41:56 +08:00
Silent1566
02016f6b20 fix: harden mobile global history touch and persistence
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.history.GlobalHistorySettingSourceTest --tests com.fongmi.android.tv.ui.activity.PlayerPlaybackRegressionSourceTest BUILD SUCCESSFUL; git diff --check PASS; merged origin/beta 8a06267880 as ancestor; Gradle daemon stopped

Task-Guard: remote-beta-review-20260925
2026-09-25 13:37:25 +08:00
Silent1566
93e9f8ea06 Merge remote-tracking branch 'origin/beta' into dev2 2026-09-25 13:33:20 +08:00
Silent1566
8a06267880
Merge pull request #370 from Silent1566/dev4
合并 dev4:修复自动换线后画面丢失并同步 beta 最新改动
2026-09-25 13:29:29 +08:00
Silent1566
4d24168e8d fix(detail): show profile light backdrop at full opacity
Verification: profileBackdropKeepsHistoricalTransparencyOverPosterArt passed; mobile arm64 debug build installed over existing package on 192.168.50.3:5555; runtime Profile light detail shows original artwork colors (top pixels vary 72,93,98 to 117,83,92) instead of washed translucent surface; Gradle daemons stopped

Task-Guard: PROFILE-LIGHT-ORIGINAL-BACKDROP
2026-09-25 13:26:47 +08:00
Silent1566
8113c3176f docs: record beta sync review state
Verification: Gradle targeted tests and mobile/leanback arm64 Java compilation passed; git diff --check passed; origin/beta is ancestor

Task-Guard: C23-beta-sync-review-dev4-20260925
2026-09-25 13:24:28 +08:00
Silent1566
ced589c09a Merge remote-tracking branch 'origin/beta' into dev4 2026-09-25 13:15:27 +08:00
Silent1566
40c7b3122f fix(detail): keep profile cards translucent
Verification: profileBackdropKeepsHistoricalTransparencyOverPosterArt passed; mobile arm64 debug build installed over existing package on 192.168.50.3:5555; runtime Profile card interior pixels are translucent (225-226 RGB) rather than opaque 255,255,255; Gradle daemons stopped

Task-Guard: THEME-PROFILE-TRANSPARENT-BACKDROP-2
2026-09-25 13:09:16 +08:00
Silent1566
119370caa6 Require explicit touch for global history setting
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.history.GlobalHistorySettingSourceTest BUILD SUCCESSFUL; explicit touch-sequence regression assertions PASS; git diff --check PASS; Gradle daemon stopped

Task-Guard: global-history-mobile-explicit-click
2026-09-25 13:08:07 +08:00
Silent1566
9991cc095a fix(player): rebind surface after automatic line fallback
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.PlayerPlaybackRegressionSourceTest --no-daemon passed; :app:compileMobileArm64_v8aDebugJavaWithJavac and :app:compileLeanbackArm64_v8aDebugJavaWithJavac --no-daemon passed; git diff --check passed

Task-Guard: auto-line-surface-rebind-fix
2026-09-25 12:49:06 +08:00
Silent1566
ba8c3ed699 fix(theme): derive the binder baseline from the compiled flavour palette
Verification: TV (leanback) ignored the custom theme profile entirely. The leanback source set overrides every webhtv_color_* token with the dark table (53/53 identical to main/values-night) and ships no values-night, so TV views always inflate dark; but ThemeController.frozenPalette()/resolveWith() inferred the baseline from Resources.getSystem().uiMode, so on a light-mode device the baseline was the light table while inflation produced the dark one. ThemeBinder only rewrites colours that exactly equal the baseline, so the entire TV channel silently degraded to a no-op. Falsifying experiment on one build, flipping only the system dark setting: light-mode TV home 0 / settings 0 changed pixels vs dark-mode TV home 159174 / settings 11474. Fix: compiledDarkPalette() compares the flavour's actually compiled webhtv_color_* resources against the 19 binder roles and darkPaletteFor() makes an identifiable compiled table authoritative (TV stays dark even when the user picks light), falling back to the old uiMode rule only when the palette cannot be identified; both entry points share the decision. Automated: :app:testMobileArm64_v8aDebugUnitTest --tests 'com.fongmi.android.tv.theme.*' 105 tests green, adding tvFlavourBaselineMustDescribeTheCompiledResources and darkPaletteDecisionKeepsMobileStableAndForcesTvOntoItsCompiledTable. Device (dev3 192.168.50.3:5559, API 28, light system, overwrite install of the final revision): TV home 36999 / settings 11474 changed pixels, the settings value matching the earlier dark-mode control exactly; mobile stayed responsive on a deterministic native settings page (19827 px), so no regression. Device state restored byte-identically (cmp against /tmp/w_base.xml), night mode back to no, mobile flavour reinstalled. Also recorded but not fixed (pre-existing, out of scope): launching SearchActivity without the keyword extra NPEs in SearchFragment.setKeyword:208.

Task-Guard: TV-THEME-PALETTE-20260925

Verification: TV 主题通道失效定位与修复。根因:app/src/leanback/res/values/webhtv_tokens.xml 的 53 项 token 全量覆盖为暗色表且与 main/res/values-night 53/53 逐项相同,leanback 无 values-night,故 TV 侧 inflate 颜色恒为暗色;而 ThemeController.frozenPalette()/resolveWith() 原先用 Resources.getSystem().uiMode 推断浅深,浅色系统下基线取 ThemeTokens.light(),与 ThemeBinder 的精确匹配契约不符,导致 TV 整条主题通道静默 no-op。证伪实验(同一 TV 包仅切系统深浅):浅色 TV 首页 0 / 设置页 0 像素,深色 TV 首页 159174 / 设置页 11474 像素。修复:新增 compiledDarkPalette() 用本 flavor 实际编译的 webhtv_color_* 资源逐项比对 19 个 binder role,再由 darkPaletteFor(compiled, mode, systemDark) 让可识别的编译表成为权威(TV 恒暗,显式选浅色也仍暗),仅无法识别时回退原 uiMode 规则;frozenPalette() 与 resolveWith() 共用该判定。自动化::app:testMobileArm64_v8aDebugUnitTest --tests 'com.fongmi.android.tv.theme.*' 105 项全绿,新增 tvFlavourBaselineMustDescribeTheCompiledResources 与 darkPaletteDecisionKeepsMobileStableAndForcesTvOntoItsCompiledTable 两个契约测试。设备(dev3 192.168.50.3:5559 API28 浅色系统 覆盖安装 最终版本):修复后 TV 首页 36999 / 设置页 11474 像素,设置页数值与修复前深色对照值 11474 完全一致;手机端同探针确定性原生设置页 19827 像素仍正常响应,无回归。环境还原:偏好按字节恢复至 /tmp/w_base.xml(cmp 一致)、夜间模式恢复 no、设备装回 mobile flavor 且可正常启动。顺带记录未修的既有缺陷:无 keyword extra 启动 SearchActivity 会 NPE(SearchFragment.java:208),与本次改动无关。

Task-Guard: TV-THEME-PALETTE-20260925
2026-09-25 12:28:19 +08:00
Silent1566
245ddc0fbf fix(detail): restore profile backdrop transparency
Verification: profileBackdropKeepsHistoricalTransparencyOverPosterArt passed; mobile arm64 debug build installed over existing package on 192.168.50.3:5555; runtime Profile screenshot shows translucent art-influenced background with non-uniform pixels instead of opaque white surface; Gradle daemons stopped

Task-Guard: THEME-PROFILE-TRANSPARENT-BACKDROP
2026-09-25 12:14:58 +08:00
Silent1566
4081b5f683 Prevent mobile global history entry click
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.history.GlobalHistorySettingSourceTest BUILD SUCCESSFUL; delayed mobile listener regression assertion PASS; git diff --check PASS

Task-Guard: global-history-mobile-entry-click
2026-09-25 11:58:27 +08:00
Silent1566
d97de03001 Fix global history mode restart persistence
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.history.GlobalHistorySettingSourceTest BUILD SUCCESSFUL; git diff --check PASS

Task-Guard: global-history-mobile-restart
2026-09-25 11:55:33 +08:00
Silent1566
e8ebb6b9a6
Merge pull request #369 from Silent1566/dev2
合并 beta 最新代码并优化追更卡片操作布局
2026-09-25 11:46:45 +08:00
Silent1566
b5cac8a339 merge: sync latest remote beta and review following actions
Verification: FollowingUiSourceTest 13/13 passed; mobile arm64 debug build/cover-install passed; 1920x1080 and 720x1280 layout verification passed; XML parse and git diff --check passed; no reverted remote paths reintroduced

Task-Guard: C22-beta-sync-review-dev2-20260925
2026-09-25 11:43:37 +08:00
Silent1566
d964faca5e fix(detail): restore cinema style theme control
Verification: DetailModeControllerTest 7/7 passed; mobile arm64 debug build installed over existing package on 192.168.50.3:5555; runtime dump shows Cinema title bounds [28,178][1064,281] and Profile title bounds [276,275][1864,341], restoring distinct card/layout templates; Gradle daemons stopped

Task-Guard: THEME-CINEMA-LAYOUT-STYLE
2026-09-25 11:43:10 +08:00
Silent1566
c32ff7872c Merge remote-tracking branch 'origin/beta' into dev2 2026-09-25 11:12:51 +08:00
Silent1566
3200f6bb01 Place following card actions evenly in the lower row
Verification: FollowingUiSourceTest 13 passed; mobile arm64 debug installed on 192.168.50.3:5557; card actions sit in an equal-width row under the poster

Task-Guard: following-card-actions
2026-09-25 10:40:13 +08:00
www.xueximeng.com
61156ab629 docs(cache): record low-space trigger device evidence
Verification: 5563 root 可逆构造 400MB tmpfs 真实低空间:dumpsys jobscheduler 记录 CacheCleanupJobService 执行,清理历史新增 {mode:STANDARD, reason:low-space, status:COMPLETED, deletedFiles:1},未执行 L3;事后卸载 tmpfs、恢复偏好并确认应用正常启动

Task-Guard: CACHE-MGMT-01-LOWSPACE-EVIDENCE
2026-09-25 10:34:04 +08:00
Silent1566
ac56065718 fix(theme): distinguish light cinema from profile chrome
Verification: TmdbCinemaThemeTest 4/4 passed; mobile arm64 debug build installed over existing package on 192.168.50.3:5555; runtime screenshots show Profile light and Cinema light distinct warm/gold; Gradle daemons stopped

Task-Guard: THEME-CINEMA-DISTINCT
2026-09-25 10:32:37 +08:00
Silent1566
2d21687ad5
Merge pull request #368 from Silent1566/dev4
合并 beta 最新代码并完善点播/直播线路同步
2026-09-25 10:27:20 +08:00
Silent1566
a8f5e6005f test(config): enforce live sync pairing policy
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.server.process.ConfigUseLiveSyncAcceptanceTest passed; :app:compileMobileArm64_v8aDebugJavaWithJavac passed; :app:compileLeanbackArm64_v8aDebugJavaWithJavac passed; scripts/build_arm64_debug_install.sh --flavor leanback --serial 192.168.50.3:5561 built and installed by adb install -r successfully; ./gradlew --stop stopped daemon; origin/beta merged at 6e3d3ede9a; removed-theme implementation commits absent and theme tree unchanged in PR delta

Task-Guard: C22-beta-sync-review-dev4-20260925
2026-09-25 10:23:52 +08:00
Silent1566
3b25f0598b docs(theme): record playback regression acceptance evidence
Verification: dev3 192.168.50.3:5559 (V1923A/API28) with the playprobe custom theme fully active — playback via the exported HomeActivity push path (HomeActivity.checkAction -> VideoActivity.push) rather than the TMDB detail page. Exo kernel (player=EXO), phase=READY with firstFrameMs=3748, dropped=0/rebufferCount=0/rebufferTotalMs=0, decoder profile firstFrames=1 failures=0 drops=0 recoverableErrors=0 blacklisted=false, audio established via OMX.google.aac.decoder + SoftAAC2 44100Hz, position advanced 3363 -> 4362ms with OSD 00:10/03:00, 209922 pixels changed inside the player bbox (28,13,728,464) between samples, and Back -> HomeActivity -> VideoActivity resumed at positionMs=47314 (phase=READY); FATAL EXCEPTION=0 throughout and the theme touched only surrounding UI, never the video surface. Recorded the environment blocker honestly: the device cannot reach api.themoviedb.org (ping 2/2 loss), so the TMDB detail page stalls at its loading state (>10 min, unrelated to theming) and the push path was used to decouple the player from that dependency; two earlier false negatives from am start on the non-exported VideoActivity are documented as invalid evidence. Device prefs restored byte-identical to the pre-experiment baseline (cmp equal, md5 da54c8a4a153a04aa18c8fbdf4e64c7d) and the pushed test video removed. WebHome global skin stays independent per the user's 2026-09-24 decision; TV remote remains the only unexecuted matrix row (needs a leanback device).

Task-Guard: THEME-PLAYBACK-REGRESSION-20260925

Verification: 播放回归设备验收:dev3 192.168.50.3:5559 (V1923A/API28) 探针主题 playprobe 全激活下经 HomeActivity 推送入口播放 180s mp4;Exo 路径 player=EXO,phase=READY firstFrameMs=3748,dropped=0/rebufferCount=0/rebufferTotalMs=0,TV-exo-decoder-profile firstFrames=1 failures=0 drops=0 recoverableErrors=0 blacklisted=false,音频 OMX.google.aac.decoder + SoftAAC2 44100Hz 建立,播放推进 pos 3363->4362ms 且 OSD 00:10/03:00,两帧间播放器区域 bbox(28,13,728,464) 变化 209922 像素,Back 回首页再入 VideoActivity 二段 phase=READY positionMs=47314 续播,全程 FATAL EXCEPTION=0;主题仅改周边 UI 未侵入播放器画面。环境阻塞记录:设备到 api.themoviedb.org 100% 丢包导致 TMDB 详情页停在加载态(>10 分钟无进展,与主题无关),故以本地推送链路解耦验证;直接 am start 非导出 VideoActivity 的两次误判已纠正并写入文档。设备偏好已按字节还原至实验前基线 /tmp/w_base.xml(cmp byte-identical,md5 da54c8a4a153a04aa18c8fbdf4e64c7d;profile=Default theme_color=-291840 detail_open_mode=1),设备测试视频已删除。

Task-Guard: THEME-PLAYBACK-REGRESSION-20260925
2026-09-25 10:18:14 +08:00
www.xueximeng.com
9c491312cc feat(cache): add rollback feature switch for cache management
Verification: 缓存包独立 JUnit 41/41 通过(新增 CachePolicyStoreTest 2 例);双形态 Java 编译通过;5563 覆盖安装实测开关关闭时设置页显示旧式 60.3MB、点击直接全清并回显「无」(cache 由约 60MB 降至 4KB),随后恢复开关为 true

Task-Guard: CACHE-MGMT-01-FEATURE-SWITCH
2026-09-25 10:16:56 +08:00
Silent1566
e52e91441d merge: sync latest remote beta 2026-09-25 10:13:07 +08:00
Silent1566
74bf29f92b fix(config): preserve independent live interface on vod switch
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.server.process.ConfigUseLiveSyncAcceptanceTest passed; :app:compileMobileArm64_v8aDebugJavaWithJavac passed; :app:compileLeanbackArm64_v8aDebugJavaWithJavac passed; corrected APK installed on 192.168.50.3:5561; Gradle daemon stopped

Task-Guard: independent-live-sync-guard
2026-09-25 10:05:29 +08:00
Silent1566
6e3d3ede9a
Merge pull request #367 from Silent1566/dev2
修复 TMDB 中文语言展示并同步 beta
2026-09-25 10:04:44 +08:00
Silent1566
0a9ababdca
Merge pull request #366 from Silent1566/dev1
修复追更站点大小写键匹配
2026-09-25 09:56:55 +08:00
Silent1566
f8853289f3 Merge remote-tracking branch 'origin/beta' into dev2 2026-09-25 09:54:58 +08:00
Silent1566
d73c7eecba fix(tmdb): enforce display language identity
Verification: Leanback Arm64 Debug Java compile passed; targeted Leanback/Mobile JVM and source regressions passed; second review fixed zh-CN/zh-TW identity and removed duplicate Leanback fallback helpers

Task-Guard: C21-tmdb-zh-language-fix
2026-09-25 09:53:47 +08:00
Silent1566
5d788440bb docs: record following key fix recovery state
Verification: VodConfigSiteKeyTest passed; diff checks passed; remote transport and gh auth recovered; excluded commits confirmed absent from HEAD ancestors

Task-Guard: C20-beta-sync-review-dev1-followup
2026-09-25 09:53:09 +08:00
www.xueximeng.com
4a83d02f68 docs(cache): record rollback switch and legacy entry deltas
Verification: 核对第24节第1/6条:FileUtil.getCacheSize/clearCache 仍保留但已无调用方(旧全量入口不再暴露);§23.1 的 cache_mgmt_enabled 功能开关未实现,回滚依赖提交与恢复标签,两处偏离已补入§20.6

Task-Guard: CACHE-MGMT-01-DELTA-SWITCH
2026-09-25 09:28:52 +08:00
www.xueximeng.com
0486a05f03 docs(cache): record EPG retention device evidence
Verification: 5563 实测 EPG 保留策略:构造今日 today.xml 与 2 天前 old.xml 后执行轻度清理,结果释放 4 bytes/删除 1 文件,old.xml 删除、today.xml 保留;验收清单改为部分通过并注明自动刷新仍待直播源

Task-Guard: CACHE-MGMT-01-EPG-EVIDENCE
2026-09-25 09:27:39 +08:00
www.xueximeng.com
d89c7624f2 docs(cache): record lyrics and subtitle device evidence
Verification: 5563 实测:歌词多源检索链路(QQMusic/LRCLIB/ranking/空结果降级)正常执行;外挂 SRT 在 00:02:32 实际渲染中文台词;据此勾选歌词/字幕验收项并记录 K 歌待真实指纹实测

Task-Guard: CACHE-MGMT-01-MEDIA-EVIDENCE
2026-09-25 09:13:36 +08:00
Silent1566
7cd8799dfe Fix following source key case lookup
Verification: app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.api.config.VodConfigSiteKeyTest

Task-Guard: FIX-following-site-key-case
2026-09-25 08:58:00 +08:00
www.xueximeng.com
1cb7cffdc0 fix(cache): protect in-flight temp transfers from cleanup
Verification: 缓存包独立 JUnit 39/39 通过(新增 CacheTempFilePolicyTest 3 例);双形态 Java 编译通过;5563 覆盖安装后轻度清理实测 update.apk 保留、过期 pushed-stale.apk 删除(释放 512KB/删除 1 文件)

Task-Guard: CACHE-MGMT-01-TEMP-ACTIVE
2026-09-25 08:32:52 +08:00
Silent1566
1bbb751861 fix(home): sync matching live interface on vod selection
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.server.process.ConfigUseLiveSyncAcceptanceTest passed; :app:compileLeanbackArm64_v8aDebugJavaWithJavac passed; Gradle daemon stopped

Task-Guard: home-vod-live-sync
2026-09-25 00:41:04 +08:00
Silent1566
8e86516f3d fix(mobile): sync matching live interface in settings
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.server.process.ConfigUseLiveSyncAcceptanceTest passed; :app:compileMobileArm64_v8aDebugJavaWithJavac passed; Gradle daemon stopped

Task-Guard: mobile-setting-vod-live-sync
2026-09-25 00:39:49 +08:00
Silent1566
3a49bfbfef fix(setting): sync matching live interface on vod switch
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.server.process.ConfigUseLiveSyncAcceptanceTest passed; :app:compileLeanbackArm64_v8aDebugJavaWithJavac passed; Gradle daemon stopped

Task-Guard: setting-vod-live-sync
2026-09-25 00:38:14 +08:00
Silent1566
07a0e9c64a fix(config): sync matching live interface on managed vod switch
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.server.process.ConfigUseLiveSyncAcceptanceTest passed; leanback arm64 debug build installed via scripts/build_arm64_debug_install.sh; Gradle daemon stopped

Task-Guard: interface-switch-sync-live
2026-09-25 00:37:01 +08:00
Silent1566
5efe8f14f2
Merge pull request #365 from Silent1566/dev1
合并 dev1:同步 beta 最新代码并评审播放同步稳定身份匹配
2026-09-25 00:25:42 +08:00
Silent1566
8b552cbaa5 docs: record beta sync review delivery
Task-Guard: C20-beta-sync-review-dev1-20260924
2026-09-25 00:17:34 +08:00
Silent1566
4b38555402 merge: sync latest remote beta and align identity schema test
Verification: Mobile/Leanback full JVM suites and Java compile passed (4915 and 4008 tests, 0 failures/errors, 2 skipped each); shared identity fixtures 3/3, Vercel 4/4, Deno 4/4, Cloudflare 6/6 passed; git diff checks passed; mobile arm64 debug APK overlaid on 192.168.50.3:5555 and startup smoke had no crash

Task-Guard: C20-beta-sync-review-dev1-20260924
2026-09-25 00:15:38 +08:00
Silent1566
70592cbc8b
Merge pull request #364 from Silent1566/dev4
优化追更手动已读与 TV 焦点操作
2026-09-24 23:51:56 +08:00
Silent1566
50de93a4d7 docs: add TMDB zh-CN language fix design
Verification: docs/C21-tmdb-zh-language-fix.md created; markdown structure inspected; git diff --check passed

Task-Guard: C21-tmdb-zh-language-fix
2026-09-24 23:43:52 +08:00
Silent1566
8ed3955b6b fix(following): restore left focus from read all
Verification: FollowingUiSourceTest passed via :app:testLeanbackArm64_v8aDebugUnitTest; Gradle daemons stopped

Task-Guard: following-readall-left-focus
2026-09-24 23:32:53 +08:00
Silent1566
cc85ad350c feat: implement playback sync stable identity matching
Verification: 共享身份 fixture 3/3、Vercel 4/4、Deno 4/4、Cloudflare 6/6、Go 测试、Rust 4/4、git diff --check、task_guard check 均通过;Mobile/Leanback 编译与定向单元测试通过;Mobile arm64 Debug 测试包已覆盖安装并完成启动健康检查

Task-Guard: playback-sync-identity-matching-design
2026-09-24 23:24:02 +08:00
Silent1566
236b440b4e fix(following): focus read all on TV remote
Verification: FollowingUiSourceTest passed via :app:testLeanbackArm64_v8aDebugUnitTest; Gradle daemons stopped

Task-Guard: following-readall-focus
2026-09-24 23:22:41 +08:00
Silent1566
94323cd231 feat(following): restore manual read confirmation
Verification: FollowingUiSourceTest passed via :app:testLeanbackArm64_v8aDebugUnitTest; Gradle daemon stopped

Task-Guard: manual-following-read
2026-09-24 23:05:31 +08:00
Silent1566
76c4f09106
Merge pull request #362 from Silent1566/dev4
优化前台追更刷新时效
2026-09-24 22:41:19 +08:00
Silent1566
b23c409f81
Merge pull request #363 from Silent1566/dev2
fix(player): 自动换线路前强制重建播放器
2026-09-24 22:41:04 +08:00
Silent1566
4d5bb4e845 fix(player): force rebuild before automatic line fallback
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.PlayerPlaybackRegressionSourceTest --no-daemon passed (14 tests); :app:compileMobileArm64_v8aDebugJavaWithJavac and :app:compileLeanbackArm64_v8aDebugJavaWithJavac --no-daemon passed; beta merge completed and PR353 theme-removal commits are not ancestors

Task-Guard: auto-line-audio-beta-review
2026-09-24 22:29:38 +08:00
Silent1566
d3077b1dee Merge remote-tracking branch 'origin/beta' into dev2 2026-09-24 22:20:49 +08:00
Silent1566
81cb271024 fix(following): keep foreground refresh cadence bounded
Verification: FollowingSchedulePolicyTest 6/6 passed; Mobile/Leanback arm64 Java compile passed; git diff --check passed; final review passed

Task-Guard: C20-beta-review
2026-09-24 22:18:32 +08:00
Silent1566
8d00bbbe96 Improve foreground following refresh cadence
Verification: FollowingSchedulePolicyTest 5/5 passed; Mobile/Leanback arm64 Java compile passed; git diff --check passed

Task-Guard: C20-foreground-following-refresh
2026-09-24 21:04:45 +08:00
www.xueximeng.com
35e7d678b5 docs(cache): record design-to-implementation deltas
Verification: 逐项核对设计第11.2/14.6节与实现,记录五处偏离(统一上限档位、自动清理默认关、触发条件不可配置、临时文件固定24h、边界文案复用),并将验收清单两项表述与实现对齐

Task-Guard: CACHE-MGMT-01-DESIGN-DELTA
2026-09-24 20:42:40 +08:00
www.xueximeng.com
7f3f32e58d docs(cache): correct acceptance evidence after symlink fix
Verification: 修正文档中因符号链接误判而失真的两处验收证据:20.3 的 16 条 warning 实为祖先符号链接误判(已修复,设备复测无警告);20.2 符号链接项补充祖先别名与最终组件判定说明

Task-Guard: CACHE-MGMT-01-DOC-ACCURACY
2026-09-24 20:36:47 +08:00
www.xueximeng.com
306b627fd9 docs(cache): record 5563 regression verification evidence
Verification: 5563 设备回归:播放/seek/预载实测通过、播放中标准清理不中断、播放器切换前后模块统计一致、备份与恢复闭环成功;EPG 与歌词/字幕/K歌因无源未验证

Task-Guard: CACHE-MGMT-01-REGRESSION-EVIDENCE
2026-09-24 20:34:15 +08:00
Silent1566
00c6087dfe
Merge pull request #361 from Silent1566/dev4
fix(mobile): 修复追番页面内容与操作按钮裁切
2026-09-24 20:21:58 +08:00
Silent1566
fcc412debf fix(mobile): keep following actions evenly spaced
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.FollowingUiSourceTest(BUILD SUCCESSFUL);两份移动端布局 XML 解析通过;git diff --check 通过;修复后二次复评确认所有 Activity/Adapter 绑定 ID 与动作按钮覆盖完整

Task-Guard: following-mobile-layout-review-20260924
2026-09-24 20:17:54 +08:00
Silent1566
51ecec7222 fix(mobile): prevent following page content clipping
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.FollowingUiSourceTest(12 tests,BUILD SUCCESSFUL);1920x1080 与 589x1280 设备截图核验按钮/文本完整可见;git diff --check;已执行 ./gradlew clean

Task-Guard: following-mobile-layout-visibility-20260924
2026-09-24 19:55:28 +08:00
www.xueximeng.com
b93e7ac3bb fix(cache): stop ancestor symlinks from hiding cache roots
Verification: 缓存包独立 JUnit 36/36 通过;双形态 Java 编译通过;5563 覆盖安装后缓存统计恢复为 59.1MB/478.9MB、歌词 1MB/1文件、无部分结果警告;标准清理实测释放 165.3KB/删除 8 文件

Task-Guard: CACHE-MGMT-01-SYMLINK
2026-09-24 19:15:40 +08:00
Silent1566
9801f2b31f
Merge pull request #360 from Silent1566/dev4
dev4:同步 beta 并修复详情页焦点回归
2026-09-24 18:40:39 +08:00
Silent1566
d3e87170c3 fix: reconcile beta focus correction with immediate detail focus
Verification: 双 flavor Java 编译 BUILD SUCCESSFUL in 2m16s;移动端全量 JVM 单测修复后 BUILD SUCCESSFUL in 40s(4908 tests, 0 failed, 2 skipped);git diff --check 与 --cached --check 通过;两轮组合复评通过

Task-Guard: C19-beta-sync-review-dev4-20260924
2026-09-24 18:37:48 +08:00
Silent1566
f16d95efe9 docs: design playback sync identity matching
Verification: git diff --check;文档锚点与 5 个完整 commit ID 校验通过;设计文档包含协议、客户端/服务端实现清单、迁移/冲突/回滚、共享测试与 ID-01 至 ID-24 验收矩阵

Task-Guard: playback-sync-identity-matching-design
2026-09-24 18:27:52 +08:00
Silent1566
00bc40e1f7 merge: sync latest remote beta 2026-09-24 18:26:52 +08:00
Silent1566
21ebe9612b fix: make super parse work on legacy WebView
Verification: compileMobileArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; scripts/build_arm64_debug_install.sh --serial 192.168.50.3:5561 BUILD SUCCESSFUL and coverage install; clean 5561 run with parse-super start json=0 web=10, wh-wasm JS HMAC shim, webview-sniff m3u8, parse-super web success 虾米, parse-job success, VideoActivity READY firstFrameMs=12310 rebufferCount=0; generated build artifacts and temporary logs cleaned, Gradle daemon stopped

Task-Guard: PLAYBACK-PARSE-SUPER-20260923
2026-09-24 18:07:35 +08:00
Silent1566
882f2033f8 fix(theme): apply dialog opacity to the dialog shell
Verification: 4.7 device matrix: 16-slot row and light/dark row pass with distinguishable probes (light panel #E0E0E0 + #C62828 primary + #C8E6C9 container vs dark #212121 + #80DEEA + #006064, groups independent, no restart needed); resource-recycling row passes over 55 cold-start/dialog/back cycles with FATAL=0 and PSS 331175 -> 313289 kB (no growth); transparency row FAILED and exposed dialogOpacity as a functional dead slot - lower bound 0.70 vs upper bound 1.00 produced byte-identical dialogs (0 changed pixels) because the token only reached the editor preview swatch and the Web snapshot; fixed by applying it to the dialog window shell (ThemeBinder.applyShellOpacity unwraps InsetDrawable and scales the drawable's own fill alpha, strict no-op at >=1 so the frozen baseline stays byte-identical, never touches text alpha per the documented contract); after the fix 0.70 vs 1.00 differ by 374849 px with bbox x474..1444 y342..736 exactly the dialog, 1.00 shows opaque #7B1FA2 and 0.70 shows #6D2B8C (the probe blended 70/30 with the wallpaper) while the title stays #FFFFFF unchanged; new contract+arithmetic test dialogOpacityReachesTheWindowShellAndNotTheText with ThemeBinder.scaleAlpha made package-private for JVM assertion; two failures fixed by correcting my own wrong expectation (0.70*255 rounds to 179) and by updating the 4.19 assertion that still matched the old return-shape; mobile+leanback theme tests and both flavors compileWithJavac PASS; check_ui_tokens --strict PASS; device prefs byte-verified restored. New residual: scrimOpacity and overlayOpacity remain zero-reference dead slots (needs a semantic decision, not just a fix)

Task-Guard: THEME-DIALOG-OPACITY-20260924
2026-09-24 17:26:47 +08:00
Silent1566
4eafca8539
Merge pull request #359 from Silent1566/dev1
修复 TMDB 路由选择的电视遥控操作
2026-09-24 16:36:13 +08:00
Silent1566
940ae26b7f fix: keep TMDB route activation after focus navigation
Verification: 契约测试通过::app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.dialog.TmdbSourceDialogInflationContractTest(2 tests)。mobile/arm64-v8a Debug APK 通过 scripts/build_arm64_debug_install.sh 覆盖安装到 192.168.50.3:5555。实机验证通过:语言输入 Down 自动打开 API 选择器,Back 后 Enter 可重新打开,API Down 可进入图片选择器。二次评审、task_guard check、git diff --check 通过;验证临时 XML 已清理。

Task-Guard: C19-beta-sync-review-dev1-20260924-fix
2026-09-24 16:29:26 +08:00
Silent1566
a4b4fd759f fix(theme): map framework text roles onto webhtv tokens
Verification: root-caused and fixed the framework-text-role gap at the theme layer: Material's Base.V14.Theme.Material3.Light points android:textColorPrimary/Secondary at its own m3_default_color_* selectors which hard-reference m3_sys_color_light_on_surface(_variant) (#1D1B20/#49454F), so Activity-context widgets never followed ?attr/colorOnSurface* and ThemeBinder could not rewrite those unknown values; Theme.WebHTV previously mapped only the Material colorOnSurface attrs, not the framework android:textColor* ones (only the Dialog overlay did); added webhtv_text_primary/secondary selectors preserving Material's disabled emphasis and mapped textColorPrimary/Secondary/Tertiary/Hint onto them so both fixes apply: widgets now resolve our tokens AND match baseline roles so the binder can rewrite them; new guard ThemeBaseWiringTest#activityThemeMapsFrameworkTextRolesToWebhtvTokens; check_ui_tokens --strict first rejected my new resource (hex literals in my own comment) which I removed, now violations=0 legacy=0 contrast 38/0 colors=56; mobile+leanback theme tests and FULL suites pass with zero failing classes, both flavors compileWithJavac PASS; dev3 192.168.50.3:5559 API28 same-coordinate before/after: import-dialog EditText hint went from Material default #50545C to exactly #44474F (webhtv_color_on_surface_variant) and then followed to #FFFFFF under the white onSurfaceVariant probe (it previously ignored themes entirely); panel #CFD8EB vs #7B1FA2 confirms same-screen comparison; device prefs byte-verified restored

Task-Guard: THEME-TEXTCOLOR-MAPPING-20260924
2026-09-24 16:27:25 +08:00
www.xueximeng.com
c59a13ca5f fix(cache): keep deep cleanup confirmation in one dialog
Verification: 双形态 Java 编译通过;5563 覆盖安装 com.silent.android.webhtv 实测:深度清理首次仅一个确认框(取消/继续),点继续后同一窗口更新为不可撤销文案与取消/开始深度清理,无叠加弹窗

Task-Guard: CACHE-MGMT-01-DEEP-CONFIRM
2026-09-24 16:22:25 +08:00
Silent1566
d5065df980 merge: sync latest remote beta
Verification: Beta merge from origin/beta (4b8e6bb80) completed without conflicts; merge content retained in index after resetting only the accidental task-guard baseline commit. No runtime validation run for this reconciliation commit; task review and risk-based verification continue before release.

Task-Guard: C19-beta-sync-review-dev1-20260924
2026-09-24 16:17:13 +08:00
Silent1566
37f92430da fix(player): rebuild player before automatic line fallback
Verification: PlayerPlaybackRegressionSourceTest.autoLineFallbackRebuildsRememberedPlayerBeforeFetchingNextLine (mobile ARM64) passed; :app:compileLeanbackArm64_v8aDebugJavaWithJavac and :app:compileMobileArm64_v8aDebugJavaWithJavac passed; Gradle daemon stopped

Task-Guard: fix-auto-line-audio-only
2026-09-24 16:04:15 +08:00
Silent1566
643f0618c5 fix: open TMDB route picker on focus
Verification: Focused contract test passed: :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.dialog.TmdbSourceDialogInflationContractTest. Runtime TV verification on 192.168.50.3:5555: scrolling to API route opened picker automatically; DPAD_DOWN+ENTER selected official API; focused API input then reopened its picker by ENTER. Leanback arm64 debug APK built and overwritten via scripts/build_arm64_debug_install.sh. Device preference backup restored byte-for-byte (SHA-256 1c8d9b5e39c28f88d39e72af5bfe6f55c31df07981a0c6849c9554f15b5b2da0).

Task-Guard: FIX-TV-TMDB-ROUTE-AUTO-SWITCH
2026-09-24 15:57:19 +08:00
Silent1566
4b8e6bb803
Merge pull request #358 from Silent1566/dev2
合并上游测试兼容更新并通过 beta 评审
2026-09-24 15:45:52 +08:00
Silent1566
9446ea0caf fix(guard): recover committed and finished task state
Verification: bash -n passed; real repository start/check/finish/tag/archive lifecycle exercised; a separate fresh start succeeded after archive.

Task-Guard: task-guard-recovery-archive
2026-09-24 15:44:02 +08:00
Silent1566
ebf10f99bf merge upstream/main test compatibility update
Verification: :app:compileLeanbackArm64_v8aDebugUnitTestJavaWithJavac --no-daemon BUILD SUCCESSFUL (34s)

Task-Guard: beta-upstream-sync-20260924
2026-09-24 15:32:51 +08:00
Silent1566
ecc2de7e4b fix(theme): give programmatic editor widgets explicit token colours
Verification: programmatic platform widgets in the theme editor now take explicit semantic colours: root cause is that Theme.WebHTV (the Activity theme) never maps android:textColorPrimary, so new TextView/Button/EditText from the Activity context fall back to Material's #49454F (= its on_surface_variant, not our #44474F token) which the binder correctly refuses to rewrite as an unknown colour; ThemeDialog mobile+leanback set colorOnSurface on the edit-mode label and the light/dark toggle plus a new outlinedPill() background (framework Button ships its own light plate, so text-only colouring would go white-on-white), and ThemeColorPickerDialog colours hexLabel/hexInput(text+hint)/useHex the same way; new guard test programmaticEditorWidgetsAlwaysGetTokenColours matches new TextView/EditText/Button incl. fully-qualified forms and follows the declared variable name so widgets coloured elsewhere still pass (it caught and corrected one false positive on first run); mobile+leanback theme tests PASS (ThemeEditorContractTest 12/0) and full mobile+leanback suites PASS with zero failing classes, both compileWithJavac PASS, check_ui_tokens --strict PASS violations=0 legacy=0 contrast 38/0; dev3 192.168.50.3:5559 API28 on the #7B1FA2 probe: 正在编辑 label went #49454F -> #FFFFFF and the 浅色 toggle became a coordinating outlined pill; device prefs byte-verified restored

Task-Guard: THEME-PROGRAMMATIC-WIDGETS-20260924
2026-09-24 15:12:52 +08:00
Silent1566
7a8b7c8962
Merge pull request #356 from Silent1566/dev2
修复电视首页切换站点后的旧分类残留
2026-09-24 14:10:50 +08:00
Silent1566
2559082e3d test(leanback): cover stale home type clearing chain
Verification: :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.HomeSiteSwitchClearsTypesTest; task_guard check; git diff --check

Task-Guard: beta-merge-review-20260924
2026-09-24 14:06:17 +08:00
www.xueximeng.com
cfbafad5ec fix(cache): make TV cache dialog full-height with clear focus and contrast
Verification: PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: TV device NX627J Android 9 192.168.50.3:5563 - panel now spans y~50..1030 of 1080 (was 652px) after switching from MaterialAlertDialog to a self-drawn Dialog; PASS: module title/detail now use theme text colors (were hardcoded #202124/#5F6368) fixing dark-on-dark contrast; PASS: focus ring is a 3dp white stroke with no fill so labels stay bright, and the primary Close button was switched to TonalButton so nothing looks pre-focused; PASS: focus order explicitly wired - device verified UP from refresh now lands on cleanupStandard (previously skipped the whole cleanup row to totalLimit), then retention; no FATAL EXCEPTION

Task-Guard: CACHE-MGMT-01-TVUI2
2026-09-24 13:53:18 +08:00
Silent1566
6edd360509 fix(theme): keep primary readable on every surface role
Verification: restored the Material 3 primary-on-surface readability contract: ThemeResolver.readableAccent keeps hue/chroma and walks HCT tone to the nearest passing tone (Material baseline scheme measures 4.97-10.91:1, our frozen palettes 4.94-10.76, both confirmed from the Material 1.14.0 AAR and measured locally); onPrimary is recomputed when primary is repaired; focus now follows primary because the frozen palette ships focus==primary and the binder only rewrites a shared baseline colour when both roles agree (that divergence was why the first repair attempt left buttons unchanged); ThemeContrast gained the four primary/surface pairs so repair and gate can never disagree; 6 new tests incl. byte-identical empty profile and seed palettes not repaired; 2 pre-existing tests updated where they encoded pre-fix behaviour (documented, not silently loosened); mobile+leanback theme tests and full suites plus both compileWithJavac PASS with zero failing classes; check_ui_tokens --strict PASS; dev3 API28 device: dialog action text went from #0B57D0 (1.28:1, unreadable) to #AAC0FF (4.57:1) on the #7B1FA2 probe while keeping hue, preset chips likewise became legible; device prefs byte-verified restored

Task-Guard: THEME-PRIMARY-CONTRAST-20260924
2026-09-24 13:53:11 +08:00
Silent1566
438c455330 fix(leanback): clear stale home types on site switch
Verification: :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.HomeSiteSwitchClearsTypesTest

Task-Guard: home-site-clear-types
2026-09-24 13:35:09 +08:00
Silent1566
90e16653b2 fix: use TV pickers for TMDB route selection
Verification: Runtime TV remote verification on 192.168.50.3:5555: API route opened picker, DPAD_DOWN+ENTER selected official API and input text changed to 官方 API(直连); image route opened picker, three DPAD_DOWN+ENTER selected wsrv.nl 图片代理 and input text changed to wsrv.nl 图片代理. Unit contract test passed: :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.dialog.TmdbSourceDialogInflationContractTest. Leanback arm64 debug APK built and overwritten via scripts/build_arm64_debug_install.sh.

Task-Guard: FIX-TV-TMDB-ROUTE-REMOTE-PICKER
2026-09-24 13:29:33 +08:00
Silent1566
3f8ad880de
Merge pull request #355 from Silent1566/dev2
修复电视详情页焦点裁切与滚动留白
2026-09-24 13:25:05 +08:00
Silent1566
374687e585 fix(theme): theme the remaining AppCompat and platform dialogs
Verification: 9 production AlertDialog.Builder sites in 8 files converted to WebHtvAlertDialogBuilder (ThemeColorPickerDialog, mobile+leanback ThemeDialog, AudioActivity platform AlertDialog, DebugLogDialog x2, leanback HomeMenuKeyDialog/HomeActivity/HistoryActivity); CrashActivity deliberately exempt with in-code rationale; guard test now uses a regex that also catches fully-qualified new androidx.appcompat.app.AlertDialog.Builder( and immediately surfaced 4 previously-unlisted sites; mobile+leanback theme unit tests BUILD SUCCESSFUL; both flavors compileWithJavac PASS; check_ui_tokens --strict PASS violations=0 legacy=0 contrast 38/0; dev3 192.168.50.3:5559 API28: theme colour picker 504631 px and theme editor dialog 637313 px exactly #7B1FA2 with all four corner samples #CFD8EB -> #7B1FA2, FATAL=0; device prefs byte-verified restored

Task-Guard: THEME-ALERTDIALOG-BUILDER-20260924
2026-09-24 13:20:03 +08:00
Silent1566
6d62c593c6 评审远端 beta 合并后的电视详情页焦点修复并补齐相关视频导航
Verification: Leanback arm64-v8a Debug 单元测试通过;leanback/arm64-v8a Debug APK 通过 scripts/build_arm64_debug_install.sh 构建并覆盖安装到 192.168.50.3:5557,安装校验通过;应用启动无本次新增崩溃;最终复评通过:焦点监听安装/移除各一次、相关视频裁切与内边距各一次、相关视频行已纳入 first/last 程序化导航且顺序正确。

Task-Guard: BETA-SYNC-REVIEW-DEV2-20260924
2026-09-24 13:01:26 +08:00
www.xueximeng.com
d187f6ae8b test(exo): avoid package-private recoverable access
Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests 'com.fongmi.android.tv.player.danmaku.*' --no-daemon (BUILD SUCCESSFUL in 44s)

Task-Guard: fix-exo-recoverable-test-compile
2026-09-24 12:45:28 +08:00
Silent1566
b297c7fc93 Merge remote-tracking branch 'origin/beta' into dev2 2026-09-24 12:27:30 +08:00
Silent1566
e56adc7e5e test(base): realign the stale touch-optimization source assertion
Verification: TouchOptimizationHelperSourceTest realigned to the 584b64514 single-inflate form while preserving its original intent (fragment callbacks registered before the content view; wall then sync inside the override) plus a new assertion that the resolved binding instance is the one passed to setContentView; full mobile unit suite ./gradlew :app:testMobileArm64_v8aDebugUnitTest reports 712 classes / 4863 tests / 0 failures / 0 errors with fresh result timestamps, so the suite is green again after the pre-existing stale red

Task-Guard: TEST-STALE-BASETHEME-ASSERT-20260924
2026-09-24 12:25:35 +08:00
Silent1566
d8c51593a2 refactor(theme): route flavour alert dialogs through the themed builder
Verification: 33 flavor files / 69 raw MaterialAlertDialogBuilder sites migrated to WebHtvAlertDialogBuilder (mobile 20/38, leanback 13/31); guard test widened to main+mobile+leanback (0 raw builders repo-wide) and AdRuleManageDialogLayoutTest updated; mobile+leanback theme unit tests BUILD SUCCESSFUL; both flavors compileWithJavac PASS; full mobile unit suite (712 classes) passed except one pre-existing stale assertion TouchOptimizationHelperSourceTest (its file and the leanback BaseActivity it asserts on are untouched by this task, breakage dates to 584b64514); check_ui_tokens --strict PASS violations=0 legacy=0 contrast 38/0; dev3 192.168.50.3:5559 API28 mobile HistoryActivity delete-all confirm dialog now shows 307808 px exactly #7B1FA2 with four panel sample points #CFD8EB -> #7B1FA2 under the purple probe; device prefs byte-verified restored

Task-Guard: THEME-DIALOG-BUILDER-FLAVORS-20260924
2026-09-24 12:21:52 +08:00
Silent1566
2cdd60fc55 fix: make TMDB route dropdowns TV remote operable
Verification: Unit contract test passed: :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.dialog.TmdbSourceDialogInflationContractTest; Leanback arm64 debug APK built with scripts/build_arm64_debug_install.sh and overwritten to 192.168.50.3:5555

Task-Guard: FIX-TV-TMDB-ROUTE-REMOTE
2026-09-24 12:17:37 +08:00
Silent1566
5afd512551 fix(theme): recolour the alert dialog window background
Verification: mobile+leanback theme unit tests BUILD SUCCESSFUL incl. new dialogWindowBackgroundIsRecolouredOutsideTheViewTree; leanback compileWithJavac PASS; check_ui_tokens --strict PASS violations=0 legacy=0 contrast 38/0; dev3 192.168.50.3:5559 API28 FollowingActivity import-subscription dialog (same scenario that previously kept the compiled panel) now shows 391852 changed pixels of which 364824 are exactly the probe #7B1FA2, four panel sample points all #CFD8EB -> #7B1FA2, title white via onSurface, primary untouched so buttons stayed baseline blue; device prefs byte-verified restored

Task-Guard: THEME-WINDOW-BACKGROUND-20260924
2026-09-24 12:03:29 +08:00
Silent1566
567bfdcd39 fix(theme): stop passing a self-referencing overlay as a dialog theme
Verification: mobile+leanback theme unit tests BUILD SUCCESSFUL incl. new dialogThemeArgumentsResolveColorPrimaryConcretely (parses every res style + parent chain, requires nearest colorPrimary to be a concrete @color, correctly allows inheriting ThemeOverlay.WebHTV.Dialog.NoInset and would reject the removed self-referencing overlay); mobile+leanback compileWithJavac PASS; check_ui_tokens --strict PASS violations=0 legacy=0 contrast 38/0; dev3 192.168.50.3:5559 API28 FollowingActivity cancel-following dialog now renders with FATAL EXCEPTION=0 and Theme.AppCompat=0 (100% reproducible crash before); decisive device experiment proved ThemeOverlay_WebHTV_Dialog as themeResId does NOT crash so only the self-referencing overlay was defective and the other 36 sites were left untouched

Task-Guard: THEME-DIALOG-THEME-ID-FIX-20260924
2026-09-24 11:54:14 +08:00
Silent1566
005efaf637 按钮/控件焦点也保留上下间距,消除贴屏顶部的拥挤
Verification: leanback/arm64-v8a Debug 构建通过并覆盖安装到 192.168.50.3:5557;实测焦点按钮不再贴屏幕顶端:play [32,24]-[1068,116](原为 y=0)、changeSourceDetail [32,24]-[226,108]、线路芯片 [32,24]-[208,86]、episodeTitle [120,24]-[290,96],均为 12dp 上间距;焦点头部按钮金色描边完整(上 y32-37、下 y102-107),屏幕 0-3 行无金色像素;下方外部链接行 bottom 间距 16dp(y912-992);向下遍历 22 步无元素 y1<0 或 y2>1080;回归验证卡片行标题锚定仍生效(猜你喜欢[24,67]、个性推荐·TMDB[24,67]、主创[24,67] 均完整,无标题残缺)。

Task-Guard: TV-BUTTON-ROW-FOCUS-MARGIN
2026-09-24 11:52:06 +08:00
Silent1566
36d2c1faad
Merge pull request #354 from Silent1566/dev1
修复全局历史显式关闭的持久化
2026-09-24 11:36:47 +08:00
Silent1566
0f4401b57f 卡片行聚焦改为按分区标题锚定,消除标题被切与下一个标题残缺
Verification: leanback/arm64-v8a Debug 构建通过并覆盖安装到 192.168.50.3:5557;在 TmdbDetailActivity(墨雨云间)逐行上下导航实测:聚焦任意卡片行时其分区标题完整可见(如 personalTmdbTitle y24-67、relatedVideoTitle y24-67,均留 12dp 顶部间距),且底部不再出现残缺的下一个标题(DOWN #2 时 personalDoubanList 行底 y1040、下一标题完全移出可视区;其余各步均无标题跨底边);相关视频行焦点卡片四边金色描边完整(上 y710-713、下 y1030-1033、左 x37-40、右 x607-610),上一轮修复无回归。

Task-Guard: TV-CARD-ROW-TITLE-ANCHOR
2026-09-24 11:28:51 +08:00
Silent1566
2c2712e55d refactor(theme): route shared alert dialogs through a themed builder
Verification: mobile+leanback compile BUILD SUCCESSFUL; mobile+leanback theme.* and ui.dialog.* unit tests PASS incl. new guard materialAlertDialogsAreBuiltThroughTheThemedBuilder (0 raw builders left in src/main) and updated DialogRoundedCornerSourceTest; check_ui_tokens --strict PASS violations=0 legacy=0 contrast 38/0; dev3 192.168.50.3:5559 API28 FollowingActivity 导入订阅 dialog (1-arg builder, previously unbound) shows 9980 changed pixels with title/message text moving to #ADADAD under the purple probe vs 0 rewrites before; baseline 409c1b52b rebuilt and reinstalled to prove the 12 ThemeOverlay-as-themeResId crashes are pre-existing (identical stack via git stash), not a migration regression; device prefs byte-verified restored

Task-Guard: THEME-DIALOG-BUILDER-MAIN-20260924
2026-09-24 11:21:30 +08:00
Silent1566
73c4a7d7d5 Fix explicit global history off persistence
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.history.GlobalHistorySettingSourceTest BUILD SUCCESSFUL

Task-Guard: global-history-off-persistence
2026-09-24 11:06:50 +08:00
Silent1566
ac95866199
Merge pull request #353 from Silent1566/dev1
merge: 同步 dev1 最新修复并合入 beta
2026-09-24 10:57:01 +08:00
Silent1566
6aabb5b23a merge: 同步远端 dev1 主题与详情修复
合并 origin/dev1 的主题系统、TMDB 详情模式与播放修复,保留 beta 合并后的外链浏览器与追更/动作卡改动。

Verification: 定向主题/详情/追更/动作卡测试通过;mobile 与 leanback Arm64 Java 编译通过。

Task-Guard: beta-sync-review-dev1-remote-20260924
2026-09-24 10:45:25 +08:00
Silent1566
891c1f4dd4 卡片行焦点留白,避免放大描边被可视区裁切
Verification: leanback/arm64-v8a Debug 构建通过并覆盖安装到 192.168.50.3:5557;复现'从下方行向上选到相关视频'场景,relatedVideoList 行顶保留 24dp 留白(y=48px),焦点卡片 [37,54]-[611,378] 四边金色描边完整(上 y54-57、下 y374-377、左 x37-40、右 x607-610);向下进入该行亦完整(行 y631-967、卡片 y637-961);上下往返多次稳定无抖动。

Task-Guard: TV-CARD-ROW-FOCUS-MARGIN
2026-09-24 10:12:53 +08:00
Silent1566
1c53bf14fe fix: 清理 TMDB 详情页重复导入
Verification: 合并 origin/beta=87c147af5b49e7447a41a0a2267155d3dbef85a0;评审 beta 与 dev1 已提交未推送改动并清理 TmdbDetailActivity 重复导入;bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.web.CatWebWiringTest --tests com.fongmi.android.tv.content.ActionCardHelperTest --tests com.fongmi.android.tv.ui.activity.FollowingUiSourceTest :app:compileMobileArm64_v8aDebugJavaWithJavac :app:compileLeanbackArm64_v8aDebugJavaWithJavac --console=plain:BUILD SUCCESSFUL;bash scripts/build_arm64_debug_install.sh --flavor leanback --serial 192.168.50.3:5555:Debug 构建成功并 install -r 覆盖安装;启动首页无崩溃;git diff --check 与重复导入静态检查通过

Task-Guard: beta-sync-review-dev1-20260924
2026-09-24 09:42:17 +08:00
Silent1566
409c1b52bc fix(theme): recolour Material shape panels in the binder
Verification: mobile theme unit tests BUILD SUCCESSFUL incl. backgroundChannelCoversMaterialShapeAndInsetWrappers; dev3 192.168.50.3:5559 API28 LinkDialog(BaseAlertDialog) A/B with 32s cold start + tap: 277730 changed pixels of which 245581 are exactly the probe #7B1FA2 (dialog panel recoloured, previously skipped because the fill is a MaterialShapeDrawable wrapped in InsetDrawable) plus 7870 px text #ADADAD and 4145 px #FFFFFF; primary left untouched so the confirm button stayed baseline blue; device prefs byte-verified restored

Task-Guard: THEME-SHAPE-BACKGROUND-20260924
2026-09-24 09:16:08 +08:00
Silent1566
2a0b8af916 fix(theme): bind FollowingActivity into the shared appearance contract
Verification: mobile+leanback theme unit tests BUILD SUCCESSFUL incl. followingPageOptsIntoTheSharedAppearanceContract and nativeActivitiesOutsideBaseActivityAreExplicitlyAccountedFor; dev3 192.168.50.3:5559 API28 FollowingActivity A/B with 14s cold start: exactly the 14735 baseline #0B57D0 (colorPrimary) pixels left the baseline under the orange #FB8C00 preset and became #88511D, secondaryContainer #DFE2E6 -> #FFDCC2, error #552422 unchanged as expected, FATAL EXCEPTION=0; device prefs byte-verified restored

Task-Guard: THEME-FOLLOWING-BIND-20260924
2026-09-24 09:02:29 +08:00
Silent1566
45eb9b620b Merge remote-tracking branch 'origin/beta' into dev1 2026-09-24 08:53:37 +08:00
Silent1566
49b9b4c3e6 修复电视 TMDB 详情页相关视频焦点放大裁切
Verification: leanback/arm64-v8a Debug 构建通过并覆盖安装到 192.168.50.3:5557;在 TmdbDetailActivity(墨雨云间·剧幕)聚焦相关视频首卡,实测四边金色描边完整:顶边 y184-187、底边 y504-507、左边 x37-40、右边 x607-610,均落在 relatedVideoList 容器边界内。

Task-Guard: TV-RELATED-VIDEO-FOCUS-CLIP-2
2026-09-24 01:58:46 +08:00
www.xueximeng.com
43307d50d3 fix(cache): make TV cache dialog 90% adaptive and fully scrollable
Verification: PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: TV device NX627J Android 9 192.168.50.3:5563 - dialog outer bounds [96,54][1824,1026] = 1728x972 of 1920x1080 (~90%x90%); PASS: content root is a scrollable Android ScrollView covering the content area; PASS: after remote-key scrolling, autoCleanup/retention/totalLimit/cleanupLight/cleanupStandard/cleanupDeep/refresh/close all visible and focusable; PASS: no FATAL EXCEPTION after fixing the LayoutParams ClassCastException regression

Task-Guard: CACHE-MGMT-01-TVUI
2026-09-24 01:05:52 +08:00
Silent1566
db06ebb4cb fix(theme): bind the native dialog families to the theme tokens
Verification: mobile+leanback theme unit tests BUILD SUCCESSFUL incl. alertDialogFamiliesBindThroughTheSharedDialogChannel; :app:compileLeanbackArm64_v8aDebugJavaWithJavac PASS; dev3 192.168.50.3:5559 API28 ConfigDialog(BaseAlertDialog) A/B with 30s cold start: noise baseline default-vs-default=320 px, teal seed=9545 px, slot probe=22498 px, and exactly the 7870 pixels rendered #181A1C (?attr/colorOnSurface) in default turned #ECECEC under the onSurface=#FFFFFF probe (0 rewrites before this change); device prefs restored to pre-test values

Task-Guard: THEME-DIALOG-BIND-20260923
2026-09-24 00:47:29 +08:00
Silent1566
f54d4de2d6 fix(tv): guard WebView anti-darkening by capability
Verification: bash ./gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.web.CatWebWiringTest :app:compileLeanbackArm64_v8aDebugJavaWithJavac --console=plain: BUILD SUCCESSFUL; bash scripts/build_arm64_debug_install.sh --flavor leanback --serial 192.168.50.3:5555: BUILD SUCCESSFUL and install -r success; runtime check on 192.168.50.3:5555 confirmed builtin browser opens CatWebActivity without UnsupportedOperationException on Android 9 WebView.

Task-Guard: catwebbrowser-disable-auto-darkening
2026-09-24 00:40:44 +08:00
www.xueximeng.com
46b19b9111 docs(cache): record recovery anchor and device blocker
Verification: PASS: recovery anchor updated with current test count 34/34, completed offline hardening, exact offline blocker (assigned device 192.168.50.3:5563 offline for 3 consecutive goal turns), the 5 device-dependent acceptance items, and the single next action to take once the device recovers

Task-Guard: CACHE-MGMT-01-ANCHOR
2026-09-23 22:00:44 +08:00
www.xueximeng.com
44d9afb9ef fix(cache): confine retention deletions to the declared root
Verification: PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; PASS: cache pure JUnit 34/34 including new out-of-root deletion guard and symlink-skip tests

Task-Guard: CACHE-MGMT-01-BOUNDS
2026-09-23 21:37:01 +08:00
www.xueximeng.com
a2d4e7467e docs(cache): record playback hot-path diff audit
Verification: PASS: git diff vs baseline 8e4d9333de over player/, androidx/media3/mpvplayer, web/, api/, service/, App and AndroidManifest is 110 insertions, 0 modifications, 0 deletions (pure additions); DiskCacheCapacityPolicy and LRU evictor untouched; recorded as structural evidence only, device-level in-play cleanup remains unverified

Task-Guard: CACHE-MGMT-01-HOTPATH
2026-09-23 21:03:35 +08:00
www.xueximeng.com
91fdd30412 feat(cache): validate module registry root invariants
Verification: PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; PASS: cache pure JUnit 32/32 including new CacheModuleRegistryTest (unique ids, cache-dir containment, nested/duplicate root rejection, registry purity)

Task-Guard: CACHE-MGMT-01-REGISTRY
2026-09-23 20:51:20 +08:00
Silent1566
87c147af5b
Merge pull request #352 from Silent1566/dev4
修复动作卡片空回传误报并同步 beta
2026-09-23 20:36:16 +08:00
Silent1566
1f42fa6927 fix: preserve action success and error semantics
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.content.ActionCardHelperTest and :app:compileMobileArm64_v8aDebugJavaWithJavac passed (BUILD SUCCESSFUL)

Task-Guard: ACTION-CARD-EMPTY-SUCCESS-20260923
2026-09-23 20:23:06 +08:00
Silent1566
090918863c 修复电视详情页相关视频焦点放大裁切
Verification: 资源编译通过;192.168.50.3:5557 上聚焦《墨雨云间》相关视频卡片,焦点四边、标题及底部标签均完整显示,无裁切。

Task-Guard: TV-RELATED-VIDEO-FOCUS-CLIP
2026-09-23 20:14:29 +08:00
www.xueximeng.com
b143c13e96 fix(cache): keep total-limit rescan off the main thread
Verification: PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; PASS: cache pure JUnit 25/25; FIX: total-limit follow-up scan now runs on scheduler background thread instead of main thread

Task-Guard: CACHE-MGMT-01-LIMIT
2026-09-23 20:05:51 +08:00
Silent1566
fdf31d00d1 Merge remote-tracking branch 'origin/beta' into dev4 2026-09-23 19:39:46 +08:00
Silent1566
a993531170 feat(tv): choose browser for TMDB external links
Verification: bash ./gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.web.CatWebWiringTest :app:compileLeanbackArm64_v8aDebugJavaWithJavac :app:compileMobileArm64_v8aDebugJavaWithJavac --console=plain: BUILD SUCCESSFUL; bash scripts/build_arm64_debug_install.sh --flavor leanback --serial 192.168.50.3:5555: BUILD SUCCESSFUL and install -r success; runtime check on 192.168.50.3:5555 confirmed external link click shows choice dialog and builtin choice opens CatWebActivity.

Task-Guard: tmdb-external-link-choice-browser
2026-09-23 19:28:39 +08:00
Silent1566
04908bf044 fix(theme): make theme colour actually reach native views
Verification: theme unit tests (mobile theme scope) BUILD SUCCESSFUL incl. new seed-mapping/fallback/button-channel contracts; scripts/check_ui_tokens.sh --strict PASS (violations=0 legacy=0 contrast 38/0 min=4.28); dev3 192.168.50.3:5559 API28 device A/B: slot probe turned exactly the 4008 baseline onSurface pixels white and 3094 pixels respond to a teal seed on HistoryActivity, unbindable FollowingActivity unchanged as control

Task-Guard: THEME-COLOR-BASELINE-20260923
2026-09-23 19:15:52 +08:00
www.xueximeng.com
6e430268e5 docs(cache): record vod config blocker root cause
Verification: PASS: blocker root cause captured from device stack trace: VodConfig.load NPE at OkHttp.newCall(OkHttp.java:174) because the configured VOD url was null; this blocks source-dependent playback/lyrics/karaoke/EPG regression checks and is unrelated to cache-management changes

Task-Guard: CACHE-MGMT-01-ROOTCAUSE
2026-09-23 18:57:49 +08:00
Silent1566
55a8412d2a fix: treat empty action response as success
Verification: ActionCardHelperTest 2/2 passed; mobile arm64 debug assembled and installed to 192.168.50.3:5561; APK sha256 35f754ba09ab18ca146e0e2c05cfefb47651cfadc33573aa4d52e484abc3005d

Task-Guard: ACTION-CARD-EMPTY-SUCCESS-20260923
2026-09-23 18:43:27 +08:00
www.xueximeng.com
561b65fd0f docs(cache): record playback regression blockers
Verification: PASS: regression blocker recorded with direct device evidence; local ijk-smoke.mp4 and remote sample both returned MediaSession Source error state=7 on NX627J; no VOD/EPG config loaded; TV uiautomator returned null root; these acceptance items remain unchecked

Task-Guard: CACHE-MGMT-01-REGRESS
2026-09-23 18:17:38 +08:00
www.xueximeng.com
202529c530 docs(cache): record config preservation evidence
Verification: PASS: on NX627J Android 9, after running STANDARD cleanup (result PARTIAL, 0 deleted), mpv.conf md5 remained d696b6350e5cfc151769066120b803c3, fonts.conf remained cf92f416c8dc00d940765b5243032887, and WebHome preference lines hash remained 7963216a978342b128f5a24e3c909fee

Task-Guard: CACHE-MGMT-01-CONFIG
2026-09-23 17:50:41 +08:00
www.xueximeng.com
15df276114 docs(cache): record persistent job scheduling evidence
Verification: PASS: NX627J Android 9 auto-cleanup toggle persisted cache_mgmt_auto_enabled=true/false; PASS: dumpsys jobscheduler shows persisted 7-day CacheCleanupJobService with batteryNotLow; PASS: disabling auto cleanup left pending queue empty and job cancelled

Task-Guard: CACHE-MGMT-01-JOB
2026-09-23 16:40:08 +08:00
www.xueximeng.com
9b827c8195 docs(cache): record compatibility acceptance evidence
Verification: PASS: NX627J Android 9 rotation and night-mode toggles kept cache dialog controls intact with process alive and no FATAL/ANR; PASS: tv main database content file unchanged by cache dialog; PASS: cache_mgmt_inventory_duration_ms=9, modules=13, warnings=16

Task-Guard: CACHE-MGMT-01-COMPAT
2026-09-23 15:16:07 +08:00
Silent1566
61ddddec4b
Merge pull request #351 from Silent1566/dev2
dev2 同步 beta:追更播放与 TV 布局修复
2026-09-23 15:12:26 +08:00
Silent1566
ca0456c30b Merge remote-tracking branch 'origin/beta' into dev2 2026-09-23 14:50:48 +08:00
Silent1566
223060c18f feat(tv): open TMDB external links in app browser
Verification: bash ./gradlew :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.web.CatWebWiringTest :app:compileLeanbackArm64_v8aDebugJavaWithJavac :app:compileMobileArm64_v8aDebugJavaWithJavac --console=plain: BUILD SUCCESSFUL; bash scripts/build_arm64_debug_install.sh --flavor leanback --serial 192.168.50.3:5555: BUILD SUCCESSFUL and install -r success; aapt2/aapt APK inspection confirms detail_external_opening string and CatWebActivity/TmdbDetailActivity manifest entries.

Task-Guard: tmdb-external-link-in-app-browser
2026-09-23 14:43:23 +08:00
Silent1566
1366f2dba6
Merge pull request #350 from Silent1566/dev4
合并 dev4:完善 TMDB 自动线路与追更页适配
2026-09-23 14:41:51 +08:00
www.xueximeng.com
9d9a644c16 docs(cache): remove stale head reference
Verification: PASS: documentation state no longer pins a stale HEAD; reflects current verified implementation and remains committed

Task-Guard: CACHE-MGMT-01-DOCS2
2026-09-23 14:34:35 +08:00
www.xueximeng.com
b496926734 docs(cache): reconcile acceptance checklist with device evidence
Verification: PASS: acceptance checklist updated from authoritative evidence: inventory scan 9ms/13 modules/16 warnings on NX627J Android 9; mobile and TV cleanup completion verified; cache pure JUnit 25/25; arm64 debug builds compiled and installed

Task-Guard: CACHE-MGMT-01-DOCS
2026-09-23 14:30:25 +08:00
www.xueximeng.com
aa6abd4dd4 feat(cache): persist inventory scan metrics
Verification: PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; PASS: device inventory metrics on NX627J Android 9 at 192.168.50.3:5563: 13 modules, 16 warnings, 9 ms scan duration; PASS: leanback debug install via temporary applicationId override without source change

Task-Guard: CACHE-MGMT-01-OBS
2026-09-23 14:25:27 +08:00
www.xueximeng.com
41fb1689a6 docs(cache): record mobile and TV acceptance evidence
Verification: PASS: mobile device smoke on LIO-AN00 Android 9 (192.168.50.3:5555) including cleanup completion dialog; PASS: TV leanback device smoke on NX627J Android 9 (192.168.50.3:5563) including cleanup completion dialog and persisted cache_mgmt_cleanup_history; PASS: cache pure JUnit 25/25

Task-Guard: CACHE-MGMT-01-EVIDENCE
2026-09-23 13:29:29 +08:00
Silent1566
587c4d51f6 调整去广告实验性设置提示
Verification: bash ./gradlew :app:processMobileArm64_v8aDebugResources --offline --console=plain: BUILD SUCCESSFUL

Task-Guard: AD-SETTINGS-EXPERIMENTAL-WARNING
2026-09-23 13:29:04 +08:00
Silent1566
e6634543ee Merge remote-tracking branch 'origin/beta' into dev4 2026-09-23 13:04:12 +08:00
Silent1566
0f110b3115 fix(following): scroll TV header with content
Verification: git diff --check passed; XML parsing passed for main and leanback layouts; FollowingUiSourceTest BUILD SUCCESSFUL; leanback arm64-v8a Debug build and overwrite installation to 192.168.50.3:5561 succeeded; runtime reproduction verified the TV header scrolls with content, FollowingActivity remained resumed, screenshots captured, and no FATAL EXCEPTION occurred; Gradle daemons stopped

Task-Guard: FOLLOWING-TV-FOCUS-VISIBILITY-20260923
2026-09-23 12:49:15 +08:00
Silent1566
584b64514a fix(base): keep one inflated binding per activity
Verification: mobile arm64 debug build + overwrite install Success on 192.168.50.3:5559; bottom bar taps verified: 点播 -> VodFragment visible/SettingFragment hidden, 设置 -> SettingFragment visible, 直播 -> LiveActivity resumed; FATAL EXCEPTION=0; leanbackArm64_v8aDebug assemble SUCCESS

Task-Guard: HOME-NAV-BINDING-20260923
2026-09-23 12:43:20 +08:00
www.xueximeng.com
d404d93aae feat(cache): add safe plugin cache retention
Verification: PASS: cache pure JUnit 25/25 (CacheInventoryTest + CachePolicyEngineTest + CacheRetentionManagerTest + CacheAutoCleanupPolicyTest + CacheCleanupJournalTest + CacheTotalLimitPolicyTest) via direct JUnit runner; PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac

Task-Guard: CACHE-MGMT-01-P2D
2026-09-23 11:40:14 +08:00
www.xueximeng.com
e6630f6f1b fix(cache): keep cache dialog actions visible on mobile
Verification: PASS: mobile debug install and device smoke on LIO-AN00 Android 9 at 192.168.50.3:5555; PASS: cache dialog layout shows module list, limits, auto-cleanup, retention, total limit, and all three cleanup buttons; PASS: light cleanup confirmation and result dialog completed; NOTE: leadback package installs on HD1910 but TV focus-navigation acceptance remains unverified

Task-Guard: CACHE-MGMT-01-UI
2026-09-23 11:26:54 +08:00
Silent1566
c575029dc5
Merge pull request #349 from Silent1566/dev1-following-resume-fix-20260923
修复追更页继续播放的来源绑定与详情模式路由
2026-09-23 11:02:06 +08:00
Silent1566
6b1d8652a8 Merge remote-tracking branch 'origin/beta' into dev1 2026-09-23 10:55:17 +08:00
Silent1566
90faa2bada fix(following): respect configured detail mode on continue playback
Verification: :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.FollowingUiSourceTest BUILD SUCCESSFUL; :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.FollowingUiSourceTest BUILD SUCCESSFUL; git diff --check passed; Gradle daemons stopped

Task-Guard: tv-following-respect-tmdb-mode
2026-09-23 10:36:29 +08:00
www.xueximeng.com
0790ca363a fix(cache): enforce temp file limit and hide unimplemented plugin limit
Verification: PASS: cache pure JUnit 24/24 (CacheInventoryTest + CachePolicyEngineTest + CacheRetentionManagerTest + CacheAutoCleanupPolicyTest + CacheCleanupJournalTest + CacheTotalLimitPolicyTest) via direct JUnit runner; PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac

Task-Guard: CACHE-MGMT-01-P2C
2026-09-23 10:11:15 +08:00
www.xueximeng.com
0392c5e095 feat(cache): add P2 total cache soft limit
Verification: PASS: cache pure JUnit 23/23 (CacheInventoryTest + CachePolicyEngineTest + CacheRetentionManagerTest + CacheAutoCleanupPolicyTest + CacheCleanupJournalTest + CacheTotalLimitPolicyTest) via direct JUnit runner; PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac

Task-Guard: CACHE-MGMT-01-P2B
2026-09-23 10:09:09 +08:00
www.xueximeng.com
214eae7ba1 fix(cache): persist auto cleanup across reboot and guard symlinks
Verification: PASS: cache pure JUnit 20/20 (CacheInventoryTest + CachePolicyEngineTest + CacheRetentionManagerTest + CacheAutoCleanupPolicyTest + CacheCleanupJournalTest) via direct JUnit runner; PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; PASS: processMobileArm64_v8aDebugManifest; PASS: processLeanbackArm64_v8aDebugManifest

Task-Guard: CACHE-MGMT-01-P3B
2026-09-23 10:06:05 +08:00
www.xueximeng.com
b405167163 feat(cache): add P4 runtime governance and cleanup journal
Verification: PASS: cache pure JUnit 19/19 (CacheInventoryTest + CachePolicyEngineTest + CacheRetentionManagerTest + CacheAutoCleanupPolicyTest + CacheCleanupJournalTest) via direct JUnit runner; PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; LIMIT: reboot recovery still not implemented

Task-Guard: CACHE-MGMT-01-P4
2026-09-23 09:34:21 +08:00
www.xueximeng.com
b3e89479ae feat(cache): add P3 automatic cleanup scheduler
Verification: PASS: cache pure JUnit 18/18 (CacheInventoryTest + CachePolicyEngineTest + CacheRetentionManagerTest + CacheAutoCleanupPolicyTest) via direct JUnit runner; PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; LIMIT: process-local scheduler only; reboot recovery not implemented

Task-Guard: CACHE-MGMT-01-P3A
2026-09-23 09:17:36 +08:00
www.xueximeng.com
eeca921eb6 feat(cache): add P2 module limits and retention policy
Verification: PASS: cache pure JUnit 15/15 (CacheInventoryTest + CachePolicyEngineTest + CacheRetentionManagerTest) via direct JUnit runner; PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; NOTE: full unit-test task still blocked by pre-existing ExoCompressedAudioDirectPolicyTest isRecoverable visibility error

Task-Guard: CACHE-MGMT-01-P2
2026-09-23 08:51:10 +08:00
www.xueximeng.com
d6393243d6 feat(cache): add P1 tiered module cleanup
Verification: PASS: cache pure JUnit 12/12 (CacheInventoryTest + CachePolicyEngineTest) via direct JUnit runner; PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; NOTE: full unit-test task still blocked by pre-existing ExoCompressedAudioDirectPolicyTest isRecoverable visibility error

Task-Guard: CACHE-MGMT-01-P1
2026-09-23 08:38:33 +08:00
Silent1566
5559baa86d fix(following): force TV continue playback to direct history route
Verification: :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.FollowingUiSourceTest BUILD SUCCESSFUL; :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.FollowingUiSourceTest BUILD SUCCESSFUL; scripts/build_arm64_debug_install.sh --flavor leanback --abi arm64-v8a --serial 192.168.50.3:5555 BUILD SUCCESSFUL and adb install -r Success; FollowingContinueDeviceTest OK (1 test); git diff --check passed

Task-Guard: tv-following-direct-resume-route
2026-09-23 04:22:33 +08:00
Silent1566
d1ef28cf97 fix(following): keep TV content below top edge
Verification: git diff --check passed; FollowingUiSourceTest BUILD SUCCESSFUL; leanback arm64-v8a Debug build and overwrite install to 192.168.50.3:5561 succeeded; runtime UI verified top controls at y=56, recycler at y=152, first item title at y=192, FollowingActivity resumed, no FATAL EXCEPTION

Task-Guard: FOLLOWING-TV-TOP-INSET-20260923
2026-09-23 01:53:15 +08:00
Silent1566
4b18b8266a fix(following): preserve source binding on continue playback
Verification: :app:testLeanbackArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.FollowingUiSourceTest BUILD SUCCESSFUL; git diff --check passed

Task-Guard: tv-following-continue-source
2026-09-23 00:03:55 +08:00
www.xueximeng.com
77b5d10421 feat(cache): add P0 cache inventory and management overview
Verification: PASS: compileMobileArm64_v8aDebugJavaWithJavac; PASS: compileLeanbackArm64_v8aDebugJavaWithJavac; NOTE: unit-test source added, full unit-test compilation blocked by pre-existing ExoCompressedAudioDirectPolicyTest isRecoverable visibility error

Task-Guard: CACHE-MGMT-01-P0
2026-09-22 23:42:33 +08:00
Silent1566
6c3d1bbb8e fix(nav): keep core bottom navigation items visible by default
Verification: mobile arm64 debug build Success and overwrite install on 192.168.50.3:5559; cold start nav_position=1 renders 3 bottom items vod/live/setting at 295x140 each with icons, while lab/following stay hidden by config; FATAL EXCEPTION=0

Task-Guard: HOME-NAV-VISIBILITY-20260922
2026-09-22 22:16:05 +08:00
www.xueximeng.com
987f19e90e docs: add cache management upgrade design
Verification: Documentation-only design review: CACHE-MGMT-01 design includes evidence-backed architecture, module boundaries, cleanup policy, UI/TV behavior, phased plan, test matrix, acceptance criteria, rollback, and implementation checklist; static checks passed after replacing Markdown hard-break trailing spaces: 1724 lines, no conflict markers, guard check PASS, task_guard.sh start/finish used.

Task-Guard: CACHE-MGMT-01
2026-09-22 20:55:45 +08:00
Silent1566
adb79ea256 feat(theme): color preset chips and mark active seed
Verification: mobile arm64 debug build Success and overwrite install on 192.168.50.3:5559; on-device theme editor: default/wallpaper neutral chips, blue/teal/green/orange rendered in their own colors, selected chip switches blue->teal with visible high-contrast stroke; cancel closes dialog; FATAL EXCEPTION=0

Task-Guard: THEME-EDITOR-USABILITY-20260922
2026-09-22 20:05:13 +08:00
Silent1566
99e96c9218
Merge pull request #348 from Silent1566/dev2
feat: 增强 TMDB 自动线路与故障切换
2026-09-22 17:46:52 +08:00
Silent1566
bec5a5c098 Merge remote-tracking branch 'origin/beta' into dev2 2026-09-22 17:38:47 +08:00
Silent1566
ae1c334080 fix(tmdb): migrate legacy default routes to auto
Verification: 历史官方默认配置(无 routeMode)在 Android 9 UI dump 中 API/图片均显示自动;新增 routeMode 保证新用户明确选择官方直连后仍保持直连、历史自定义不改变;相关配置测试通过;Debug 包覆盖安装到 192.168.50.3:5557

Task-Guard: tmdb-route-mode-legacy
2026-09-22 17:27:19 +08:00
Silent1566
ae67e809a1 fix(tmdb): put auto route first and align failover labels
Verification: API 与图片两个自动选项文案统一为低延迟优先/失败切换,并均已放到各自下拉第一项;TmdbProxyTest 与 UI 聚焦测试通过,BUILD SUCCESSFUL;git diff --check 通过

Task-Guard: tmdb-api-auto-order-fix
2026-09-22 17:13:22 +08:00
Silent1566
70674c7fc4 fix(tmdb): put auto route first and align labels
Verification: API 与图片自动选项文案统一为低延迟优先/失败切换;两类下拉列表均将自动置于第一项;TmdbProxyTest 与 UI 资源/Java 聚焦测试通过,BUILD SUCCESSFUL;git diff --check 通过

Task-Guard: tmdb-auto-label-order
2026-09-22 16:48:37 +08:00
Silent1566
f72b4ec260 fix(tmdb): default legacy official routes to auto
Verification: 历史官方默认配置 UI dump 现显示 API/图片自动选项;显式 routeConfigured 自定义/直连保留测试通过;Mobile ARM64 Debug 聚焦测试 BUILD SUCCESSFUL;Debug 包已覆盖安装到 192.168.50.3:5557,未卸载已有包

Task-Guard: tmdb-auto-default-legacy
2026-09-22 16:28:02 +08:00
Silent1566
e2bce36ed5 feat(tmdb): add automatic route failover and wsrv image proxy
Verification: 研究记录已写入 docs/tmdb-proxy-design.md;自定义 API/图片配置、wsrv.nl URL 包装、API/图片自动候选、低延迟排序与失败冷却单测通过;Mobile ARM64 Debug 聚焦测试 BUILD SUCCESSFUL;scripts/build_arm64_debug_install.sh 已构建并覆盖安装到 192.168.50.3:5557,运行时 UI dump 确认 API/图片线路下拉控件;未卸载已有包

Task-Guard: tmdb-auto-failover
2026-09-22 15:43:27 +08:00
Silent1566
975708fe6b
Merge pull request #347 from Silent1566/dev4
合并 dev4:稳定接口身份与多地址支持
2026-09-22 15:29:25 +08:00
Silent1566
1f008c0da9 fix: preserve remote config identity during upsert
Verification: 已完成全量改动复评并修复远端配置稳定身份缺口;StableInterfaceIdentityAcceptanceTest 定向测试 BUILD SUCCESSFUL;git diff --check 通过;task_guard check 通过;Gradle daemon 已停止并回收资源

Task-Guard: dev4-beta-sync-20260922
2026-09-22 15:24:24 +08:00
Silent1566
d2ea042db7 Merge remote-tracking branch 'origin/beta' into dev4 2026-09-22 15:06:58 +08:00
Silent1566
5394255511 fix: wait for TV interface target before focusing
Verification: Fixed the remaining runtime gap where one DPAD move could leave focus on the previous row: Leanback ConfigAdapter now waits for the target RecyclerView child to attach, requests focus once, and removes the attach listener after success; no smooth scrolling or repeated delayed focus callbacks; InterfaceEntryInteractionTest and ConfigHistoryCurrentSourceGuardTest passed (10 tests total); Leanback arm64-v8a Debug APK compiled and was covered-installed with install -r to 192.168.50.3:5561; runtime UI dump verified consecutive DPAD_DOWN sequence across all six interface rows and add button, then DPAD_UP back across all six rows; Gradle daemon stopped and temporary UI dumps cleaned; git diff --check passed

Task-Guard: leanback-interface-focus-attach
2026-09-22 14:51:24 +08:00
Silent1566
85ebd3bcfa fix: remove TV interface focus flicker
Verification: InterfaceEntryInteractionTest passed with 8 tests, 0 failures, and 0 errors; ConfigHistoryCurrentSourceGuardTest passed with 2 tests; leanback arm64-v8a debug Java compilation passed; latest Debug APK built and covered-installed to 192.168.50.3:5561; removed smooth scrolling and repeated delayed focus callbacks, now stopping scroll and using one post-on-animation focus after direct layout-manager positioning to eliminate focus flicker; current interface remains focusable but cannot be reused; git diff --check passed; task_guard check passed

Task-Guard: leanback-interface-focus-no-flash
2026-09-22 13:56:15 +08:00
Silent1566
da20a189f6 fix(tmdb): remove invalid routes and split api image
Verification: 真实线路探测:itv666 API /3/configuration 返回 200 有效 JSON、/t/p/w342 图片返回 200 image/png;Worker 池各地址 TLS connect EOF,NAStool API 返回 HTML且图片404,已移除;布局检查通过(ScrollView单子节点,apiHostInput/imageHostInput各一次);聚焦 Mobile ARM64 Debug 单测全部通过;Debug APK 构建成功并使用 adb install -r 覆盖安装到 192.168.50.3:5557,运行时确认 API/图片双线路下拉控件

Task-Guard: tmdb-route-validity-split
2026-09-22 13:37:27 +08:00
Silent1566
eaf7f3bf1f i18n(theme): localize theme editor strings
Verification: zh-rCN/zh-rTW theme_editor_* key coverage complete (46/46 each); XML parse OK; :app:compileMobileArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; scripts/build_arm64_debug_install.sh --serial 192.168.50.3:5559 build+overwrite install Success; device zh-CN verified 外观与语言 and 主题色彩 dialog/editor show Chinese labels, presets and slot names; FATAL EXCEPTION=0

Task-Guard: THEME-EDITOR-I18N-20260922
2026-09-22 13:34:59 +08:00
Silent1566
29b2f0fd2b fix: align TV dialog sizing and focus
Verification: InterfaceEntryInteractionTest passed with 8 tests, 0 failures, and 0 errors; ConfigHistoryCurrentSourceGuardTest passed with 2 tests; leanback arm64-v8a debug Java compilation passed; latest Debug APK built and covered-installed to 192.168.50.3:5561; ConfigDialog now uses the LightDialog full-size overload so its on-show sizing is not overwritten by the compact default; dialog margins are 24dp (48dp total); DPAD row focus gets delayed retries after scroll; git diff --check passed; task_guard check passed

Task-Guard: leanback-interface-current-focus-and-config-fill
2026-09-22 13:28:18 +08:00
Silent1566
c808cfecd6
Merge pull request #345 from Silent1566/dev5
合并 beta 并同步手机版触屏设置测试
2026-09-22 12:58:56 +08:00
Silent1566
2985605626 refactor(tmdb): unify route selector input
Verification: 布局结构检查通过:ScrollView 仅一个直接子节点、仅保留 apiHostInput;聚焦 Mobile ARM64 Debug 单测通过(TmdbProxyTest、TmdbConfigImageHostTest、TmdbConfigEffectiveTest、TmdbConfigTestServiceTest、TmdbSourceDialogInflationContractTest);:app:processMobileArm64_v8aDebugResources/Java 编译通过;scripts/build_arm64_debug_install.sh 覆盖安装到 192.168.50.3:5557,运行时 UI dump 确认默认 https://api.tmdb.org、单一线路输入框,并实际打开下拉检查内置线路

Task-Guard: tmdb-unified-route
2026-09-22 12:27:22 +08:00
Silent1566
c6002dca3b fix: keep current interface focusable on TV
Verification: InterfaceEntryInteractionTest passed with 8 tests, 0 failures, and 0 errors; ConfigHistoryCurrentSourceGuardTest passed with 2 tests; leanback arm64-v8a debug Java compilation passed; Debug APK built and covered-installed to 192.168.50.3:5561; ConfigDialog content hierarchy now expands to the full screen-sized window; current interface row remains focusable while click reuse and delete protection remain; git diff --check passed; task_guard check passed

Task-Guard: leanback-interface-current-focus-and-config-fill
2026-09-22 12:19:01 +08:00
Silent1566
fd9cccfa03 docs(theme): record residual device matrix evidence
Verification: dev3 mobile: invalid hex #GGGGGG rejected with Enter #RRGGBB and no profile write; TAB/DPAD focus traversed 16 settings rows without crash; FATAL=0; theme remains reset to default

Task-Guard: L2-MATRIX-RESIDUAL-20260922
2026-09-22 12:15:36 +08:00
Silent1566
0d105eedd8 fix(theme): restore home navigation and theme editor entry
Verification: mobile arm64 debug build + overwrite install Success on 192.168.50.3:5559; cold start nav_position=1 opens native settings -> appearance -> theme editor; Blue preset, light #FF0000 apply/cancel/restart, dialog opacity 1.0, dark #00FF00 restart persistence verified; reset to default; FATAL=0

Task-Guard: NAV-THEME-ENTRY-20260922
2026-09-22 12:10:49 +08:00
Silent1566
838167adde feat(tmdb): add built-in proxy route selector
Verification: Layout XML/ScrollView structural check passed; focused Mobile ARM64 Debug unit tests passed (TmdbProxyTest, TmdbConfigImageHostTest, TmdbConfigEffectiveTest, TmdbConfigTestServiceTest, TmdbSourceDialogInflationContractTest); :app:processMobileArm64_v8aDebugResources passed; scripts/build_arm64_debug_install.sh mobile arm64-v8a installed over existing package on 192.168.50.3:5557 and runtime UI dump confirmed proxy selector/label without overlap

Task-Guard: tmdb-proxy-selector
2026-09-22 12:09:54 +08:00
Silent1566
6a01d426d8 修复手机版触屏优化移除后的测试断言
Verification: 聚焦 :app:testMobileArm64_v8aDebugUnitTest 通过 PushParserTest 与 TouchOptimizationHelperSourceTest;git diff --check 通过;第二轮改动复评通过

Task-Guard: dev5-beta-merge-review-20260922
2026-09-22 12:09:00 +08:00
Silent1566
be7dad1b7d Merge remote-tracking branch 'origin/beta' into dev5 2026-09-22 11:34:54 +08:00
Silent1566
1b42d66244 移除手机版个性设置中的触屏优化入口
Verification: :app:compileMobileArm64_v8aDebugJavaWithJavac BUILD SUCCESSFUL; mobile sources contain no touchOptimization references; leanback entry/runtime retained

Task-Guard: remove-mobile-touch-optimization
2026-09-22 11:06:40 +08:00
Silent1566
ca0e3dc2aa fix(tmdb): keep proxy input inside scroll content
Verification: layout structure passed: ScrollView has exactly one direct child and proxyHostInput is present once; git diff --check passed; :app:processMobileArm64_v8aDebugResources passed with BUILD SUCCESSFUL

Task-Guard: tmdb-proxy-layout-crash
2026-09-22 09:44:07 +08:00
Silent1566
f6f90ba7ba feat(theme): extend web token snapshot with opacity fields
Verification: mobile/leanback theme+备份定向单测全部通过(含新增 ThemeWebBridgeTokenTest 5项:原13字段保留、focusScale格式不变、三个透明度字段两位小数格式稳定、取值与ThemeTokens一致、不泄漏profile/seed、null回退light);scripts/check_ui_tokens.sh --strict violations=0 legacy=0、38组对比度0失败;snapshotJson仅追加字段不改key名,HomeWebController仍为只读通道。未完成:Layer2设备功能矩阵因dev3首页被内置WebHome接管、原生外观入口自动化不可达,已在文档记录

Task-Guard: L2D-THEME-CLOSURE-20260922
2026-09-22 03:24:56 +08:00
Silent1566
89b3095f76 feat(theme): add layer 2C profile editor with live preview
Verification: mobile/leanback theme 定向单测全部通过(含新增 ThemeEditorContractTest 11项:逐槽往返/透明度边界/非法值拒绝/浅深独立/预览不落盘/旧14色路径移除);:app:compileMobileArm64_v8aDebugJavaWithJavac 与 :app:compileLeanbackArm64_v8aDebugJavaWithJavac 通过;scripts/check_ui_tokens.sh --strict violations=0 legacy=0、38组对比度0失败;编辑器经 ThemeProfileStore.apply 原子写入,取消不改动持久化。未完成:dev3真机编辑器交互验收因首页被内置WebHome接管、原生外观入口自动化不可达,已在文档记录为残余风险

Task-Guard: L2C-THEME-EDITOR-20260921
2026-09-22 03:16:22 +08:00
Silent1566
f50897455c fix: expand TV interface dialogs
Verification: InterfaceEntryInteractionTest passed with 7 tests, 0 failures, and 0 errors; leanback arm64-v8a debug Java compilation passed; TV interface list and edit dialogs now use screen-sized windows with 32dp margins on every edge, transparent window backgrounds, and zero decor padding; git diff --check passed; task_guard check passed

Task-Guard: leanback-interface-dialog-fullscreen-margins
2026-09-22 01:52:57 +08:00
Silent1566
f128caa286 feat(tmdb): add configurable proxy base
Verification: git diff --check passed; XML parse passed; :app:testMobileArm64_v8aDebugUnitTest focused TMDB tests passed with BUILD SUCCESSFUL

Task-Guard: tmdb-proxy
2026-09-22 01:31:23 +08:00
Silent1566
fb4fd00438 fix: improve TV interface dialog focus navigation
Verification: InterfaceEntryInteractionTest passed with 7 tests, 0 failures, and 0 errors; leanback arm64-v8a debug Java compilation passed; BUILD SUCCESSFUL in 32s with 98 actionable tasks; TV interface dialog now focuses the editable name field initially and provides an explicit D-pad focus chain through URL, backup addresses, file actions, confirm, and cancel; git diff --check passed; task_guard check passed

Task-Guard: leanback-interface-dialog-focus-chain
2026-09-22 01:14:10 +08:00
Silent1566
673bc67ec5 fix: use existing TV interface name hint
Verification: leanback arm64-v8a debug Java compilation passed after correcting the interface-name hint resource and cleaning regenerable build caches; git diff --check passed; task_guard check passed

Task-Guard: leanback-interface-name-hint-fix
2026-09-21 23:56:29 +08:00
Silent1566
5f043081ca fix: adapt TV interface dialogs
Verification: InterfaceEntryInteractionTest passed with 7 tests, 0 failures, and 0 errors; leanback arm64-v8a debug Java compilation passed; BUILD SUCCESSFUL in 45s with 98 actionable tasks; TV interface list and edit dialogs now adapt to screen width and height; the list exposes the add button; the edit dialog supports modifying and saving the interface name; git diff --check passed; task_guard check passed

Task-Guard: leanback-interface-dialog-adaptive-layout
2026-09-21 23:35:57 +08:00
Silent1566
0ec701f31b feat(theme): apply B-safe layer 2B profile through controlled binder
Verification: mobile/leanback theme 定向单测全部通过;scripts/check_ui_tokens.sh --strict violations=0 legacy=0、38组对比度0失败;dev3 192.168.50.3:5559 API28 mobile 覆盖安装:默认路径无binder活动且FATAL=0,注入13槽profile后bound=10 costMs=6-7ms,列表滚动/对话框/返回正常FATAL=0;修复API28上Class.getRecordComponents NoSuchMethodError与ColorStateList.createFromXml ClassCastException两个设备专属缺陷,改用内容哈希签名与公开ColorStateList构造器

Task-Guard: L2B-THEME-BINDER-20260921
2026-09-21 23:31:35 +08:00
Silent1566
65b27e395f fix: persist history line binding immediately
Verification: VideoActivityLayoutTest passed; mobile and leanback arm64-v8a debug Java compilation passed; BUILD SUCCESSFUL in 58s with 98 actionable tasks; both players now persist sourceBindingKey immediately through syncHistory after selecting a playback line; git diff --check passed; task_guard check passed

Task-Guard: history-flag-binding-save
2026-09-21 22:28:34 +08:00
Silent1566
6435115120 feat(theme): add B-safe layer 2A profile and resolver
Verification: mobile/leanback theme+backup 定向单测各69项0失败0错误(含新增 ThemeProfileCodecTest/ValidatorTest/MigrationTest/ThemeResolverOverrideTest);scripts/check_ui_tokens.sh --strict violations=0 legacy=0、38组对比度0失败min=4.28、hex计数全0;空profile解析与不传profile逐字段相同,播放器/健康/品牌token在所有覆盖测试中与默认一致;未修改ThemeController与任何布局资源,运行时视觉保持Layer 1

Task-Guard: L2A-THEME-PROFILE-20260921
2026-09-21 21:33:39 +08:00
Silent1566
5bdd9372e3 fix: persist history interface binding
Verification: StableInterfaceIdentityAcceptanceTest, PlaybackProgressWriterEpisodeTest, and HistoryResumePayloadTest passed; mobile and leanback arm64-v8a debug Java compilation passed; BUILD SUCCESSFUL in 1m 39s with 98 actionable tasks; History.sourceBindingKey is persisted through Room migration 46 to 47; generated Room schema 47 is included; git diff --check passed; task_guard check passed

Task-Guard: history-source-binding-persistence
2026-09-21 21:23:16 +08:00
Silent1566
04e996e50c fix: explain protected current interface deletion
Verification: InterfaceEntryInteractionTest and ConfigHistoryCurrentSourceGuardTest passed with 0 failures; mobile and leanback arm64-v8a debug Java compilation passed; mobile arm64-v8a Debug APK built successfully and installed with overlay installation on 192.168.50.3:5561; clean relaunch verified app remains responsive; runtime UI verified current source 嗷呜 remains visible, its use action is disabled, edit remains available, delete remains visible with protected-current feedback behavior, and add remains available; git diff --check passed; task_guard check passed

Task-Guard: interface-current-delete-feedback
2026-09-21 20:33:15 +08:00
Silent1566
44ac882d6a fix: protect current interface actions
Verification: InterfaceEntryInteractionTest and ConfigHistoryCurrentSourceGuardTest passed; mobile and leanback arm64-v8a debug Java compilation passed; mobile arm64-v8a Debug APK built and installed with adb install -r on 192.168.50.3:5561; runtime UI verified current source 嗷呜 remains visible with disabled use text and hidden delete action while edit remains available; git diff --check; task_guard check

Task-Guard: interface-current-source-actions
2026-09-21 18:51:59 +08:00
Silent1566
73b046cc07 fix: persist backup addresses for new interfaces
Verification: InterfaceEntryInteractionTest passed; mobile and leanback arm64-v8a debug Java compilation passed; mobile arm64-v8a Debug APK built and installed with adb install -r on 192.168.50.3:5561; new interface test autobackup persisted urlsJson=["http://primary.test.invalid","backup.test.invalid"] and reopened with backup address visible; temporary test configs removed; git diff --check; task_guard check

Task-Guard: interface-new-backup-persistence
2026-09-21 18:28:55 +08:00
Silent1566
0c926dba8d fix(following): align panel scrim with home buttons per mode
Verification: check_ui_tokens.sh --strict violations=0 legacy=0、38组对比度0失败、hex计数全0;FollowingUiSourceTest新增模式极性契约后mobile/leanback定向单测通过;dev3 192.168.50.3:5559两端覆盖安装FATAL EXCEPTION=0;TV深色顶栏(76,73,89)/卡片(71,70,84)对onSurface为6.77:1/7.17:1且与首页按钮20%黑纱同源,手机浅色正文8.52:1、次要文字4.63:1(修复前1.95:1/1.06:1)

Task-Guard: FOLLOWING-SCRIM-ALIGN-20260921
2026-09-21 18:27:51 +08:00
Silent1566
0b1af5033f fix(following): use global wallpaper with translucent panels
Verification: check_ui_tokens.sh --strict violations=0 legacy=0、38组对比度0失败、hex计数全0(drawables=550 colors=53);FollowingUiSourceTest新增壁纸契约+ThemeControllerContractTest旧断言更新后,mobile/leanback定向单测均通过;mobile与leanback arm64 debug覆盖安装dev3 192.168.50.3:5559成功且FATAL EXCEPTION=0;mobile追更页面板外像素(88,78,113)壁纸透出、面板内(208,207,217),leanback深色半透明面板正常、空态图标可见;TV临时勾选首页追更按钮验收后已恢复7/10

Task-Guard: FOLLOWING-WALLPAPER-20260921
2026-09-21 17:47:49 +08:00
Silent1566
46d3e74898 feat: separate interface list add and edit actions
Verification: InterfaceEntryInteractionTest passed; mobile and leanback arm64-v8a debug Java compilation passed; mobile arm64-v8a Debug APK built successfully; APK installed with adb install -r on 192.168.50.3:5561; runtime UI verified that the VOD interface manager lists all configs and exposes explicit edit, delete, and add actions; git diff --check passed; task_guard check passed

Task-Guard: interface-config-list-interaction
2026-09-21 16:15:18 +08:00
Silent1566
6cdc7dd362 feat(theme): integrate webhtv semantic theme into flavor bases
Verification: check_ui_tokens.sh --strict violations=0 legacy=0、38组对比度0失败(min=4.28)、hex计数全0;mobile全量单测705套件/4790项0失败,leanback全量单测614套件/3917项0失败;新增ThemeBaseWiringTest 5项、ThemeControllerContractTest增补2项契约;mobile与leanback arm64 debug均覆盖安装到dev3 192.168.50.3:5559并冷启动,API28 FATAL EXCEPTION=0,mobile主色实测token蓝#0B57D0、leanback #A8C7FA;一并修复3f3ab82b1f遗漏的colorOnSurface_20/70/80/90导致leanback资源链接失败;git diff --check通过

Task-Guard: L1-THEME-INTEGRATE-20260921
2026-09-21 15:48:40 +08:00
Silent1566
30ba0f48f9 feat(theme): unify remaining modules and add theme mode
Verification: strict检查violations=0 legacy=0且hex计数全0、38组对比度通过;ThemeControllerContractTest/ThemeResolverTest/ThemeContractTest/UiStyleSourceTest/UiLayoutSourceTest/WebThemeTokenSourceTest/NativeBridgeGuardTest/JarLoader*/setting.*全部通过;mobile/leanback arm64 debug编译通过;dev3 192.168.50.3:5559上同一接口连续重载3次进程保持前台FATAL EXCEPTION=0;外观与语言显示5项且主题模式可切浅色/深色并即时生效;AudioMiniPlayer主题切换NPE已修复;git diff --check通过。

Task-Guard: UI-UNIFY-REMAINING-MODULES-20260921
2026-09-21 13:46:41 +08:00
Silent1566
327ac86f86 fix: retain saved backup addresses for dialog reopen
Verification: ConfigDialogNamePersistenceTest passed; mobile and leanback arm64 debug Java compilation passed; BUILD SUCCESSFUL in 33s, 98 actionable tasks; git diff --check; task_guard check

Task-Guard: interface-address-reopen-prefill
2026-09-21 13:16:14 +08:00
Silent1566
daac96ebb8 fix(loader): retain translated dex loaders to avoid houdini crash
Verification: NativeBridgeGuardTest与既有JarLoaderTest/LoaderQueueTest通过;assembleMobileArm64_v8aDebug通过;dev3 192.168.50.3:5559覆盖安装后同一接口连续重载3轮进程保持前台、FATAL EXCEPTION=0、日志出现retained translated loaders;git diff --check通过。

Task-Guard: FIX-VOD-RELOAD-HOUDINI-20260921
2026-09-21 12:18:16 +08:00
Silent1566
a8e6949e9c fix: localize interface backup address hint
Verification: 简体中文和繁体中文均已补充 dialog_config_addresses;mobile/leanback arm64 debug 资源处理成功;BUILD SUCCESSFUL in 27s;git diff --check;task_guard check

Task-Guard: interface-address-localization
2026-09-21 12:03:21 +08:00
Silent1566
6064be078b test: cover stable interface identity acceptance
Verification: StableInterfaceIdentityAcceptanceTest passed for mobile and leanback arm64 debug variants; BUILD SUCCESSFUL in 42s, 114 actionable tasks; git diff --check; task_guard check

Task-Guard: interface-stable-identity-acceptance-tests
2026-09-21 11:29:11 +08:00
Silent1566
4d211e6188 feat: add interface backup address editor
Verification: ConfigDialogNamePersistenceTest passed; mobile and leanback arm64 debug Java compilation passed; BUILD SUCCESSFUL in 58s, 98 actionable tasks; git diff --check; task_guard check

Task-Guard: interface-stable-address-ui
2026-09-21 11:13:42 +08:00
Silent1566
b60203504a test(ui): align detail photo radius contract
Verification: TmdbDetailActivityLayoutTest更新cardCornerRadius断言为@dimen/webhtv_card_radius_default后单测通过;git diff --check通过;未修改生产行为,仅同步source contract与文档。

Task-Guard: VISUAL-TEST-ALIGN-20260921
2026-09-21 10:53:22 +08:00
Silent1566
fceef8bed9 test(ui): freeze card geometry and surface variant contracts
Verification: strict检查 violations=0 legacy=0 hex_layouts=0 hex_drawables=0 hex_colors=0 allowlisted=191,38组对比度通过;新增UiLayoutSourceTest并通过,连同UiStyleSourceTest/WebThemeTokenSourceTest/Theme*全部通过;mobile/leanback arm64 debug Java编译通过;dev3模拟器覆盖安装启动HomeActivityCurrent,FATAL EXCEPTION=0;git diff --check通过。

Task-Guard: VISUAL-LAYOUT-CONTRACT-20260921
2026-09-21 10:46:29 +08:00
Silent1566
bc2ce54d2f fix: preserve config identity during backup restore
Verification: git diff --check; task_guard check; mobile and leanback arm64 debug Java compilation => BUILD SUCCESSFUL in 39s, 58 actionable tasks

Task-Guard: interface-stable-identity-completion
2026-09-21 10:28:25 +08:00
Silent1566
dba04ebc01 fix(ui): reconcile semantic token tests and visual regressions
Verification: 严格检查 check_ui_tokens --strict:violations=0 legacy=0 hex_layouts=0 hex_drawables=0 hex_colors=0 allowlisted=191,对比度38组通过;10个失败测试用例全部修复,覆盖DialogGlassChip/TMDB详情/历史与VOD adapter/AdRule/GroupRule/MultiThreadProxy/TmdbSource/GlobalHistory/SiteHealth;mobile/leanback arm64 debug编译通过;dev3模拟器覆盖安装启动HomeActivityCurrent,FATAL EXCEPTION=0;git diff --check通过。

Task-Guard: VISUAL-FOLLOWUP-20260921
2026-09-21 10:12:22 +08:00
Silent1566
3f3ab82b1f chore(ui): finalize token cleanup and legacy asset allowlist
Verification: check_ui_tokens.sh --strict返回violations=0 legacy=0,hex_layouts/hex_drawables/hex_colors均为0,allowlisted=191且对比度38组全通过;UiStyleSourceTest、WebThemeTokenSourceTest、ThemeResolverTest、ThemeContractTest通过;mobile/leanback arm64 debug Java编译通过;dev3模拟器覆盖安装后HomeActivityCurrent启动,FATAL EXCEPTION=0;git diff --check通过。

Task-Guard: VISUAL-SYSTEM-F-20260921
2026-09-21 09:57:26 +08:00
Silent1566
eb76ab8a82 feat: add stable interface identity and failover addresses
Verification: git diff --check; task_guard check; ./gradlew --no-daemon :app:compileMobileArm64_v8aDebugJavaWithJavac :app:compileLeanbackArm64_v8aDebugJavaWithJavac :app:testMobileArm64_v8aDebugUnitTest :app:testLeanbackArm64_v8aDebugUnitTest --stacktrace => BUILD SUCCESSFUL in 1m 34s, 114 actionable tasks

Task-Guard: interface-stable-identity
2026-09-21 09:55:39 +08:00
Silent1566
810fd5ef7a feat(web): unify built-in web theme tokens
Verification: check_ui_tokens.sh --stage E真实扫描assets后返回violations=0 legacy=0 allowlisted=2;新增WebThemeTokenSourceTest连同WebTheme*/Theme*测试通过;mobile/leanback arm64 debug Java编译通过;dev3模拟器覆盖安装后HomeActivityCurrent启动,FATAL EXCEPTION=0;git diff --check通过。

Task-Guard: VISUAL-SYSTEM-E-20260921
2026-09-21 09:50:58 +08:00
Silent1566
e1ea7ab346 feat(ui): unify detail and player control tokens
Verification: check_ui_tokens.sh --stage D返回violations=0 legacy=0;UiStyleSourceTest含播放器色隔离断言、ThemeResolverTest、ThemeContractTest共16项通过;mobile/leanback arm64 debug Java编译通过;dev3模拟器进入TMDB详情并播放第一集,VideoActivity OSD正常、FATAL EXCEPTION=0;git diff --check通过。

Task-Guard: VISUAL-SYSTEM-D-FULL-20260921
2026-09-21 09:13:19 +08:00
Silent1566
f63b0a81bc feat(ui): unify home list and card tokens
Verification: check_ui_tokens.sh --stage C返回violations=0 legacy=0;ThemeResolverTest、ThemeContractTest、UiStyleSourceTest通过;mobile/leanback arm64 debug Java编译通过;dev3模拟器覆盖安装后列表/卡片与去广告规则管理Dialog正常,FATAL EXCEPTION=0;git diff --check通过。

Task-Guard: VISUAL-SYSTEM-C-20260921
2026-09-21 00:57:05 +08:00
Silent1566
0af2340d49 feat(ui): unify dialog and settings visual system
Verification: ThemeResolverTest、ThemeContractTest、DialogRoundedCornerSourceTest、UiStyleSourceTest全部通过;mobile/leanback arm64 debug Java编译通过;check_ui_tokens.sh --stage B返回violations=0 legacy=0;dev3模拟器192.168.50.3:5559完成手机浅色/深色Dialog实机进入测试,FATAL EXCEPTION=0;git diff --check通过。

Task-Guard: VISUAL-SYSTEM-B-FULL-20260921
2026-09-21 00:41:53 +08:00
Silent1566
c5593731c1 docs: assess stable interface identity design
Verification: 文档共351行;git diff --check通过;章节结构和最终建议已核验;task_guard check通过

Task-Guard: INTERFACE-IDENTITY-1
2026-09-20 23:45:01 +08:00
Silent1566
72c1bec917 feat(ui): add unified semantic token foundation
Verification: ThemeResolverTest、ThemeContractTest、UiStyleSourceTest 共13项通过;mobile/leanback arm64 debug Java编译通过;check_ui_tokens.sh --baseline 38组对比度全通过,最低4.28:1;git diff --check通过;未改既有页面资源。

Task-Guard: VISUAL-SYSTEM-A-20260920
2026-09-20 23:29:16 +08:00
Silent1566
1edbc9b9c9 Merge latest origin/beta into dev1
Verification: :app:testMobileArm64_v8aDebugUnitTest --offline --tests com.fongmi.android.tv.ui.activity.TmdbDetailActivityLayoutTest --no-daemon --max-workers=1 --console=plain (BUILD SUCCESSFUL in 38s)

Task-Guard: beta-sync-review-dev1-20260917
2026-09-17 19:26:33 +08:00
Silent1566
adaff1abee Merge origin/beta into dev1
Verification: :app:testMobileArm64_v8aDebugUnitTest --offline --rerun-tasks --tests com.fongmi.android.tv.ui.detail.* --tests com.fongmi.android.tv.ui.activity.TmdbDetailActivityLayoutTest --tests com.fongmi.android.tv.ui.activity.VideoActivityHistoryTitleTest --no-daemon --max-workers=1 --console=plain (BUILD SUCCESSFUL in 1m 14s)

Task-Guard: beta-sync-review-dev1-20260917
2026-09-17 19:12:55 +08:00
Silent1566
24f97cbaa1 fix(detail): initialize mode controller before view setup
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.detail.* --no-daemon --max-workers=1 --console=plain (BUILD SUCCESSFUL in 42s)

Task-Guard: tmdb-detail-controller-init-order-null
2026-09-17 16:32:24 +08:00
Silent1566
9f51d9ab9c fix(detail): guard early lifecycle theme mode
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeControllerSourceTest --no-daemon --max-workers=1 --console=plain (BUILD SUCCESSFUL in 35s)

Task-Guard: tmdb-detail-controller-early-lifecycle-null
2026-09-17 16:14:52 +08:00
Silent1566
02f6377afc test(detail): update controller regression assertions
Verification: :app:testMobileArm64_v8aDebugUnitTest --no-daemon --max-workers=1 --console=plain (BUILD SUCCESSFUL in 1m 16s; 4425 tests, 1 skipped)

Task-Guard: tmdb-detail-controller-regression-tests
2026-09-17 15:29:04 +08:00
Silent1566
ba91aca4d8 refactor(detail): centralize TMDB mode decisions
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.detail.* --no-daemon --max-workers=1 --console=plain (BUILD SUCCESSFUL in 49s)

Task-Guard: tmdb-detail-mode-controller-phase4
2026-09-17 15:15:59 +08:00
Silent1566
48942fb4d0 test(detail): cover TMDB mode controllers
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.detail.* (BUILD SUCCESSFUL)

Task-Guard: tmdb-detail-mode-controller-tests
2026-09-17 14:43:59 +08:00
Silent1566
37eab07288 fix(player): preserve video-owned colors
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeControllerSourceTest --no-daemon --max-workers=1 (BUILD SUCCESSFUL in 39s)

Task-Guard: preserve-video-player-theme
2026-09-17 13:51:39 +08:00
Silent1566
85af02f1fc fix(theme): scope custom highlight colors
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeControllerSourceTest --no-daemon --max-workers=1 (BUILD SUCCESSFUL in 47s)

Task-Guard: theme-highlight-scope
2026-09-17 13:28:31 +08:00
Silent1566
6504b98729 fix(player): use black shutter across mobile layouts 2026-09-16 19:12:43 +08:00
Silent1566
934c9669cf fix(player): use black shutter for tmdb inline playback
Verification: ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeControllerSourceTest --no-daemon (BUILD SUCCESSFUL in 36s); git diff --check; task_guard check PASS

Task-Guard: fix-tmdb-player-white-shutter
2026-09-16 14:31:23 +08:00
Silent1566
86b46e5117 fix(theme): preserve colorful detail theme state
Verification: ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeControllerSourceTest --no-daemon (BUILD SUCCESSFUL); git diff --cached --check

Task-Guard: theme-highlight-player-overlay-scope
2026-09-16 13:24:16 +08:00
Silent1566
bd280278d8 Merge remote-tracking branch 'origin/beta' into dev1 2026-09-16 00:05:08 +08:00
Silent1566
297ece432a fix(theme): preserve navigation default unchecked style
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeControllerSourceTest (BUILD SUCCESSFUL, 8 tests, 0 failures)

Task-Guard: nav-highlight-default-style
2026-09-15 23:49:08 +08:00
Silent1566
fe6d302918 fix(theme): restore default preset to reset semantics
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeResolverTest (BUILD SUCCESSFUL, 5 tests, 0 failures)

Task-Guard: theme-default-preset-reset
2026-09-15 23:20:13 +08:00
Silent1566
76882b51d0 test(theme): align episode highlight contract
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.ui.activity.VideoActivityLayoutTest(153 个测试,0 failures,0 errors,BUILD SUCCESSFUL);git diff --check 通过

Task-Guard: beta-sync-review-20260915
2026-09-15 20:27:48 +08:00
Silent1566
33b3dc1cbf Merge remote-tracking branch 'origin/beta' into dev1
# Conflicts:
#	app/src/mobile/java/com/fongmi/android/tv/ui/dialog/SiteDialog.java
#	app/src/testMobile/java/com/fongmi/android/tv/ui/dialog/SiteDialogThemeSourceTest.java
2026-09-15 19:48:40 +08:00
Silent1566
7e4e4f887b fix(theme): limit highlight selection to primary role
Verification: :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeResolverTest (BUILD SUCCESSFUL); git diff --check; task_guard check PASS

Task-Guard: theme-highlight-scope-consistency
2026-09-15 19:28:42 +08:00
Silent1566
b7a93d3094 fix(theme): guarantee bottom navigation contrast
Verification: ThemeControllerSourceTest BUILD SUCCESSFUL; assembleMobileArm64_v8aDebug BUILD SUCCESSFUL; APK installed on 192.168.50.3:5555; screenshot verified clearer unchecked navigation icons

Task-Guard: theme-bottom-nav-contrast-followup
2026-09-15 16:09:34 +08:00
Silent1566
8a6b331f5f fix(theme): improve bottom navigation contrast
Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeControllerSourceTest passed; git diff --check passed

Task-Guard: theme-bottom-nav-contrast
2026-09-15 13:40:25 +08:00
Silent1566
353d187825 fix(theme): limit accent fill to primary buttons
Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeControllerSourceTest passed; git diff --check passed

Task-Guard: theme-button-accent-contrast
2026-09-15 13:33:07 +08:00
Silent1566
a158c8c732 fix(theme): keep reset theme switch enabled
Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeControllerSourceTest passed; git diff --check passed

Task-Guard: theme-reset-native-default
2026-09-15 12:48:02 +08:00
Silent1566
c872cee68c fix(theme): reset colors and add picker presets
Verification: bash ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests com.fongmi.android.tv.theme.ThemeControllerSourceTest passed; git diff --check passed

Task-Guard: theme-editor-reset-color-picker
2026-09-15 12:36:17 +08:00
Silent1566
a27f164aa3 fix(theme): expose master switch in mobile editor
Verification: ThemeControllerSourceTest passed; mobile Java/resource compilation passed; Gradle daemons stopped; available memory 3.5 GiB

Task-Guard: theme-color-switch-ui-20260915
2026-09-15 11:49:38 +08:00
Silent1566
3c3885256a fix(theme): fall back to current theme when master switch is off
Verification: ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests 'com.fongmi.android.tv.ui.dialog.SiteDialogThemeSourceTest' --no-daemon --console=plain; ./gradlew :app:compileMobileArm64_v8aDebugJavaWithJavac --no-daemon --console=plain

Task-Guard: site-dialog-off-fallback-20260915
2026-09-15 11:09:35 +08:00
Silent1566
8523d049e2 feat(theme): add master switch for theme colors, default off
Verification: ./gradlew :app:testMobileArm64_v8aDebugUnitTest --tests 'com.fongmi.android.tv.theme.*' --no-daemon --console=plain

Task-Guard: theme-color-switch-off-20260914
2026-09-15 00:10:43 +08:00
Silent1566
0503f8e3cf feat(theme): restore dynamic theme color system from validated history
Verification: Mobile/Leanback Arm64 Java compilation passed; Mobile theme-related JVM tests passed (13 classes, 59 tests, 0 failures); Leanback theme-related JVM tests passed (4 classes, 13 tests, 0 failures); git diff --check and task_guard check passed

Task-Guard: theme-revival-20260914
2026-09-14 21:44:22 +08:00
1299 changed files with 96792 additions and 29631 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

Some files were not shown because too many files have changed in this diff Show more