# -*- coding: utf-8 -*- r'''验「手表快捷栏」和「允许新设备连接」。 == 验什么 == 一、快捷栏的形状 * 一共 4 格,**每格都能自定义** * 「显示桌面」是普通可选动作(默认放第 4 格,但能换掉) * 8 个动作都在 * 挂进了 onCreate、加进了 showOnly 的数组 * 设置页里有入口 * 鼠标触摸板在 二、按钮大小 用户反馈原话:"屏幕最上面这个按钮有些按不到,按钮太小了,做大一点" * 栏高做成常量(>= 44dp),而且**栏高 / 面板 margin / addView 三处用同一个常量** —— 分三处写的话改一个忘一个, 表现是"内容被盖住"或"上面空一条" 三、电脑端 allow_new_pairing 这个 feature == 这条断言改过一次,值得记下来 == 用户最早说:"最多可以加 4 个,其中有一个按钮不能改, 功能就是一键返回桌面"。 第一版就按"第 4 格写死 desktop"实现,也按那个断言测。 后来用户看到实物改了主意:"第一个第二个第三个第四个都能换东西"。 所以现在验的是**相反**的事。教训: **别把"用户当时的一句话"当成永久约束焊进结构里**; 真是约束的话,注释里要写清是什么时候的要求, 下次他改主意时才知道该动哪儿。 ''' from __future__ import annotations import re import sys import tempfile from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "windows")) FAILS: list = [] def check(name: str, ok: bool, why: str = "") -> None: print(" [%s] %s%s" % ("PASS" if ok else "FAIL", name, (" —— " + why) if why else "")) if not ok: FAILS.append(name) def main() -> int: print("=" * 58) print(" 手表快捷栏 + 允许新设备连接") print("=" * 58) print() main_java = (ROOT / "android" / "src" / "com" / "lanremote" / "watch" / "MainActivity.java").read_text(encoding="utf-8") # ---------------- 一、快捷栏 ---------------- print("一、快捷栏(手表端)") for token, label in [ ("private void buildQuickBar()", "有 buildQuickBar"), ("private void refreshQuickBar()", "有 refreshQuickBar"), ("private void onQuickClick(int slot)", "有点击分发 onQuickClick"), ("private void runQuickAction(String id)", "有动作执行 runQuickAction"), ("private void loadQuickSlots()", "有读取设置"), ("private void saveQuickSlots()", "有保存设置"), ]: check(label, token in main_java) # 4 格,全部可自定义。这条断言改过一次,见文件头。 check("★ 一共 4 格", "QUICK_SLOTS = 4" in main_java, "用户要求 4 格") check("★ 旧的 3+1 分法已经不在了", "QUICK_CUSTOM_SLOTS" not in main_java, "还有残留说明没改干净") check("★ 没有把 desktop 写死在某格", ': "desktop";' not in main_java, "还有地方把 desktop 写死成某一格") check("★ desktop 在可选动作里(能换到任意一格)", bool(re.search(r'\{"desktop",\s*"显示桌面"\}', main_java)), "不在 QUICK_ACTIONS 里的话用户没法把它放到别的格") check("四格平等:都从 quickSlots 取", "String id = quickSlots[i];" in main_java and "String id = quickSlots[slot];" in main_java, "还有特殊分支") check("默认值就是那 4 个", 'new String[]{"file", "mirror", "sfx", "desktop"}' in main_java) check("desktop 走的是 show_desktop 命令", '"cmd", "show_desktop"' in main_java) print() print(" 8 个可选动作:") for aid, label in [("file", "传文件"), ("mirror", "屏幕镜像"), ("mouse", "鼠标控制"), ("sfx", "音效"), ("power", "关机"), ("tts", "语音播报"), ("image", "投图到电脑"), ("desktop", "显示桌面")]: check(" %s(%s)" % (aid, label), ('{"%s", "%s"}' % (aid, label)) in main_java) # ---------------- 二、按钮大小 ---------------- print() print("二、按钮大小(用户反馈「太小了、按不到」)") m = re.search(r"QUICK_BAR_DP = (\d+)", main_java) check("★ 栏高做成常量 QUICK_BAR_DP", m is not None, "没有常量的话栏高和面板 margin 会各写各的") h = int(m.group(1)) if m else 0 check("★ 栏高 >= 44dp(接近 Android 建议的可点区域 48)", h >= 44, "现在是 %d" % h) check("★ 面板 topMargin 用同一个常量", "flp.topMargin = dp(QUICK_BAR_DP);" in main_java, "分两处写的话改一个忘一个") check("★ addView 也用同一个常量", "MATCH_PARENT, dp(QUICK_BAR_DP)));" in main_java) check("★ 按钮字号 >= 13(原来是 11)", "b.setTextSize(14f);" in main_java) # ⚠ 只查**代码**,不查注释。 # 第一版这里是 `"dp(30)" not in main_java`,结果被 # applyPanelTopMargin 的 javadoc 里那句旧说明误报了 —— # 注释里提一下旧值是很正常的事("以前是 30dp"), # 不该因为它变红。 code_lines = [ln for ln in main_java.splitlines() if not ln.strip().startswith(("*", "//", "/*"))] hard = [ln.strip() for ln in code_lines if "dp(30)" in ln] check("没有残留的 dp(30)(旧栏高)", not hard, "还有代码用旧的 30dp:%s" % hard) # ---------------- 三、挂接 ---------------- print() print("三、挂接(漏了就白写)") i0 = main_java.find("protected void onCreate") oncreate = main_java[i0:i0 + 7000] check("★ onCreate 里调了 buildQuickBar()", "buildQuickBar();" in oncreate, "没挂上去栏根本不显示") check("★ onCreate 里调了 loadQuickSlots()", "loadQuickSlots();" in oncreate) check("★ 快捷栏最后 addView(保证在最上层)", oncreate.find("root.addView(quickBar") > oncreate.find("root.addView(mousePanel"), "加在面板之前会被面板整个盖住") check("★ showOnly 的数组里有 mousePanel", "settingsPanel, mousePanel};" in main_java, "漏了的话鼠标面板永远不隐藏") check("有 applyPanelTopMargin(面板给栏让位)", "private void applyPanelTopMargin" in main_java) # ---------------- 四、设置页 ---------------- print() print("四、设置页入口(没入口用户改不了)") check("有 fillSettingsExtra", "private void fillSettingsExtra()" in main_java) check("有 pickQuickSlot(选动作)", "private void pickQuickSlot(final int slot)" in main_java) check("设置页里加了「快捷栏」小节", 'sectionTitle("快捷栏")' in main_java) check("设置页里加了「允许新设备连接」小节", 'sectionTitle("允许新设备连接")' in main_java) check("界面上说明了四格都能换", "每一个都能换" in main_java, "不说明的话用户不知道第四格也能改") # ---------------- 五、鼠标触摸板 ---------------- print() print("五、鼠标触摸板") check("有 buildMousePanel", "private void buildMousePanel()" in main_java) check("用 GestureDetector(单击/双击/拖动)", "GestureDetector" in main_java and "onSingleTapConfirmed" in main_java and "onDoubleTap" in main_java and "onScroll" in main_java) check("单击 -> 左键", 'sendMouse("click", "left", 0)' in main_java) check("双击 -> 右键", 'sendMouse("click", "right", 0)' in main_java) check("滚轮上/下", 'sendMouse("scroll", "", 3)' in main_java and 'sendMouse("scroll", "", -3)' in main_java) check("拖动 -> moveby", '"moveby"' in main_java) check("isConnected() 存在(第一版漏了,编译才发现)", "private boolean isConnected()" in main_java) # ---------------- 六、电脑端 allow_new_pairing ---------------- print() print("六、电脑端 allow_new_pairing feature") from lanremote.config import Store from lanremote.features import Features class FakeActions: """Features 要的是 actions 对象,不是 store(第一版就是这里传错了)。""" def __init__(self, store): self.store = store self.ui = None st = Store(Path(tempfile.mkdtemp())) feats = Features(FakeActions(st)) check("能查到当前状态", "允许" in feats.run("allow_new_pairing", {})) out = feats.run("allow_new_pairing", {"allow": False}) check("能关掉", "禁止" in out, "拿到 %r" % out) check(" ★ 存储里真的变了", st.get("allow_new_pairing") is False, "拿到 %r" % st.get("allow_new_pairing")) check(" 关掉之后再查也是关", "不允许" in feats.run("allow_new_pairing", {})) out = feats.run("allow_new_pairing", {"allow": True}) check("能打开", "已允许" in out, "拿到 %r" % out) check(" 存储里也变了", st.get("allow_new_pairing") is True) try: feats.run("allow_new_pairing_typo", {}) check("不认识的名字会报错", False, "它竟然没报错") except ValueError: check("不认识的名字会报错", True) print() print("=" * 58) if FAILS: print(" 失败 %d 项:%s" % (len(FAILS), ", ".join(FAILS))) return 1 print(" 全部通过") return 0 if __name__ == "__main__": sys.exit(main())