""" 连接守护技能 - 确保设备与服务器始终保持连接 职责: 1. 自动处理系统弹窗(USB调试授权、权限请求、系统更新提示等) 2. 监控 ADB / WiFi / 网络连通性 3. 检测并恢复 uiautomator2 服务 4. 检测屏幕状态,必要时亮屏 5. 与 Hawk 网络层协同做断网恢复 这是一个「常驻」技能,Agent 启动后以后台循环方式持续运行。 """ import logging import time import re from typing import Dict, Any, List, Optional import sys import os _agent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if _agent_dir not in sys.path: sys.path.insert(0, _agent_dir) try: from skills.base import BaseSkill except ImportError: from ..base import BaseSkill logger = logging.getLogger(__name__) # ═══════════════════════════════════════════════════════════════ # 弹窗规则库 — 覆盖主流安卓品牌:小米/华为/OPPO/vivo/三星/一加/荣耀/realme # # 每条规则: detect = 界面关键词(正则), click = 要点击的按钮文案 # 规则按优先级排列,匹配到即执行,不会重复点击 # ═══════════════════════════════════════════════════════════════ _POPUP_DISMISS_RULES: List[Dict[str, Any]] = [ # ─── 1. USB 调试授权(所有品牌) ─── {"detect": [ "USB调试", "USB debugging", "允许USB调试", "Allow USB debugging", "USB 偵錯", # 繁体 "USB调试(安全设置)", # MIUI 特有 "RSA.*密钥指纹", "RSA.*key fingerprint", # 弹窗正文 "允许通过USB调试", # 华为 "이 컴퓨터에서 USB 디버깅", # 三星韩语 ], "click": [ "确定", "允许", "OK", "Allow", "始终允许", "Always allow", "확인", # 三星韩语 "允許", # 繁体 ]}, # ─── 1b. USB 连接模式(仅充电 → 文件传输,防 MIUI 间歇掉 ADB) ─── {"detect": [ "USB 用于", "USB用途", "USB 用途", "连接方式", "仅充电", "充电模式", "Charging only", "Charge only", "USB 偏好设置", "Use USB for", "传输文件", "文件传输", "Transfer files", ], "click": [ "传输文件", "文件传输", "Transfer files", "File transfer", "传输照片", "Photo transfer", "MTP", ]}, # ─── 2. MIUI 安全弹窗(小米/红米特有) ─── {"detect": [ "安全警告", "此操作具有风险", "模拟点击", "模拟输入", # MIUI「检测到模拟操作」 "检测到.*模拟", "监控.*辅助", "开发者选项.*安全", "安全设置已关闭", ], "click": ["确定", "继续", "我已知晓", "允许", "仍然允许"]}, # ─── 3. 华为/荣耀 特有弹窗 ─── {"detect": [ "允许HiSuite通过", "HiSuite", "仅充电", "传输文件", "传输照片", # USB 模式选择 "华为手机助手", "应用.*未通过安全检测", # 华为安全中心 "可能会损坏您的设备", ], "click": ["允许", "继续安装", "我已知晓", "确定", "仍然安装"]}, # ─── 4. OPPO/realme/一加 特有弹窗 ─── {"detect": [ "ColorOS", "USB 传输", "允许.*调试", "您要允许.*USB调试", "开发者模式.*已开启", "USB用途", "连接方式", "充电模式.*传输", # OPPO USB 模式选择 ], "click": ["确定", "允许", "OK", "传输文件"]}, # ─── 5. vivo/iQOO 特有弹窗 ─── {"detect": [ "Funtouch", "OriginOS", "是否允许USB调试", "开启USB调试.*安全风险", "vivo.*安全", ], "click": ["确定", "允许", "OK", "已了解风险"]}, # ─── 6. 三星 特有弹窗 ─── {"detect": [ "One UI", "Samsung", "您是否要允许USB调试", "Allow USB debugging", "Knox.*安全", "设备保护", ], "click": ["确定", "允许", "OK", "Allow"]}, # ─── 7. ADB 通用权限 ─── {"detect": ["允许通过", "ADB", "adb"], "click": ["确定", "允许", "OK"]}, # ─── 8. 安装应用(所有品牌) ─── {"detect": [ "要安装", "允许安装", "安装应用", "安装未知应用", "Install unknown apps", "来源不明", "Unknown sources", "是否安装该应用", "Install this app", "外部来源", "第三方来源", "继续安装.*可能有风险", # 各品牌安全提示 "仍要安装", ], "click": [ "继续安装", "允许", "安装", "Install", "仍然安装", "仍要安装", "我已了解", ]}, # ─── 9. 权限请求弹窗(通用) ─── {"detect": [ "允许.*访问", "允许.*使用", "Allow.*access", "请求.*权限", "needs.*permission", "允许.*读取", "允许.*写入", "允许.*拍照", "允许.*录音", "允许.*定位", "Allow.*location", "允许.*通知", "Allow.*notification", "允许.*悬浮窗", "允许.*显示在其他应用", "Display over other apps", "允许.*存储", "Allow.*storage", ], "click": [ "允许", "始终允许", "Allow", "While using the app", "仅在使用时允许", "仅使用期间", "始终", "Always", ]}, # ─── 10. 系统更新/升级 ─── {"detect": [ "系统更新", "System update", "升级", "发现新版本", "New version", "现在更新", "Update now", "MIUI.*更新", "EMUI.*更新", "ColorOS.*更新", "OTA.*更新", ], "click": ["稍后", "以后", "取消", "Later", "Cancel", "Not now", "稍后再说"]}, # ─── 11. 电池优化(各品牌措辞不同) ─── {"detect": [ "电池优化", "Battery optimization", "忽略电池优化", "省电策略", "后台耗电", "电量管理", "自启动管理", "自启动", "是否允许.*后台运行", "关闭.*省电", "Ignore battery optimizations", "不再受限", # 华为 ], "click": ["允许", "是", "Yes", "Allow", "不受限制", "确定"]}, # ─── 12. 后台运行保活 ─── {"detect": [ "后台运行", "后台活动", "Background", "锁定.*后台", "保持运行", "允许.*自启动", "允许.*后台", "关闭后台清理", "电池管理.*允许", ], "click": ["允许", "Allow", "确定", "开启"]}, # ─── 13. 通知权限 ─── {"detect": [ "通知权限", "发送通知", "Post notifications", "允许.*推送", "开启通知", "Notification access", ], "click": ["允许", "Allow", "确定"]}, # ─── 14. 悬浮窗/无障碍 ─── {"detect": [ "悬浮窗", "Display over", "draw over", "无障碍", "Accessibility", "辅助功能", ], "click": ["允许", "Allow", "确定", "开启"]}, # ─── 15. Termux / 终端相关 ─── {"detect": ["Termux", "终端", "com.termux"], "click": ["允许", "Allow", "确定"]}, # ─── 16. 应用崩溃/ANR ─── {"detect": [ "ANR", "没有响应", "not responding", "停止运行", "已停止", "has stopped", "keeps stopping", "无响应.*关闭", "close app", ], "click": ["等待", "Wait", "再次打开", "Reopen"]}, # ─── 17. 隐私合规弹窗(国内APP常见) ─── {"detect": [ "隐私政策", "用户协议", "Privacy policy", "个人信息保护", "我已阅读", "同意并继续", "Agree.*continue", ], "click": ["同意", "同意并继续", "Agree", "我已阅读并同意", "确定"]}, # ─── 18. 广告/推广弹窗 ─── {"detect": [ "领取.*红包", "签到.*奖励", "今日推荐", "猜你喜欢", "开通.*会员", "免费试用", "跳过", "Skip", ], "click": ["跳过", "关闭", "Skip", "Close", "×", "✕"]}, # ─── 19. SIM卡/运营商弹窗 ─── {"detect": [ "SIM.*未插入", "SIM.*not inserted", "无SIM卡", "No SIM", "紧急呼叫", "Emergency", ], "click": ["确定", "OK", "关闭"]}, ] class ConnectionGuardSkill(BaseSkill): """ 连接守护技能 — 常驻后台,确保设备与服务器始终连接 核心能力: 1. dismiss_popups() — 自动关闭系统弹窗(USB授权、权限请求等) 2. ensure_screen_on() — 确保屏幕亮起 3. check_u2_alive() — 检测 uiautomator2 是否存活 4. check_network() — 检测网络连通性(委托 Hawk) 5. full_guard_cycle() — 执行一轮完整守护(上述全部) """ PACKAGE = "" NAME = "连接守护" def _shell(self, cmd: str) -> str: """安全执行 shell 并返回输出""" try: r = self.d.shell(cmd) return (getattr(r, "output", None) or str(r) or "").strip() except Exception: return "" # ========== 1. 弹窗自动处理 ========== def dismiss_popups(self) -> Dict[str, Any]: """ 扫描当前界面,自动点击 USB 授权/权限请求/系统弹窗的确认按钮。 返回本轮处理了哪些弹窗。 """ dismissed = [] if not self.d: return {"dismissed": dismissed, "count": 0} try: xml = self.d.dump_hierarchy() except Exception as e: logger.debug(f"dump_hierarchy 失败: {e}") return {"dismissed": dismissed, "count": 0, "error": str(e)} for rule in _POPUP_DISMISS_RULES: detected = False for keyword in rule["detect"]: if re.search(keyword, xml, re.IGNORECASE): detected = True break if not detected: continue for btn_text in rule["click"]: try: if self.d(text=btn_text).exists(timeout=0.5): self.d(text=btn_text).click() dismissed.append({"popup": rule["detect"][0], "clicked": btn_text}) logger.info(f"🛡️ 自动处理弹窗: 检测到「{rule['detect'][0]}」→ 点击「{btn_text}」") time.sleep(0.5) break except Exception: continue # textContains 兜底 try: if self.d(textContains=btn_text).exists(timeout=0.3): self.d(textContains=btn_text).click() dismissed.append({"popup": rule["detect"][0], "clicked": btn_text}) logger.info(f"🛡️ 自动处理弹窗: 检测到「{rule['detect'][0]}」→ 点击「{btn_text}」(contains)") time.sleep(0.5) break except Exception: continue return {"dismissed": dismissed, "count": len(dismissed)} # ========== 2. 屏幕状态 ========== def ensure_screen_on(self) -> Dict[str, Any]: """确保屏幕亮起(某些 ADB 操作需要屏幕点亮)""" if not self.d: return {"screen_on": False, "action": "no_device"} try: info = self.d.info if info.get("screenOn"): return {"screen_on": True, "action": "already_on"} self.d.press("power") time.sleep(0.5) # 如果有锁屏,向上滑动解锁 self.d.swipe_ext("up", scale=0.8) time.sleep(0.3) return {"screen_on": True, "action": "woke_up"} except Exception as e: logger.debug(f"ensure_screen_on 异常: {e}") return {"screen_on": False, "action": "error", "error": str(e)} # ========== 3. uiautomator2 存活检测 ========== def check_u2_alive(self) -> Dict[str, Any]: """检测 uiautomator2 是否正常工作,异常时尝试重启""" if not self.d: return {"alive": False, "action": "no_device"} try: info = self.d.info return {"alive": True, "package": info.get("currentPackageName", ""), "action": "ok"} except Exception as e: logger.warning(f"u2 异常: {e},尝试重启服务") try: self._shell("am instrument -w -r -e debug false " "-e class androidx.test.runner.AndroidJUnitRunner " "io.appium.uiautomator2.server.test/" "androidx.test.runner.AndroidJUnitRunner 2>/dev/null &") time.sleep(3) info = self.d.info return {"alive": True, "action": "restarted"} except Exception as e2: return {"alive": False, "action": "restart_failed", "error": str(e2)} # ========== 4. 网络检测(委托 Hawk) ========== def check_network(self) -> Dict[str, Any]: """检测网络连通性,断网时委托 Hawk 尝试恢复""" try: from hawk import is_network_available, try_reconnect_network if is_network_available(self.d): return {"network": True, "action": "ok"} logger.info("🌐 检测到断网,启动 Hawk 网络恢复...") result = try_reconnect_network(self.d) return { "network": result.get("success", False), "action": "hawk_reconnect", "detail": result, } except ImportError: # Hawk 不可用时仅做 ping 检测 out = self._shell("ping -c 1 -W 2 8.8.8.8 2>/dev/null && echo ok || echo fail") return {"network": "ok" in out, "action": "ping_only"} except Exception as e: return {"network": False, "action": "error", "error": str(e)} # ========== 5. ADB 连接检测 ========== def check_adb_connection(self) -> Dict[str, Any]: """检测 ADB 连接状态""" out = self._shell("getprop ro.serialno 2>/dev/null || echo unknown") if out and out != "unknown": return {"adb": True, "serial": out} return {"adb": False, "serial": ""} # ========== 6. 完整守护周期 ========== def full_guard_cycle(self) -> Dict[str, Any]: """ 执行一轮完整守护检查(Agent 心跳循环中调用): 1. 自动处理弹窗(USB授权等) 2. 确保屏幕亮起 3. 检测 u2 存活 4. 检测网络 5. 检测 ADB """ results = {} results["popups"] = self.dismiss_popups() results["screen"] = self.ensure_screen_on() results["u2"] = self.check_u2_alive() results["network"] = self.check_network() results["adb"] = self.check_adb_connection() all_ok = ( results["u2"].get("alive", False) and results["network"].get("network", False) ) results["all_ok"] = all_ok results["timestamp"] = int(time.time()) if not all_ok: issues = [] if not results["u2"].get("alive"): issues.append("u2不可用") if not results["network"].get("network"): issues.append("网络断开") logger.warning(f"⚠️ 守护检查发现问题: {', '.join(issues)}") else: popup_count = results["popups"].get("count", 0) if popup_count > 0: logger.info(f"🛡️ 守护周期完成: 处理了 {popup_count} 个弹窗,连接正常") return results