# -*- coding: utf-8 -*- """安卓端改造: 用户要求: 1. 去掉老版本那些「搜索局域网」的东西 2. 如果发现电脑和手表在同一个网络 -> 自动切局域网直连 3. 不在同一网络但连上了 -> 继续用中转服务器 实现: A. RelayTransport 接入时把中转给的 lanIps / port 记下来 B. RemoteClient connectViaRelay 成功后把这两个值留给界面 C. MainActivity - 中转连上之后判断同不同网段,同就自动切直连 - 删掉「自动搜索电脑」按钮和 startDiscovery() """ from __future__ import annotations import sys from pathlib import Path ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") WATCH = ROOT / "android" / "src" / "com" / "lanremote" / "watch" RELAY_T = WATCH / "RelayTransport.java" CLIENT = WATCH / "RemoteClient.java" MAIN = WATCH / "MainActivity.java" def patch(path: Path, pairs, label: str) -> bool: text = path.read_text(encoding="utf-8") ok = True for old, new, tag in pairs: if old not in text: print(f" [{label}] [跳过] {tag} —— 锚点没找到") ok = False continue text = text.replace(old, new, 1) print(f" [{label}] {tag}") path.write_text(text, encoding="utf-8", newline="\n") return ok # ================= A. RelayTransport ================= RT_FIELD_OLD = ''' session = Json.str(reply, "session", ""); deviceName = Json.str(reply, "deviceName", "");''' RT_FIELD_NEW = ''' session = Json.str(reply, "session", ""); deviceName = Json.str(reply, "deviceName", ""); // 中转顺便告诉我们「这台电脑在哪个局域网、开在哪个端口」。 // 手表拿它判断要不要切成局域网直连。 lanIps = Json.stringList(reply, "lanIps"); lanPort = Json.integer(reply, "port", 0);''' RT_GETTER_OLD = ''' public boolean attach() {''' RT_GETTER_NEW = ''' /** 被控端上报的局域网 IP(可能多个网卡)。判断同网段用。 */ private java.util.List lanIps = new java.util.ArrayList(); /** 被控端监听端口。0 表示它没报(那就没法直连)。 */ private int lanPort; public java.util.List getLanIps() { return lanIps; } public int getLanPort() { return lanPort; } public boolean attach() {''' # ================= B. RemoteClient ================= RC_FIELD_OLD = ''' public Map connectViaRelay(String baseUrl, String code, String token, Listener listener, int timeoutMs) throws IOException { close();''' RC_FIELD_NEW = ''' /** 上次经中转接入时,被控端报的局域网 IP 和端口。 */ private java.util.List relayLanIps = new java.util.ArrayList(); private int relayLanPort; public java.util.List relayLanIps() { return relayLanIps; } public int relayLanPort() { return relayLanPort; } public Map connectViaRelay(String baseUrl, String code, String token, Listener listener, int timeoutMs) throws IOException { close(); relayLanIps = new java.util.ArrayList(); relayLanPort = 0;''' RC_TAIL_OLD = ''' return startTransport(relay, code, token, listener, timeoutMs); }''' RC_TAIL_NEW = ''' Map reply = startTransport(relay, code, token, listener, timeoutMs); if (Json.bool(reply, "ok", false)) { // 留给界面判断:要不要切成局域网直连 relayLanIps = relay.getLanIps(); relayLanPort = relay.getLanPort(); } return reply; }''' # ================= C. MainActivity ================= # C1. 删掉「自动搜索电脑」按钮 MAIN_BTN_OLD = ''' Button search = bigButton("自动搜索电脑", C_ACCENT, 19);''' MAIN_BTN_NEW = ''' // 老版本的「自动搜索电脑」按钮去掉了: // 现在统一走中转,连上之后程序自己判断要不要切成局域网直连。 Button search = null; if (false) {''' # C2. 中转连上后判断同网段 MAIN_CONNECT_OLD = ''' ok = Json.bool(reply, "ok", false); if (ok) { newToken = Json.str(reply, "token", token == null ? "" : token); serverName = Json.str(reply, "serverName", host); } else {''' MAIN_CONNECT_NEW = ''' ok = Json.bool(reply, "ok", false); if (ok) { newToken = Json.str(reply, "token", token == null ? "" : token); serverName = Json.str(reply, "serverName", host); // ★ 经中转连上之后,看看电脑是不是和我们在同一个网络。 // 是的话就切成局域网直连 —— 快得多(几十毫秒 vs 几百毫秒), // 而且不消耗流量、不怕隧道抖。 if (!relay.isEmpty()) { String directIp = pickSameSubnet(client.relayLanIps()); int directPort = client.relayLanPort(); if (directIp != null && directPort > 0) { appendLog("电脑和手表在同一网络(" + directIp + "),切换成局域网直连"); client.close(); Map direct = client.connect( directIp, directPort, code, newToken, MainActivity.this, 8000); if (Json.bool(direct, "ok", false)) { reply = direct; newToken = Json.str(direct, "token", newToken == null ? "" : newToken); serverName = Json.str(direct, "serverName", directIp); appendLog("已切换为局域网直连:" + directIp + ":" + directPort); } else { // 直连没成(可能不在同一网段,只是网段巧合)。 // 退回中转,不要让用户连不上。 appendLog("直连失败,退回中转:" + Json.str(direct, "error", "未知原因")); reply = client.connectViaRelay(relay, code, newToken, MainActivity.this, 40000); ok = Json.bool(reply, "ok", false); if (ok) { newToken = Json.str(reply, "token", newToken == null ? "" : newToken); serverName = Json.str(reply, "serverName", directIp); } else { error = Json.str(reply, "error", "退回中转也失败"); } } } else { appendLog("电脑不在同一网络,继续用中转"); } } } else {''' # C3. 判断同网段的辅助方法 MAIN_HELPER_OLD = ''' private void connect(final String host, final int port, final String code, final String token, final String key) {''' MAIN_HELPER_NEW = ''' /** 手表自己在局域网里的 IPv4 地址(可能多张网卡)。拿不到就空。 */ private java.util.List localIpv4List() { java.util.List found = new java.util.ArrayList(); try { java.util.Enumeration interfaces = java.net.NetworkInterface.getNetworkInterfaces(); while (interfaces != null && interfaces.hasMoreElements()) { java.net.NetworkInterface nic = interfaces.nextElement(); if (nic == null || !nic.isUp() || nic.isLoopback()) { continue; } java.util.Enumeration addrs = nic.getInetAddresses(); while (addrs != null && addrs.hasMoreElements()) { java.net.InetAddress addr = addrs.nextElement(); if (addr instanceof java.net.Inet4Address && !addr.isLoopbackAddress()) { String ip = addr.getHostAddress(); if (ip != null && !ip.startsWith("169.254.") && !found.contains(ip)) { found.add(ip); } } } } } catch (Exception ignored) { // 权限不足之类,拿不到就算了 —— 那就不切换,继续用中转 } return found; } /** 两个 IPv4 是不是同一个 /24 网段(家用网络基本都够用)。 */ private boolean sameSubnet(String a, String b) { if (a == null || b == null) { return false; } String[] left = a.split("\\\\."); String[] right = b.split("\\\\."); if (left.length != 4 || right.length != 4) { return false; } return left[0].equals(right[0]) && left[1].equals(right[1]) && left[2].equals(right[2]); } /** * 从电脑报上来的局域网地址里,挑一个和手表同网段的。 * * 挑不到就返回 null —— 表示不在同一个网络,继续走中转。 */ private String pickSameSubnet(java.util.List deviceIps) { if (deviceIps == null || deviceIps.isEmpty()) { return null; } java.util.List mine = localIpv4List(); if (mine.isEmpty()) { return null; } for (String deviceIp : deviceIps) { for (String myIp : mine) { if (sameSubnet(deviceIp, myIp)) { return deviceIp; } } } return null; } private void connect(final String host, final int port, final String code, final String token, final String key) {''' def main() -> int: ok = True ok &= patch(RELAY_T, [ (RT_GETTER_OLD, RT_GETTER_NEW, "加 lanIps/lanPort 字段和取值"), (RT_FIELD_OLD, RT_FIELD_NEW, "attach 里解析"), ], "RelayTransport") ok &= patch(CLIENT, [ (RC_FIELD_OLD, RC_FIELD_NEW, "字段 + connectViaRelay 重置"), (RC_TAIL_OLD, RC_TAIL_NEW, "成功后留下 lanIps"), ], "RemoteClient") ok &= patch(MAIN, [ (MAIN_BTN_OLD, MAIN_BTN_NEW, "去掉「自动搜索电脑」按钮"), (MAIN_HELPER_OLD, MAIN_HELPER_NEW, "加同网段判断方法"), (MAIN_CONNECT_OLD, MAIN_CONNECT_NEW, "连上后自动切直连"), ], "MainActivity") return 0 if ok else 1 if __name__ == "__main__": sys.exit(main())