Files
workphone-sdk/sdk/agent/skills/connection_guard.py

429 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
连接守护技能 - 确保设备与服务器始终保持连接
职责:
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