# -*- coding: utf-8 -*- """检查手表 APK 是否真的把「遥控诊断」合并进去了。 合并前是两个 APK(手表端 + 独立的遥控诊断 App)。 合并后只需要一个:主程序里有「设备诊断」按钮。 这里从 APK 的 dex 和清单里核实,而不是只信源码。 """ from __future__ import annotations import re import subprocess import sys import tempfile import zipfile from pathlib import Path ROOT = Path(__file__).resolve().parent.parent # 别写死版本号 —— 一升级就坏(这坑踩过好几回了)。 # 直接扫 dist 里最新的手表 APK。 def _latest_apk() -> Path: candidates = sorted( (ROOT / "dist").glob("*watch*.apk"), key=lambda p: p.stat().st_mtime, reverse=True) return candidates[0] if candidates else \ ROOT / "dist" / "ykxmz-watch-2.1.6.apk" APK = _latest_apk() DIAG_APK = ROOT / "dist" / "LanRemote-Diag-1.1.0.apk" AAPT = (Path(r"C:\Users\shu\Desktop\deepseekagent\starklink\toolchain") / "android-sdk" / "build-tools" / "34.0.0" / "aapt.exe") PASSED = 0 FAILED = 0 def check(name: str, ok: bool, detail: str = "") -> None: global PASSED, FAILED if ok: PASSED += 1 print(f" [PASS] {name}") else: FAILED += 1 print(f" [FAIL] {name} {detail}") def main() -> int: print("=" * 68) print("一、手表 APK 里有没有内置诊断") print("=" * 68) if not APK.is_file(): print(f" 找不到 {APK}") print(" 请先运行 android\\build_apk.ps1") return 1 with zipfile.ZipFile(APK) as archive: dex = archive.read("classes.dex") names = {item.decode() for item in re.findall(rb"Lcom/lanremote/[A-Za-z0-9_/]+;", dex)} print(f" dex 中的类: {len(names)} 个") check("APK 里有 Diagnostics 类(诊断逻辑已内置)", "Lcom/lanremote/watch/Diagnostics;" in names, str(sorted(names))) check("APK 里有 DiscoveryCore(诊断与主程序共用同一份发现实现)", "Lcom/lanremote/watch/DiscoveryCore;" in names) check("APK 里有 MainActivity", "Lcom/lanremote/watch/MainActivity;" in names) # 注意:不能用 "diag" 做子串判断 —— Diagnostics 里也含 diag。 # 要查的是旧的独立包名 com/lanremote/diag/。 legacy = [n for n in names if n.startswith("Lcom/lanremote/diag/")] check("不含旧的独立诊断包 com.lanremote.diag", not legacy, str(legacy)) print() print("=" * 68) print("二、清单权限(诊断读 WiFi 名称需要定位权限)") print("=" * 68) # aapt 不认中文路径,先拷到纯 ASCII 路径 with tempfile.TemporaryDirectory() as tmp: simple = Path(tmp) / "watch.apk" simple.write_bytes(APK.read_bytes()) try: proc = subprocess.run( [str(AAPT), "dump", "badging", str(simple)], capture_output=True, timeout=60) badging = proc.stdout.decode("utf-8", errors="replace") except Exception as exc: # noqa: BLE001 badging = "" print(f" aapt 调用失败: {exc}") if badging: for permission, why in ( ("android.permission.ACCESS_FINE_LOCATION", "读 WiFi 名称"), ("android.permission.INTERNET", "网络通信"), ("android.permission.CHANGE_WIFI_MULTICAST_STATE", "UDP 广播"), ): check(f"声明了 {permission.split('.')[-1]}({why})", permission in badging) check("没有申请定位以外的敏感权限", "READ_CONTACTS" not in badging and "READ_SMS" not in badging and "CAMERA" not in badging and "RECORD_AUDIO" not in badging) else: check("能读取 APK 清单", False, "aapt 没有输出") print() print("=" * 68) print("三、构建脚本不再依赖独立诊断 APK") print("=" * 68) package = (ROOT / "_build" / "package.py").read_text(encoding="utf-8") check("package.py 不再要求 Diag APK", "DIAG_APK" not in package, "仍引用 DIAG_APK") check("package.py 说明里写明诊断已合并", "合并" in package) src = (ROOT / "android" / "src" / "com" / "lanremote" / "watch" / "MainActivity.java").read_text(encoding="utf-8") # 用户要求:"安卓端中转设置下面,把那个设备诊断给删了, # 换成输入配对码,输入对应配对码就可以连接对应电脑" # 所以这里反过来断言:不该再有设备诊断入口,而是配对码输入。 import re as _re code = _re.sub(r"//[^\n]*", "", src) check("主界面不再有「设备诊断」入口", "设备诊断" not in code) check("主界面不再调用 Diagnostics.build", "Diagnostics.build(" not in code) check("主界面不再调用 Diagnostics.manualTest", "Diagnostics.manualTest(" not in code) check("换成了「输入配对码连接」入口", 'bigButton("输入配对码连接"' in code) check("配对码连接走中转(connectByCode)", "private void connectByCode(" in code) print() print("=" * 68) print(f"通过 {PASSED} 项,失败 {FAILED} 项") print("=" * 68) return 1 if FAILED else 0 if __name__ == "__main__": sys.exit(main())